7.4 Symbol 作为属性键

创建永远不会冲突的键,在以下两种场景中非常有用:

  • 在继承树中定义非公开的属性。
  • 保证子层属性不会和父层属性冲突。

7.4.1 Symbol 用作非公开属性键

无论何种形式的 JavaScript 继承(例如通过类,通过 mixin ,或者就是单纯的原型的方式),都会存在两种类型的属性:

  • 公开属性 能被客户代码访问。
  • 私有属性 在组成继承结构的代码片段(例如类、 mixin 或者对象)内部使用。(受保护的属性 被若干个代码片段共享,和私有属性面临同样的问题。)

为了可用性的缘故,公开属性通常用字符串作为键。但是,对于字符串作为键的私有属性,意外的命名冲突可能会导致一些问题。因此, symbol 是一个好的选择。例如,在下面的代码中, symbol 用于私有属性 _counter_action

const _counter = Symbol('counter');
const _action = Symbol('action');
class Countdown {
    constructor(counter, action) {
        this[_counter] = counter;
        this[_action] = action;
    }
    dec() {
        let counter = this[_counter];
        if (counter < 1) return;
        counter--;
        this[_counter] = counter;
        if (counter === 0) {
            this[_action]();
        }
    }
}

注意 Symbol 仅会使你免于命名冲突,并不会阻止未授权的属性访问,因为可以通过 Reflect.ownKeys() 找出对象所有自有属性键,包括 symbol 。如果你想对属性做保护,可以选择在这一节中讲述的方法之一:类的私有数据

7.4.2 Symbol 用作元级属性键

相对于“普通的”属性键,Symbol 的唯一性使其在一个不同的纬度成为理想的公开属性,因为元级键和普通键不能冲突。举个元级键的例子,对象可以实现一些特定的方法,从而自定义某个库如何处理该对象。使用 Symbol 键可以避免第三方库错误地将普通方法当成自定义方法。

ECMAScript 6 中的可迭代性就属于这种自定义方法的场景。如果一个对象有一个键为 Symbol.iterator 的方法,那么它就是可迭代的。在下面的例子中,obj 是可迭代的。

const obj = {
    data: [ 'hello', 'world' ],
    [Symbol.iterator]() {
        const self = this;
        let index = 0;
        return {
            next() {
                if (index < self.data.length) {
                    return {
                        value: self.data[index++]
                    };
                } else {
                    return { done: true };
                }
            }
        };
    }
};

obj 的可迭代性使其可用于 for-of 循环,以及类似的 JavaScript 特性中:

for (const x of obj) {
    console.log(x);
}

// Output:
// hello
// world

7.4.3 JavaScript 标准库命名冲突举例

或许你觉得命名冲突不要紧,下面有三个例子,展示了在 JavaScript 标准库演化过程中命名冲突带来的问题:

  • 在引入新方法 Array.prototype.values() 的时候,破坏了已有的 with 与数组结合使用的代码,它会在外面的作用域中隐藏一个 values 变量(bug report 1bug report 2)。因此,引入了一种隐藏属性的机制( System.unscopables )。
  • String.prototype.contains 和 MooTools 中添加的一个方法冲突了,不得不重命名成 String.prototype.includesbug report )。
  • ES2016 中的 Array.prototype.contains 方法也和 MooTools 中添加的方法冲突了,不得不重命名成 Array.prototype.includesbug report )。

相比之下,通过属性键 System.iterator 给一个对象增加可迭代性就不会引起问题,因为该键并不会和任何键冲突。

上述例子展示了成为一门 web 开发语言意味着什么:向后兼容是至关重要的,这就是为什么在改进语言的时候,偶尔妥协是必须的。这种向后兼容的好处就是,改进老的代码库很轻松,因为新的 ECMAScript 版本绝不会(好吧,是几乎不会)破坏这些老代码。

results matching ""

    No results matching ""