Skip to content

JS-ES6-Symbol #140

Open
Open
@yaofly2012

Description

@yaofly2012

Symbol

一、概述

  1. ES2015引入的新的数据类型,并且是基本类型(值类型)

  2. 可以作为属性值,也可以作为属性名(跟字符串类似);

  3. 和其他基本类型不同的是Symbol没有字面量形式的写法,只能通过工厂函数创建;
    因为无法通过直面量表示唯一值。

  4. Symbolsymbol的关系就像是Numbernumber的关系一样:

  • 前者是构造函数(也可以当做工厂函数使用),后者是类型字符串
Symbol instanceof Function // true
typeof Symbol() // "symbol"

但是Symbol无法作为构造函数。

  1. 虽然Symbol具有prototype属性,但是不能作为构造函数。Symbol的装箱类型只能通过Object转换。
// TypeError: Symbol is not a constructor
new Symbol() 

var symbol = Symbol();
var symbolObj = Object(symbol);

typeof symbolObj // "object"
symbol instanceof Symbol // false
symbolObj instanceof Symbol // true,有点绕,但原型链就是这样的

共享Symbol(全局Symbol

  1. “共享”的目的是为了再次获取。在全局Symbol注册表里添加的Symbol;
  2. 解决多页面,多模块等直接的Symbol共享。

二、为啥需要Symbol

解决唯一属性值和唯一属性名。

  1. 避免属性命名冲突;
    JS一些方法的实现利用这个特性,如Well-known symbols
  2. Symbol命名属性具有一定的私密性:
  • for...in, Object.keysObject.getOwnPropertyNames()都无法枚举/访问Symbol命名的属性。
  1. 作为常量值替代字符串或者数字常量(库和框架里经常遇到):
 // The Symbol used to tag the ReactElement-like types. If there is no native Symbol
  // nor polyfill, then a plain number is used for performance.
  var hasSymbol = typeof Symbol === 'function' && Symbol.for;
  var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;

通过值的“唯一性”来保证值不可被修改。还可以看看这个例子

三、如何使用

3.1 创建方式:Symbol([description])

  1. 参数的用处?
    for debugging purposes only.
  2. 不支持new方式创建,其他基本类型却支持:
let sym = new Symbol()  // TypeError
let sym = Symbol()  // symbol
let symObj = Object(sym ) // Symbol
let s = new String(1)  // 字符串对象:String {"1"}
let s = String(1)  // 字符串: "1"

即:Symbol的装箱对象不能通过new方式创建,而其他基本类型的装箱对象类型可以通过new方式构造。

3.2 创建方式:Symbol.for(key)

  1. 用于创建共享Symbol
  2. 实参key是字符串,非字符串会自动转成字符串;
  3. 本质上来说共享Symbol的key就是表示Symbol的唯一性了。为了保证key的唯一性,一般通过过命名空间的方式命名key 。
Symbol.for('react.element')
Symbol.for('react.concurrent_mode')
  1. Symbol.keyFor(sym)获取共享Symbol的key。

3.3 作为属性名

  1. Object.getOwnPropertySymbols()可以获取对象的Symbol属性名列表。

3.4 使用预定义的Symbol,也叫做 well-known symbols

Symbol函数对象上预定义了一些Symbol(Symbol.iterator等)供JS内部使用。
for...of内部调用对象的Symbol.iterator属性:

var fib = {
    iterator: function() {  },
    [Symbol.iterator]: function*() {
        let i = 0, k = 1;
        yield k;
        while(k < 100) {
            k = i + k;
            i = k - i;
            yield k;
        }
    }
};

for (let k in fib) console.log(k); // iterator
for (let k of fib) console.log(k); // 1, 1, 2, 3, ... , 144

3.5 类型判断

基本类型直接使用typeof,但是对于装箱类型(即对象)得借助Object.prototype.toString函数了。

let sym = Symbol('foo')
typeof sym      // "symbol"
let symObj = Object(sym)
typeof symObj   // "object"

Object.prototype.toString.call(sym) // [object Symbol]
Object.prototype.toString.call(symObj) // [object Symbol]
  1. underscorejs实现
import tagTester from './_tagTester.js';

export default tagTester('Symbol');

直接使用Object.prototype.toString

  1. lodash实现
function isSymbol(value) {
  const type = typeof value
  return type == 'symbol' || (type === 'object' && value != null && getTag(value) == '[object Symbol]')
}

lodash逻辑相对复杂些,优先使用typeof,对于对象才使用Object.prototype.toString
但是为啥不像underscore那样直接使用呢?试了下两者性能差异不大。

四、唯一性的实现原理?

4.1 JS运行时会动态创建一个匿名的唯一值

dynamically produces an anonymous, unique value.

4.2 共享Symbol实现

  1. JS编译器内部维护Symbol注册表( the global symbol registry)记录所有的共享Symbols。
    image
  2. The Global Registry是跨执行上下文的,即window,iframe, serviceWorker都是同一个The Global Registry。
<head>
    <script>
        function test(arr) {
            var iframe = frames[0];
            console.log(Array === iframe.Array); // false
            console.log(arr instanceof Array); // false
            console.log(arr instanceof iframe.Array); // true

            // But: symbols are the same
            console.log(Symbol.iterator ===
                        iframe.Symbol.iterator); // true
        }
    </script>
</head>
<body>
    <iframe srcdoc="<script>window.parent.test([])</script>">
</iframe>
</body>

五、Symbol PK 其他基本类型(Number, Boolean, String)

相同点

  1. 都存在对应的装箱对象类型;
  2. 都存在创建值的工厂函数:
Number(1) // 1
Boolean(1) // true
String(1) // "1"
Symbol(1) // Symbol(1) 

不同点

  1. Symbol没有字面量;
  2. Symbol不能通过new方式构造;
  3. Symbol是真值,但不能隐藏的转成Number, String;
  4. Symbol不是JSON的类型,因此无论是Symbol属性名还是属性值,在JSON化时都被忽略掉;
var a = { [Symbol()]: 1} 
JSON.stringify(a) // "{}"

var b = { name: Symbol()} 
JSON.stringify(b) // "{}"

六、Symbol PK String

  1. 都可以作为属性名,但是Symbol属性名不能被for-in遍历到,必须使用专属的遍历方式Object.getOwnPropertySymbols()
  2. Symbol不会隐式的转成字符串的,因为两者都可以作为属性名,如果可以转成字符串那属性名字容易误解。

参考

  1. MDN Symbol
  2. ES6 In Depth: Symbols
  3. Symbols in ECMAScript 6

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions