ES6常用知识点总结

322 阅读18分钟

一、块级作用域

ES5 只有全局作用域和函数作用域(例如,我们必须将代码包在函数内来限制作用域),这导致很多问题:

  • 情况1:内层变量覆盖外层变量
if (condition) {
    var value = 1;
}
console.log(value);

代码相当于是

var value;
if(condition) {
    value = 1;
}
console.log(value);

如果我们这丽的condition是false,那么最终的到的就是undefined。

var tmp = new Date();
function f() {
  console.log(tmp); //undefined
  if (false) {   
    var tmp = "hello world";
  }
}

因为存在变量提升,代码相当于是

var tmp = new Date();
function f() {
  var tmp = 'undefined';
  console.log(tmp); //undefined
  if (false) {   
    // 如果是false,则永远不会赋值
    tmp = "hello world";
  }
}
  • 情况2:变量泄露,成为全局变量

在 for 循环中:

for (var i = 0; i < 10; i++) {
    ...
}
console.log(i); // 10
// 在这里也能访问到i

即便循环已经结束了,我们依然可以访问 i 的值。

为了加强对变量生命周期的控制,ECMAScript 6 引入了块级作用域。

块级作用域存在于:

函数内部
块中(字符 { 和 } 之间的区域)

1、let 和 const

1、 不会被提升
2、 存在暂时性死区
3、 不能重复声明,会报错
4、 提倡声明后再使用
5、 不会绑定到全局属性
if (false) {
    let value = 1;
}
// value一直在if快中
console.log(value); // Uncaught ReferenceError: value is not defined
  • const

const用来限制不能更改变量的绑定。

const data = {
    value: 1
}

// 没有问题
data.value = 2;
data.num = 3;

// 报错
data = {}; // Uncaught TypeError: Assignment to constant variable.

临时性死区

console.log(typeof value); // Uncaught ReferenceError: value is not defined
let value = 1;

意思就是在变量声明之前,该变量不会提升,但是会进入到一个暂时性死区中,如果该变量目前存在暂时性死区,那么是无法访问该变量的,会报错,只有当程序执行到变量声明处的时候,才会将其从暂时性死区中,拿出来,这时候我们才可以访问。

var value = "global";

// 例子1
(function() {
    console.log(value);

    let value = 'local';
}());

// 例子2
{
    console.log(value);

    const value = 'local';
};

上例,两处都会报错。

2、for循环中的块级作用域

var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs[i] = function () {
        console.log(i);
    };
}
funcs[0](); // 3

一个经典的面试题目:

// 解决办法
var funcs = [];
for (var i = 0; i < 3; i++) {
    funcs[i] = (function (i) {
        console.log(i);
    })(i);
}
funcs[0](); // 3

//上诉的解决办法利用的是函数执行上下文

当然使用我们的let变量就可以解决,但是const会报错。

我们再来一个例子:

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

for (var i = 0; i < 3; i++) {
  var i = 'abc';
  console.log(i);
}
// abc

我们使用var的时候,其实i的作用域在for循环所在的作用域,内外都共同指向一个i。当我们第一次循环的时候,'abc'赋值给了i,此时i再去跟3比较,就会跳出循环了。

然而我们的let却是另一种情况。let循环,在底层做了独特的改变,简单的来说,就是在 for (let i = 0; i < 3; i++)中,即圆括号之内建立一个隐藏的作用域。

// 伪代码
(let i = 0) {
    funcs[0] = function() {
        console.log(i)
    };
}

(let i = 1) {
    funcs[1] = function() {
        console.log(i)
    };
}

(let i = 2) {
    funcs[2] = function() {
        console.log(i)
    };
};

二、模版字符串

1、基础语法

let message = `Hello World`;
console.log(message);

如果你碰巧要在字符串中使用反撇号,你可以使用反斜杠转义:

let message = `Hello \` World`;
console.log(message);

值得一提的是,在模板字符串中,空格、缩进、换行都会被保留:

