JavaScript规定了几种语言类型?
七种: 1.Undefined, 2.Null, 3.Boolean, 4.String, 5.Number, 6.Symbol, 7.Object
Symbol的概念
Symbol表示独一无二的值,它是一切非字符串的对象key的集合。
Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,但是即使描述相同,Symbol值也不相等。
let s1 = Symbol('foo'); let s2 = Symbol('foo');
s1 === s2 // false
一些标准中提到的 Symbol,可以在全局的 Symbol 函数的属性中找到。例如,我们可以使用 Symbol.iterator 来自定义 for…of 在对象上的行为:
var o = new Object
o[Symbol.iterator] = function() {
var v = 0
return {
next: function() {
return { value: v++, done: v > 10 }
}
}
};
for(var v of o)
console.log(v); // 0 1 2 3 ... 9
Iterator(遍历器)的概念
Javascript表示集合的数据结构,有数组(Array)、对象(Object)、Map、Set四种。用户可以组合使用,定义自己的数据结构。例如:数组的成员为Map,Map的成员为对象。这样就需要一种统一的机制,来处理不同的数据结构。
遍历器(Iterator)就是这样一种机制,它是一种接口,为不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator机制,就可以完成遍历操作。(即依次处理该数据结构的所有成员)。
Iterator(遍历器)的作用
- 为各种数据结构,提供一个统一的、简便的访问接口
- 使得数据结构的成员能够按照某种次序排序
- ES6提供了一种新的遍历命令 for...of 循环,Iterator接口主要供 for...of 消费
Iterator(遍历器)的遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员。
- 第二次调用指针对象的 next 方法,指针将指向数据结构的第二个成员。
- 不断调用指针对象的 next 方法,直到指针指向数据结构的结束位置。
每一次调用数据结构的 next 方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含 value 和 done 两个属性的对象。其中, value 是当前成员的值, done 是一个布尔值,表示遍历是否结束。(true表示结束,false表示不结束)
下面是一个模拟遍历器 next 方法返回值的例子:
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++]} :
{done: true};
}
};
}
默认Iterator接口
Iterator接口的目的,就是为所有的数据结构,提供一种统一的访问机制,即 for...of 循环。当使用 for...of 循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
一种数据结构只要部署了Iterator接口,就称这种数据结构是可遍历的。(iterable)
ES6规定,默认的Iterator接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是可遍历的。 Symbol.iterator 就是当前数据结构默认的遍历器生成函数,返回值为一个遍历器。
const obj = {
[Symbol.iterator]: function () {
return {
next: function () {
return {
value: 0,
done: true
}
}
}
}
}
属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。
ES6 的有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被for...of循环遍历。原因在于,这些数据结构原生部署了Symbol.iterator属性,另外一些数据结构没有(比如对象)。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
原生具备 Iterator 接口的数据结构:
- Array
- Map
- Set: 类似于数组,只不过其成员值都是唯一的,没有重复的值。
- String
- TypedArray: 一个TypedArray对象描述一个底层的二进制数据缓存区的一个类似数组(array-like)视图。
- 函数的 arguments 对象
- NodeList 对象: NodeList 对象是一个节点的集合,是由 Node.childNodes 和 document.querySelectorAll返回的.NodeList不是一个数组,是一个类似数组的对象(Like Array Object).虽然NodeList不是一个数组,但是可以使用forEach()对其进行迭代。还可以使用Array.from()将其转换为实际数组。
以数组为例:
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。
一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)
class RangeIterator {
constructor (start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator] () {
return this;
}
next () {
const value = this.value;
if (value < this.stop) {
this.value ++;
return {done: false, value: value}
}
return {done: true, value: undefined}
}
}
function range (start, stop) {
return new RangeIterator(start, stop);
}
for (const value of range(0, 3) {
console.log(value); // 0, 1, 2
}
range(0, 3).next(); // {done: false, value: '0'}
range(0, 3).next(); // {done: false, value: '1'}
range(0, 3).next(); // {done: false, value: '2'}
range(0, 3).next(); // {done: true, value: undefined}
上面代码是一个类部署 Iterator 接口的写法。Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器对象。