符号: 符号是ES6新增的一个数据类型, 通过
Symbol(符号名)
来创建
目录:
在开始之前, 咱先回顾一下ES6之前的我们所知道的数据类型
string, number, boolean, object, undefined, null
符号设计的初衷: 是为了给对象设置私有属性
在过去我们JS是没有私有成员这一说的( 当然你通过特殊手段实现的不算 ), 但是私有成员又是非常有必要的, 特别是想__proto__这类属性, 官方其实是不想让你看到的, 但是我们不仅看到了我们还操控了, 相较于我们个人而言, 自己在开发的时候也有很多东西是想直接不让别人操控的, 过去我们都会习惯性的用语义化命名( 变量前加上_
)来提示其他合作伙伴这个属性是我私有的, 你别去操作
ES6的Symbol( 符号 )帮助我们处理了这个问题, 至于怎么处理的, 继续往下看吧
const syb = Symbol();
// Symbol函数接收的参数为描述信息, 用于方便开发者调试( 知道这个符号到底是干什么用 )
const syb2 = Symbol('helloWorld');
符号具有以下特点:
const syb = Symbol();
console.log( typeof syb ); // symbol
const syb = Symbol('abc');
const syb2 = Symbol('cbd');
console.log(syb === syb2); // false
符号可以作为对象的属性名存在, 这种属性称之为符号属性
由于第三点和这一点, 开发者可以通过精妙的设计, 让符号属性无法通过常规操作被外界访问到
const obj = (function () {
const valueSymbol = Symbol('valueSymbol');
return {
[valueSymbol]: '我是永远无法被更改的',
name: 'loki'
}
}())
console.log(obj)
obj.name = 'thor';
console.log(obj.name); // thor
console.log(obj[Symbol('valueSymbol')]); // undefined
如上, 我们的Symbol(valueSymbol)
属性别人虽然可以看到但是永远无法访问和修改, 比我们自己写_
注明安全多了吧
符号属性是不可枚举的, 因此在for…in中无法读取到符号属性, Object.keys也无法读取到符号属性
const obj = {
name: 'loki',
[Symbol('helloWorld')]: 'helloWorld'
}
for(const item in obj) {
console.log(item); // 只会输出name
}
const keys = Object.keys( obj );
console.log(keys); // ["name"]
Object.getOwnPropertyNames尽管可以得到无法枚举的属性, 但是仍然无法读取到Symbol属性
const obj = {
name: 'loki',
[Symbol('helloWorld')]: 'helloWorld'
}
const keys = Object.getOwnPropertyNames( obj );
console.log(keys); // ["name"]
ES6新增Object.getOwnPropertySymbols方法, 用于获取符号列表, 同时可以进行操作
const obj = {
name: 'loki',
[Symbol('helloWorld')]: 'helloWorld'
}
const symbols = Object.getOwnPropertySymbols( obj );
console.log(symbols); // [Symbol(helloWorld)]
console.log(obj[ symbols[0] ]); // helloWorld
obj[ symbols[0] ] = 123;
console.log(obj[ symbols[0] ]); // 123
符号无法进行隐式类型转换, 因此无法用于数学运算, 字符串拼接或者其他隐式类型转换的场景, 但是符号可通过String构造函数进行显示类型转换, console.log
函数能够输出符号也是因为在log函数中进行了显示转换
const symb = Symbol('hello');
const str = '123' + symb; // 这一句直接报错 Cannot convert a Symbol value to a string
啥是共享符号, 共享符号就是说, 在某些情况下, 你想让两个对象或者两个地方用同一个符号, 我们用上面普通符号的知识往往会这样做
const symb = Symbol('needRepeatSymbol');
const obj1 = {
[symb]: 'helloWorld'
}
const obj2 = {
[symb]: 'helloWorld2'
}
const symbs1 = Object.getOwnPropertySymbols(obj1);
const symbs2 = Object.getOwnPropertySymbols(obj2);
console.log(symbs1[0] === symbs2[0]); // true
上面那么写其实略显麻烦, 而且有一些问题( 比如如果这两哥们在不同的组件和作用域下, 那你还怎么给人家共享一个Symbol )
所以ES6还推出了一个创建共享符号的api: Symbol.for
const symb = Symbol.for('hello');
const symb2 = Symbol.for('hello');
const symb3 = Symbol('hello');
const symb4 = Symbol('hello');
console.log(symb === symb2); // true
console.log(symb3 === symb4); // false
console.log(symb === symb3); // false
知名符号是一些具有特殊含义的符号, 通过Symbol的静态属性可以拿到, 就是系统已经给你内置好了, 不是你自己写的, 包括系统很多的内置方法都有或多或少的用到这些知名符号
该符用于定义构造函数的静态成员, 它将影响
instanceof
的判定
const arr = [1 ,2, 3];
console.log(arr instanceof Array); // true, 这是我们的常规写法
// 同时arr instanceof Array也等效于如下写法
console.log(Array[Symbol.hasInstance](obj)); // true
上面说到它将影响instanceof的判定, 因为他是insetanceof的底层实现, 那我们可以来试一试
Object.defineProperty(Array, Symbol.hasInstance, {
value: function( obj ) {
return false;
}
})
const arr = [1, 2, 3];
console.log( arr instanceof Array ); // false
经过我们这么一折腾, 数组的instanceof已经出现了事与愿违的效果了, 所以这些知名符号其实就是ES6暴露给我们开发者, 让我们在某些情况下是可以直接参与到JS的内部实现的, 否则比如instanceof你看到他这语法都能给你整懵圈, 这是咋实现的啊? a instanceof b
, 现在你就知道了吧
该符号会影响数组的concat
const arr = [1, 2, 3];
const arr3 = arr.concat( 10, [4, 5, 5] );
console.log(arr3); // [1, 2, 3, 10, 4, 5, 5]
// 上面我们会发现concat方法在连接数组的时候会将数组展开, 如果我们不想让他展开
// 就操作Symbol.isConcatSpreadable
const newArr = [4, 5, 5];
newArr[Symbol.isConcatSpreadable] = false; // 设置为true就会展开, false就不会
const newArr2 = arr.concat(10, newArr);
console.log(newArr2); // [1, 2, 3, 10, [4, 5, 5]]
该符号会影响类型转换的结果
const obj = {
a: 1,
b: 123
}
const resp = obj + 123;
console.log(resp); // [object Object]123
// 上面的结果是因为发生了隐式类型转换, 过去我们是无法参与到这种JS的内部操作的, 但现在可以
obj[Symbol.toPrimitive] = function() {
return 10;
}
const newResp = obj + 123;
console.log(newResp); // 133还是number类型的