一、块级作用域
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 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
- 几种循环方式