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() 的参数是一个带有方法的对象。这允许 startpullcancel 使用 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 的类有利,我在关于类的那一章中有介绍。

results matching ""

    No results matching ""