Symbol
概述
ES6 引入了一种新的原始数据类型Symbol
,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined
、null
、Boolean
、String
、Number
、Object
直接调用Symbol
函数即可生成一个Symbol,注意Symbol
函数前不能使用new
命令,否则会报错。
Symbol
函数可以接受一个字符串作为参数,表示对 Symbol 的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1 // Symbol(foo)
s2 // 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
Symbol作为属性
Symbol 值可以作为标识符,用于对象的属性名,由于每一个 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!"
Symbol 作为属性名,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
但是,有一个Object.getOwnPropertySymbols
方法,可以获取指定对象的所有 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.for()
不会每次调用就返回一个新的 Symbol 类型的值,
而是会先检查给定的key
是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")
30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")
30 次,
会返回 30 个不同的 Symbol 值。
内置Symbol
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.species
Symbol.match
Symbol.replace
Symbol.search
Symbol.split
Symbol.toPrimitive
Symbol.toStringTag
Symbol.unscopables
Symbol.iterator
,对象的Symbol.iterator
属性,指向该对象的默认生成遍历器
的方法。
实例:
消除魔术字符串
function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { 100, height: 100 }); // 魔术字符串
上面代码中,字符串Triangle
就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。
常用的消除魔术字符串的方法,就是把它写成一个变量。
const shapeType = {
triangle: 'Triangle'
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { 100, height: 100 });
但是如果没有办法保证有人写了一句
const shapeType = {
triangle: 'Triangle',
rectangle:'Triangle'
};
接下来一旦出现bug会很难调试
如果仔细分析,可以发现shapeType.triangle
等于哪个值并不重要,只要确保不会跟其他shapeType
属性的值冲突即可。因此,这里就很适合改用 Symbol 值。
const shapeType = {
triangle: Symbol(),
rectangle:Symbol()
};
实现私有属性
第一种方式:用一个字符串或者下划线的方式
var Person = (function() {
let _name = `_name`
// 或者 let name = 'shfjkshfkjsjkf'
function Person(name) {
this[_name] = name;
}
Person.prototype.getName = function() {
return this[_name];
};
return Person;
}());
缺点很显然,不是真正的私有,依然可以遍历
第二种方式:闭包
var Person = (function() {
function Person(name) {
this.getName = function() {
return name;
};
}
return Person;
}());
真的私有了,但是仍然存在缺点:实例无法共享方法,浪费内存空间
第三种方式:使用Symbol
var Person = (function() {
var nameSymbol = Symbol('name');
function Person(name) {
this[nameSymbol] = name;
}
Person.prototype.getName = function() {
return this[nameSymbol];
};
return Person;
}());
缺陷:仍然会被Object.getOwnPropertySymbols
获取到属性,进而修改该属性对应的值