wangjing013 / blog

📝 记录

Home Page:https://wangjing013.github.io/blog/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ES6 之 Symbol 对象

wangjing013 opened this issue · comments

Symbol

Createing symbols

ES6 新增 Symbol 作为新的原始类型. 它不像其它原始类型如 numberbooleannullundefined字符串, Symbol 类型不包含字面量形式.

去创建一个 symbol, 你可以使用全局 Symbol() 方法, 如下:

let s = Symbol('foo');

每次调用 Symbol() 函数时都将创建一个新的唯一值:

console.log(Symbol() === Symbol()); // false

Symbol() 函数接收一个可选参数 description, description 参数将让 symbol 更具有描述性.

下面实例创建两个 symbol: firstName and lastName.

let firstName = Symbol('first name'),
    lastName = Symbol('last name');

你可以使用 toString() 方法去访问 description 的值, console.log() 方法隐式的调用 symboltoString()方法, 如下示例:

console.log(firstName); // Symbol(first name)
console.log(lastName); // Symbol(last name)

ES2019 中, 可以通过 symbol.description 方式访问描述属性值:

console.log(firstName.description); // "first name"

由于 Symbol 是原始类型, 可以使用 typeof 操作符去检查一个变量是否为 symbol 类型. 当传入的是 symbol 变量时, typeof 将返回 symbol 字符串 .

console.log(typeof firstName); // symbol

由于 Symbol 是原始类型, 如果尝试使用 new 操作符去创建一个 symbol, 将会报错:

let s = new Symbol(); // error

Sharing symbols

ES6 为您提供了全局 Symbol 注册表,允许您全局共享 Symbol. 如果想创建被共享 symbol, 你可以使用 Symbol.for() 方法, 而不是 Symbol() 方法.

Symbol.for() 方法接收一个参数, 用于表示 symbol 的描述信息, 如下

let ssn = Symbol.for('ssn');

Symbol.for() 方法首先在全局注册表中查找对应 key 为 ssnsymbol. 如果存在则返回存在 symbol. 否则 Symbol.for() 方法将创建一个新的 symbol, 使用指定的 key(ssn) 将其注册到全局的 Symbol 列表中,并返回 symbol.

最后,如果调用 Symbol.for()时使用相同的 key, Symbol.for方法将返回存在 symbol.

let citizenID = Symbol.for('ssn');
console.log(ssn === citizenID); // true

上面例子中, 我们使用 Symbol.for()方法查找keyssn 的 symbol. 因为全局注册表中已经包含它. 所以 Symbol.for() 返回已经存在的 symbol.

如果想获取与 symbol 相关的 key 时, 可以通过 Symbol.keyFor() 方法, 如下示例:

console.log(Symbol.keyFor(citizenID)); // 'ssn'

如果symbol 在全局注册表中不存在, 则 Symbol,keyFor()方法将返回 undefined.

let systemID = Symbol('sys');
console.log(Symbol.keyFor(systemID)); // undefined

Symbol useges

  • 使用 symbol 作为唯一值

每当你在代码中使用字符串或数字时, 你应该使用 symbols 替代. 例如: 你可能在应用需要管理任务的状态,在 ES6 之前, 你可以使用
openin progresscompletedcanceledon hold 等字符串表示不同任务状态. 在 ES6 中, 你可以使用 symbol, 如下:

let statuses = {
    OPEN: Symbol('Open'),
    IN_PROGRESS: Symbol('In progress'),
    COMPLETED: Symbol('Completed'),
    HOLD: Symbol('On hold'),
    CANCELED: Symbol('Canceled')
};
// complete a task
task.setStatus(statuses.COMPLETED);
  • 使用 symbol 作为对象中计算属性的名字

你可以使用 symbol 作为计算属性的名称, 如下示例:

let status = Symbol('status');
let task = {
    [status]: statuses.OPEN,
    description: 'Learn ES6 Symbol'
};
console.log(task);

要获取对象所有可枚举属性, 可以使用 Object.keys() 方法.

console.log(Object.keys(task)); // ["description"]

要获取有所可枚举和不可枚举的属性, 可以使用 Object.getOwnPropertyNames() 方法.

console.log(Object.getOwnPropertyNames(task)); // ["description"]

要获取对象的所有 symbols 属性, 可以用 Object.getOwnPropertySymbols() 方法.

console.log(Object.getOwnPropertySymbols(task)); //[Symbol(status)]

Object.getOwnPropertySymbols() 方法返回数组, 该数组只包含对象自身的 symbol 属性.

Well-known symbols

ES6 提供一些预先定义好的 well-known symbols. 这些 well-known symbols 表示 JavaScript 中的常见行为.
每个 well-known symbol 都是作为 Symbol 对象的静态属性.

Symbol.hasInstance

Symbol.hasInstance 是一个更改 instanceof 操作符行为的 symbol. 通常, 当你使用 instanceof 操作符时:

obj instanceof type;

JavaScript 将调用 Symbol.hasIntance 方法如下:

type[Symbol.hasInstance](obj);