let message = `
	<ul>
		<li>1</li>
		<li>2</li>
	</ul>
`;
console.log(message);

嵌入变量$

let x = 1, y = 2;
let message = `<ul><li>${x}</li><li>${x + y}</li></ul>`;
console.log(message); // <ul><li>1</li><li>3</li></ul>

值得一提的是,模板字符串支持嵌套:

let arr = [{value: 1}, {value: 2}];
let message = `
	<ul>
		${arr.map((item) => {
			return `
				<li>${item.value}</li>
			`
		})}
	</ul>
`;
console.log(message);

会发现有一个,号。

这是因为: map之后会返回的是一个数组,["↵ <li>1</li>↵ ", "↵ <li>2</li>↵ "]。${}大括号中的值不是字符串时,会将其转为字符串,比如一个数组 [1, 2, 3] 就会被转为 1,2,3,逗号就是这样产生的。

解决办法:

let arr = [{value: 1}, {value: 2}];
let message = `
	<ul>
		${arr.map((item) => {
			return `
				<li>${item.value}</li>
			`
		}).join('')}
	</ul>
`;
console.log(message);

2、标签模版

let x = 'Hi', y = 'Kevin';
var res = message`${x}, I am ${y}`;
// 函数后面紧跟我们的参数模版
console.log(res);

我们定义函数

// literals 文字
// 注意在这个例子中 literals 的第一个元素和最后一个元素都是空字符串
function message(literals, value1, value2) {
	console.log(literals); // [ "", ", I am ", "" ]
	console.log(value1); // Hi
	console.log(value2); // Kevin
}

再次拼合回去:

function message(literals, ...values) {
	let result = '';

	for (let i = 0; i < values.length; i++) {
		result += literals[i];
		result += values[i];
	}

	result += literals[literals.length - 1];

	return result;
}

标签模版

三、箭头函数

1、基础语法

ES6 增加了箭头函数:

let func = value => value;

相当于:

let func = function (value) {
    return value;
};

如果需要给函数传入多个参数:

let func = (value, num) => value * num;

如果函数的代码块需要多条语句:

let func = (value, num) => {
    return value * num
};

如果需要直接返回一个对象:

let func = (value, num) => ({total: value * num});

与变量解构结合:

传入一个对象,返回一个新的对象:

let func = ({value, num}) => ({total: value * num})

// 使用
var result = func({
    value: 10,
    num: 10
})

console.log(result); // {total: 100}

2、箭头函数于非箭头函数的区别

  • this绑定问题
箭头函数中没有this,通过其外部最近的非箭头函数的this来定。(查找作用域)

也不能用 call()、apply()、bind() 这些方法改变 this 的指向
  • 没有arguments对象

箭头函数没有自己的 arguments 对象,这不一定是件坏事,因为箭头函数可以访问外围函数的 arguments 对象:

function constant() {
    return () => arguments[0]
}

var result = constant(1);
console.log(result()); // 1

那如果我们就是要访问箭头函数的参数呢?

你可以通过命名参数或者 rest 参数的形式访问参数:

let nums = (...nums) => nums;
  • 不能通过new来实例

JavaScript 函数有两个内部方法:[[Call]][[Construct]]

当通过 new调用函数时,执行[[Construct]] 方法,创建一个实例对象,然后再执行函数体,将 this 绑定到实例上。

当直接调用的时候,执行 [[Call]]方法,直接执行函数体。

箭头函数并没有 [[Construct]]方法,不能被用作构造函数,如果通过 new的方式调用,会报错。

var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
  • 没有原型和super

四、Symbol

Symbol 是一种特殊的、不可变的数据类型,可以作为对象属性的标识符使用,表示独一无二的值。

1、基础用法:

var sym = Symbol('zzz');

console.log(sym);

var obj = {};
obj[sym] = 'xxxhhh111';

console.log(obj);
console.log(obj.sym);
console.log(obj[sym])

