var关键字声明的变量会被视为声明于所在作用域的顶部,即变量提升:
function getvalue(condition) {
if (condition) {
var value = "blue";
return value;
} else {
// value在此处可访问,值为undefined
return nu1l;
}
// value在此处可访问,值为undefined
}
事实上value会进行变量提升,相当于:
function(){
var value;
if(){
value = "blue";
}
}
value的声明提升到顶部,而初始化保留在原处。因此在else分支内访问value值为undefined(未初始化)。
ES6引入块级作用域,所声明的变量在指定块的作用域外无法被访问。块级作用域被创建的情况:
const person = [
name: "Nicholas"
};
//工作正常
person.name = "Greg";
//抛出错误
person = {
name: "Greg"
};
修改成员并没有修改person的绑定值(对象的引用),而当person自身赋值(字面量)会改变绑定导致报错。
即:const阻止对于变量绑定的修改,而非对成员值的修改。
遇到let
与const
时会将声明放在暂时性死区内,访问其中的变量会报错runtime error
,直到执行到声明语句时才会从暂时性死区中移除安全使用,typeof
也会受此影响(当typeof在该代码块外时应当报错undefined
)
var funcs = [];
for (let i= 0; i< 10; 1++) {
funcs. push(function() {
console .10g(i);
});
}
funcs.forEach(function(func) {
func();
})
每次迭代时都会创建新的绑定:每一个循环都是新的代码块,会保存新的 i 变量副本(var声明会提升而let声明不会,但是let在循环内部的行为是特别定义的,与变量提升关系不大)。
如果在循环中使用const,只要不改变值就没有问题(显然(const i = 0;i<10;i++)
在第二次迭代时会报错):
var funcs = [],
object = {
a: true,
b: true,
C: true
};
//不会导致错误
for (const key in object) {
funcs.push( function() {
console .1og(key);
});
}
funcs.forEach(function(func) {
func();
// 依次输出 "a"、"b"、 "C"
});
在全局作用域上使用var
,会创建一个全局变量,作为全局对象的一个属性(浏览器中是window),则可能会无意覆盖window的原有的属性:
//在测览器中
var RegExp = "Hello!";
console.log( window.RegExp);
// "Hello!"
事实上应当是保存正则表达式的构造函数:
而使用let
和const
时,会在全局作用域创建新的绑定,创建的变量的绑定屏蔽了全局的RegExp:
//在浏览器中
let RegExp = "He1lo!";
console.log(RegExp);// "Hello!"
console .log(window.RegExp === RegExp); // false
let与const不会在全局对象上添加属性。全局作用域不会被污染。(如果需要代码在全局对象中可以被访问则使用var声明)
块级绑定中的最佳实践:在默认情况下使用const
,当变量值需要更改使用let
。
includes()
:存在子串返回true;startsWith()
:子串在起始位置返回true;endsWith()
:子串在结束位置返回true;都接收两个参数:子串与搜索起始位置,前二者从索引位置开始正向匹配,后者反向查找(起始位置为长度减索引位置)。
与indexOf()
不同的是,它们返回是否存在而非具体查找位置,并且不可以接收正则表达式作为参数(会报错)。
接收参数作为字符串的重复次数,返回新串:
console.log("abc".repeat(3));
//"abcabcabc"
反引号包裹字符串:
let message = `Hello world!`;
在字符串中包含反引号需要反斜杠 \
转义
使用反斜杠\
:延续下一行的字符串:
var message = "Multiline \
string";
console.log(message); // "Multiline string"
为了在输出中能够换行:使用模板字符串:
let message = `Multiline
string`;
console.log(message);
//"Multiline
//string"
console.log(message.length);// 16
注意:模板字符串中的空白字符会视为自身的一部分,记入长度。
此外,当然可以使用\n
。
在模板字符串中嵌入任何有效的JS表达式,使用$
与一对花括号{}
:
替换位变量名:
let name = "nico",
message = `Hello, ${
name}`;
console.log(message);//"Hello, nico"
嵌入函数、表达式:
let count = 10,
price = 0.25,
message = `${
count} items cost $${
(count * price).toFixed(2)}.`;
console.log(message);//"10 items cost $2.50."
同时,模板字符串也是表达式,可以进行嵌套。
模板标签可以对模板字符串进行转换并返回字符串值,标签指定与模板字符串的起始处:
let message = tag`Hello world`;
tag是会应用到模板字面量上的标签。
标签是处理模板字面量的函数,接收的数据被划分为片段:它接收的参数:第一个是字符串数组,包含模板字符串中除去替换位的部分(如果由替换位开始,则第一个数组元素为空串以确保字符串的起始),随后的参数依次是替换位的解释值:
let count = 10,
price = 0.25,
message = pass`${
count} items cost $${
(count * price).toFixed(2)}.`;
此处,pass()函数接收到三个参数,第一个字符串数组为:
["", " items cost $", "."]
随后接收的两个参数分别是count值为数值10与计算结果字符串“2.50”。
pass()函数:
function passthru(literals, ...substitutions) {
let result ="";
//避免另一个数组越界
for (let i = 0; i < substitutions.1ength; i++) {
result += literals[i];
result += substitutions[i];
}
//添加最后一个字面量
result += literals[literals.1ength -1];
return result;
}
console.1og(message);
// "10 items cost $2.50."
使用内置的String.raw
标签访问模板字符串的原始形式:
1et message1 =‘Multiline\nstring ,
message2 = string.raw`Multiline\nstring`;
console .log(message1);
// "Multiline
// string"
console.log(message2);
// "Multiline\\nstring"
y
标志影响正则表达式搜索时的粘连sticky
属性,表示从lastIndex
属性值的位置开始检索。
var text = "hel101 hello2 he1lo3",
pattern = /hello\d\s?/,
result = pattern. exec(text),
globalpattern = /hello\d\s?/g,
globalResult = globalpattern. exec(text),
stickyPattern = /hello\d\s?/y,
stickyResult = stickypattern. exec(text);
console .log(resu1t[0]);
console .log(globa1Resu1t[0]);
console. log(stickyResu1t[0]);
// "hello1"
pattern.1astIndex = 1;
globalpattern.1astIndex = 1;
stickypattern.lastIndex = 1;
result = pattern. exec(text);
g1obalResult = globalPattern. exec(text);
stickyResult = stickyPattern. exec(text);
console.log(resu1t[0]);
// "he1lo1"
console.log(globalResu1t[0]);
// "hello2"
console.log(stickyResu1t[0]);
// Error! stickyResult is null
注意:只有在正则表达式对象上的方法(exec()
、test()
),lastIndex
属性才会生效;而将正则表达式作为参数传给字符串的方法(match()
)不会生效(无粘连特性)
检测y标志是否存在:
var pattern = /hello/y;
console.log(pattern.sticky);//true
存在时sticky属性值为true(由y标志存在与否决定)。
将正则表达式传给RegExp
构造器进行复制:
var re1 = /ab/i,
//re2 = new RegExp(re1);
//ES5会报错,ES6可行
re2 = new RegExp(re1,"g");
ES6中允许使用第二个参数,并使其覆盖第一个参数中的标志。
正则表达式的source
属性返回其文本,对应地添加flags
属性返回其标志(由所有标志组成的字符串)
function request(url, timeout, callback){
timeout = timeout || 2000;
callback = callback || function(){
};
//更安全的方法:
timeout = (typeof timeout !== "undefined")? timeout : 2000;
}
ES6使用初始化的形式简化了参数默认值的设置:
function request(url, timeout = 2000, callback = function(){
}){
}
未传递的、传入undefined
的参数将使用默认值。(null
值是有效的)
ES6中,arguments
对象表现与ES5严格模式一致,参数默认值触发了arguments与命名参数的分离。arguments此时仅包含被正确传入的参数,使用默认值的参数在对应的arguments中是undefined
;更改命名参数不会对arguments产生影响。
当使用其他参数来为参数进行默认赋值是,仅允许使用前面的参数,前面的参数不能访问后面的参数。对应参数在暂时性死区内:
function add(first = second, second) {
return first + second;
}
console.log(add(1, 1));// 2
console.log(add(undefined, 1)); //抛出错误
找到数组中的最大值:
let values = [25,56,82,955,1025];
console.log(Math.max.apply(Math, values));
ES6中使用扩展运算符...
来传入整个数组,JS引擎会将数组分隔为独立参数传入:
Math.max(...values);
扩展运算符可以与其他参数混用:使Math.max()
返回的最小值为0,将0单独传入:
console.log(Math.max(...values, 0));
JS函数不要求参数的数量等于已定义的参数数量,提供arguments
对象来查看传入的参数。
剩余参数(rest parameter)由三个点...
与紧跟的命名参数指定,是包含传递给函数的其余参数的数组。
剩余参数在一个函数中只能有一个,且必须在传参的最后(first,...keys)
,形如(first, ...keys, last)
的参数会导致语法错误。
setter属性不可使用剩余参数,因为setter限定只能使用单个参数:
let object = {
//报错
set name(...value){
}
}
Function构造器可以使用字符串动态创建新函数:
var add = new Function("first", "second", "return first + second");
ES6允许Function构造器使用默认参数与剩余参数:
var add = new Function("first = 0", "...args", "return first + args[0]");
确保构造函数与函数声明形式具有相同的所有能力。
ES6中所有函数都有适当的name
属性值
函数声明的优先级高于函数赋值目标的变量名。
getter与setter函数会在名称属性值前带有get
/set
前缀,bind()创建的函数会有bound
前缀,Function构造器创建的函数会有anonymous
前缀。
name
属性是为了在调试时获得相关信息,不能用它来获取对函数的引用。
ES5及之前,函数使用new
调用时,其内部的this
是一个新对象,并作为函数的返回值:
function Person(name){
this.name = name;
}
var person = new Person("nicho"); //[Object object]
var notPerson = Person("nichi"); //undefined
其中,JS为函数提供了两种内部方法:[[Call]]
与[[Construct]]
,当函数未使用new
调用,[[Call]]
方法执行,运行函数体;使用new
调用,[[Construct]]
方法执行,负责创建一个新对象,并使用它作为this
去执行函数体。拥有[[Construct]]
方法的函数被称作构造器。
一般使用instanceof
判断是否为构造器,对this
值检查,判断是否为构造器的一个实例;但是在使用Person.call(new Person("nico"), "Mico");
时,内部的this被设置为Person的实例,使用instanceof无法区分。
ES6引入new.target
元属性,元属性指非对象上的属性,提供关联它的目标的附加信息。当函数的[[Construct]]
方法被调用,new.target
被填入new
运算符的作用目标(通常是新创建的对象实例的构造方法),并作为函数体内部的this
值;如果[[Call]]
被执行则其值为undefined
。
function Person(name){
if(typeof new.target === Person){
this.name = name;
}else{
throw new Error("please use new");
}
}
function AnotherPerson(name){
Person.call(this, name);
}
var person = new Person("nico");
//报错,因为new.target值为undefined
var anotherPerson = new AnotherPerson("nocho");
在函数外使用new.target会有语法错误。
ES5的严格模式不允许在代码块内部声明函数:
"use strict"
if(true){
function foo(){
}
}
在ES6中,foo()函数被视为块级声明,在所定义的代码块内可以被访问,函数声明提升至当前代码块的顶部,使用let声明的函数表达式不会提升(let foo = function(){}
)。
ES6在非严格模式下也允许使用块级函数,但是其作用域会被提升至所在函数或全局环境的顶部,而非当前代码块的顶部。
尾调用指在函数最后语句中调用函数。
ES5中尾调用的处理与其他函数一样:新栈帧被创建并推入调用栈来表示该次调用。
ES6在严格模式下为特定尾调用减少调用栈,需要满足:
"use strict";
function dosth(){
return other();
//不满足的情况:
//return 1+other();有其他操作
//other();不作为返回值
}
此时,尾调用优化会清除当前栈帧并再次利用,而非创建新的栈帧。
尾递归优化的主要用例在递归中:考虑阶乘:
function factorial(n){
if(n <= 1){
return 1;
}else{
return n * factorial(n-1);
}
}
随n增长,调用栈大小增长,可能导致堆栈溢出。
考虑优化,则需要最后的语句不发生乘法运算,将乘法移至函数内:
function factorial(n, p = 1){
if(n <= 1){
return 1 * p;
}else{
let result = n * p;
return factorial(n-1, result);
}
}
此时,p参数保存前一次乘法的结果。
尾调用优化提供显著的性能提升,尤其是在计算复杂度高时。
箭头函数(arrow function),使用=>
定义:
this
、super
、arguments
、new.target
:这些值由所在的最靠近的非箭头函数决定。new
调用:没有[[Construct]]
方法,不能作为构造函数,否则会报错。prototype
属性。this
:this值在函数内部不能修改,在整个生命周期内保持不变。使用call、apply、bind方法时this值不受影响。arguments
对象:必须使用具体的命名参数或者剩余参数来传参。常规函数作为构造器时this异变不利于优化。
箭头函数也拥有name属性,规则与常规函数相同。
即使不使用return也会将函数体内的变量返回:
var reflect = value => value;
//等价于:
var reflect = function(value){
return value;
};
单个参数不需要圆括号包裹;无入参则为:
var get = () => "nico";
函数体包含多个语句则使用花括号包裹:
var sum = (num1 + num2) => {
return num1 + num2;
};
当需要返回对象字面量时:函数体的花括号需要变成圆括号:
var getItem = id => ({
id: id, name: "nico"});
创建立即调用函数表达式,immediately-invoked function expression,IIFE。
当需要创建作用域并隔离在程序其他部分之外:
let person = function(name){
return {
getName: function(){
return name;
}
};
}("Nico");
console.log(person.getName());//"Nico"
此时IIFE用于创建一个包含getName方法的对象,在箭头函数中:
let person = ((name) => {
return {
getName: function(){
return name;
}
};
})("Nico");
传统函数中,(function(){}());
与(function(){})();
都是有效的,在箭头函数中只有后者有效:括号仅包裹箭头函数的定义。
传统的数组排序:
var result = values.sort(function(a,b){
return a-b;
});
使用箭头函数使之简洁:
var result = values.sort((a,b) => a-b);
function Person(name, age){
return {
name: name,
age: age
};
}
简化为:
return {
name,
age
};
当对象字面量中属性只有名称时。JS引擎会在周边作用域查找同名变量。
let person= {
name: "name",
//sayName: function(){alert(this.name);}
sayName(){
alert(this.name);
}
};
函数简写。
注意:方法简写可以使用super
,非简写的方法不能使用。
计算属性名:使用方括号表示法:
let suffix = " name";
let person = {
["first" + suffix]: "noco",
["last" + suffix]: "zakas"
};
//属性名结果为字符串
严格等于中,+0
与-0
相等,NaN === NaN
会返回false
。
ES6引入Object.is()
方法,接收两个参数,比较二者值与类型相等返回true
。一般与严等相同,除了上述两个例子结果相反。
浅拷贝方法,接收参数为一个接收者与任意数量的供应者(依次拷贝属性,若相同则后接收的属性会覆盖先接收的值)。使用赋值运算符,供应者的访问器属性会变成接收者的数据属性。
ES5中严格非严格模式都会检查重复的属性,ES6不会。当存在重复属性时,后面的值会覆盖成为属性的实际值。
数字升序,字符串、符号类型按照添加顺序。定义顺序优先于动态添加。
ES5添加Object.getPrototypeOf()
方法用于获取对象原型;ES6添加Object.setPrototypeOf()
方法来修改对象原型,接收两个参数:被修改原型的对象与成为原型的对象。
super
是指向当前对象的原型的指针,即等同于Object.getPrototypeOf(this)
的值。
当使用super
来调用原型的方法时,则该方法必须位于简写的方法内,否则使用具名方法会导致语法错误:
let f = {
greet(){
return super.greet();
}
/* 错误的用法:
greet: function(){
return super.greet();
}*/
另外,由于this
值引发的问题可以由super
更好地解决,因为super
的引用非动态不受影响,总是指向正确的原型对象。
此前方法指对象的函数属性(区别于数据属性)。
ES6正式定义方法为:拥有[[HomeObject]]
内部属性的函数,该属性指向方法所属的对象,由此区别对象内外的函数。
对super的引用会判断[[HomeObject]]
属性的值,在其上调用Object.getPrototypeOf()
方法获取原型的引用,然后查找同名函数,最后创建this
绑定并调用方法。
对象解构左侧使用对象字面量,右侧必须有初始化器:
let node= {
type: "def",
name: "doc"
};
let {
type, name}= node;
解构赋值:
let node= {
type: "def",
name: "doc"
},
type= "chl",
name= 56;
( {
type, name}= node);
使用圆括号是因为暴露的花括号视为代码块,而其右侧不允许有等号。圆括号代表其中的语句非代码块,而是解释为表达式。
**解构赋值表达式的值为其等号右侧的值。**若右侧值为undefined/null
会报错runtime error
。
当未找到同名变量时,解构赋值的默认值为undefined
。也可以指定默认值:
let node= {
type: "def",
name: "doc"
};
let {
type, name, color = "red"}= node;
则在对应属性缺失或为undefined
时赋值为指定默认值。
当使用不同名的变量赋值时,则使用扩展语法:
类似于属性初始化:
let node= {
type: "def",
name: "doc"
};
let {
type: localType, name: localName}= node;
//对应赋值给localType与localName变量
嵌套的对象解构:
let node= {
type: "def",
loc: {
start: {
line: 2,
column: 1
},
end: {
line: 1,
column: 1
}
}
};
let {
type, loc: {
start} }= node;
使用花括号表示在属性内部寻找嵌套的属性。
使用非同名变量的情况:
let node= {
type: "def",
loc: {
start: {
line: 2,
column: 1
},
end: {
line: 1,
column: 1
}
}
};
let {
type, loc: {
start: localStart} }= node;
使用数组字面量,根据位置进行解构(而非对象的同名使用变量名):
let color= ["red", "blue", "green"];
let [fir, sec]= color;
//"red", "blue"
let [, , last]= color;
//"green"
//解构赋值
[fir, sec, last]= color;
逗号占位使变量获取对应位置的值。
解构赋值在交换变量值的应用:
let a=1, b=2;
[a, b]= [b, a];
右侧为临时创建的数组,左侧为对应解构的赋值,完成了两个变量值的互换。
默认值的情况:
let color= ["red"];
let [fir, sec= "green"]= color;
嵌套的情况:
let color= ["red", ["green", "blue"], "white"];
let [fir, [sec]]= color;//"red", "green"
使用剩余项(剩余参数)将剩余的项目赋值给指定的变量:
let color= ["red", "green", "white"];
let [fir, ...sec]= color;
//"red"
//["green", "white"]
复制数组时往往使用concat方法(本意为合并,当无参数时为复制):
let color= ["red", "green", "white"];
let clone= color.concat();
使用剩余项可以达到相同效果:
let color= ["red", "green", "white"];
let [...clone]= color;
剩余项必须是解构模式的最后一部分,否则语法错误。
数组和对象的结构可以同时混合使用。
在函数接收可选参数时,常用模式为:将附加的参数赋给options
对象作为具名属性,将options对象传入函数:
function setCookies(name, value, options){
options= options || {
};
let secure = options.secure,
path = options.path,
domain= options.domain,
expires= options.expires;
}
参数解构提供了更明确的方案:使用对象或数组的结构模式替代了具名参数,当未传入时参数值为undefined
:
function setCookies(name, value, {
secure, path, domain, expires}){
}
需要注意的问题:解构的右侧值为null/undefined
时会报错,也就是当右侧未传值时会出错。也就是当参数为可选参数时需要提供参数默认值来处理:
function setCookies(name, value, {
secure, path, domain, expires}={
}){
}
也可以使用解构默认值:
function setCookies(name, value, {
secure=false, path="/", domain="example.com", expires="new Date(Date.now() + 360000000)} = {
}){
}
新的基本类型:符号Symbol
。
创建:没有字面量形式,使用全局Symbo()
函数创建:
注意:使用new Symbol()
会报错,因为Symbol
是基本类型。
let fir = Symbol();
let person = {
};
person[fir] = "nico";
可以使用typeof
运算符判断Symbol
值(返回"symbol"
)。
共享符号值:跨文件或代码访问Symbol相对困难,因此提供了“全局符号注册表”:使用Symbol.for()
接收单个字符串参数,作为其标识符与描述信息。
Symbol.for()
方法会先搜索全局符号注册表,判断是否存在该字符串为键的符号值,若是则返回该符号值,否则创建新的符号值并记录注册表,然后返回。
对应Symbol.keyFor()
方法在全局注册表中根据符号值Symbol返回其键值String。(使用Symbol()
函数创建的值没有在全局注册所以不会查询到结果会返回undefined
)
在全局注册时应当使用命名空间前缀来避免命名冲突。
符号值不可以直接转换为字符串(使用加号连接空串报错)、数字(数学运算报错),逻辑运算中作为非空值等价于true
。
console.log(symbol)
会调用了Symbol的String()
方法来进行输出,即等于String(symbol)
,而该方法调用symbol.toString()
来获取其字符串描述信息。
Object.keys()/getOwnPropertyNames()
方法(可枚举/不论是否可枚举)都不返回符号类型属性,可以使用Object.getOwnPropertySymbols()
方法来检索对象的符号类型属性。
(待追加的部分:Symbol属性,因为对下一章比较感兴趣所以先康康好玩的)
//作为容器,确保没有继承属性
let ves = Object.create(null);
//作为set
ves.foo = true;
if(ves.foo){
...}
//作为map
ves.foo = "nib";
局限性:
需要保证任意两个键不会被转换为相同的字符串:由于对象的属性只能是字符串,数值类型、对象类型键会在内部转换为字符串,因此ves[5]
与ves["5"]
引用相同的属性。
且,若值存在且为假值,会在if
条件中混淆其存在性。
无重复值的有序列表。
使用new Set()
创建,add()
方法添加项目,size
属性查看项数。
let set = new Set();
set.add(5);
set.add("5");
console.log(set.size);//2
Set
中 不会强制类型转换(内部使用Object.is()
来判断重复的项,+0与-0是相等的)。
可以使用 数组 来初始化Set
(可接受任意可迭代对象作为参数)
let set= new Set([1,2,3,4,4,4,5,6,8,8,8,8,8,]);
console.log(set.size);//7
使用has()
方法来查询某个值是否在Set中:
console.log(set.has(5));//true
使用delete()
方法来移除值,使用clear()
方法移除其中所有值(清空)。
迭代:
使用forEach()方法,由于该方法的三个参数:值、键、容器本身,而Set没有键,所以其前两个参数值是一样的(每一项同时认定为键值):
let set = new Set([1,2]);
set.forEach(function(value, key, ownerset){
console.log(value+ " "+ key);
});
//1 1
//2 2
若需要在回调函数中使用this,则需要给forEach()传入this作为第二个参数:
let set = new Set([1,2]);
let processor = {
output(value){
console.log(value);
},
process(dataSet){
dataSet.forEach(function(value){
this.output(value);
}, this);
}
//使用箭头函数而无需传入this:
process(dataSet){
dataSet.forEach((value)=>this.output(value));
}
};
processor.process(set);
转换为数组:
使用扩展运算符将可迭代对象(例如Set)转换为数组:
let set = new Set([1,2,3,3]);
let array = [...set];//[1,2,3]
Weak Set:
let set = new WeakSet();
与Set相同,拥有add
、has
、delete
方法,构造器可传入数组;
与Set不同:
add
方法传入非对象的参数会报错,delete
或has
方法则对基本类型返回false
;has
来证实)keys、values
方法),无法在for-of
循环中使用,没有forEach
方法size
属性一般在追踪对象的引用时应当使用WeakSet。
键值对的有序列表。
键、值可为任意类型,因此5
与"5"
可以同时作为键,可以使用Object.is()
方法比较键。
使用set()
、get()
来设置和获取键值:
let map = new Map();
map.set("name", "nico");
console.log(map.get("name"));//"nico"
当获取键不在Map
中则get
方法返回undefined
。
Map
有与Set相似的方法:has(key)
判断指定键是否存在、delete(key)
删除对应键值、clear()
清除所有键值。
Map
拥有size
属性,包含其中键值对的数量。
使用数组初始化Map:
let map = new Map([["name","nico"],["age",13]]);
传入参数数组的每一项都是数组,为了保证在传入Map之前不会被强制转换为其他类型。
Map的ForEach方法
与Set与数组类似,接受回调函数包含三个参数:值、键、Map自身:
map.forEach(function(value, key, map){
console.log(key+" "+value);
});
Weak Map:
与Weak Set相似,键为对象,弱引用不会干扰垃圾回收。
(需要注意键是弱引用而值不是,值中存储对象会阻止垃圾回收)
Weak Map是键值对的无序列表,初始化与Weak Set类似。不可枚举,没有size、clear()。
往往在对应DOM元素与自定义对象时使用,也用于私有变量的保存与获取。 如果只想用对象作为键则应当使用Weak Map,确保数据不可用后被销毁。
专用于迭代的对象。
每个对象都有next()
方法(返回一个结果对象,包含下一个值value
与布尔类型的done
,值为true
时代表无值可用)。
若在最后一个值返回后使用next()
方法则其done
属性为true
且value
属性为迭代器自身返回值(往往是undefined
)。
在ES5中创建迭代器:
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;
};
}
};
}
生成器generator函数返回一个迭代器。生成器函数由function
关键字后的*
星号表示,可使用新的yield
关键字(指定迭代器next()
方法调用时按顺序返回的值)
每个yield
语句执行后会停止执行。
可迭代对象,即包含Symbol.iterator属性对象,所有的集合(数组、Set、Map)与字符串都是可迭代对象。
for-of
循环执行时会调用next()
方法:
let values = [1,2,3];
for(let num of values){
console.log(num);
访问默认迭代器的方法:
let iterator = values[Symbol.iterator]();
console.log(iterator.next());
可以检测一个对象是否可以进行迭代:
function isIterable(obj){
return typeof obj[Symbol.iterator] === "function";
}
isIterable(new Map());//true
isIterable(new WeakMap());//false
创建可迭代对象:
let collection = {
items: [],
*[Symbol.iterator](){
for(let item of this.items){
yield item;
}
}
};
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);
三种集合对象类型:数组、Map、Set;都具有以下迭代器:
entries()
:返回包含键值对的迭代器values()
:返回包含值的迭代器keys()
:返回包含键的迭代器entries()
迭代器的next()
方法返回双项数组包含键与值,数组的键为索引,Set的键与值相同。
let a = [1,2,3];
for(let entry of a.entries()){
console.log(entry);
}
//[0,1]...
若数组包含添加的具名属性,for-of
会迭代其索引而for-in
会迭代其索引与所有属性。
字符串、NodeList也可以使用for-of
迭代其所有字符。
(扩展部分:生成器的高级用法)