然后, 它依赖该方法来确定 obj 是否是 type对象的实例. 请参考下面的示例:

class Stack {
}
console.log([] instanceof Stack); // false

[]数组不是 Stack 类的实例, 因此, 在本例中 instanceof 操作符返回为 false.

假设你想要 [] 数组被当作是 Stack 类型的实例, 你可以在 Stack 类中添加 Symbol.hasInstance方法, 如下:

class Stack {
    static [Symbol.hasInstance](obj) {
        return Array.isArray(obj);
    }
}
console.log([] instanceof Stack); // true

Symbol.iterator

Symbol.iterator 指定该函数是否返回对象的迭代器.

通常对象包含 Symbol.iterator 属性则称它为可迭代对象.

ES6, 所有集合对象(ArraySetMap) 和 strings 属于迭代对象.

ES6 提供 for...of 循环, 用来遍历可迭代对象. 查看如下示例:

var numbers = [1, 2, 3];
for (let num of numbers) {
    console.log(num);
}

// 1
// 2
// 3

在内部, JavaScript 引擎首先调用Symbol.iterator 方法,获取 numbers 数组的迭代对象. 然后,它调用 iterator.next() 方法并且把迭代器对象的 value 属性值赋值给 num 变量. 依次执行三次迭代, 当迭代返回的结果对象中 done 属性为 true. 循环♻️退出.

你可以通过 Symbol.iterator 去访问默认的迭代器对象, 如下:

var iterator = numbers[Symbol.iterator]();

console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: 2, done: false}
console.log(iterator.next()); // Object {value: 3, done: false}
console.log(iterator.next()); // Object {value: undefined, done: true}

默认情况下, 集合是不可迭代的. 然而, 你可以使用 Symbol.iterator 把它标记为迭代对象. 如下例所示:

class List {
    constructor() {
        this.elements = [];
    }

    add(element) {
        this.elements.push(element);
        return this;
    }

    *[Symbol.iterator]() {
        for (let element of this.elements) {
            yield  element;
        }
    }
}

let chars = new List();
chars.add('A')
     .add('B')
     .add('C');

// because of the Symbol.iterator
for (let c of chars) {
    console.log(c);
}

// A
// B
// C

Symbol.isConcatSpreadable

通常拼接两个数组, 使用 concat() 方法,如下例所示:

let odd  = [1, 3],
    even = [2, 4];
let all = odd.concat(even);
console.log(all); // [1, 3, 2, 4]

在例子中, 合并后的结果包含两个数组中的元素. 此外, concat() 方法也接受非数组参数.

let extras = all.concat(5);
console.log(extras); // [1, 3, 2, 4, 5]

从上面例子可以看到, 当传递一个数组给 concat() 方法时, concat() 方法会把数组展开为单个元素再添加到数组中. 然后, 它对单个原始参数处理方式有点不同. 在 ES6 之前. 你不可能更改这个行为.

这就是引入 Symbol.isConcatSpreadable 的原因.

Symbol.isConcatSpreadable 属性是一个 Boolean 值, 它决定是否将对象单独添加到 concat() 方法的结果中.

参考下面的示例:

let list = {
    0: 'JavaScript',
    1: 'Symbol',
    length: 2
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", Object]

类数组使用 concat() 时, 其并没有把类数组中每个属性添加到最终返回结果中.

如果实现期望的效果, 需要在类对象中添加 Symbol.isConcatSpreadable.

let list = {
    0: 'JavaScript',
    1: 'Symbol',
    length: 2,
    [Symbol.isConcatSpreadable]: true
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", "JavaScript", "Symbol"]

注意: 如果设置Symbol.isConcatSpreadable值为 false且将类数组传递给 concat() 方法时, 它将把整个对象添加到数组中.

Symbol.toPrimitive

Symbol.toPrimitive 方法决定当一个对象转换为一个原始值时会发生什么.

JavaScript 引擎在每个标准类型的原型上定义了 Symbol.toPrimitive方法.

Symbol.toPrimitive 方法接收一个 hint 参数, 其可能值为这三个:stringnumberdefault. 该 hint 参数指定该类型应该返回的值. hint 参数的值是 JavaScript 引擎根据上下文对象去填充的.

下面使用 Symbol.toPrimitive 方法的案例:

function Money(amount, currency) {
    this.amount = amount;
    this.currency = currency;
}
Money.prototype[Symbol.toPrimitive] = function(hint) {
    var result;
    switch (hint) {
        case 'string':
            result = this.amount + this.currency;
            break;
        case 'number':
            result = this.amount;
            break;
        case 'default':
            result = this.amount + this.currency;
            break;
    }
    return result;
}

var price = new Money(799, 'USD');

console.log('Price is ' + price); // Price is 799USD
console.log(+price + 1); // 800
console.log(String(price)); // 799USD

在这篇文章中, 您应该学习了 JavaScript symbols以及如何将 symbols 用于唯一值和对象的属性. 此外, 您还学习了如何使用 Well-known symbols来修改对象行为.