- ES6 引入了一种新的原始数据类型
Symbol
,表示独一无二的值,是 JS 的第七种数据类型- 通过调用
Symbol()
函数,来生成symbol
这种数据类型Symbol()
函数接受String 或 Number
类型的参数// 感受一下symbol类型的独一无二
let s1 = Symbol();
let s2 = Symbol();
console.log(s1); // Symbol()
console.log(s2); // Symbol()
console.log(typeof s1); // symbol
console.log(s1 === s2); // false 这两个不相等,所以和对象属性结合,有妙用
Symbol函数的参数
```javascript // Symbol(Number|String)函数的传参 let s1 = Symbol(); // Symbol() let s2 = Symbol(‘’); // Symbol() let s3 = Symbol(undefined); // Symbol() let s4 = Symbol(123); // Symbol(123) let s5 = Symbol(‘abc’); // Symbol(abc) let s6 = Symbol(null); // Symbol(null) let s3 = Symbol(false); // Symbol(false) let s7 = Symbol(NaN); // Symbol(NaN) let s8 = Symbol({}); // Symbol([object Object])
- 通过调用
// 传入的是数组,会将数组的括号去掉。把元素按顺序拼起来,并用逗号隔开 let s9 = Symbol([4, 5, 6]); // Symbol(4,5,6); let s9 = Symbol([4, 5, {}]); // Symbol(4,5,[object Object]);
- Symbol 值不能与其他类型的值进行运算,会报错
- Symbol 值可以转为字符串,也可以转为布尔值,但不能转数字
```javascript
// 案例一:不能与其他类型的值,进行运算
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值转字符串
let s1 = Symbol('123');
s1.toString(); // Symbol(123) 是 String类型
// symbol值转布尔值
Boolean(s1); // 必为true
// 转数字报错,
Number(s1); // 报错
symbol类型与对象结合
- 由于symbol类型的值是独一无二的,若 对象A 的
key(属性名)
属于 symbol 类型,就不会与其他属性名产生冲突- ES5 的对象属性名都是字符串,这容易造成属性名的冲突
- 比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突 ```javascript // ES5 var obj = { run: function() { console.log(1) } } obj.run = function() { console.log(2) } obj.run(); // 2
// ES6 let s1 = Symbol(‘run’) let s2 = Symbol(‘run’) let obj = { s1: () => console.log(1) } obj[s2] = () => console.log(2)
- Symbol 作为属性名,需要注意
- 遍历对象的时候,该属性不会出现在`for...in`、`for...of`循环
- 也不会被`Object.keys()`、`Object.getOwnPropertyNames()`、`JSON.stringify()`返回
- 所以出现两个新的 API
- `Object.getOwnPropertySymbols()`:可以获取指定对象的所有 Symbol 属性名
- `Reflect.ownKeys()`:可以返回所有类型的键名,包括常规键名和 Symbol 键名
```javascript
// 案例一
let s1 = Symbol('a');
let s2 = Symbol('b');
let obj = {
[s1]: 1,
[s2]: 2,
s3: 4
};
// for循环,只打印了s3
for(let i in obj) {
console.log(i);
}
console.log(Object.keys(obj)); // ['s3']
console.log(Object.getOwnPropertyNames(obj)); // ['s3']
console.log(JSON.stringify(obj)); // {"s3":4}
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(a), Symbol(b)]
console.log(Reflect.ownKeys(obj)); // ['s3', Symbol(a), Symbol(b)]
Symbol的属性和方法
方法
Symbol.for(key)
:会先检查给定的key
是否在symbol 注册表
已经存在,如果不存在才会新建一个值- 该方法会把
key
登记全局 symbol 注册表
中供搜索
- 该方法会把
Symbol.keyFor(symbol)
:返回一个已登记的 Symbol 类型值的key
,没有就是undefined
```javascript // 假设在index.html中,引入了a.js和b.js // a.js let s1 = Symbol.for(‘test’); let s2 = Symbol(‘456’); let obj = { s1: ()=> console.log(‘test’) }
Symbol.keyFor(s1); // test Symbol.keyFor(s2); // undefined // b.js let s3 = Symbol.for(‘test’); objs3; // test
<a name="cxCwg"></a>
### 属性
<a name="MiZ57"></a>
#### Symbol.hasInstance
- `Symbol.hasInstance`:既是`Symbol`的一个属性,也是 ES6 在 `函数和 class` 的原型(`__proto__`)上内置的一个方法
- 作用:当其他对象使用`instanceof`运算符,判断是否为该构造函数的实例时,会调用这个方法
- 所以 ES6 提供的这个方法,可以让我们改变`instanceof`的行为
- `myClass[Symbol.hasInstance](obj)`:判断`obj`实例对象 的 `constructor` 是不是 `myClass` 或 `constructor的原型链上` 有没有 `myClass`
```javascript
// 案例一
class MyClass {}
let c1 = new MyClass;
// ES5可以这么写
c1 instanceof MyClass;//true
[] instanceof MyClass;//false
// ES6可以这么写
MyClass[Symbol.hasInstance](c1); // true
// 案例二
// 用 Symbol.hasInstance 改变 instanceof 的行为
class myClass {
// 动态方法,在myClass类的prototype上
// 动态方法,修改类的实例的 instanceof 的指向
[Symbol.hasInstance](instanceObject) {
console.log(instanceObject)
return Number(instanceObject) % 2 === 0;
}
// 静态方法,在myClass类上
// 静态方法,修改类的 instanceof 的指向
static [Symbol.hasInstance](instanceObject) {
return instanceObject instanceof Array;
}
}
let test = new myClass();
test instanceof myClass; // false
[] instanceof myClass; // true
2 instanceof test; // true
Symbol.iterator
Symbol.iterator
:既是Symbol
的一个属性,也是 ES6 在数据结构(Array/Map/Set/String/ TypeArray/函数arguments/NodeList)
上内置的一个方法- 一个数据结构 只要具有
Symbol.iterator
属性,就可以认为是“可遍历的” 数据结构(Array/Map/Set/String/ TypeArray/函数arguments/NodeList)
中,Symbol.iterator
属性本身是一个函数,就是当前 数据结构 默认的遍历器 的生成函数。执行这个函数,就会返回一个遍历器
- 一个数据结构 只要具有
遍历器(Iterator):
- 为何出现:JS 原有的表示“集合”的数据结构,主要是数组
(Array)
和对象(Object)
,ES6 又添加了Map
和Set
。这样就有了四种数据集合,可以任意组合使用,定义自己的数据结构。比如数组的成员是Map
,Map
的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构 - 作用:
- 一是为各种数据结构,提供一个统一的、简便的访问接口。
- 二是使得数据结构的成员能够按某种次序排列。
- 三是 ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费
- 遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置
- 也就是说,遍历器对象本质上,就是一个指针对象
- 比如:
let iter = arr[Symbol.Iterator]()
。arr
是数组的实例 - 该对象
(iter)
上,有**next()**
方法,throw()
和return()
方法return()
方法是,如果for...of
循环提前退出(通常是因为出错,或者有break
)throw()
方法主要是配合Generator函数
使用,一般的遍历器对象用不到这个方法
- 第一次调用指针对象的
next
方法,可以将指针指向数据结构的第一个成员- 比如:
iter.next()
- 比如:
- 不断调用指针对象的
next
方法,直到它指向数据结构的结束位置- 每次调用
next
方法,都返回一个包含value
和done
两个属性的对象value
属性是当前成员的值done
属性是一个布尔值,表示遍历是否结束 ```javascript // 案例一:模拟遍历器的原理 function makeIterator(arr) { let nextIndex = 0; return { next: function () { return nextIndex < arr.length ? { value: arr[nextIndex++], done: false } : { value: undefined, done: true } } } } let arr = [1, 2, 5] let iter = makeIterator(arr);
- 每次调用
- 创建一个指针对象,指向当前数据结构的起始位置
- 为何出现:JS 原有的表示“集合”的数据结构,主要是数组
console.log(iter.next()); // { value: 1, done: false } console.log(iter.next()); // { value: 2, done: false } console.log(iter.next()); // { value: 5, done: false } console.log(iter.next()); // { value: undefined, done: true }
```javascript
// 案例二:用ES6内置的遍历器,代替自己模拟的遍历器
let arr = [1, 2, 5]
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 5, done: false }
console.log(iter.next()); // { value: undefined, done: true }
- 对象
(Object)
之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定- 本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。
- 不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了 ```javascript // 案例三:给对象 部署 Iterator 接口 Object.prototype[Symbol.iterator] = function () { let nextIndex = 0; return { next: () => { let keys = Reflect.ownKeys(this); return nextIndex < keys.length ? { value: this[keys[nextIndex++]], done: false } : { value: undefined, done: true } } } }
let s1 = Symbol(); let obj = { a: 55,
};
// Object对象有了Iterator接口,下面这些场合就能用了 // for…of for(let val of obj) { console.log(val); }
// 顺带复习ES5的for in for(let key in obj) {}
// 扩展运算符… let arr = […obj]; // [55, 123]
// 用Array.fromz转成数组 let arr2 = Array.from(obj); // [55, 123]
```javascript
// 案例四:自定义对象的遍历器的 return() 方法
// return()方法必须返回一个对象,这是 Generator 语法决定的
Object.prototype[Symbol.iterator] = function () {
let nextIndex = 0;
return {
next: () => {
let keys = Reflect.ownKeys(this);
return nextIndex < keys.length ?
{ value: this[keys[nextIndex++]], done: false }
: { value: undefined, done: true }
},
// 添加return()方法
return() {
console.log('return');
return { done: true };
}
}
}
let s1 = Symbol();
let obj = {
a: 55,
b: 2,
[s1]: 123,
};
for (let val of obj) {
if (val === 2) {
break;
}
}
- 会调用
Iterator
遍历器的场合- 解构赋值
- 扩展运算符
yield*
for...of
Array.from()
Map()、Set()、WeakMap()、WeakSet()
- 比如:
new Map([ ['a', 1], ['b', 2] ])
- 比如:
Promise.all()、Promise.race()
- 更多属性请看,《阮一峰的ES6》
Symbol 的应用
- 用于解决属性名冲突问题,构造唯一的属性名和变量
- 作为私有属性 ```javascript function Child(name) { const symbol = symbol() this[symbol] = ‘my symbol’ this.name = name; }
const obj = new Child(‘konsoue’) obj.name // konsoue // symbol 拿不到
3. 使得对象可遍历
```javascript
const obj = {
count: 0,
[Symbol.iterator]: () => {
return {
next: () => {
obj.count++
if (obj.count <= 10) {
return {
value: obj.count,
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
}
for (let item of obj) {
console.log(item); // 1 2 3 4 5 6 7 8 9 10
}
const obj = {
count: 0,
name: 'konsoue',
add: () => this.count++,
[Symbol.iterator]: function() {
let keyIndex = 0;
const keys = Object.keys(this);
const objWithNext = {
next: () => {
if (keyIndex === keys.length) return { value: undefined, done: true };
const result = {
value: this[keys[keyIndex]],
done: false
}
keyIndex++
return result;
}
}
return objWithNext
}
};
for (let item of obj) {
console.log(item)
}
Object.prototype[Symbol.iterator] = function () {
let keyIndex = 0;
const keys = Object.keys(this);
const targetWithNext = {
next: () => {
if (keyIndex === keys.length) return { value: undefined, done: true };
const result = {
value: this[keys[keyIndex]],
done: false
}
keyIndex++
return result;
}
}
return targetWithNext
}