普通符号
什么是符号?
符号是ES6新增的一个数据类型,属于原始值
原始值:developer.mozilla.org/zh-CN/docs/…
js数据类型与数据结构:developer.mozilla.org/zh-CN/docs/…
怎么创建符号?
我们之前学习的number,string,boolean等,都是可以通过字面量的形式创建,但是符号只能通过执行函数Symbol(描述信息)创建,没有其他的创建方式。
const symb = Symbol('这是一个符号');
console.log(symb); // Symbol(这是一个符号)
console.log(typeof symb); // symbol
符号有什么用?
添加符号类型的初衷,是为了给对象设置私有属性。
我们看下面这个例子:
const player = {
aggressivity: 30, // 攻击力
defence: 10, // 防御力
hp: 100, // 血量
attack() { // 攻击
// 执行攻击函数,随机产生伤害,伤害 = 攻击力 * 随机百分比
const hurt = this.aggressivity * this.getRandom(0.6, 1);
console.log(hurt);
},
getRandom(min: number = 0, max: number = 1) {
return Math.random() * (max - min) + min;
}
}
在这个例子中,玩家对象具有攻击力,防御力,血量三个属性,和一个攻击方法,执行攻击方法可以产生一个随机的伤害值,伤害值在攻击力的60%~100%之间随机获取,因此对象中有一个获取随机数的函数。
现在,外部可以访问到player对象的所有属性,但是我们会发现,player.getRandom这个方法,对于外部来说是没必要暴露出去的,暴露出去后不仅没什么用,反而增加了这个对象的使用难度。
解决这个问题,通常我们有以下两种方法:
-
将getRandom函数定义在attack函数内部,但是这样会有以下问题:
- 每次执行attack函数的时候,都会重新创建一个getRandom函数
- 如果player对象的其他地方也要使用getRandom函数,是没有办法共享的
-
将getRandom函数定义在外部,这样player对象就不会有getRandom这个方法,但是这样也会有问题:
- 如果这个getRandom函数需要用到player对象的其他属性呢?这个方法又行不通了。
造成以上问题的根本原因,就是缺失“私有属性”
符号的特点
- 每次调用
Symbol函数创建的符号,永远不相等,不管符号描述是否一样。这也是为什么符号可以作为私有属性的原因。
const syb1 = Symbol('desc');
const syb2 = Symbol('desc');
console.log(syb1 === syb2) // false
- 符号可以作为对象的属性名存在,我们称之为“符号属性”
在符号出现之前,我们对象的属性名全部是以string的形式存在
- 由于符号“唯一”的特点,开发者可以通过精心设计,让符号属性无法被外界访问
const player = (() => {
const getRandomSyb = Symbol('get-random');
return {
aggressivity: 30, // 攻击力
defence: 10, // 防御力
hp: 100, // 血量
attack() { // 攻击
// 执行攻击函数,随机产生伤害,伤害 = 攻击力 * 随机百分比
const hurt = this.aggressivity * this[getRandomSyb](0.6, 1);
console.log(hurt);
},
[getRandomSyb](min: number = 0, max: number = 1) {
return Math.random() * (max - min) + min;
}
}
})();
- 符号属性不可枚举,所以是无法在forin循环中获取到符号属性,Object.keys方法也无法获取,即使是可以获取到所有无法枚举属性的
Object.getOwnPropertyNames也无能为力 - 但是,ES6为了防止某些特殊情况下必须获取符号属性,ES6给我们提供了一个
Object.getOwnPropertySymbols方法让我们可以获取到符号属性 - 符号无法进行隐式类型转换,像数学运算、字符串拼接等等,都不可以。但是符号可以通过String()显示类型转换为字符串,console.log输出的符号就是先通过String将符号转化为字符串,然后再将字符串染色输出到控制台。
共享符号
通过“普通符号”知道,我们可以通过Object.getOwnPropertySymbols获取符号属性,那我们有没有一种更加简单方式获取同一个符号呢?有,这就是共享符号。
什么是共享符号?
可以根据符号描述来获取同一个符号的符号,换句话说,只要符号的描述信息相同,符号就相同
怎么创建共享符号?
通过Symbol.for(描述信息) 创建
const syb1 = Symbol.for('desc');
const syb2 = Symbol.for('desc');
console.log(syb1 === syb2) // true
我们运用共享符号改写player对象
const player = (() => {
const getRandomSyb = Symbol.for('get-random');
return {
aggressivity: 30, // 攻击力
defence: 10, // 防御力
hp: 100, // 血量
attack() { // 攻击
// 执行攻击函数,随机产生伤害,伤害 = 攻击力 * 随机百分比
const hurt = this.aggressivity * this[getRandomSyb](0.6, 1);
console.log(hurt);
},
[getRandomSyb](min: number = 0, max: number = 1) {
return Math.random() * (max - min) + min;
}
}
})();
console.log(player[Symbol.for('get-random')]); // 可以访问到
此时我们可以通过创建含有相同“描述信息”的符号,来获取对应的符号属性。
共享符号平常使用很少,因为既然我们允许外面访问属性,那我们直接使用字符串做属性名就可以啦!
实现一个共享符号
const SymbolFor = (() => {
const global = {};
return (name: string) => {
if (!(name in global)) {
global[name] = Symbol(name);
}
return global[name];
}
})();
知名符号
什么是知名符号?
知名符号是一些具有特殊含义的“共享符号”,知名符号不可以创建,但是JS内部本身就创建好了一些知名符号,我们可以Symbol的静态属性得到知名符号。
知名符号有什么用?
减少魔法,暴露内部实现。
知名符号举例
- Symbol.hasInstance
这个符号用于判断一个对象是否是某个构造函数的实例,使用它可以修改instanceof的判定行为。
u instanceof User等效于User[Symbol.hasInstance](u),很明显,现在Symbol.hasInstance这个符号是构造函数User上的一个静态方法,我们可以通过给这个符号重新赋值来自定义它的行为
class User {
static [Symbol.hasInstance](ins) {
return false;
}
}
const u = new User();
console.log(u instanceof User);
这样 instanceof 在User构造函数上就永远返回false了。
现在,我们就可以参与instaceof的内部实现啦!
- 其他知名符号: