Skip to main content

Symbol_ES6 Notes 7

Free2016-05-08#JS#es6 symbol#js symbol#js symbol是什么

Symbol is a refined feature that solves the big problem of extending existing APIs

I. What is Symbol

typeof Symbol() === 'symbol', symbol is the 7th primitive type in js (the original 6 are null, undefined, Number, Boolean, Object, String), not a string nor an object.

Purpose: symbol is used to avoid naming conflicts, solves the aftermath of tampering (adding properties) to native objects, no need to worry about property names conflicting with native property names or other library operations in the future.

II. Syntax

There are 3 ways to get Symbol, as follows:

1. Symbol(desc)

Returns symbol, desc is optional, symbol.toString() returns `Symbol(${desc})`, for example:

var obj = {
    a: 1
};

// 不用 new,Symbol 不是构造器
var safeKey = Symbol();
obj[safeKey] = 'value';
console.log(obj[safeKey]);  // value

var anotherSafeKey = Symbol('isAnimActive');
console.log(anotherSafeKey);    // Symbol(isAnimActive)

The var safeKey = Symbol(); above looks a bit strange, what about the new operator? Symbol is not a constructor, cannot be called through new operator. If you insist on new, you'll get an error like this:

Uncaught TypeError: Symbol is not a constructor

This is indeed strange, in coding conventions generally uppercase first letter indicates type name, should be called through new operator, but the API gives a Symbol function that doesn't conform to coding conventions.

Characteristics:

  • Symbol('key') !== Symbol('key'), each call to Symbol() returns a different value

  • Symbol can be used as property name, and is not equal to anything (string, number, other Symbol), so Symbol-type property names won't conflict with any existing property names, nor will they conflict with any new property names in the future

  • for...in, Object.keys(obj), Object.getOwnPropertyNames(obj) will skip symbol properties

  • Object.getOwnPropertySymbols(obj) returns all symbol property names of the object

  • Reflect.ownKeys(obj) returns all property names of the object (including symbol property names and string property names)

  • symbol is read-only, similar to string, adding properties to symbol in strict mode will error TypeError

  • symbol won't automatically convert to string, attempting to concatenate symbol will error TypeError, can manually call toString() then concatenate

Examples (continuing from previous example) as follows:

obj[Symbol('ready')] = true;
//!!! undefined
// 因为特点 1
console.log(obj[Symbol('ready')]);  // undefined
console.log(obj);   // Object {Symbol(): "value", Symbol(ready): true}
// 跳过 symbol 属性
for (var key in obj) {
    console.log(`obj[${key}] = ${obj[key]}`);
}   // obj[a] = 1

// 获取所有 obj 上所有 Symbol 类型的属性名
console.log(Object.getOwnPropertySymbols(obj)); // Array [ Symbol(), Symbol(ready) ]
// 获取 objs 上的所有属性名
console.log(Reflect.ownKeys(obj));  // Array [ "a", Symbol(), Symbol(ready) ]

// 只读
var s = Symbol();
s.a = 1;
console.log(s.a);   // undefined

// 不会自动转字符串
// console.log(Symbol('123') + '4');   // TypeError: can't convert symbol to string
console.log(Symbol('123').toString() + '4');    // Symbol(123)4

2. Symbol.for(str)

Represents symbol registry, used to create shared symbols, characteristics as follows:

  • Symbol.for('ready') === Symbol.for('ready')

  • Symbol('str') !== Symbol.for('str')

Examples (continuing from all previous examples):

// Symbol.for()
obj[Symbol.for('ready')] = 'ready';
console.log(obj[Symbol.for('ready')]);  // ready
console.log(Object.getOwnPropertySymbols(obj));
// log print: Array [ Symbol(), Symbol(ready), Symbol(ready) ]

P.S. There are two Symbol(ready) in the output array at the end, but the symbols they correspond to are not equal, only the toString return result is the same (characteristic 2).

Note: For shared Symbol, can only guarantee property name doesn't conflict with future DOM API, cannot guarantee no conflict with other code. Library authors shouldn't use it, recommended to use only in business code when cross-module or cross-page Symbol sharing is needed.

3. Symbol.xxx

Used to get native Symbol, for example Symbol.iterator, characteristics as follows:

  • New API is backward compatible, for example implementing iterable interface: obj[Symbol.iterator] = gen, won't affect old code

  • Prepared hooks, with help of symbol future new feature APIs won't affect old code

Unimplemented new features:

  • Symbol.hasInstance extends instanceof

  • Symbol.unscopables prevents methods from joining dynamic scope

  • Symbol.match extends str.match

These Symbols have been reserved as native Symbols, will be implemented soon, future (enhancing existing API) new features can be added through Symbol simply and safely.

III. Application Scenarios

1. Add a Marker Property to DOM Element

Maybe most of the time we don't need to add custom attributes to DOM elements, because this has side effects (custom attribute names may conflict with other code, or with future DOM API), so generally choose to maintain a table structure to save additional information like state corresponding to elements, each time look up table to get additional information of target element. If table is very large, looking up table will be time-consuming. If table structure is very complex, the table lookup operation itself will become very troublesome... Wait, why do we need to look up table to get element's additional information? Because of side effects, what if this side effect is gone?

Symbol is used to eliminate this side effect. Element-related information can be directly added to DOM element with Symbol object as key, simple, rough, safe and effective, for example:

var IS_MOVING = Symbol('isMoving');
var INFO = Symbol('info');

if (!elem[IS_MOVING]) {
    anim(elem);
    elem[IS_MOVING] = true;
    elem[INFO] = {
        step: 1;
    };
}

Need to hold custom Symbol-type key, put this key in your own scope, then there won't be any side effects.

2. Generate Unique Key

Previously generating unique key might need a slightly complex small algorithm, now Symbol is the simplest unique key generation method, for example:

// 不关注属性名,只是想简单地存取 obj
var data = {
    dir: {},
    save(val) {
        var key = Symbol();
        this.dir[key] = val;
        return key;
    },
    get(key) {
        return this.dir[key];
    }
}
var key = data.save('data');
console.log(data.get(key)); // data

3. Implement Supported API Extension Through Native Symbol

Standards will provide more and more hooks, let us implement API extension methods, for example making custom objects iterable:

var obj = {
'a': 1,
'b': 2
}
// generator 实现迭代器
obj[Symbol.iterator] = function*() {
    for (var key in this) {
        yield this[key];
    }
}
for (var val of obj) {
    console.log(val);
}

IV. Summary

Symbol is a refined feature that solves the big problem of extending existing APIs (provides a hook mechanism for new features, can safely extend in the future).

References

  • "ES6 in Depth": Free e-book provided by InfoQ Chinese station

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment