12.2 风格思考
有些关于使用实体调用时的推荐。
12.2.1 推荐使用箭头函数作为回调
无论什么时候,只要可以,都应该使用箭头函数作为回调,而不是传统的方式。箭头函数更加方便,因为可以拿到词法的 this
,并且语法上更加紧凑。
12.2.1.1 问题:作为隐式参数的 this
不幸的是,一些 JavaScript API 使用 this
作为回调函数的隐式参数,此时就没法使用箭头函数了。例如:行 B 中的 this
是行 A 函数的隐式参数。
beforeEach(function () { // (A)
this.addMatchers({ // (B)
toBeInRange: function (start, end) {
···
}
});
});
12.2.1.2 解决方案1:修改 API
修复很简单,但是需要修改 API :
beforeEach(api => {
api.addMatchers({
toBeInRange(start, end) {
···
}
});
});
我们将 API 从一个隐式的变量 this
转换成了显示的参数 api
。我喜欢这种明确性。
12.2.1.3 解决方案2:用另外的方式获取 this
的值
在一些 API 中,有一些可选的方式得到 this
值。例如,下面代码使用 this
。
var $button = $('#myButton');
$button.on('click', function () {
this.classList.toggle('clicked');
});
但是这个事件的目标也可以通过 event.target
拿到:
var $button = $('#myButton');
$button.on('click', event => {
event.target.classList.toggle('clicked');
});
12.2.2 当心函数定义
只要不访问 this
,函数定义和非方法函数一样安全。
function foo(arg1, arg2) {
···
}
也可以可选地使用 const
和箭头函数,这会得到词法的 this
,但是 - 可能 - 看起来不漂亮:
const foo = (arg1, arg2) => {
···
};
12.2.3 对于方法,推荐使用方法定义
方法定义是唯一的一种创建方法(使用 super
)的方式。这是对象字面量和类(定义方法的唯一方式)明显的选择,但是要给已经存在的对象添加一个方法会怎么样呢?例如:
MyClass.prototype.foo = function (arg1, arg2) {
···
};
下面是一个在 ES6 中完成同样事情的快速(某种程度上会造成污染)方式。
Object.assign(MyClass.prototype, {
foo(arg1, arg2) {
···
}
});
更多的信息和说明,参看关于 Object.assign()
的那一节。
12.2.4 方法和回调函数属性之间的区别
在对象方法和作为回调集合的对象之间有一些细微的差别。
例如,你可以像下面一样使用 WHATWG 流 API:
let stream = new ReadableStream({
start(controller) {
···
},
pull() {
···
},
cancel() {
···
}
});
也就是说, ReadableStream()
的参数是一个带有方法的对象。这允许 start
, pull
和 cancel
使用 this
来彼此调用,访问本地对象( object-local )状态。
另一方面,你可能轻易地看到这三个指向回调函数的属性,这些回调函数与父作用域(例如一个方法)交互。此时 this
应该就是词法范围的,代码看起来像下面这样。
let stream = new ReadableStream({
start: controller => {
···
},
pull: () => {
···
},
cancel: () => {
···
}
});
12.2.5 在 ES6 中避免 IIFE
本节给出在 ES6 中避免 IIFE 的小技巧。
12.2.5.1 用代码块替换 IIFE
在 ES5 中,如果想保持一个变量本地化,就必须要使用 IIFE :
(function () { // open IIFE
var tmp = ···;
···
}()); // close IIFE
12.2.5.2 使用模块替换 IIFE
在 ECMAScript 5 代码中,并不会通过库(比如 RequireJS , browserify 或者 webpack )来使用模块,直接的模块形式很流行,并基于 IIFE 。它的好处就是清楚地区分了公开的和私有的:
var my_module = (function () {
// Module-private variable:
var countInvocations = 0;
function myFunc(x) {
countInvocations++;
···
}
// Exported by module:
return {
myFunc: myFunc
};
}());
上例中的模块形式产生一个全局的变量,可以像下面这样使用:
my_module.myFunc(33);
在 ECMAScript 6 中,模块是内置的,这就是为什么采取这种方式的障碍很小:
// my_module.js
// Module-private variable:
let countInvocations = 0;
export function myFunc(x) {
countInvocations++;
···
}
该模块不会产生一个全局的变量,使用方式如下:
import { myFunc } from 'my_module.js';
myFunc(33);
12.2.5.3 立即调用的箭头函数
在 ES6 中有一种场景仍然需要立即调用函数:有时候想通过一组语句产生一个结果,而不是通过一个单一的表达式。如果想内联这些语句,就必须要立即调用一个函数。在 ES6 中,如果想的话,可以使用立即调用的箭头函数:
const SENTENCE = 'How are you?';
const REVERSED_SENTENCE = (() => {
// Iteration over the string gives us code points
// (better for reversal than characters)
let arr = [...SENTENCE];
arr.reverse();
return arr.join('');
})();
注意,必须像上面展示的一样加上小括号(小括号包住箭头函数,而不是整个函数调用)。在箭头函数那一章有详细的解释。
12.2.6 使用类
ES6 的类是不完美的,有一些批评者。但是我还是建议使用类,因为也有一些面向对象的论据对 ES6 的类有利,我在关于类的那一章中有介绍。