Open
Description
Symbol
一、概述
-
ES2015引入的新的数据类型,并且是基本类型(值类型);
-
可以作为属性值,也可以作为属性名(跟字符串类似);
-
和其他基本类型不同的是
Symbol
没有字面量形式的写法,只能通过工厂函数创建;
因为无法通过直面量表示唯一值。 -
Symbol
和symbol
的关系就像是Number
和number
的关系一样:
- 前者是构造函数(也可以当做工厂函数使用),后者是类型字符串。
Symbol instanceof Function // true
typeof Symbol() // "symbol"
但是Symbol
无法作为构造函数。
- 虽然
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
)
- “共享”的目的是为了再次获取。在全局Symbol注册表里添加的Symbol;
- 解决多页面,多模块等直接的Symbol共享。
二、为啥需要Symbol
解决唯一属性值和唯一属性名。
- 避免属性命名冲突;
JS一些方法的实现利用这个特性,如Well-known symbols
Symbol
命名属性具有一定的私密性:
for...in
,Object.keys
,Object.getOwnPropertyNames()
都无法枚举/访问Symbol
命名的属性。
- 作为常量值替代字符串或者数字常量(库和框架里经常遇到):
// 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])
- 参数的用处?
for debugging purposes only. - 不支持
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)
- 用于创建共享
Symbol
; - 实参
key
是字符串,非字符串会自动转成字符串; - 本质上来说共享Symbol的
key
就是表示Symbol的唯一性了。为了保证key的唯一性,一般通过过命名空间的方式命名key 。
Symbol.for('react.element')
Symbol.for('react.concurrent_mode')
Symbol.keyFor(sym)
获取共享Symbol的key。
3.3 作为属性名
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]
import tagTester from './_tagTester.js';
export default tagTester('Symbol');
直接使用Object.prototype.toString
。
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
实现
- JS编译器内部维护Symbol注册表( the global symbol registry)记录所有的共享Symbols。
- 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)
相同点
- 都存在对应的装箱对象类型;
- 都存在创建值的工厂函数:
Number(1) // 1
Boolean(1) // true
String(1) // "1"
Symbol(1) // Symbol(1)
不同点
Symbol
没有字面量;Symbol
不能通过new方式构造;Symbol
是真值,但不能隐藏的转成Number, String;Symbol
不是JSON的类型,因此无论是Symbol
属性名还是属性值,在JSON化时都被忽略掉;
var a = { [Symbol()]: 1}
JSON.stringify(a) // "{}"
var b = { name: Symbol()}
JSON.stringify(b) // "{}"
六、Symbol
PK String
- 都可以作为属性名,但是Symbol属性名不能被
for-in
遍历到,必须使用专属的遍历方式Object.getOwnPropertySymbols()
; Symbol
不会隐式的转成字符串的,因为两者都可以作为属性名,如果可以转成字符串那属性名字容易误解。