红宝书[Symbol]基础

127 阅读5分钟

3.4.7: Symbol类型

Symbol(符号)时ECMAScript6新增的数据类型。符号时原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险

3.4.7.1:符号的基本用法

符号需要使用Symbol()函数初始化,因为符号本身是原始类型,所以typeof操作符对符号返回symbol。

let sym = Symbol();
console.log(typeof sym); //symbol

调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),将来可以通过这个字符串来调试代码,但是,这个字符串参数与符号定义或标示完全无关:

let genericsSymbol = Symbol();
let otherGenericSymbol = Symbol();
​
let fooSymbol = Symbol('foo');
let otherFooSymbol = Symbol('foo');
​
​
console.log(genericSymbol == otherGenericSymbol);  // false 
console.log(fooSymbol == otherFooSymbol);          // false

符号没有字面量语法,这也是它们发挥作用的关键,按照规范,只要你创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性。

let genericSymbol = Symbol();
console.log(genericSymbol);// Symbol()
let fooSymbol = Symbol('foo');
console.log(fooSymbol);// Symbol(foo);

注意:Symbol()函数不能与new关键字一起作为构造函数使用。这样做是为了避免创建符号包装对象,像使用Boolean、String 或Number那样,它们都支持构造函数且可以用于初始化包含原始值的包装对象:

let myBoolean = new Boolean();
console.log(typeof myBoolean); //"object"let myString = new String();
console.log(typeof myString); //"object"let muyNumber = new Number();
console.log(typeof muyNumber); //objectlet mySymbol = new Symbol(); //TypeError:Symbol is not a constructor

如果你确实想使用符号包装对象,可以借用Object()函数:

let mySymbol = Symbol();
let myWrappedSymbol = Object(mySymbol);
console.log(typeof myWrappedSymbol); //"object"

3.4.7.2:使用全局符号注册表

如果运行的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。

为此要使用Symbol.for()方法:

let fooGlobalSymbol = Symbol.for('foo');
console.log(typeof fooGlobalSymbol); //symbol

Symbol.for()对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后会返回该符号实例。

let fooGlobalSymbol = Symbol.for('foo'); //创建新符号
let otherFooGlobalSymbol = Symbol.for('foo');//重用已有符号
console.log(fooGlobalSymbol === otherFooGlobalSymbol) //true

即使采用相同的符号描述,在全局注册表中定义的符号跟使用Symbol()定义的符号也并不等同:

let localSymbol = Symbol('foo')
let globalSymbol = Symbol.for('foo');
console.log(localSymbol === globalSymbol); //false

全局注册表中的符号必须使用字符串键来创建,因此作为参数传给Symbol.for()的任何值都会被转换称为字符串,此外,注册表中使用的键同时也会被用作符号描述。

let emptyGlobalSymbol = Symbol.for();
console.log(emptyGlobalSymbol);// Symbol(undefined)

还可以使用Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字符串键。如果查询的不是全局符号,则返回undefined。

//创建全局符号
let s = Symbol.for('foo');
console.log(Symbol.keyFor(s)); //foo//创建普通符号
let s2 = Symbol('bar');
console.log(Symbol.keFor(s2)) //undefinede

如果传给Symbol.keyFor()的不是符号,则该方法抛出TypeError;

Symbol.keyFor(123); TypeError: 123 is not a symbol

3.4.7.3:使用符号作为属性

凡是可以使用字符串或数值作为属性的地方,都可以使用符号。这就包括了对象字面量属性和Object.defineProperty()//Objecrt.defineProperties()定义的属性,对象字面量只能在计算属性语法中使用符号作为属性。

let s1 = Symbol('foo');
    s2 = Symbol('bar');
    s3 = Symbol('baz');
    s4 = Symbol('qux');
let o = {
  [s1]: 'foo val'
};
//这样也可以:o[s1] = 'foo val';
​
console.log(o);
//{Symbol(foo):foo val}
​
Object.defineProperty(o,s2,{value:'var val'});
console.log(o);
//{Symbol(foo): foo val, Symbol(bar): bar val}
Object.defineProperties(o,{
  [s3]: {value: 'baz val'},
  [s4]: {value: 'qux val'}
})
console.log(o);
// {Symbol(foo): foo val, Symbol(bar): bar val,
// Symbol(baz): baz val, Symbol(qux): qux val}

Object.defineProperty()方法会在一个对象上定义一个新的,或者修改一个对象的现有属性,并返回这个属性对象 Object.defineProperty 需要三个参数(object , propName , descriptor) 1 object 对象 => 给谁加 2 propName 属性名 => 要加的属性的名字 【类型:String】 3 descriptor 属性描述 => 加的这个属性有什么样的特性【类型:Object】

Object.defineProperties()方法直接在一个对象上定义一个或多个新的属性或修改现有属性,并返回该对象。

类似于Object.getOwnPropertyNames()返回对象实例的常规属性数组,Object.getOwnProperty-Symbols()返回对象实例的符号属性数组。这两个方法的返回值彼此互斥。Object.getOwnProperty-Descriptors()会返回同时包含常规和符号属性描述符的对象。Reflect.ownKeys()会返回两种类型的键:

let s1 = Symbol('foo'),
    s2 = Symbol('bar');
​
let o = {
  [s1]: 'foo val',
  [s2]: 'bar val',
  baz: 'baz val',
  qux: 'qux val'
};
​
console.log(Object.getOwnPropertySymbols(o));
 // [Symbol(foo), Symbol(bar)]
console.log(Object.getOwnPropertyNames(o))
// ["baz","qux"]
console.log(Object.getOwnPropertyDescriptors(o))
{
  baz: {
    value: 'baz val',
    writable: true,
    enumerable: true,
    configurable: true
  }, //以此类推
}
​
console.log(Reflect.ownKeys(o));
//["baz", "qux", Symbol(foo), Symbol(bar)]

因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失。但是,如果没有显示地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键:

let o = {
  [Symbol('foo')]: 'foo val',
  [Symbol('bar')]: 'bar val'
}
console.log(o);
// {Symbol(foo): "foo val", Symbol(bar): "bar val"}
let barSymbol = Object.getOwnPropertySymbols(o).find(symbol) => symbol.toString().match(/bar/);
console.log(barSymbol) //Symbol(bar)

总结:

  • symbol在全局注册的时候和用symbol注册不相等
  • symbol在里面传参即使是相同字符串也是不相等
  • symbol不能用new 构建
  • Symbol.keyFor()来查询全局注册表,但是普通的符号是查找不到哦,还有就是传入不是符号会抛出错误
  • Object.defineProperty可以配合文章中symbol使用,请阅读,文章中也有基本解释