结果如下:

2、创建一个Symbol:

Symbol([description]) description 可选

以下两种形式都可以,只是有参数可能容易区分一些:

var s1 = Symbol('symbol1');
s1 //Symbol(symbol1); 

var s2 = Symbol();
s2 //Symbol(); 

因为Symbol函数返回的值都是独一无二的,所以Symbol函数返回的值都是不相等的。

//无参数
var s1 = Symbol();
var s2 = Symbol();

s1 === s2 // false

//有参数
 var s1 = Symbol('symbol');
 var s2 = Symbol('symbol');
 
 s1 === s2 //false

3、Symbol可以作为属性名

由于每一个Symbol值都是不相等的,那么作为属性标识符是一种非常好的选择。

let symbolProp = Symbol();

var obj = {};
obj[symbolProp] = 'hello Symbol';

//或者
var obj = {
    [symbolProp] : 'hello Symbol';
}

//或者
var obj = {};
Object.defineProperty(obj,symbolProp,{value : 'hello Symbol'});

注意: 这里设置和访问的时候只能放在[]中.

4、Symbol.for

他可以全局共享,在创建的时候首先会去全局查找是否存在该项,如果存在就直接返回,如果不存在就创建。

let s1 = Symbol.for("uid");

console.log(s1);// Symbol(uid)

let s2 = Symbol.for("uid");

console.log(s1 == s2); // true

5、Symbol.keyFor()

Symbol.keyFor方法返回一个已登记的Symbol类型的值的key。

var s1 = Symbol.for('foo');
Symbol.keyFor(s1) //"foo"

var s2 = Symbol('foo');
Symbol.keyFor(s2);//undefiend

6、获取对象中的Symbol属性

let uid = Symbol('uid')
let obj = {
    [uid]: 'uid'
}

console.log(Object.keys(obj)) // []
console.log(Object.getOwnPropertyNames(obj)) // []
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(uid)]

7、Symbol的一些注意事项

  • 使用 typeof,结果为 "symbol"
var s = Symbol();
console.log(typeof s); // "symbol"
  • instanceof 的结果为 false
var s = Symbol();
console.log(s instanceof Symbol); // false
  • Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
var s1 = Symbol('foo');
console.log(s1); // Symbol(foo)
  • 如果 Symbol 的参数是一个对象,就会调用该对象的 toString 方法,将其转为字符串,然后才生成一个 Symbol 值。因为参数只能为字符串类型。
const obj = {
  toString() {
    return 'abc';
  }
};
const sym = Symbol(obj);
console.log(sym); // Symbol(abc)
  • Symbol 值不能与其他类型的值进行运算,会报错。也就是不能使用 + - 等这些,即使是字符串拼接也不行。
var sym = Symbol('My symbol');

console.log("your symbol is " + sym); // TypeError: can't convert symbol to string
  • Symbol 值可以显式转为字符串。
var sym = Symbol('My symbol');

console.log(String(sym)); // 'Symbol(My symbol)'
console.log(sym.toString()); // 'Symbol(My symbol)'
  • Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。
var obj = {};
var a = Symbol('a');
var b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

var objectSymbols = Object.getOwnPropertySymbols(obj);

console.log(objectSymbols);
// [Symbol(a), Symbol(b)]

8、Symbol.toPrimitive

这个用的就多了,进行类型转换的时候,对象会进行尝试转换成原始类型,就是通过toPrimitive.这个方法,标准类型的原型上都存在。

对象在转换基本类型时,首先会调用 valueOf 然后调用 toString。并且这两个方法你是可以重写的。

let a = {
    valueOf() {
    	return 0
    }
}

当然你也可以重写 Symbol.toPrimitive ,该方法在转基本类型时调用优先级最高。

let a = {
  valueOf() {
    return 0;
  },
  toString() {
    return '1';
  },
  [Symbol.toPrimitive]() {
    return 2;
  }
}
1 + a // => 3
'1' + a // => '12'

