在JavaScript中,原本是有六种数据类型:undefined,null,数值,字符串,布尔值,对象。在ES6种,新增了Symbol这种数据类型
Symbol的构建
Symbol值通过Symbol函数来生成,表示独一无二的值,其方法可以传入一个字符串作为参数,表示对Symbol值的描述。但不管是否传入参数,参数是否相同,Symbol值都是独一无二的,没有两个Symbol值会相等。
var s1=Symbol()
var s2=Symbol()
s1===s2
//false
var s1=Symbol('s')
var s2=Symbol('s')
s1===s2
//false
若传入的参数不为字符串,那么会将其转为字符串,如果参数是对象,那么会调用参数的toString方法
var s=Symbol(1,2,3)
s
//Symbol(1)
var s=Symbol(true)
s
//Symbol(true)
var obj={}
var s=Symbol(obj)
s
//Symbol([object Object])
var obj = {
toString() {
return 'abc';
}
};
var sym = Symbol(obj);
sym // Symbol(abc)
Symbol不能参与其他类型的运算
let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string
Symbol可以使用String方法和toString方法显示地转为字符串,也可以使用Boolean方法转为布尔值(都为true),但不能转为数值
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
let sym = Symbol();
Boolean(sym) // true
!sym // false
if (sym) {
// ...
}
Number(sym) // TypeError
sym + 2 // TypeError
作为属性名的Symbol
Symbol值由于其独一无二的特性,用来做为属性的键名很合适,不会被其它模块的属性名覆盖。
Symbol值作为属性名要使用方括号定义,不能使用点运算符
var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
var a = {
[mySymbol]: 'Hello!'};
// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果a[mySymbol] // "Hello!"
var mySymbol = Symbol();
var a = {};
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
如上面代码中使用了点运算符,被理解为是用字符串作为属性名
使用Symbol值来定义常量也十分合适,可以保证常量的值都是不相等的
const COLOR_RED = Symbol();
const COLOR_GREEN = Symbol();
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_GREEN:
return COLOR_RED;
default:
throw new Error('Undefined color');
}
}
这样子只有使用唯一的常量值才能触发对应得代码块。
还有一点需要注意,Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。
消除魔术字符串
在阮一峰的ES6入门中提到魔术字符串是“在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值”。如下:
function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串
Triangle就是一个魔术字符串,它与代码强耦合,但是修改一处又得修改多处,这样就会导致代码的维护出现困难。消除魔术字符串可以通过使用变量来实现,只要变量最终的值不与其他值冲突就可以,这种情况很适合使用Symbol值。
const shapeType = {
triangle: Symbol()
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 });
Symbol值作为属性时的遍历
Symbol 作为属性名,不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,该方法返回一个数组,数组成员为指定对象的所有 Symbol 属性名。
var obj={}
var a=Symbol('a')
var b=Symbol('b')
obj[a]='a'
obj[b]='b'
for (let i in obj)
console.log(i)
//undefined
Object.keys(obj)
//[]
Object.getOwnPropertyNames(obj)
//[]
JSON.stringify(obj)
//"{}"
Object.getOwnPropertySymbols(obj)
//[Symbol(a), Symbol(b)]
使用Reflect.ownKeys也可以遍历到Symbol键名,除此外,常规的键名也能遍历到。
var a=Symbol('a');
var b=Symbol('b');
var obj={
foo:1
}
obj[a]=1;
obj[b]=2;
Reflect.ownKeys(obj);
//["foo", Symbol(a), Symbol(b)]
重用Symbol值
Symbol值有时也有要重用的时候,但是Symbol值是独一无二的,无法重用。这是可以使用Symbol.for方法来登记Symbol值,以达到重用的效果。Symbol.for接受一个参数,搜索全局中是否有该参数作名称的Symbol值,若有,则返回该Symbol值,达到重用的效果,若没有,则新建并返回一个以该参数作名称的Symbol值。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
Symbol.for和Symbol都可以新建一个Symbol值,不同的是,前者新建的Symbol值会登记,多次创建若传入参数相同则返回相同的Symbol值,而后者的不会,其每次新建的Symbol值都是独一无二的。
Symbol.for("bar") === Symbol.for("bar")
// true
Symbol("bar") === Symbol("bar")
// false
Symbol.keyFor返回一个已登记的Symbol值的key,即被Symbol.for创建的Symbol值得key。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
上面代码中,s1对应的Symbol值是被登记的,而s2对应的Symbol值是没被登记的,所以Symbol.keyFor(s1)返回”foo”,而Symbol.keyFor(s1)返回undefined
要注意的是,Symbol.for为 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。
iframe = document.createElement('iframe');
iframe.src = String(window.location);
document.body.appendChild(iframe);
iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
// true
内置的Symbol值
ES6提供了11个内置的Symbol值,在调用某些对象的方法和操作时会调用这些值指向的方法
Symbol.hasInstance
对象的Symbol.hasInstance属性指向一个内部方法,当其他对象在使用instanceof运算符判断是否为该对象实例时,会调用该方法
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
[1, 2, 3] instanceof new MyClass() // true
上面代码中,[1,2,3]使用instanceof运算符判断是否为MyClass的实例时调用了MyClass的[Symbol.hasInstance](foo)。
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable属性用于表示对象用于Array.prototype.concat()时是否可以展开。对于数组,默认是可以展开的,所以在Symbol.isConcatSpreadable属性为true和undefined时可以展开,为false时不可展开。(Symbol.isConcatSpreadable属性默认为undefined)
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']
arr2[Symbol.isConcatSpreadable] = true;
['a', 'b'].concat(arr2, 'e') // ['a', 'b', 'c', 'd', 'e']
上面代码中可以看到,没有设置Symbol.isConcatSpreadable属性时,默认可以展开,其值为undefined,将其设置为false后,即不能展开,将其设置回true后,又可以展开了。
类似数组正好相反,默认不能展开,即Symbol.isConcatSpreadable属性为true时可展开,为undefined和false时不可展开
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']
obj[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']
Symbol.isConcatSpreadable属性也可以定义在类里面。定义的位置可以是在实例上,也可以在类本身,效果相同
class A1 extends Array {
constructor(args) {
super(args);
this[Symbol.isConcatSpreadable] = true;
}
}
class A2 extends Array {
constructor(args) {
super(args);
}
get [Symbol.isConcatSpreadable] () {
return false;
}
}
let a1 = new A1();
a1[0] = 3;
a1[1] = 4;
let a2 = new A2();
a2[0] = 5;
a2[1] = 6;
[1, 2].concat(a1).concat(a2)
// [1, 2, 3, 4, [5, 6]]
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
像上面的代码中,b和c是a的衍生对象,虽然b和c是使用数组方法生成的,但它们实际上是MyArray的实例。为了让其变为Array的实例,可以使用该属性返回Array
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new MyArray();
const b = a.map(x => x);
b instanceof MyArray // false
b instanceof Array // true
上面代码中,因为定义了该属性,所以在使用该实例来构建衍生对象时,衍生对象的实例会是Array实例。
总结来说,Symbol.species属性是用于当我们要创建一个继承其他类的实例的衍生对象时,如果想让其变为其继承的类的实例的话,就可以使用这个属性。
“它主要的用途是,有些类库是在基类的基础上修改的,那么子类使用继承的方法时,作者可能希望返回基类的实例,而不是子类的实例。”---------阮一峰的ES6入门
Symbol.match
对象的Symbol.match属性,指向一个函数,当使用str.match(object)时,如果该属性存在,会调用该属性,返回对应的值。
class MyMatcher {
[Symbol.match](string) {
return 'hello world'.indexOf(string);
}
}
'e'.match(new MyMatcher()) // 1
上面’e’这个字符串调用了match方法,参数为MyMatcher类的实例对象,所以调用了该对象的Symbol.match属性指向的函数,即返回了'hello world'.indexOf('e'),所以最后返回了1。
Symbol.replace
对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
const x = {};
x[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(x, 'World') // ["Hello", "World"]
Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,上面例子是Hello,第二个参数是替换后的值,上面例子是World。上面代码用扩展运算符获取这两个参数,所以s为这两个参数作为成员的数组。
Symbol.search
对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
class MySearch {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
'foobar'.search(new MySearch('foo')) // 0
上面代码中在调用search方法时调用了Mysearch类中Symbol.search属性指向的方法,即返回了查找的字符串在调用该方法的字符串中的位置。
Symbol.split
对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.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'
Symbol.iterator
对象的Symbol.iterator属性,指向该对象的默认遍历器方法。
class Collection {
*[Symbol.iterator]() {
let i = 0;
while(this[i] !== undefined) {
yield this[i];
++i;
}
}
}
let myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;
for(let value of myCollection) {
console.log(value);
}
// 1
// 2
Symbol.toPrimitive
对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。
Number:该场合需要转成数值
String:该场合需要转成字符串
Default:该场合可以转成数值,也可以转成字符串
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 返回number 即变为123
3 + obj // '3default' 返回default 即变为'default'
obj == 'default' // true
String(obj) // 'str'
Symbol.toStringTag
对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object]或[object Array]中object后面的那个字符串。
class Collection {
get [Symbol.toStringTag]() {
return 'xxx';
}
}
let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"
上面的代码在使用Collection实例调用toString方法时调用了Symbol.toStringTag属性指向的方法,返回了其定义好的内容。
ES6 新增内置对象的Symbol.toStringTag属性值如下。
- JSON[Symbol.toStringTag]:'JSON'
- Math[Symbol.toStringTag]:'Math'
- Module 对象M[Symbol.toStringTag]:'Module'
- ArrayBuffer.prototype[Symbol.toStringTag]:'ArrayBuffer'
- DataView.prototype[Symbol.toStringTag]:'DataView'
- Map.prototype[Symbol.toStringTag]:'Map'
- Promise.prototype[Symbol.toStringTag]:'Promise'
- Set.prototype[Symbol.toStringTag]:'Set'
- %TypedArray%.prototype[Symbol.toStringTag]:'Uint8Array'等
- WeakMap.prototype[Symbol.toStringTag]:'WeakMap'
- WeakSet.prototype[Symbol.toStringTag]:'WeakSet'
- %MapIteratorPrototype%[Symbol.toStringTag]:'Map Iterator'
- %SetIteratorPrototype%[Symbol.toStringTag]:'Set Iterator'
- %StringIteratorPrototype%[Symbol.toStringTag]:'String Iterator'
- Symbol.prototype[Symbol.toStringTag]:'Symbol'
- Generator.prototype[Symbol.toStringTag]:'Generator'
- GeneratorFunction.prototype[Symbol.toStringTag]:'GeneratorFunction'
Symbol.unscopables
对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。
Object.keys(Array.prototype[Symbol.unscopables])
// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys']
上面的代码说名列出来的这7个属性会被with命令排除
// 没有 unscopables 时
class MyClass {
foo() { return 1; }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 1
}
// 有 unscopables 时
class MyClass {
foo() { return 1; }
get [Symbol.unscopables]() {
return { foo: true };
}
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 2
}
上面代码在MyClass中将Symbol.unscopables属性指向的对象中的foo设置为true,使得在使用with命令时忽略了其中的foo,返回了外部的foo。
参考自阮一峰的《ECMAScript6入门》
ES6学习笔记目录