ES5 的对象属性名都是字符串,这容易形成属性名冲突的问题。好比,你使用别人的模块/对象, 又想为之添加新的属性,这就容易使得新属性名与原有属性名冲突。这就是 ES6 引入Symbol的缘由,symbol可以保证每一个属性的名字独一无二。javascript
Symbol是 ES6 引入了一种新的原始数据类型,它是一种特殊的、不可变的数据类型,能够做为对象属性的标识符使用,表示独一无二的值。凡是属性名属于 Symbol 类型,就都是独一无二的,能够保证不会与其余属性名产生冲突。前端
Symbol 值经过Symbol() 函数生成,Symbol()函数前不能使用new命令,不然会报错。这是由于生成的 Symbol 是一个原始类型的值,不是对象。也就是说,因为 Symbol 值不是对象,因此不能添加属性。基本上,它是一种相似于字符串的数据类型。
java
Symbol([description])数组
description :可选的字符串。表示对symbol的描述,可用于调试但不访问符号自己的符号的说明。若是不加参数,在控制台打印的都是Symbol,不利于区分。
微信
let s = Symbol();
typeof s // "symbol"
// 上面代码中,变量s就是一个独一无二的值。
// typeof运算符的结果,代表变量s是Symbol数据类型,而不是字符串之类的其余类型。复制代码
let s1 = Symbol()
let s2 = Symbol()
s1 // Symbol()
s2 // Symbol()
// s1和s2是两个 Symbol 值。若是不加参数,它们在控制台的输出都是Symbol(),不利于区分。
let s3 = Symbol('foo');
let s4 = Symbol('bar');
s3 // Symbol(foo)
s4 // Symbol(bar)
// 有了参数之后,就等于为它们加上了描述,输出的时候就可以分清,究竟是哪个值。
s3.toString() // "Symbol(foo)"
s4.toString() // "Symbol(bar)"复制代码
// Symbol函数的参数只是表示对当前Symbol值的描述,所以相同参数的Symbol函数的返回值是不相等的。
// 没有参数的状况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的状况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false复制代码
【3.1】Symbol不能与其余类型的值进行运算,会报错函数
let sym = Symbol('My symbol');
console.log("your symbol is " + sym) // TypeError: can't convert symbol to string
console.log(`your symbol is ${sym}`) // TypeError: can't convert symbol to string
console.log(2 + sym) // TypeError: can't convert symbol to number复制代码
【3.2】 Symbol 值能够显式转为字符串,也能够转为布尔值,可是不能转为数值。ui
let sym = Symbol('My symbol');
// 转字符串
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
// 转布尔值
Boolean(sym) // true
!sym // false
// 转数字
Number(sym) // TypeError
sym + 2 // TypeError复制代码
【3.3】Symbol.prototype.descriptionthis
上面代码中,咱们能够将Symbol值显示转换字符串,可是这种用法不是很方便,ES2019提供了一个实例属性description
,直接返回 Symbol 的描述。spa
const sym = Symbol('foo');
sym.description // "foo"
// sym的描述就是字符串foo复制代码
【4.1】做为属性名的使用prototype
因为每个 Symbol 值都是不相等的,这意味着 Symbol 值能够做为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的状况很是有用,能防止某一个键被不当心改写或覆盖。注意,Symbol 值做为对象属性名时,不能用点运算符。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, {
value: 'Hello!'
});
// 以上写法都获得一样结果
a[mySymbol] // "Hello!"
// 不能用点运算符
const sym = Symbol();
const b = {};
b.sym = 'Hello!';
b[sym] // undefined
b['sym'] // "Hello!" 字符串复制代码
【4.1】定义常量
Symbol 类型还能够用于定义一组常量,保证这组常量的值都是不相等的。
const RED = Symbol();
const BLACK = Symbol();
function getComplement(color) {
switch (color) {
case RED:
return '红色';
case BLACK:
return '黑色';
default:
throw new Error('不存在');
}
}
getComplement(RED) // 红色复制代码
【4.3】Symbol类型的属性具备必定的隐藏性
let name = Symbol('name');
let obj = {
age: 22,
[name]: 'Joh'
};
console.log(Object.keys(obj)); // ["age"], 打印不出类型为Symbol的[name]属性
// 使用for-in也打印不出 类型为Symbol的[name]属性
for (let k in obj) {
console.log(k); // age
}
// 使用 Object.getOwnPropertyNames 一样打印不出 类型为Symbol的[name]属性
console.log(Object.getOwnPropertyNames(obj)); ["age"]
// 使用 Object.getOwnPropertySymbols 能够
let key = Object.getOwnPropertySymbols(obj)[0];
console.log(obj[key]); // Joh复制代码
【4.4】Symbol.for 的应用
有时,咱们但愿从新使用同一个 Symbol 值,Symbol.for
方法能够作到这一点。它接受一个字符串做为参数,而后搜索有没有以该参数做为名称的 Symbol 值。若是有,就返回这个 Symbol 值,不然就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
// 上面代码中,s1和s2都是Symbol值,可是它们都是由一样参数的Symbol.for方法生成的,因此其实是同一个值。
let s3 = Symbol('foo');
let s4 = Symbol('foo');
s3 === s4 // false
// 因为Symbol()写法没有登记机制,因此每次调用都会返回一个不一样的值。复制代码
Symbol.for()
与
Symbol() 这两种写法,都会生成新的 Symbol。它们的区别是:
- Symbol.for() 会被登记在全局环境中供搜索,Symbol() 不会
- Symbol.for() 不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的 key 是否已经存在,若是不存在才会新建一个值。好比,若是你调用
Symbol.for("cat")
30 次,每次都会返回同一个 Symbol 值,可是调用
Symbol("cat")
30 次,会返回 30 个不一样的 Symbol 值。
【4.5】Symbol.keyFor的应用
Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的key。注意,
Symbol.keyFor()在函数内部运行,可是生成的 Symbol 值也是登记在全局环境的。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
// 上面代码中,变量s2属于未登记的 Symbol 值,因此返回undefined
function bar() {
return Symbol.for('bar');
}
const x = bar();
const y = Symbol.for('bar');
console.log(x === y); // true
// 上面代码中,Symbol.for('bar')是函数内部运行的,可是生成的 Symbol 值是登记在全局环境的
// 因此,第二次运行Symbol.for('bar')能够取到这个 Symbol 值。复制代码
除了定义本身使用的 Symbol 值之外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
【5.1】Symbol.hasInstance
对象的Symbol.hasInstance
属性,指向一个内部方法。当其余对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。好比,foo instanceof Foo
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
[1, 2, 3] instanceof new MyClass() // true
// 上面代码中,MyClass是一个类,new MyClass()会返回一个实例。
// 该实例的Symbol.hasInstance方法,会在进行instanceof运算时自动调用,判断左侧的运算子是否为Array的实例。复制代码
【5.2】Symbol.isConcatSpreadable
对象的Symbol.isConcatSpreadable
属性等于一个布尔值,表示该对象用于Array.prototype.concat()时,是否能够展开。
let arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined
let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
// 上面代码说明,数组的默认行为是能够展开,Symbol.isConcatSpreadable默认等于undefined。
// 该属性等于true时,也有展开的效果,和undefined同样
// 该属性等于false时,不展开 复制代码
相似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable
属性设为true,才能够展开。
let obj = {length: 2, 0: 'c', 1: 'd'};
['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']
obj[Symbol.isConcatSpreadable] = true;
['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
// 上面相似数组Symbol.isConcatSpreadable设为true才能够展开复制代码
【5.3】Symbol.species
对象的Symbol.species
属性,指向一个构造函数。建立衍生对象时,会使用该属性。
class MyArray extends Array {
}
const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);
b instanceof MyArray // true
c instanceof MyArray // true
// 上面代码中,子类MyArray继承了父类Array,a是MyArray的实例,b和c是a的衍生对象。
// 你可能会认为,b和c都是调用数组方法生成的,因此应该是数组(Array的实例),但实际上它们也是MyArray的实例。复制代码
Symbol.species
属性就是为了解决这个问题而提供的。如今,咱们能够为myArray设置
Symbol.species
属性。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
// 上面代码中,因为定义了Symbol.species属性,建立衍生对象时就会使用这个属性返回的函数,做为构造函数。
// 这个例子也说明,定义Symbol.species属性要采用get取值器
const a = new MyArray();
const b = a.map(x => x);
b instanceof MyArray // false
b instanceof Array // true
// 上面代码中,a.map(x => x)生成的衍生对象b,就不是MyArray的实例,而直接就是Array的实例。复制代码
【5.4】Symbol.match
对象的 Symbol.match
属性,指向一个函数。当执行 str.match(myObject) 时,若是该属性存在,会调用它,返回该方法的返回值。
String.prototype.match(regexp)
// 等同于
regexp[Symbol.match](this)
class MyMatcher {
[Symbol.match](string) {
return 'hello world'.indexOf(string);
}
}
'e'.match(new MyMatcher()) // 1复制代码
【5.5】Symbol.replace
对象的Symbol.replace
属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
Symbol.replace
方法会收到两个参数,第一个参数是
replace
方法正在做用的对象,第二个参数是替换后的值
String.prototype.replace(searchValue, replaceValue)
// 等同于
searchValue[Symbol.replace](this, replaceValue)
const x = {};
x[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(x, 'World') // ["Hello", "World"]
// 上面代码中, Symbol.replace方法收到两个参数
// 第一个参数是replace方法正在做用的对象,上面例子是Hello
// 第二个参数是替换后的值,上面例子是World。复制代码
【5.6】Symbol.search
对象的Symbol.search
属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)
class MySearch {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
'foobar'.search(new MySearch('foo')) // 0复制代码
【5.7】Symbol.split
对象的Symbol.split
属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。
String.prototype.split(separator, limit)
// 等同于
separator[Symbol.split](this, limit)
// 下面方法使用Symbol.split方法,从新定义了字符串对象的split方法的行为
class MySplitter {
constructor(value) {
this.value = value;
}
[Symbol.split](string) {
let index = string.indexOf(this.value);
if (index === -1) {
return string;
}
return [
string.substr(0, index),
string.substr(index + this.value.length)
];
}
}
'foobar'.split(new MySplitter('foo'))
// ['', 'bar']
'foobar'.split(new MySplitter('bar'))
// ['foo', '']
'foobar'.split(new MySplitter('baz'))
// 'foobar'复制代码
【5.8】Symbol.iterator
对象的Symbol.iterator
属性,指向该对象的默认遍历器方法
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]复制代码
【5.9】Symbol.toPrimitive
对象的Symbol.toPrimitive
属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
Symbol.toPrimitive
被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。
let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
};
2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'复制代码
【5.10】Symbol.toStringTag
对象的Symbol.toStringTag
属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,若是这个属性存在,它的返回值会出如今
toString
方法返回的字符串之中,表示对象的类型。也就是说,这个属性能够用来定制
[
object Object]
或[o
bject Array]
中object后面的那个字符串。
// 例一
({[Symbol.toStringTag]: 'Foo'}.toString())
// "[object Foo]"
// 例二
class Collection {
get [Symbol.toStringTag]() {
return 'xxx';
}
}
let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"复制代码
【5.11】Symbol.unscopables
对象的Symbol.unscopables
属性,指向一个对象。该对象指定了使用with关键字时,那些属性会被
with
环境排除。
Array.prototype[Symbol.unscopables]
// {
// copyWithin: true,
// entries: true,
// fill: true,
// find: true,
// findIndex: true,
// includes: true,
// keys: true
// }
Object.keys(Array.prototype[Symbol.unscopables])
// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys']
// 上面代码说明,数组有 7 个属性,会被with命令排除。复制代码
// 没有 unscopables 时
class MyClass {
foo() { return 1; }
}
let foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 1
}
// 有 unscopables 时
class MyClass {
foo() { return 1; }
get [Symbol.unscopables]() {
return { foo: true };
}
}
let foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 2
}
// 上面代码经过指定Symbol.unscopables属性,使得with语法块不会在当前做用域寻找foo属性,即foo将指向外层做用域的变量。复制代码
文章每周持续更新,能够微信搜索「 前端大集锦 」第一时间阅读,回复【视频】【书籍】领取200G视频资料和30本PDF书籍资料