ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突
一、Symbol()
Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法
语法:Symbol([description])
参数:description 可选的,字符串。symbol的description可以用于调试,但无法访问到symbol本身
let str = 'foo';
let sym1 = Symbol();
let sym2 = Symbol(str);
let sym3 = Symbol(str);
console.log(sym2 === sym3); //false
注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型
二、Symbol静态属性
1.Symbol.hasInstance属性
用于判断某对象是否为某构造器的实例,因此你可以用它自定义 instanceof 操作符在某个类上的行为。对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法
class MyPrime {
static [Symbol.hasInstance](obj) {
let element = Number(obj)
for (let start = 2; start < element; start++) {
if (element % start === 0){
return false;
break;
}
}
return true;
}
}
console.log(5 instanceof MyPrime);//true
console.log(10 instanceof MyPrime);//false
2.Symbol.isConcatSpreadable属性
用于配置某对象作为Array.prototype.concat()方法的参数时是否展开其数组元素。对于数组对象,默认情况下,用于concat时,会按数组元素展开然后进行连接(数组元素作为新数组的元素)。重置Symbol.isConcatSpreadable可以改变默认行为。对于类似数组的对象,用于concat时,该对象整体作为新数组的元素,重置Symbol.isConcatSpreadable可改变默认行为。
let array1 = ['a','b','c'];
let array2 = ['d','e','f'];
console.log(array1.concat(array2)); //['a','b','c','d','e','f']
array2[Symbol.isConcatSpreadable] = false;
console.log(array1.concat(array2)); //['a','b','c',['d','e','f']]
let arrayLike = {length:2,0:'g',1:'h'};
console.log(array1.concat(arrayLike)); //["a","b","c",{"0":"g","1":"h","length":2}]
console.log(arrayLike[Symbol.isConcatSpreadable]); //undefined
arrayLike[Symbol.isConcatSpreadable] = true;
console.log(JSON.stringify(array1.concat(arrayLike))); //["a","b","c","g","h"]
3.Symbol.iterator属性
为每一个对象定义了默认的迭代器,该迭代器可以被 for...of 循环使用。对象进行for...of循环时,会调用Symbol.iterator方法,返回该对象的默认遍历器
let iterator = {};
iterator[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
console.log([...iterator]); //[1,2,3]
class MyIterator {
constructor(value) {
this.value = value;
}
*[Symbol.iterator]() {
let i = 0;
while (this.value[i] !== undefined) {
yield this.value[i];
i++;
}
}
}
let myIterator = new MyIterator([1,2,3,4]);
for (let value of myIterator) {
console.log(value);//1 2 3 4
}
4.Symbol.match属性
指定了匹配的是正则表达式而不是字符串。String.prototype.match() 方法会调用此函数;String.prototype.startsWith(),String.prototype.endsWith() 和 String.prototype.includes() 这些方法会检查其第一个参数是否是正则表达式,是正则表达式就抛出一个TypeError。 如果 match symbol 设置为 false(或者一个 假值),就表示该对象不打算用作正则表达式对象
let reg = /foo/;
reg[Symbol.match] = false;
console.log("/foo/".startsWith(reg)); //true
class MyMatch {
[Symbol.match](string) {
return '/foo/'.startsWith(string);
}
}
console.log('/foo/'.match(new MyMatch()));//true
5.Symbol.replace属性
指定了当一个字符串替换所匹配字符串时所调用的方法。String.prototype.replace() 方法会调用此方法
let x = {};
x[Symbol.replace] = (...s) => {
console.log(s);//["hello","world"]
};
'hello'.replace(x,'world');
6.Symbol.search属性
指定了一个搜索方法,这个方法接受用户输入的正则表达式,返回该正则表达式在字符串中匹配到的下标。这个方法由以下的方法来调用 String.prototype.search()
class MySearch{
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
console.log('foobar'.search(new MySearch('foo'))); //0
7.species属性
访问器属性允许子类覆盖对象的默认构造函数
class MyArray extends Array {
}
let a = new MyArray(1,2,3);
let b = a.map(x => x);
let c = a.filter(x => x > 1);
console.log(b instanceof MyArray);//true
console.log(c instanceof MyArray);//true
class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}
let a = new MyArray(1,2,3);
let b = a.map(x => x);
console.log(b instanceof MyArray);//false
console.log(b instanceof Array);//true
8.Symbol.split属性
指向 一个正则表达式的索引处分割字符串的方法。这个方法通过 String.prototype.split() 调用
class MySplit{
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)];
}
}
console.log('hello,world,js'.split(new MySplit(',')));//["hello","world,js"]
9.Symbol.toPrimitive属性
指将被调用的指定函数值的属性转换为相对应的原始值。Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式:
Number:该场合需要转成数值String:该场合需要转成字符串
Default:该场合可以转成数值,也可以转成字符串
let obj1 = {};
console.log(+obj1); //NaN
console.log(`${obj1}`); //[object Object]
console.log(obj1 + ''); //[object Object]
let obj2 = {
[Symbol.toPrimitive](hint) {
if (hint == "number") {
return 10;
}
if (hint == "string") {
return "hello";
}
return true;
}
};
console.log(+obj2); //10
console.log(`${obj2}`); //hello
console.log(obj2 + ''); //true
10.Symbol.toStringTag属性
通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签。通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里。许多内置的 JavaScript 对象类型即便没有 toStringTag 属性,也能被 toString() 方法识别并返回特定的类型标签
Object.prototype.toString.call('foo'); // "[object String]"
Object.prototype.toString.call([1, 2]); // "[object Array]"
Object.prototype.toString.call(3); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
class MyClass {}
console.log(Object.prototype.toString.call(new MyClass())); // [object Object]
class MyClass {
get [Symbol.toStringTag]() {
return "MyClass";
}
}
console.log(Object.prototype.toString.call(new MyClass()));// [object MyClass]
11.Symbol.unscopables属性
指用于指定对象值,其对象自身和继承的从关联对象的 with 环境绑定中排除的属性名称
Object.keys(Array.prototype[Symbol.unscopables])
// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys']
上面代码说明,数组有 7 个属性,会被with命令排除
class MyWith {
foo() { return 1; }
}
let foo = function () { return 2; };
with (MyWith.prototype) {
console.log(foo()); // 1
}
class MyWith {
foo() { return 1; }
get [Symbol.unscopables]() {
return { foo: true };
}
}
let foo = function () { return 2; };
with (MyWith.prototype) {
foo(); // 2
}
三、Symbol静态方法
1.Symbol.for(key)
方法会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
语法:Symbol.for(key);
参数:key
一个字符串,作为 symbol 注册表中与某 symbol 关联的键(同时也会作为该 symbol 的描述)
返回值:返回由给定的 key 找到的 symbol,否则就是返回新创建的 symbol
和 Symbol() 不同的是,用 Symbol.for() 方法创建的的 symbol 会被放入一个全局 symbol 注册表中。Symbol.for() 并不是每次都会创建一个新的 symbol,它会首先检查给定的 key 是否已经在注册表中了。假如是,则会直接返回上次存储的那个。否则,它会再新建一个
Symbol.for("foo"); // 创建一个 symbol 并放入 symbol 注册表中,键为 "foo"
Symbol.for("foo"); // 从 symbol 注册表中读取键为"foo"的 symbol
Symbol.for("bar") === Symbol.for("bar"); // true
Symbol("bar") === Symbol("bar"); // false,Symbol() 函数每次都会返回新的一个 symbol
let sym = Symbol.for("mario");
console.log(sym.toString());//Symbol(mario)
2.Symbol.keyFor(sym)
方法用来获取 symbol 注册表中与某个 symbol 关联的键
语法:Symbol.keyFor(sym);
参数:sym
存储在 symbol 注册表中的某个 symbol
let globalSym = Symbol.for("foo");
Symbol.keyFor(globalSym); // "foo"
// 创建一个 symbol,但不放入 symbol 注册表中
let localSym = Symbol();
Symbol.keyFor(localSym); // undefined,所以是找不到 key 的