知识共享许可协议

避免使用 forEach

原文:http://aeflash.com/2014-11/avoid-foreach.html

遍历集合,会产生副作用。——如 mori.each 文档所说

首先声明,本文和性能无关。执行 for 循环总是比执行 Array.forEach 快。如果性能测试显示迭代的开销足够显著并且性能优先,那么你绝对应该使用 for 循环而不是 forEach(总是使用 for 循环是典型的过早优化。forEach 仍然可以在 1 微秒内遍历长度为 50 的数组)。本文和编码风格有关,是我对 forEach 和其它 Array.prototype 方法的思考,与性能无关。

阅读全部

谈谈使用 promise 时候的一些反模式

本文翻译自 We have a problem with promises,为原文题目重新起了一个题目并且对原文有删改。

各位 JavaScript 程序员,是时候承认了,我们在使用 promise 的时候,会写出许多有问题的 promise 代码。 当然并不是 promise 本身的问题,A+ spec 规范定义的 promise 非常棒。 在过去的几年中,笔者看到了很多程序员在调用 PouchDB 或者其他 promise 化的 API 时遇到了很多困难。这让笔者认识到,在 JavaScript 程序员之中,只有少数人是真正理解了 promise 规范的。如果这个事实让你难以接受,那么思考一下我在 Twitter 上出的题:

问:下面四个使用 promise 的语句之间的不同点在哪儿?

1
2
3
4
5
6
7
8
9
10
11
doSomething().then(function () {
return doSomethingElse();
});

doSomethin().then(functiuoin () {
doSomethingElse();
});

doSomething().then(doSomethingElse());

doSomething().then(doSomethingElse);

如果你知道这个问题的答案,那么恭喜你,你已经是一个 promise 大师并且可以直接关闭这个网页了。

但是对于不能回答这个问题的程序员中 99.9% 的人,别担心,你们不是少数派。没有人能够在笔者的 tweet 上完全正确的回答这个问题,而且对于 #3 最终答案也令我感到震惊,即便我是出题人。

答案在本文的底部,但是首先,笔者必须先探究一下 promise 为何如此复杂,为什么不管是新手还是专家都有被 promise 折磨的经历。同时,笔者也会给出自认为能够快速、准确理解 promise 的方法。而且笔者确信读过这篇文章之后,理解 promise 不会那么难了。

在此之前,我们先了解一下有关 promise 的一些常识。

阅读全部

ES Decorators简介

我跟你说,我最讨厌“简介”这种文章了,要不是语文是体育老师教的,早就换标题了!

Decorators是ECMAScript现在处于Stage 1的一个提案。当然ECMAScript会有很多新的特性,特地介绍这一个是因为它能够在实际的编程中提供很大的帮助,甚至于改变不少功能的设计。

先说说怎么回事

如果光从概念上来介绍的话,官方是这么说的:

Decorators make it possible to annotate and modify classes and properties at design time.

我翻译一下:

装饰器让你可以在设计时对类和类的属性进行注解和修改。

什么鬼,说人话!

所以我们还是用一段代码来看一下好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function memoize(target, key, descriptor) {
let cache = new Map();
let oldMethod = descriptor.value;
descriptor.value = function (...args) {
let hash = args[0];
if (cache.has(hash)) {
return cache.get(hash);
}
let value = oldMethod.apply(this, args);
cache.set(hash, value);
return value;
};
}

class Foo {
@memoize;
getFooById(id) {
// ...
}
}

别去试上面的代码,瞎写的,估计跑不起来就是了。这个代码的作用其实看函数的命名就能明白,我们要给Foo#getFooById方法加一个缓存,缓存使用第一个参数作为对应的键。

可以看出来,上面代码的重点在于:

阅读全部

JavaScript 代码静态质量检查

自鸿蒙初判,Brendan Eich 10 天捏出 Mocha 之后,即便进化成 ECMAScript,这个语言依旧毁誉相随。那些经过重重劫难,侥幸渡劫成功的苦主标识了诸多天坑(见 JavaScript Garden) —— 当然,你也可以称之 feature。据无责任乱猜,Douglas Crockford 也没少踩坑,于是才有了蝴蝶书《JavaScript: The Good Parts》,下雨天与 JSLint 一起使用会更配哟。