进行类型转换的时候,toPrimitive会被强制的调用一个参数,在规范中这个参数被称之为hint. 这个参数是三个值('number', 'string', 'default')其中的一个。

顾名思义,string返回的是string, number返回的是number,default是没有特别指定,默认。

function Temperature(degrees) {
    this.degrees = degrees;
}

Temperature.prototype[Symbol.toPrimitive] = function(hint) {
	console.log('hint is', hint)
};

freezing + 2 // hint is default
freezing / 2 // hint is number
freezing + "333"  // hint is default
String(freezing) // hint is string 

五、数组的扩展

1、Array.from 将伪数组对象或可遍历对象转换为真数组

function fun() {
    console.log(arguments);
}
var a = 10;
var o = {};
fun(a, o);

function fun() {
    console.log(arguments);
    console.log(Array.from(arguments));
}
var a = 10;
var o = {};
fun(a, o);

2、Array.of(v1, v2, v3) : 将一系列值转换成数组

let items = new Array(2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // undefined
console.log(items[1]) ;

let items = new Array(1, 2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // 1
console.log(items[1]) ; // 2

Array()传入一个数值参数是表示数组的长度,传入多个则是表示单个元素。

Array.of( )该方法的作用非常类似Array构造器,但在使用单个数值参数的时候并不会导致特殊结果。Array.of( )方法总会创建一个包含所有传入参数的数组,而不管参数的数量与类型

let items = Array.of(1, 2);
console.log(items.length); // 2
console.log(items[0]); // 1
console.log(items[1]); // 2


items = Array.of(2);
console.log(items.length); // 1
console.log(items[0]); // 2

3.数组实例的 find() 和 findIndex()

数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。

[1, 4, -5, 10].find((n) => n < 0 } // -5

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

4.数组实例的includes()

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值。该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。

[1, 2, 3].includes(2)   // true
[1, 2, 3].includes(3, -1); // true
[1, 2, 3, 5, 1].includes(1, 2); // true

没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判.

[NaN].indexOf(NaN) // -1
[NaN].includes(NaN) // true

5、数组实例的 entries(),keys() 和 values()

ES6 提供entries()keys()values(),用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

六、rest 参数(剩余参数)

ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。

function addNumbers(a,b,c,d,e){
  var numbers = [a,b,c,d,e];
  return numbers.reduce((sum,number) => {
    return sum + number;
  },0)
 }
 console.log(addNumbers(1,2,3,4,5));//15

改为ES6写法:

function addNumbers(...numbers){
  return numbers.reduce((sum,number) => {
    return sum + number;
  },0)
 }
 console.log(addNumbers(1,2,3,4,5));//15

rest 参数还可以与箭头函数结合

const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)// [1,2,3,4,5]  

也可以与解构赋值组合使用

var array = [1,2,3,4,5,6];
var [a,b,...c] = array;
console.log(a);//1
console.log(b);//2
console.log(c);//[3, 4, 5, 6]

注意

  • 每个函数最多只能有一个rest参数,并且必须在末尾。不然会报错

  • rest参数不能用于对象字面量setter之中

七、扩展运算符

与剩余参数关联最密切的就是扩展运算符。剩余参数允许你把多个独立的参数合并到一个数组中;而扩展运算符则允许将一个数组分割,并将各个项作为分离的参数传给函数。

1、栗子

let values = [25,50,75,	100]
//等价于console.log(Math.max(25,50,75,100));
console.log(Math.max(...values));	//100

2、扩展运算符还可以与其他参数混用

let values = [-25,-50,-75,-100]
console.log(Math.max(...values,0));	//0

3、扩展运算符拆解字符串与数组

var array = [1,2,3,4,5];
console.log(...array);//1 2 3 4 5
var str = "String";
console.log(...str);//S t r i n g

4、还可以实现拼接

var defaultColors = ["red","greed"];
var favoriteColors = ["orange","yellow"];
var fallColors = ["fire red","fall orange"];
console.log(["blue","green",...fallColors,...defaultColors,...favoriteColors]
//["blue", "green", "fire red", "fall orange", "red", "greed", "orange", "yellow"]

八、解构赋值

ES6 新增了解构,这是将一个数据结构分解为更小的部分的过程。

ES5的写法如下:

var expense = {
   type: "es6",
   amount:"45"
};
var type = expense.type;
var amount = expense.amount;
console.log(type,amount);

当把数据结构分解为更小的部分时,从中提取你要的数据使用解构赋值会变得容易许多。

上诉栗子可以是这样:

const { type,amount } = expense;
console.log(type,amount);

在看一个例子:

let node = {type:"Identifier",	name:"foo"},	
type = "Literal",name = 5;
({type,name}= node);//	使用解构来分配不同的值 
console.log(type); //	"Identifier" 
console.log(name); //	"foo"

注意: 你必须用圆括号包裹解构赋值语句,这是因为暴露的花括号会被解析为代码块语句,而块语句不允许在赋值操作符(即等号)左侧出现。圆括号标示了里面的花括号并不是块语句、而应该被解释为表达式,从而允许完成赋值操作。

1、默认值

let node = {
  type: "Identifier",
  name: "foo"
};
let {
  type,
  name,
  value = true
} = node;

console.log(type); //	"Identifier" 
console.log(name); //	"foo" 
console.log(value); //	true

2、嵌套对象解构

let node = {
  type: "Identifier",
  name: "foo",
  loc: {
    start: {
      line: 1,
      column: 1
    },
    end: {
      line: 1,
      column: 4
    }
  }
};

let { loc: {start, end} } = node;

console.log(start)
console.log(end);

3、解构用在参数上

function setCookie(name, value, {
  secure,
  path,
  domain,
  expires
}) {
  //	设置cookie的代码 
}


setCookie("type", "js");//报错

若解构参数是可选的,可以给解构的参数提供默认值来处理这种错误。

function setCookie(name, value, {
  secure,
  path,
  domain,
  expires
} = {}) {}
setCookie("type", "js");//不会报错

4、数组解构

const names = ["Henry","Bucky","Emily"];
const [name1,name2,name3] = names;
console.log(name1,name2,name3);//Henry Bucky Emily
const [name,...rest] = names;//结合展开运算符
console.log(rest);//["Bucky", "Emily"]

5、数组长度

const {length} = names;
console.log(length);//3

5、数组解构也可以用于赋值上下文,但不需要用小括号包裹表达式

let colors = ["red", "green", "blue"],
  firstColor = "black",
  secondColor = "purple";
[firstColor, secondColor] = colors;
console.log(firstColor); //	"red" 
console.log(secondColor);	// "green"

6、默认值

let colors = ["red"];
let [firstColor, secondColor = "green"] = colors;
console.log(firstColor); //	"red" 
console.log(secondColor);//	"green"

7、与rest参数搭配

let arr = [1, 2,3, 5];

let a = [...arr];

8、初始化

当使用解构来配合var、let、const来声明变量时,必须提供初始化程序(即等号右边的值)。下面的代码都会因为缺失初始化程序而抛出语法错误:

var { type, name }; // 语法错误! 
let { type, name }; // 语法错误!
const { type, name }; // 语法错误!

八、Class 和传统构造函数有何区别

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。但是类只是基于原型的面向对象模式的语法糖

对比在传统构造函数和 ES6 中分别如何实现类:

//传统构造函数
function MathHandle(x,y){
  this.x=x;
  this.y=y;
}
MathHandle.prototype.add =function(){
  return this.x+this.y;
};
var m=new MathHandle(1,2);
console.log(m.add())
//class语法
class MathHandle {
 constructor(x,y){
  this.x=x;
  this.y=y;
}
 add(){
   return this.x+this.y;
  }
}
const m=new MathHandle(1,2);
console.log(m.add())

这两者有什么联系?其实这两者本质是一样的,只不过是语法糖写法上有区别。

所谓语法糖是指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。

  • 对比在传统构造函数和 ES6 中分别如何实现继承:
//传统构造函数继承
function Animal() {
    this.eat = function () {
        alert('Animal eat')
    }
}
function Dog() {
    this.bark = function () {
        alert('Dog bark')
    }
}
Dog.prototype = new Animal()// 绑定原型,实现继承
var hashiqi = new Dog()
hashiqi.bark()//Dog bark
hashiqi.eat()//Animal eat
//ES6继承
class Animal {
    constructor(name) {
        this.name = name
    }
    eat() {
        alert(this.name + ' eat')
    }
}
class Dog extends Animal {
    constructor(name) {
        super(name) // 有extend就必须要有super,它代表父类的构造函数,即Animal中的constructor
        this.name = name
    }
    say() {
        alert(this.name + ' say')
    }
}
const dog = new Dog('哈士奇')
dog.say()//哈士奇 say
dog.eat()//哈士奇 eat

Class之间可以通过extends关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

Class 和传统构造函数有何区别

  • Class 在语法上更加贴合面向对象的写法
  • Class 实现继承更加易读、易理解,对初学者更加友好
  • 本质还是语法糖,使用prototype

九、ES6 系列之迭代器与 for of

1、 迭代器

所谓迭代器,其实就是一个具有next()方法的对象,每次调用 next() 都会返回一个结果对象,该结果对象有两个属性,value 表示当前的值,done 表示遍历是否结束。

function createIlter(items){
    var i = 0;
    
    return {
        next: function() {
            var done =  i >= items.length; // 是否结束
            
            var value = !done ? items[i] : undefined
            i++;
            return {
                done,
                value
            }
        }
    }
}

2、for of

上诉我们实现了一个简单的迭代器:

除了迭代器之外,我们还需要一个可以遍历迭代器对象的方式,ES6 提供了for of 语句,我们直接用 for of 遍历一下我们上节生成的遍历器对象试试:

for (let value of arr) {
    console.log(value);
}

报错了,提示说不是一个iterable。表明我们生成的 arr 对象并不是 iterable(可遍历的)。 VM585:18 Uncaught TypeError: arr is not iterable

那什么才是可遍历的呢?

其实一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是"可遍历的"(iterable)。

// 迭代器
function createIterator(items) {
    var i = 0;
    return {
        next: function() {
            var done = i >= items.length;
            var value = !done ? items[i++] : undefined;

            return {
                done: done,
                value: value
            };
        }
    };
}

// 
var o = {
    "name": "zjj",
    "age": 12
}

// 实现inerator接口
o[Symbol.iterator] = function() {
    return createIterator(Object.keys(o));
}

// 遍历
for(var i of o) {
    console.log(i)
}

由此,我们也可以发现 for of 遍历的其实是对象的 Symbol.iterator 属性。

3、for of 默认可遍历对象

const colors = ["red", "green", "blue"];

for (let color of colors) {
    console.log(color);
}

// red
// green
// blue

尽管我们没有手动添加 Symbol.iterator 属性,还是可以遍历成功,这是因为 ES6 默认部署了 Symbol.iterator 属性,当然我们也可以手动修改这个属性:

var colors = ["red", "green", "blue"];

colors[Symbol.iterator] = function() {
    return createIterator([1, 2, 3]);
};

for (let color of colors) {
    console.log(color);
}

// 1
// 2
// 3

除了数组之外,还有一些数据结构默认部署了 Symbol.iterator 属性。

所以 for...of 循环可以使用的范围包括:

  • 数组
  • Set
  • Map
  • 类数组对象,如 arguments 对象、DOM NodeList 对象
  • Generator 对象
  • 字符串

4、Iterator接口

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就需要一种统一的接口机制,来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

  • 几种循环方式

如何优雅地改善程序中for循环