引言
万丈高楼平地起,欲练此功,必先打好基本功: )
在了解 ES6 新增的变量类型前,我们必须先知道 JavaScript 在ES6之前,有如下六种基本数据类型:Null、Undefined、Number、String、Boolean和Object。而 ES6 中,新增了第七种数据类型:Symbol。 上述七种数据类型作如下类型划分:
基本类型: Undefined、Null、Boolean、String、Number,这五种类型的变量都是直接把实际值存储在栈内存当中,操作或访问的时候都是直接对实际值进行的.
引用类型: Object。Object 类型的变量是把指向堆内存的地址值存储在栈内存当中的一类数据。(关于堆栈的知识将会在后面的文章中作介绍。)
有关基本类型和引用类型的说明,网上已经有很多文章有说明介绍,为免篇幅过长,这里就不再重复叙述了。
Symbol类型:
这里我们着重说一下 Symbol 类型:
Symbol 是一个函数,调用该函数,返回的唯一值就是 Symbol 类型值;
不允许在 Symbol 前使用 new,symbol 类型的值可通过直接调用 Symbol 函数创建
let symbol = Symbol();
symbol; //Symbol()
typeof symbol; //symbol
let symbol1 = new Symbol(); //Uncaught TypeError: Symbol is not a constructor
复制代码
Symbol 函数调用时,可接受一个参数,该参数会通过 toString 方法变为字符串,作为 symbol 值的说明,传入的参数不可以为 symbol 类型的值
let testStr = 'this is a string',
testObj = {obj: 'this is a object'},
testArr = ['this','is','a','array'],
testFn = () => {
console.log('this is a function');
},
testSym = Symbol('this is a symbol'),
symbolStr = Symbol(testStr), //Symbol(this is a string)
symbolObj = Symbol(testObj), //Symbol([object Object])
symnolArr = Symbol(testArr), //Symbol([object Object])
symbolFn = Symbol(testFn), //Symbol(() => {console.log('this is a function');})
symbolSym = Symbol(testSym); //Uncaught TypeError: Cannot convert a Symbol value to a string
复制代码
Symbol 函数调用后生成的值是唯一的
let symbol1 = Symbol('test'),
symbol2 = Symbol('test');
symbol1 == symbol2; //false
symbol1 === symbol2; //false
复制代码
Symbol 值不能与其他类型值进行运算、隐式转换,否则会报错;但能通过 toString 方法显式转为字符串。
let symbol = Symbol('this is symbol'),
str = 'this is string',
num = 2,
symStr = symbol.toString();
let newStr = symbol + str; //Uncaught TypeError: Cannot convert a Symbol value to a string
let newNum = Symbol + num; //Uncaught TypeError: Cannot convert a Symbol value to a number
symStr; // Symbol(this is symbol)
复制代码
Symbol 值的唯一性,用于 Object 的属性中,可以确保不会出现同名属性
let symbol = Symbol('this is symbol'),
symbol1 = Symbol('this is symbol');
let obj = {
[symbol]: 'this is a',
[symbol1]: 'this is b'
};
obj; //{Symbol(this is symbol): "this is a", Symbol(this is symbol): "this is b"}
let str = 'test',
str1 = 'test',
obj = {};
obj[str] = '测试非symbol类型命名的属性';
obj; //{test: "测试非symbol类型命名的属性"}
obj[str1] = '再次测试非symbol类型命名的属性';
obj; //{test: "再次测试非symbol类型命名的属性"}
复制代码
Symbol 命名的属性可通过 Object.getOwnPropertySymbols 获取
let symbol = Symbol('this is symbol'),
symbol1 = Symbol('this is symbol');
let symbolObj = {
[symbol]: 'this is a',
[symbol1]: 'this is b',
}
Object.getOwnPropertySymbols(symbolObj); //[Symbol(this is symbol), Symbol('this is symbol')]
复制代码
Symbol 值命名的属性不会出现在 Object.keys、for...in...中,通过Object.getOwnPropertyNames()、JSON.stringify() 也无法得到返回值。但该属性是公开属性,不是私有属性。
let symbol = Symbol('this is symbol'),
symbol1 = Symbol('this is symbol');
let obj = {
[symbol]: 'this is a',
[symbol1]: 'this is b',
d: 'test'
};
for(key in obj){
console.log(key); // d
}
Object.keys(obj); // ['d']
Object.getOwnPropertyNames(obj); // ['d']
JSON.stringify(obj); //{d:'test'}
//仍可访问到Symbol值定义的属性键和属性值
let symKeys = Object.getOwnPropertySymbols(obj);
symKeys; //[Symbol(this is symbol), Symbol(this is symbol)]
symKeys[0]; //Symbol(this is symbol)
obj[symKeys[0]]; //this is a
复制代码
可通过 Symbol.for('xxx') 获得以'xxx'作为入参而生成的 Symbol 值,若不存在以'xxx'作为入参生成的 Symbol 值,则会自动创建一个以'xxx'为入参的 Symbol 值。
let symbol = Symbol('this is symbol'),
symbol1 = Symbol.for('this is symbol'),
symbol2 = Symbol.for('this is symbol');
symbol === symbol1 //false
symbol === symbol2 //false
symbol1 === symbol2 //true
复制代码
12. 通过 Symbol.keyFor 方法可返回一个已“登记”的 Symbol 类型值的修饰词。
通过 Symbol 创建的值有如下两种情况: 被登记的与未被登记的。
什么是被登记的?在
let s1 = Symbol.for("foo");
console.log(Symbol.keyFor(s1)); // "foo"
var s2 = Symbol("foo");
console.log(Symbol.keyFor(s2) ); // undefined
复制代码
这里还有一些 MDN 关于 Symbol 的属性介绍,因为感觉在日常开发中使用的概率比较低,因此也不赘述一些自己的理解了,有兴趣的朋友可以去看看developer.mozilla.org/zh-CN/docs/…
关于 Symbol 的总结: 对于 Symbol 的使用,实用性最高的我觉得是 symbol 值用于属性命名的情况,在一些开发情况下,对一个 Object 对象进行遍历的时候,希望某一些属性不被 for in 或 Object.keys 遍历出来,避免做 if 或 switch 情况处理,这时候使用 Symbol 定义属性名是个不错的选择。
在原有 ES5 的属性命名和赋值过程中,多人协作开发可能会导致一些属性名重命名而导致值覆盖,对“半私有属性”属性使用 Symbol 命名属性名会是个很好的选择。
Iterator - 迭代器
ES6 在原有的数据结构类型( Array、Object )上新增了两种类型( Map、Set ),我们在使用的时候还可以通过自由组合的形式使用这些结构类型达到自己想要的数据结构,这就需要一种统一的接口机制供我们调用处理不同的数据结构 —— Iterator。
ES6中,只要被遍历“对象”存在 可迭代协议 , 即System.iterator
属性,该对象都是被认为是“可遍历的”。
在 ES6 中,有三类数据结构原生具备 Iterator 接口:数组、某些类似数组的对象(如 字符串、类似数组形式的对象)、Set 和 Map 结构数据。
迭代器协议 定义了一种标准的方式来产生一个有限或无限序列的值,每次遍历都会首先调用被遍历数据集合对象中的 [Symbol.iterator]()
方法,该方法返回一个 Symbol对象的iterator属性 ,该属性拥有执行迭代对象的 next
方法,并返回一个对象,如下是一段模拟 next
方法的代码
function Iterator(arr){
let nextIndex = 0;
return {
next: function(){
return nextIndex < arr.length ? {value: arr[nextIndex++], done: false} : {value: undefined, done: true};
},
[Symbol.iterator]: function() { return this }
}
}
let example = Iterator(['a', 'b']);
example.next() // {value: "a", done: false}
example.next() // {value: "b", done: false}
example.next() // {value: undefined, done: true} //遍历结束
复制代码
返回的对象中必须包含如下两个属性
{
done, //迭代是否已经执行完毕 迭代完毕时返回true,否则返回false,返回false时会继续执行迭代
value //当前成员的值 迭代完毕时返回undefined,否则返回当前成员的值
}
复制代码
在某些情景下,JS会默认调用 Symbol.iterator
(Iterator接口)
-
扩展运算符
扩展运算符(…)会默认调用
iterator
接口。 -
解构赋值
对数组和Set结构进行解构赋值时,会默认调用
Symbol.iterator
方法。 -
yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的
iterator
接口。 -
for...of
执行
for...of
循环时,会调用iterator
接口对数据进行处理。 -
Array.from()
在
Array.form()
时,会遍历数据,调用iterator
接口返回相应数据 -
其它情况还有
Map()
,Set()
,WeakMap()
,WeakSet() (比如new Map([['a',1],['b',2]]))
,Promise.all()
,Promise.race()
。
顺带一提:
在ES6中,具有 System.iterator 属性的对象均可通过 for...of
进行遍历
let arr = ['1','2','3'];
arr.pro = 'test';
for (let i in arr) {
console.log(arr[i]); //1 2 3 test
}
for(let i of arr) {
console.log(arr[i]); //1 2 3
}
复制代码
for...of
的相比于 forEach
、 for...in
,其好处在于: forEach
循环无法通过 break
、 continue
、 return
跳出循环,而 for...of
可以; for...in
循环设计的目的是用于遍历包含键值对的对象,对数组并不是那么友好,而 for...of
遍历输出的值会在输出数据后默认遍历结束。
关于 Iterator 的总结: Iterator作为一种统一的接口机制供我们调用处理不同的数据结构,让可以用相同的方式来遍历集合,而不用去考虑集合的内部实现,若数据的形式发生改变,只要数据内部还存在 System.iterator
属性,那遍历的代码就可以不用做修改直接使用。 同时,其服务于 for...of
循环, for...of
又有效地避免了以往对数据集仅能通过 forEach
、 for..in
遍历时遇到的一部分问题。
以上。
文章观点内容如有错误欢迎指出交流,相互进步