《JavaScript: The Definitive Guide》 V.S. 《JavaScript: The Good Parts》

时至今日,代码的静态质量检查在项目质量保障方面的重要性与必要性已毋庸置疑。越来越多的开发者意识到了这一点,纷纷在项目构建流程或者源码控制系统中添加静态检查的 hook。本文将依时间顺序,选出 JavaScript 史上的主要几个 Linter 作横向比较,最终属意谁家,那就见仁见智了。

阅读全部

扁平化箭形代码

作为《嵌套条件的重构》 的姊妹篇,补充说明箭形代码的缺点,并以函数分解的方式扁平化箭形代码。

原文:Flattening Arrow Code

经常看到这种代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (rowCount > rowIdx) {
if (drc[rowIdx].table.columns.contains("avalId")) {
do {
if (attributes[attrVal.attributeClassId] == null) {
// do stuff
}
else {
if (!Array.isArray(attributes[attrVal.attributeClassId])) {
// do stuff
}
else {
if (!isChecking) {
// do stuff
}
else {
// do stuff
}
}
}
rowIdx++;
}
while (rowIdx < rowCount && parseInt(drc[rowIdx], 10) === Id);
}
else {
rowIdx++;
}
}
return rowIdx;

太多的条件嵌套使代码变成一个箭头的样子:

1
2
3
4
5
6
7
8
9
if
if
if
if
do something
endif
endif
endif
endif

当你在1280x1024 分辨率下阅读代码时会超出右边边界。这就是箭头反模式。

我重构的首要任务之一是把这样的箭形代码 “扁平化”。那些锋利的尖钩很危险!箭形代码有着很高的圈复杂度 -- 衡量贯穿代码有多少不同路径:

研究表明程序的质量与它的圈复杂度(WIKI, 百度百科)有关。低圈复杂度的程序更简单易懂,修改时的风险也更低。模块的圈复杂度与它的可测试性也是高度相关的。

在适当的地方,我通过以下方式扁平化箭形代码:

  1. 替换条件为 guard clauses,这个代码..
1
2
3
if (SomeNecessaryCondition) {
// function body code
}

.. 改成 guard clause 会更好:

1
2
3
4
if (!SomeNecessaryCondition) {
throw new RequiredConditionMissingException;
}
// function body code
  1. 用函数来分解条件块。在上例中,我们把 do..while 循环里的条件分解。
1
2
3
4
5
do {
validateRowAttribute(drc[rowIdx]);
rowIdx++;
}
while (rowIdx < rowCount && parseInt(drc[rowIdx], 10) === Id);
  1. 将否定检查转为肯定检查。主要规则是把肯定比较置前,让否定比较自然落到 else 中。我认为这样可读性肯定更好,更重要的是,避免 “我永远不会不做” 句式。
1
2
3
4
5
6
if (Array.isArray(attributes[attrVal.attributeClassId])) {
// do stuff
}
else {
// do stuff
}
  1. 总是尽快从函数返回。一旦工作完成,马上退出。这个并非永远合适的 -- 你可能需要清理资源。但无论如何,你必须放弃只应在底部有一个出口的错误想法。

目的是让代码在垂直方向滚动多些...而不是在水平方向上。

数字知多少?

某天早上阳光明媚,挺风和日丽的,刚嚼一口早餐就被同学问到一个奇怪的问题,大概情况是这样的:

1
2
3
4
5
var str = '7172328d6ddf0296e7e7d4a8';
var n = parseInt(str, 16);

// false ...
console.log(n.toString(16) === str);

一个巨大的16进值字符串转化成数字后再转化成对应的字符串就不相等了,实际输出的值还差得蛮远的... 这个问题的第一反应就是精度丢失啰,不过同学们可不是这么好打发的,都不说一个为什么怎能了事呢?那就让我们稍微挖一挖,看看究竟是为什么吧~

阅读全部