ECMAScript 6 通过在原型链上定义与Symbol相关的属性来暴露语言内部逻辑,使得开发者可以对一些语言的默认行为做配置。接下来我们来看看有哪些重要的Symbol属性可供我们使用:
1: Symbol.hasInstance 一个在执行 instanceof 时调用的方法,用于检测对象的继承信息
2: Symbol.isConcatSpreadable 一个布尔值,用于表示当传递一个集合作为Array.prototype.concat()的参数是,是否应该将集合内的元素拍平到同一层级
3: Symbol.iterator 在迭代器和生成器那篇文章已经细讲过
4: Symbol.match 一个在调用String.prototype.match()时调用的方法,用于比较字符串
5: Symbol.replace 一个在调用String.prototype.replace()时调用的方法,用于替换字符串的子串
6: Symbol.search 一个在调用String.prototype.search()时调用的方法,用于定位子串在字符串中的位置
7: Symbol.split 一个在调用String.prototype.split()时调用的方法,用于分割字符串
8: Symbol.species 用于创建派生对象的构造函数
9: Symbol.toPrimitive 一个返回对象原始值的方法
10: Symbol.toStringTag 一个在调用Object.prototype.toString()时使用的字符换,用于创建对象的描述
11: Symbol.unscopables 一个定义了一些不可被with语句引用的对象属性名称的对象集合
上面的属性很多,这里会挑一些比较重要和常用的来讲:
1: Symbol.hasInstance
Symbol.hasInstance用于确定对象是否为函数的实例。此方法定义在Function.prototype中,所以所有的函数都默认继承了此方法。当我们在调用例如
obj instanceof Array;
其实等价于:
Array[Symbol.instanceof](obj);
文章开头我们就说了这些symbol属性是为了开发者能定制化语言的内部逻辑,那我们怎样改写Symbol.hasInstance的默认行为呢?这里面有一点特殊的是,为了确保Symbol.instance不会被意外地重写,该方法是被默认定义为不可写,不可配置,和不可枚举。如果确定要改写它,必须通过Object.definePropoty()方法:
function MyObject(){};
Object.defineProperty(MyObject, Symbol.hasInstance, {
value: function () {
return false;
}
});
let obj = new MyObject();
console.log(obj instanceof MyObject); // false
以上代码,我们就重写了Symbol.hasInstance的默认逻辑,让它始终返回false,所以哪怕明明 obj instanceof MyObject 逻辑上应该返回true,但是结果得到false。 这个例子本身逻辑上没有意义,甚至是错误地,只是想要展示一下Symbol.hasInstance的用法。
我们还也可以让Symbol.hasInstance只是某些情况下返回true,但是一定要保证instanceof的左边是一个对象,这样才能触发对Symbol.hasInstance的调用,不然instanceof总是返回false。
2: Symbol.isConcatSpreadable
Symbol.isConcatSpreadable是一个布尔值,用于确定当调用数组的concat()方法时,如果传入参数是一个数组,是否需要将这个数组拍平。看下面一段代码:
Array.prototype[Symbol.isConcatSpreadable] = false;
let result = [1, 2].concat([3, 4], 5);
console.log(result);//[[1, 2], [3, 4], 5]
对比一下Symbol.isConcatSpreadable为true的情况:
Array.prototype[Symbol.isConcatSpreadable] = true;
let result = [1, 2].concat([3, 4], 5);
console.log(result); // [1, 2, 3, 4, 5]
ES6之前,对于concat()参数为数组的情况,默认是拍平为一个层级的。ES6提供了这个窗口,让开发者觉得这一行为。但是这里不建议直接修改Array的Symbol.isConcatSpreadable属性,如果真的需要修改这一属性,可以在派生数组子类里面进行修改。
与其他的Symbol属性不同的是,Symbol.isConcatSpreadable并不默认出现在标准对象中。在一个类数组对象中,如果我们设置Symbol.isConcatSpreadable属性为true,当执行concat()时,类数组对象的数值型属性也会被独立添加到concat()的调用结果中:
let collection = {
0: 'a',
1: 'b',
length: 2,
[Symbol.isConcatSpreadable]: true
};
console.log(['test'].concat(collection)); // ["test", "a", "b"]
3: Symbol.match, Symbol.replace, Symbol.search, Symbol.split
通常我们对一个字符串调用match(), replace(), search(), split()方法,它们的参数,既可以是字符串,也可以是一个正则表达式。但是它们本身内部的逻辑,我们是不能修改的。而Symbol.match, Symbol.replace, Symbol.search, Symbol.split这四个属性,就是开放了这个窗口,让开发者可以自行定义其内部逻辑,下面开一个Symbol.match的例子,其他三个也是一样的:
let hasLengthOf10 = {
[Symbol.match]: function (value) {
return value.length === 10 ? [value] : null
}
};
console.log('abcdefghij'.match(hasLengthOf10)); // [abcdefghij]
console.log('abcdefghi'.match(hasLengthOf10)); // null
4: Symbol.toPrimitive
我们知道,对象不属于基本类型。那如果我们对对象进行一些基本类型的操作会怎样呢?比如alert一个对象:alert(obj), 或者对对象进行数学计算:obj1 - obj2, obj3 / 4等。一般呢,这些情况下,需要将对象先转换为基本数据类型,但是转换的规则是什么呢?ES6提供了Symbol.toPrimitive方法给开发者,开发者可以自行定义。Symbol.toPrimitive有一个重要的参数,规范中叫做类型提示(hint)。hint为String类型,有三种可能的值:
1: 'number' 当转换场景为需要对象作为一个数字
2: 'string' 当转换场景为需要对象作为一个字符串
3: 'default' 当转换场景模棱两可的时候
这里需要注意的是,hint是JavaScript的引擎传入给Symbol.toPrimitive方法,开发者只需要针对它可能的三种值编写自己的逻辑,例如:
let person = {
'name': 'mike',
'age': 24,
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number': {
return this.age;
}
case 'string': {
return this.name
}
case 'default': {
return this.age + this.name
}
}
}
};
console.log(person / 2); // 12
alert(person);// alert弹窗内容为 mike
console.log(person + 100); // 24mike100
代码console.log(person + 100)
这里执行了一个加法操作,因为加法既可以用作字符串类型,也可以用作数字类型,所以这里就命中了hint为'default'
的情况。
5: Symbol.toStringTag
Symbol.toStringTag定义了调用Object.prototype.toString()是返回的身份标识。例如调用:
Object.prototype.toString.call([]);// '[object Array]'
'Array'这是储存在数组对象的Symbol.toStringTag属性中。
一般我们自己创建的对象,如果调用以上方法,或者对象的toString()方法会得到"[object Object]"
。我们可以重写对象的Symbol.toStringTag来自定义对象调用toString()方法时得到的值:
function Person(name) {
this.name = name;
}
Person.prototype[Symbol.toStringTag] = 'Person';
let mike = new Person('mike');
console.log(Object.prototype.toString.call(mike)); // [object Person]
console.log(mike.toString());// [object Person]
但是,你也可以重写对象的toString()方法,这样并不会对Object.prototype.toString.call()造成影响:
function Person(name) {
this.name = name;
}
let mike = new Person('mike');
Person.prototype[Symbol.toStringTag] = 'Person';
Person.prototype.toString = function () {
return this.name;
};
console.log(Object.prototype.toString.call(mike));// [object Person]
console.log(mike.toString());// mike
以上就是所有的几个比较重要的Symbol属性。