深入浅出typescript -- symbol类型
symbol我们平时用的比较少,所以可能了解也不是很多,这里就详细来说说symbol。
1、symbol 基本使用
symbol 是 ES6 新增的一种基本数据类型,它用来表示独一无二的值,可以通过 Symbol 构造函数生成。
ts
const s = Symbol();
typeof s; // symbol
注意:Symbol 前面不能加 new
关键字,直接调用即可创建一个独一无二的 symbol 类型的值。
可以在使用 Symbol 方法创建 symbol 类型值的时候传入一个参数,这个参数需要是一个字符串。如果传入的参数不是字符串,会先自动调用传入参数的 toString 方法转为字符串:
ts
const s1 = Symbol("TypeScript");
const s2 = Symbol("Typescript");
console.log(s1 === s2); // false
上面代码的第三行可能会报一个错误:This condition will always return 'false' since the types 'unique symbol' and 'unique symbol' have no overlap. 这是因为编译器检测到这里的 s1 === s2 始终是false,所以编译器提醒这代码写的多余,建议进行优化。
上面使用Symbol创建了两个symbol对象,方法中都传入了相同的字符串,但是两个symbol值仍然是false,这就说明了 Symbol 方法会返回一个独一无二的值。Symbol 方法传入的这个字符串,就是方便我们区分 symbol 值的。可以调用 symbol 值的 toString 方法将它转为字符串:
ts
const s1 = Symbol("Typescript");
console.log(s1.toString()); // 'Symbol(Typescript)'
console.log(Boolean(s)); // true
console.log(!s); // false
在TypeScript中使用symbol就是指定一个值的类型为symbol类型:
ts
let a: symbol = Symbol()
TypeScript 中还有一个 unique symbol 类型,它是symbol的子类型,这种类型的值只能由Symbol()
或Symbol.for()
创建,或者通过指定类型来指定变量是这种类型。这种类型的值只能用于常量的定义和用于属性名。需要注意,定义unique symbol类型的值,必须用 const 而不能用let来声明。下面来看在TypeScript中使用Symbol值作为属性名的例子:
ts
const key1: unique symbol = Symbol()
let key2: symbol = Symbol()
const obj = {
[key1]: 'value1',
[key2]: 'value2'
}
console.log(obj[key1]) // value1
console.log(obj[key2]) // error 类型"symbol"不能作为索引类型使用。
2、symbol 作为属性名
在ES6中,对象的属性是支持表达式的,可以使用于一个变量来作为属性名,这对于代码的简化有很多用处,表达式必须放在大括号内:
ts
let prop = "name";
const obj = {
[prop]: "TypeScript"
};
console.log(obj.name); // 'TypeScript'
symbol 也可以作为属性名,因为symbol的值是独一无二的,所以当它作为属性名时,不会与其他任何属性名重复。当需要访问这个属性时,只能使用这个symbol值来访问(必须使用方括号形式来访问):
ts
let name = Symbol();
let obj = {
[name]: "TypeScript"
};
console.log(obj); // { Symbol(): 'TypeScript' }
console.log(obj[name]); // 'TypeScript'
console.log(obj.name); // undefined
在使用obj.name访问时,实际上是字符串name,这和访问普通字符串类型的属性名是一样的,要想访问属性名为symbol类型的属性时,必须使用方括号。方括号中的name才是我们定义的symbol类型的变量name。
3、symbol 属性名遍历
使用 Symbol 类型值作为属性名,这个属性是不会被 for...in遍历到的,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 、 JSON.stringify() 等方法获取到:
ts
const name = Symbol("name");
const obj = {
[name]: "TypeScript",
age: 18
};
for (const key in obj) {
console.log(key);
}
// => 'age'
console.log(Object.keys(obj)); // ['age']
console.log(Object.getOwnPropertyNames(obj)); // ['age']
console.log(JSON.stringify(obj)); // '{ "age": 18 }
虽然这些方法都不能访问到Symbol类型的属性名,但是Symbol类型的属性并不是私有属性,可以使用 Object.getOwnPropertySymbols
方法获取对象的所有symbol类型的属性名:
ts
const name = Symbol("name");
const obj = {
[name]: "TypeScript",
age: 18
};
const SymbolPropNames = Object.getOwnPropertySymbols(obj);
console.log(SymbolPropNames); // [ Symbol(name) ]
console.log(obj[SymbolPropNames[0]]); // 'TypeScript'
除了这个方法,还可以使用ES6提供的 Reflect 对象的静态方法 Reflect.ownKeys ,它可以返回所有类型的属性名,Symbol 类型的也会返回:
ts
const name = Symbol("name");
const obj = {
[name]: "TypeScript",
age: 18
};
console.log(Reflect.ownKeys(obj)); // [ 'age', Symbol(name) ]
4、symbol 静态方法
Symbol 包含两个静态方法, for 和 keyFor 。
1)Symbol.for()
用Symbol创建的symbol类型的值都是独一无二的。使用 Symbol.for 方法传入字符串,会先检查有没有使用该字符串调用 Symbol.for 方法创建的 symbol 值。如果有,返回该值;如果没有,则使用该字符串新创建一个。使用该方法创建 symbol 值后会在全局范围进行注册。
ts
const iframe = document.createElement("iframe");
iframe.src = String(window.location);
document.body.appendChild(iframe);
iframe.contentWindow.Symbol.for("TypeScript") === Symbol.for("TypeScript"); // true // 注意:如果你在JavaScript环境中这段代码是没有问题的,但是如果在TypeScript开发环境中,可能会报错:类型"Window"上不存在属性"Symbol"。 // 因为这里编译器推断出iframe.contentWindow是Window类型,但是TypeScript的声明文件中,对Window的定义缺少Symbol这个字段,所以会报错,
上面代码中,创建了一个iframe节点并把它放在body中,通过这个 iframe 对象的 contentWindow 拿到这个 iframe 的 window 对象,在 iframe.contentWindow上添加一个值就相当于在当前页面定义一个全局变量一样。可以看到,在 iframe 中定义的键为 TypeScript 的 symbol 值在和在当前页面定义的键为'TypeScript'的symbol 值相等,说明它们是同一个值。
2) Symbol.keyFor()
该方法传入一个 symbol 值,返回该值在全局注册的键名:
ts
const sym = Symbol.for("TypeScript");
console.log(Symbol.keyFor(sym)); // 'TypeScript'