Symbol

在es5里边,属性的名字都是唯一的,如果我们使用别人提供的对象,那么这个对象里边已经有的名字我们都不能取,这样限制很大,例如:

class Person{
constructor(nickname, age) {
this.nickname = nickname;
this.age = age;
}
introduce () {
console.log(`hello,my name is ${this.nickname},I'm ${this.age} years old;`);
}
}
const ming = new Person('小明', 3);
console.log(ming);
ming.introduce();

​ 使用​​Person​​类我们实例化了一个小明对象,里边有nickname属性和age属性,以及原型上有introduce方法,那么我们就不能给ming对象添加这三个属性。

因此我们需要一种独一无二的属性名,这样就能从根本上去防止属性名的冲突。这就是Es6引入symbol类型的原因。

es6在原有的6种数据类型(Undefined,Null,Boolean,String,Number,Object)里边添加了Symbol(第七种)数据类型。

let s = Symbol();
console.log(s, typeof s);//Symbol() "symbol"

变量s代表的是一个独一无二的值。

let y = Symbol();
console.log(y, typeof y);//Symbol() "symbol"
console.log(y === s, y == s);//false false

​ 虽然y和s长得一模一样,但是他们是不相等的,因为symbol是独一无二的,这里我们可能会想到两个长得一模一样的对象也是不相等的,因为他们的内存地址不同,但Symbol是基础数据类型,不是引用类型的值。因此不能通过new来调用Symbol函数,他很像字符串,但不是字符串,因为​​'hello' === 'hello'​​ -> true

Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制条显示,或者转为字符串时比较容易区分。

let s1 = Symbol('foo');
let s2 = Symbol('bar');
console.log(s1, s2);//Symbol(foo) Symbol(bar)

括号里的foo和bar是对这两个Symbol的描述。表示我们创建了两个Symbol类型的值,第一个值的描述是foo,第二个值的描述是bar。

如果在生成Symbol类型数据的时候传入的实参不是字符串则会被隐式的转化为字符串:

let obj = { nickname: '小明', age: 3};
let s3 = Symbol(obj);
console.log(s3);//Symbol([object Object])

​ 小明对象被转换成了​​[object Object]​​这样的一个字符串。

在了解了Symbol的特点之后,我们来看看把Symbol作为对象的属性名:

let abc = Symbol('introduce');
let obj1 = {
nickname: '小明',
age: 3,
[abc]() {
console.log( `hello,my name is ${this.nickname},I'm ${this.age} years old;` )
}
}
console.log(obj1);

在控制台里我们看到了,obj1里边里的一个属性名叫做Symbol(introduce),其中括号里边的introduce表示的是描述,就是让别人一看到这个描述,就知道了这个属性是 介绍自己(introduce)的意思,我们知道对象里边的属性名全是字符串,这次我们试着去调用一下Symbol(introduce)方法:

obj1.introduce()

​ 报错,因为obj1对象里边没有introduce方法,为什么呢?因为如果对象的属性名是Symbol类型的值,而且​​Symbol​​​类型的值并不会被转化为字符串,所以不能直接​​introduce​​​调用,要通过​​abc​​来调用:

obj1[abc]();//hello,my name is 小明,I'm 3 years old;

然后现在我们再给obj1对象添加一个introduce方法:

obj1.introduce = () => { 
console.log(`hello, 我是新添加的introduce方法,你可以通过obj1.introduce来调用我.`)
};

这时我们发现 obj1对象身上多了一个introduce方法,接下来调用一下试试:

obj1.introduce();//hello, 我是新添加的introduce方法,你可以通过obj1.introduce来调用我.

非常完美!

Symbol虽然不是一个在业务需求中十分常用的数据类型,但是他由很特殊的用途,比如:

let arr = [1,2,3];
console.log(arr);

点开数组的原型我们可以发现,里边有一个Symbol(Symbol.iterator)方法,这里括号里的Symbol.iterator是一个描述,为了让我们看到iterator就知道了这个方法是迭代器方法,迭代器方法是干什么用的呢?

for( let i = 0, length = arr.length; i < length; i++ ){
console.log(arr[i])
};

我们使用了for简简单单的遍历了一个数组,然后在内部,其实是数组原型里的iterator(迭代器)方法帮助我们找到了数组的每一个小标和下表对应的值,因此我们才可以在for循环里边使用数组的每一项和每一项的下标。

那我们来想一想,假如数组的iterator方法不是一个symbol类型的值,我们给一个数组添加了一个Symbol.iterator属性之后:

arr['Symbol.iterator'] = () => { console.log('我是新加入的Symbol.iterator') };

再用for循环去遍历这个数组:按照属性的查找规则,先从私有属性里边找,找不到去原型里边找。在使用for循环去遍历arr数组的时候,for循环要使用数组的iterator方法,然后私有属性里边正好有一个iterator属性,那么是不是就不需要去原型里边找iterator属性了!

如果这样的话,如何在for循环里使用数组原型上的iterator方法呢?不使用原型上的iterator方法又如何得到数组的每一项及对应的下标呢?现在我们来遍历一下arr数组:

for( let i = 0, length = arr.length; i < length; i++ ){
console.log(arr[i])
};

依然能够遍历数组!为什么?因为数组在遍历的时候通过使用Symbol类型的(‘Symbol.iterator’)来完成遍历,而不是使用的’Symbol.iterator’属性!是不是突然觉得Symbol属性简直吊炸天,有了他以后,我们如果想要自己定义类,那么可以把一些至关重要的方法写成Symbol类型的值,这样即便别人使用我们的类去生成对象的时候,给实例化对象添加了一个和原型里至关重要的方法一样的属性名也不会影响那些至关重要的方法的运行!

//app.js
const intRoDuce = Symbol('introduce');
class Rich{
constructor(nickname, age) {
this.nickname = nickname;
this.age = age;
}
some() {
let result = this[intRoDuce]();
console.log(result);
}
[intRoDuce]() {
return `hello,my name is ${this.nickname},I'm ${this.age} years old;`
}
}
<head>
<script src="./app.js"></script>
</head>
<body>

</body>
<script>
const richMan = new Rich('小明', 3);
console.log(richMan);
richMan.some();//579
</script>

richMan的原型上有一个introduce方法,这个方法用户永远无法使用到,即使他自己定义一个Symbol类型的值,描述也为(introduce)也用不了:

let int = Symbol('introduce');
richMan[int]();//报错

因为int的introduce和intRoDuce的introduce不是同一个数据。

在工作中,当我们要完成高级需求,比如自己造轮子的时候会用到Symbol,但是平时的业务需求很少会用到Symbol。

同时Symbol数据如果作为属性名是无法被遍历的,因为他不是私有属性:

let one = Symbol('hello');
let obj2 = {
nickname: '小明',
age: 3,
[one]: 'world'
};
for( let key in obj2 ){
console.log(key)//没有Symbol(hello)
};

所以我们通过Object.keys也无法拿到:

console.log( Object.keys(obj1) );//(2) ["nickname", "age"]

​ 原生的​​js​​​为我们提供了​​Object.getOwnPropertySymbols(obj)​​​来获取所​​obj​​对象的所有属性名,返回一个数组:

console.log( Object.getOwnPropertySymbols(obj1) );//[Symbol(hello)]