{"id":13720431,"url":"https://github.com/ziyi2/es6","last_synced_at":"2025-04-09T18:30:24.088Z","repository":{"id":98890163,"uuid":"64940974","full_name":"ziyi2/es6","owner":"ziyi2","description":"es6学习笔记","archived":false,"fork":false,"pushed_at":"2016-08-23T14:00:42.000Z","size":70,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-23T20:34:04.036Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ziyi2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-08-04T14:23:13.000Z","updated_at":"2022-12-26T14:08:45.000Z","dependencies_parsed_at":"2023-03-07T19:45:20.593Z","dependency_job_id":null,"html_url":"https://github.com/ziyi2/es6","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziyi2%2Fes6","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziyi2%2Fes6/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziyi2%2Fes6/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziyi2%2Fes6/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ziyi2","download_url":"https://codeload.github.com/ziyi2/es6/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248087542,"owners_count":21045535,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T01:01:03.738Z","updated_at":"2025-04-09T18:30:24.060Z","avatar_url":"https://github.com/ziyi2.png","language":null,"funding_links":[],"categories":["笔记"],"sub_categories":["JavaScript"],"readme":"# ECMAScript 6\n\n## 使用环境\n### Chrome\n访问[chrome://flags/#enable-javascript-harmony](chrome://flags/#enable-javascript-harmony),开启`enable-javascript-harmony`特性,就可以使用`ES6`了.\n\n### Nodejs\n升级最新的Nodejs版本,在Nodejs中使用`ES6`.\n\n### Webpack\n可以访问[使用Babel 6和Webpack在浏览器端运行ES6和ES7](http://www.tuicool.com/articles/fmUze2M)查看使用方法.\n\n## let 和 const\n\n`ES5`只有两种声明变量的方式, `var`和`function`. 在`ES6`中有6种声明变量的方法,`var`,`function`,`let`,`const`,`import`和`class`.\n\n### let\n\n`let`具有块级作用域,而`var`没有\n\n``` javascript\n{\n    let a = 10;\n    var b = 1;\n}\n\nconsole.log(b); //1 \nconsole.log(a); //a is not defined  \n```\n进一步说明`let`仅在块级作用域有效\n\n``` javascript\nvar a = [],\n    b = [];\n    \nfor(var i = 0; i \u003c 10; i++) {\n    var c1 = i;\n    let c2 = i;\n    a[i] = function() {\n        console.log(c1);\n    };\n\n    b[i] = function(){\n        console.log(c2);\n    };\n}\n\na[5](); //9\nb[5](); //5\n```\n`let`更像`c`语言中的变量,不再具有**变量提升**能力\n\n``` javascript\nvar a = 1;\nlet b = 2;\n\nfunction variable(){\n    console.log(a);     //undefined\n    console.log(b);     //b is not defined\n    var a = 1;\n    let b = 2;\n}\n\nvariable();\n```\n暂时性死区\n\n``` javascript\nlet b = 1;\nif(true){\n    b = 2;  //b is not defined\n    let b;\n}\n```\n\n\u003e提示:`ES6`明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域.凡是在声明之前就使用这些变量,就会报错.总之,在代码块内,使用`let`命令声明变量之前,该变量都是不可用的.这在语法上,称为“暂时性死区”（**temporal dead zone**,简称`TDZ`）.\n\n``` javascript\nif(true){\n                                //TDZ Start\n    typeof(undefinedVariable);  //undefined\n    typeof(b);                  //b is not defined  \n\n    b = 2;                      //b is not defined\n    b = b + 1;\n\n    let b;                      //TDZ End\n    b = 10;\n    console.log(b);\n}\n```\n\n\u003e提示:此时使用`typeof`就会报错,但是如果变量没有声明反而不会报错而输出`undefined`.\n\n\n\n`let`不允许重复声明\n\n``` javascript\n{\n    var b = 1;\n    var b = 2;\n    console.log(b);     //2\n    let a = 1;\n    let a = 2;\n    console.log(a);     //Identifier 'a' has already been declared\n}\n```\n\n再次说明`let`新增了块级作用域\n\n```\nfunction f(){\n    let a = 1;      //外层代码块不受内层代码块影响\n    if(a){\n        let a = 2;  //只在该块中有作用域\n    }\n    console.log(a); //1\n}\n\nf();\n\nfunction f1(){\n    var b = 1;\n    if(b){\n        var b = 2;  //没有块级作用域\n    }\n    console.log(b); //2\n}\n\nf1();\n```\n\n\u003e提示: `ES5`只有全局作用域和函数作用域,而没有块级作用域,一般也都是利用立即执行的匿名函数来模拟块级作用域.\n\n函数的块级作用域\n\n`ES5`中规定函数只能在**顶层作用域**和**函数作用域**中声明,而不能在**块级作用域**中声明,在严格模式下会报错[非严格模式下不会报错],`ES6`中引入了类似于`let`的函数声明语句,该函数也是有作用域的,在**块级作用域**之外不能被引用.\n\n``` javascript\nfunction f(){           //第一次f函数声明\n    console.log('f is 1!');\n}\n\nif(false){              \n    function f(){       //第二次f函数声明\n        console.log('f is 2');\n    }\n}\n\nf();                    //f is 1 ES6\n                        //f is 2 ES5\n```\n\n\u003e提示:  在`ES5`中,`if`语句块中的`f`函数的声明会被提升到**全局作用域(顶层作用域)**,所以输出的是覆盖了第二次声明的`f`函数.但是在`ES6`中,由于语句块中的函数声明只是在该语句块中起作用(只能提升到该**块级作用域**的顶部),不会对语句块外产生影响,所以输出的是第一次声明的`f`函数.\n\n`ES6`中的函数\n- 允许在块级作用域内声明函数(在`ES5`中原则上是不允许的)\n- 函数声明类似于`var`,会提升到全局作用域或者函数作用域的顶部\n- 会提升到块级作用域的顶部\n\n``` javascript\nconsole.log(f);         //undefined 当做未声明过的变量来处理\n\nif(true){       \n    console.log(f);     //function f() 函数声明在块级作用域中提升\n    function f(){       \n        console.log('f is 2');\n    }\n}\n```\n\n\n### const\n\n\n``` javascript\nconst a = 1;\nconsole.log(a);         //1\na = 2;                  //Assignment to constant variable\n\nconst b;                //Missing initializer in const declaration\n```\n\u003e提示: `const`在声明的同时必须赋值,否则会报错.且不能重新赋值.\n\n`const`同样具有块级作用域\n\n``` javascript\n{\n    const a = 1;\n    console.log(a);    //1\n}\nconsole.log(a);        //a is not defined\n```\n\n和`let`一样也不可重复声明\n\n```javascript\nconst a = 1;\nconst a = 2;   //Identifier 'a' has already been declared\n\nvar b = 1;\nlet b = 2;     //Identifier 'a' has already been declared\nconst b = 3;\n```\n\n和`let`一样,变量不会提升声明,同样存在暂时性死区`TDZ`.\n\n``` javascript\nconst a = 3;\n\nif(true){\n    //TDZ Start\n    console.log(a); //a is not defined\n    const a = 1;    //TDZ End\n}\n```\n\n对于引用类型,`const`只能保证变量所指向的地址不变,而不能保证地址所在的值不变.\n\n``` javascript\nconst a = {};\na.name = 'a';\na.name = 'b';\nconsole.log(a); //Object {name: \"b\"}\n\na = {           //另一个对象赋值给a\n    name: 'c'\n}\nconsole.log(a); //Assignment to constant variable.\n```\n\u003e提示: `a`的属性值变没关系,只要`a`指向的地址不变,但是对`a`重新赋值,则导致`a`指向的地址变化了,这是不允许的. 数组也是一样.\n\n### 全局对象和属性\n`let`,`const`,`class`声明的全局变量将不再是全局对象(`window`,`global`)的属性,`var`和`function`仍然不变.\n\n``` javascript\nvar a=1;\nconsole.log(window.a);  //1\n\nlet b=2;\nconst c=3;\nconsole.log(window.b);  //undefined\nconsole.log(window.c);  //undefined\n```\n\n\n## 变量的解构赋值\n\n### Array解构\n\n将变量封装成数组的形式,然后将表达式右边的数组和对象中提取的值一一对应的赋值给这些变量,就叫做数组解析.\n\n``` javascript\nvar [a,b,c] = [1,2,3];\nconsole.log(a); //1\nconsole.log(b); //2\nconsole.log(c); //3\n```\n\n还可以以嵌套的形式赋值\n\n``` javascript\nvar [a,[[b],c]] = [1,[[2],3]];\nconsole.log(b); //2\n```\n其他的一些例子\n\n``` javascript\nlet [,,a] = ['1','2','3'];\nconsole.log(a); //3\n\nlet [b,,,c] = [1,2,3,4,5,6];    //不完全解构,6没用\nconsole.log(b); //1\nconsole.log(c); //4\n\nconst [d,...e] = [1,2,3,4,5,6];\nconsole.log(d); //1\nconsole.log(e); //[2,3,4,5,6]\n```\n\n如果解构失败,值就是`undefined`\n\n``` javascript\nlet [a] = [];\nconsole.log(a); //undefined\n\nconst [b,c,d] = [3];\nconsole.log(b); //3\nconsole.log(c); //undefined\nconsole.log(d); //undefined\n```\n\n如果右边不是数组的形式,则会报错\n\n``` javascript\nconst [a] = 1;          //1 is not iterable\nconst [b] = false;\nconst [c] = NaN;\nconst [d] = {};         //本身不具备Iterator接口\nconst [e] = null;\nconst [f] = undefined;\n```\n\u003e提示: 转为对象以后不具备`Iterator`接口,或者本身不具备`Iterator`接口.\n\n默认值\n\n``` javascript\nconst [a = 1] = [];\nconsole.log(a);         //1\n\nconst [b = 1] = [4];    \nconst [d = 1] = [null];\nconst [e = 1] = [{}];\n\nconst [c = 1] = [undefined];\n\nconsole.log(b);         //4     默认值失效\nconsole.log(d);         //null  默认值失效\nconsole.log(e);         //Object {  } 默认值失效\n\nconsole.log(c);         //1     默认值有效\n```\n\u003e提示: 如果右边的数组中的值不严格等于`undefined`,那么左边的默认值是不会生效的.例如`4`和`null`不严格等于`undefined`,所以默认值都失效了.\n\n如果默认值是一个表达式\n\n``` javascript\n\nfunction f(){\n    console.log('aaa');\n}\n\nconst [a = f()] = [1];\nconsole.log(a);         //1 默认值失效\n    \nconst [b = f()] = [];   //aaa, f()执行了   \n```\n\n默认值引用解析赋值的其他变量,但是变量必须已经声明(从左到右)\n\n``` javascript\nlet [a,b=a] = [2];\nconsole.log(a); //2\nconsole.log(b); //2\n\nlet [c=d,d] = [1,2];\nconsole.log(c); //1 右边的值不等于undefined,左边的默认值失效\nconsole.log(d); //2\n\nlet [e=f,f] = [1];\nconsole.log(e); //1\nconsole.log(f); //undefined\n\nlet [g=h,h=1] = []; //h is not defined\n```\n\n### Object解构\n\n数组是按顺序解析的,对象的属性没有次序,左边需要赋值的变量必须和右边的对象中的属性同名,这样才能一一匹配.\n\n``` javascript\nlet {a,b} = {b:'2',a:'1'};\nconsole.log(a); //1\nconsole.log(b); //2 \n\nlet {c} = {b:'2',a:'1'};\nconsole.log(c); //undefined\n```\n\n完整的写法\n``` javascript\nvar {a : v_a, b: v_b} = {b:'2',a:'1'};\nconsole.log(v_a);   //1\nconsole.log(v_b);   //2\n```\n\u003e提示:采用这种方式赋值的时候真正被赋值的是后者`v_a`而不是`模式a`.\n\n以下的解构中的`a`和`b`是模式,所以不会报错,但是解构中的`c`是变量,属于重复声明,所以报错.\n\n``` javascript\nlet a,b;\nlet {a : v_a, b: v_b} = {b:'2',a:'1'};\nconsole.log(v_a);   //1\nconsole.log(v_b);   //2\n\nlet c;\nlet{c} = {c:1};     //Identifier 'c' has already been declared\n```\n以下的`a`和`b`并不是变量,而是一种匹配模式而已\n``` javascript\nlet {a : v_a, b: v_b} = {b:'2',a:'1'};\nconsole.log(v_a);   //1\nconsole.log(v_b);   //2\nconsole.log(a);     //a is not defined\nconsole.log(b);     //b is not defined\n\nlet {c,d} = {c:'1',d:'2'};\nconsole.log(c);     //1\nconsole.log(d);     //2\n```\n\n嵌套匹配\n\n```javascript\nlet obj = {\n    a:[\n        '1',\n        {\n            b:'2'\n        }\n    ]\n};\n\nlet {\n    a:[\n        v_a,\n        {\n            b:v_b\n        }\n    ]\n} = obj;\n\nconsole.log(v_a);   //1\nconsole.log(v_b);   //2\n```\n\n\u003e提示: 和上面的一样,此时的`a`和`b`并不是变量,而是一种匹配模式.\n\n对象的解构也可以有默认值\n\n``` javascript\nlet {a=1,b} = {b:3};\nconsole.log(a); //1\n\nlet {c=3} = {c:null};\nconsole.log(c); //null\n```\n\n### String解构\n\n``` javascript\nconst [a,b,c] = 'abc';\nconsole.log(a); //a\nconsole.log(b); //b\nconsole.log(c); //c\n\nlet {length:len} = 'hello';\nconsole.log(len);\n```\n\u003e提示:  'hello'如果转化为对象有一个length属性,所以可以赋值这个属性\n\n### Number or Boolean解构\n\n先将`number`或者`boolean`转换为**包装**对象\n``` javascript\nlet {parseInt: pI} = 123;\nconsole.log(pI === Number.prototype.parseInt);  //true\n```\n\u003e提示: 相当于最开始的对象解构的同名属性匹配. 当然由于`null`和`undefined`无法转化对象,对它们解析时会报错.\n\n### Function解构\n\n函数的参数解构\n``` javascript\nfunction add({a,b,c}){\n    return a + b + c.d;\n}\nlet sum = add({a:1,b:2,c:{d:3}});\n\nconsole.log(sum);       //6\n```\n\n\u003e提示:  如果是`JSON`对象,这种方法就非常有用.当然返回的时候同样可以使用这种方式解构.\n\n``` javascript\n//Array.prototype.map(function(item,index,arr));\n//该方法返回的是新数组\n//把[1,2]和[3,4]各看成数组的元素\nlet arr = [[1,2],[3,4]].map(([a,b]) =\u003e a + b);\nconsole.log(arr);   //[3,7]\n```\n\n默认值\n\n``` javascript\nfunction f({x=0,y=1} = {}){\n    return [x,y];\n}\n\nconsole.log(f({x:1,y:2}));  //[1,2]\nconsole.log(f({x:1}));      //[1,1]\nconsole.log(f({}));         //[0,1]\nconsole.log(f());           //[0,1]\n```\n注意如果是设置函数参数的默认值,结果将是完全不一样的\n\n``` javascript\nfunction f({x,y} = {x:0,y:1}){\n    return [x,y];\n}\n\nconsole.log(f({x:1,y:2}));  //[1,2]\nconsole.log(f({x:1}));      //[1,undefined]\nconsole.log(f({}));         //[undefined,undefined]\nconsole.log(f());           //[0,1]\n```\n\n和前面一样,如果值严格等于`undefined`,就会函数的参数的解构的默认值将会生效\n\n``` javascript\nvar arr = [1,undefined,3].map((x = 2) =\u003e x);\nconsole.log(arr);   //[1,2,3]\n```\n\n###  ()问题\n解构赋值虽然很方便,但是解析起来并不容易.对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到（或解析不到）等号才能知道.由此带来的问题是,如果模式中出现圆括号怎么处理.`ES6`规定,只要有可能导致解构的歧义,就不得使用圆括号.建议只要有可能,就不要在模式中放置圆括号.\n - 变量声明语句中,不能带有圆括号.\n - 函数参数中,模式不能带有圆括号.\n - 赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中.\n\n\n### 用途\n\n- 交换变量的值\n\n``` javascript\nlet [x,y] = [1,2];\n[x,y] = [y,x];\nconsole.log(x); //2\nconsole.log(y); //1\n```\n\n -  从函数返回多个值\n \n``` javascript\n function f(){\n    return [1,2,3];\n}\n\nlet [a,b,c] = f();\nconsole.log(a); //1\n```\n\n``` javascript\nfunction f2(){\n    return {\n        a:2,\n        b:3\n    };\n}\n\nvar {a,b} = f2();\nconsole.log(a);     //2\n```\n\u003e提示: `ES5`中的函数如果要返回多个值只能是数组或者对象的形式,否则只能返回单个值.\n\n\n -  函数参数的定义\n\n``` javascript\nfunction f([x,y,z]){\n    return x + y + z;\n}\n\nf([1,2,3]);\n```\n\n\u003e提示: 传入参数也可以快速的一一对应起来.\n\n\n -  提取`JSON`数据\n\n``` javascript\nvar jsonD = {\n    name: 'ziyi2',\n    age: '23'\n};\n\nlet {name,age} = jsonD;\n\nconsole.log(name);  //ziyi2\nconsole.log(age);   //23\n```\n\n\u003e提示: 可以快速的提取`JSON`对象的数据或者给函数传入参数.\n\n -  函数参数的默认值\n\n\u003e提示: 可以避免在函数中使用 `var arg = arg || 'default arg';` \n\n -  遍历Map解构\n\n``` javascript\nvar map = new Map();\nmap.set('name','ziyi2');\nmap.set('age',23);\n\nfor(let[key,value] of map){\n    console.log(key + '-' + value);     //name-ziyi2 age-23\n}\n\nfor(let[key] of map){\n    console.log(key);                   //name age\n}\n    \nfor(let[,value] of map){                //ziyi2 23\n    console.log(value);\n}\n```\n\n \u003e提示: 何部署了`Iterator`接口的对象,都可以用`for...of`循环遍历.`Map`结构原生支持`Iterator`接口.\n  \n -  输入模块的指定特定的方法\n\n``` javascript\nconst  {f1,f2,...} = require(\"source\");\n```\n\n###  String扩展\n\n### 遍历器接口\n\n``` javascript\nlet str = 'hello es6';\n\nfor(let codePoint of str){\n    console.log(codePoint); //h e l l o  e s 6\n}\n```\n\n### at\n用于替代`charAt`方法,可以识别码点大于0xFFFF的字符.\n\n``` javascript\nlet str = 'hello es6';\nstr.at(0);  //h\n```\n\n### includes,startsWith,endsWith\n\n``` javascript\nlet str = 'hello es6';\nconsole.log(str.includes('hel'));       //true\nconsole.log(str.startsWith('h'));       //true\nconsole.log(str.startsWith('hello'));   //true\nconsole.log(str.endsWith('es6'));       //true\nconsole.log(str.endsWith('6'));         //true\n\n//fun(str,startIndex)\nconsole.log(str.includes('hel',2));     //false\nconsole.log(str.includes('llo',2));     //true\n```\n\n\n### repeat\n\n``` javascript\n//repeat(copyCount)\nlet str = 'hello es6 ';\nconsole.log(str.repeat(3));     //hello es6 hello es6 hello es6 \nconsole.log(str.repeat(3.5));   //hello es6 hello es6 hello es6 \nconsole.log(str.repeat('3.5')); //hello es6 hello es6 hello es6 \nconsole.log(str.repeat(-0.9));  //''\nconsole.log(str.repeat(NaN));   //''\nconsole.log(str.repeat('str')); //''\nconsole.log(str.repeat(-2));    //Invalid count value\n```\n\n\n### 模板字符串\n\n模板字符串中的空格和换行都会被保留\n``` javascript\n//repeat(copyCount)\nlet str = `我是大傻瓜\n我是大傻瓜`;\n\nconsole.log(str);\n//我是大傻瓜\n//我是大傻瓜\n```\n\n直接输出模板而不再使用`+`号连接\n``` javascript\n\u003cul id='list'\u003e\n\u003c/ul\u003e\n\n\u003cul id='list1'\u003e\n\u003c/ul\u003e\n\nvar name = 'ziyi2',\n    age = 23;\n\n$('#list').html(\n    '\u003cli\u003e' + name + '\u003c/li\u003e' + \n    '\u003cli\u003e'+ age + '\u003c/li\u003e'\n);\n\n$('#list1').html(`\n    \u003cli\u003e${name}\u003c/li\u003e\n    \u003cli\u003e${age}\u003c/li\u003e\n`);\n```\n\n在模板字符串中嵌入变量需要放在`${}`中\n``` javascript\nlet name = 'Bob',\n    time = 'tody';\nconsole.log(`Hello ${name}, how are you ${time}`);  //Hello Bob, how are you tody\n```\n在`${}`中可以是任意的`JavaScript`表达式,可以进行运算也可以引用对象的属性\n\n``` javascript\nlet name = 'ziyi2',\n    age = 23;\n\nfunction f(){\n    return name + age;\n}\n\n\n$('#list').html(`\n    \u003cli\u003e${name + age}\u003c/li\u003e\n    \u003cli\u003e${f()}\u003c/li\u003e\n`.trim());\n```\n\n报错情况\n``` javascript\nlet name = 'ziyi2',\n    msg = `Hello, ${name}`,     //Hello,ziyi2\n    hello = `Hello, ${age}`;    //age is not defined\n```\n\n模块字符串可以嵌套\n\n``` javascript\nlet infos = [{name:'ziyi2',age:23},{name:'ziyi3',age:24},{name:'ziyi4',age:25}];\n\nconst temp = infos =\u003e ` \n    ${infos.map(info =\u003e `\n        \u003cli\u003e${info.name}\u003c/li\u003e\n        \u003cli\u003e${info.age}\u003c/li\u003e\n    `\n    ).join('')}\n`;\n\n$('#list').html(temp(infos));\n```\n\n### 标签模板\n\n紧跟在函数后面,该函数将被调用来处理这个模板字符串,该功能称为\"标签模板\"功能.这其实就是函数的一种特殊调用形式,\"标签\"是指函数,而模板字符串是函数的参数.\n\n``` javascript\nalert  `Hello`;\nalert('Hello'); \n\nconsole.log `Hello`;    //['Hello']\n```\n\n但是如果模板字符串中还有变量,函数就会先处理成多个参数,第一个是字符串数组,第二个开始就是变量...\n\n``` javascript\nlet name = 'ziyi2';\nlet age = 23;\n\n\nfunction tag(s,v_name,v_age){\n    console.log(s);         //['Hello','your age is','']\n    console.log(v_name);    //ziyi2\n    console.log(v_age);     //23\n}\n\ntag `Hello ${name}, your age is ${age}`;\n```\n\n\u003e提示: `tag`函数实际上以这样的形式调用, `tag(['Hello','your age is',''],name,age);`\n\n另外一种写法\n``` javascript\nlet name = 'ziyi2';\nlet age = 23;\n\n\nfunction tag(s,...args){\n    console.log(s);             //['Hello','your age is','']\n    console.log(args[0]);       //ziyi2\n    console.log(args[1]);       //23\n\n    console.log(arguments[0]);  //['Hello','your age is','']\n    console.log(arguments[1]);  //ziyi2\n    console.log(arguments[2]);  //23\n}\n\ntag `Hello ${name}, your age is ${age}`;\n```\n\n这种方式还可以假象代码以其他方式运行,例如\n\n``` javascript\njsx `\n    //以jsx代码的方式运行   \n`\n\n\njava `\n    //以java代码的方式运行  \n`\n\nxml `\n    //以xml代码的方式运行   \n`\n```\n\n以这种方式运行的参数中,刚刚说了第一个参数是字符串数组,这个数组还有一个属性`raw`,这个参数是对字符串数组进行了转义处理过的数组\n\n``` javascript\nlet name = 'ziyi2';\nlet age = 23;\n\n\nfunction tag(s,...args){\n    console.log(s);             //['Hello','your age is','']\n    console.log(s.raw);         \n\n    console.log(args[0]);       //ziyi2\n    console.log(args[1]);       //23\n\n    console.log(arguments[0]);  //['Hello','your age is','']\n    console.log(arguments[1]);  //ziyi2\n    console.log(arguments[2]);  //23\n}\n\ntag `Hello ${name},\\n your age is ${age}`;\n```\n\n### String.raw\n\n``` javascript\nconsole.log(String.raw `Hi\\nHi`);   //Hi\\nHi\n```\n\n## Number扩展\n\n### Number.isFinite,Number.isNaN\n\u003e提示: 与传统方法`isFinite`和`isNaN`的区别在于传统方法先转换为`Number`再判断,而新方法只对数值有效.\n\n### Number.parseInt,Number.parseFloat\n\u003e提示: 将全局的`parseInt`和`parseFloat`方法移植到了`Number`对象上,更加模块化.\n\n\n### Number.isInteger\n\n``` javascript\nconsole.log(Number.isInteger(2));   //true\nconsole.log(Number.isInteger('2')); //false\nconsole.log(Number.isInteger(2.0)); //true\nconsole.log(Number.isInteger(2.1)); //false\n```\n\n### Number.EPSILON\n\n引入一个极小的常量\n\n``` javascript\nconsole.log(Number.EPSILON);        // 2.220446049250313e-16\n\nif(0.1 + 0.2 - 0.3 \u003c Number.EPSILON){\n    console.log(0.1 + 0.2 - 0.3);   //5.551115123125783e-17\n    console.log(true);              //true\n} \n```\n\n### Math.trunc\n\n去除小数部分\n``` javascript\nconsole.log(Math.trunc(4.23423423423)); //4\n```\n\n### Math.sign\n判断正负数,0,NaN,-0\n\n### Math.cbrt\n计算一个数的立方根\n\n### Math.hypot\n计算所有参数的平方和的平方根\n\n### 指数运算\n\n``` javascript\nconsole.log(2 ** 3);    //8\nlet a =  2;\nconsole.log(a **= 3);   //8\n```\n\n## Array扩展\n### Array.from\n把类数组转化为数组,例如类似数组的对象和可遍历的对象\n\n``` javascript\nlet arrayLike = {\n    '0': 'a',\n    '1': 'b',\n    '100': 'c',\n    length: 3\n};\n\nlet arr = Array.from(arrayLike);\nconsole.log(arr);   // [\"a\", \"b\", undefined]\n```\n\n`NodeList`集合就是类数组对象,还有`arguments`对象也是,都可以转化为真正的数组\n\n``` javascript\n\u003cp\u003e1\u003c/p\u003e\n\u003cp\u003e2\u003c/p\u003e\n\u003cp\u003e3\u003c/p\u003e\n\nlet lists = document.querySelectorAll('p');\n\nArray.from(lists).forEach(function(item,index,list){\n    console.log(item.innerHTML);\n});\n\nlists.forEach(function(item){   //lists.forEach is not a function\n    console.log(item.innerHTML);\n});\n\n```\n\n\u003e提示: `lists`是类数组,本身没有数组的`forEach`方法.\n\n`Iterator`接口的数据结构,`Array.from`都能将其转为数组.\n\n``` javascript\nlet namesSet = new Set(['ziyi2','ziyi3']);\n\nnamesSet.forEach(function(item){    \n    console.log(item);          //ziyi2,ziyi3\n});\n\nconsole.log(namesSet.length);   //undefined\n\nconsole.log(Array.from(namesSet).length);   //2\n```\n\n`(...)`扩展运算符也可以将某些数据转为数组\n\n``` javascript\nfunction f(){\n    let args = [...arguments];\n    args.forEach(function(arg){\n        console.log(arg);       //1,2,3,4,5\n    });\n\n    arguments.forEach(function(item){   //arguments.forEach is not a function\n        console.log(item);\n    })\n\n    console.log(args.length);   //5\n\n}\n\nf(1,2,3,4,5);\n```\n\n`Array.from`方法还支持类似数组的对象.所谓类似数组的对象,本质特征只有一点,即必须有`length`属性.因此,任何有`length`属性的对象,都可以通过`Array.from`方法转为数组,而此时扩展运算符就无法转换.\n\n``` javascript\nlet obj = {\n    length: 3\n};\n\nconsole.log(Array.from(obj)); //[undefined, undefined, undefined]\n```\n\n\u003e提示: 扩展运算符转换不了这个对象.\n\n `Array.from`还有第二个参数\n \n\n``` javascript\nfunction f(){\n    console.log(Array.from(arguments, x =\u003e x*x));       //[1, 4, 9, 16]\n    //类似于\n    console.log(Array.from(arguments).map(x =\u003e x*x));   //[1, 4, 9, 16]\n}\n\nf(1,2,3,4);\n```\n\n稀疏数组转化为非稀疏数组\n\n``` javascript\nconsole.log(Array.from([1,,2,,3], item =\u003e item || 0));  //[1, 0, 2, 0, 3]\n```\n\n第一个参数指定了第二个参数运行的次数\n``` javascript\nconsole.log(Array.from({length:3}, () =\u003e 'ziyi2')); //[\"ziyi2\", \"ziyi2\", \"ziyi2\"]\n```\n\n### Array.of\n\n``` javascript\nlet arr = Array.of(1,2,3,4);    //[1, 2, 3, 4]\nconsole.log(arr);\n\narr = Array.of(3);\nconsole.log(arr);               //[3]\n\narr = Array();\nconsole.log(arr);               //[]\n\narr = Array(3);\nconsole.log(arr);               //[undefined, undefined, undefined]\n\narr = Array(3,3);\nconsole.log(arr);               //[3, 3]\n```\n\n\u003e提示: `Array.of`就是为了避免`Array`的方法因为参数不同而导致的不同结果.`Array.of`基本上可以替代`Array`或`new Array`. 它的行为非常统一.\n\n\n### Array.copyWithin\n将制定位置的成员赋值到其他位置`Array.prototype.copyWithin(targetIndex,start=0,end=this.length);`\n\n- targetIndex \n从该位置开始替换数据(会覆盖原有位置的元素)\n- start\n从该位置读取数据\n- end\n到该位置前停止读取数据\n\n``` javascript\nlet arr = [1,2,3,4,5].copyWithin(0,4);  //[5,2,3,4,5]\nconsole.log(arr);\n\narr = [1,2,3,4,5].copyWithin(0,3);  //[4,5,3,4,5]\nconsole.log(arr);\n```\n\n### Array.find,Array.findIndex\n\n用于找出符合条件的数组的成员.\n\n``` javascript\nlet item = [1,2,3,4].find((n) =\u003e (console.log(n), n\u003e1));    //1 2\nconsole.log(item);  //2\n```\n\u003e提示: 执行`n`遍,直到第一个返回`true`的项则停止遍历并返回这个项.如果遍历完了仍然没有找到符合条件的值,则返回`undefined`. `find`和`forEach`等方法一样,回调函数中都有三个参数`item,index,arr`.\n\n用于找出符合条件的数组的成员的位置.\n\n``` javascript\nlet item = [1,2,3,4].findIndex((n) =\u003e (console.log(n), n\u003e1));   //1 2\nconsole.log(item);  //1  arr[1] = 2\n```\n\u003e提示: 如果没有找到匹配的数组项,则返回`-1`.这两个方法都可以接受第二个参数,用来绑定回调函数的`this`对象.\n\n### Array.fill\n\n可以抹去之前的数据\n``` javascript\nconsole.log([1,2,3].fill(4));       //[4, 4, 4]\nconsole.log(new Array(3).fill(4));  //[4, 4, 4]\n```\n\n\u003e提示: 初始化的时候应该非常有用吧!\n\n### Array.entries,Array.keys,Array.values\n\n``` javascript\nlet arr = ['a','b','c'];\n\nfor(let key of arr.keys()){\n    console.log(key);   //0 1 2\n}\n\nfor(let item of arr.values()){\n    console.log(item);  //error?\n}\n\n\nfor(let [key,value] of arr.entries()){\n    console.log([key,value]);   //[0, \"a\"] [1, \"b\"] [2, \"c\"] \n}   \n```\n\n### Array.includes\n`ES7`的方法,`Babel`已经支持,该方法判断参数是否存在于数组中\n\n``` javascript\nconsole.log([1,2,3].includes(3));   // true\n```\n\n没有该方法之前,通常使用`indexOf`方法检测是否包含某个值,`indexOf`方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观.二是,它内部使用严格相等于运算符`（===）`进行判断,这会导致对`NaN`的误判.\n\n``` javascript\nconsole.log([NaN].indexOf(NaN));    //-1\nconsole.log([NaN].includes(NaN));   //true\n```\n### 稀疏数组\n\n``` javascript\nconsole.log((0 in [undefined,undefined]));  //true\nconsole.log((0 in [,,]));                   //false\n```\n\u003e提示: 第二个是稀疏数组,第一个不是.\n\n`ES5`对于稀疏数组的处理:\n - `forEach,filter,every,some`都会跳过空位\n - `map`遍历的时候跳过空位,但是返回的时候会保留这个位置的空\n - `join`和`toString`会将空位视为`undefined`,而`undefined`会被处理成空字符串\n\n``` javascript\nvar arr = [1,,2,,3];\narr.forEach( (item,index) =\u003e console.log(item));    //1,2,3\nconsole.log(arr.filter(item =\u003e true));              //[1, 2, 3]\nconsole.log(arr.every(item =\u003e item \u003e= 1));          //true\n\nconsole.log(arr.map(item =\u003e item*item));            //[1, undefined, 4, undefined, 9]\nconsole.log(arr.join('-'));                         //1--2--3\nconsole.log(arr.toString());                        //1,,2,,3\n```\n\n`ES6`明确将空位转为`undefined`,`Array.from`方法会将数组的空位,转为`undefined`\n\n``` javascript\nconsole.log(Array.from([1,,2,,3])); //[1, undefined, 2, undefined, 3]\nconsole.log([...[1,,2,,3]]);        //[1, undefined, 2, undefined, 3]\n```\n\n`for...of`循环会遍历空位\n\n``` javascript\nlet arr = [,,,];\n\nfor(let key of arr){\n    console.log(key);   //undefined undefined undefined\n}\n```\n\u003e提示: 其他的函数也可以试试是什么情况.\n\n## Function扩展\n\n形参的初始值\n\n``` javascript\nfunction f(x,y='es6'){\n    console.log(x + y);\n}\n\nf('hello ');            //hello es6\nf('hello ','world');    //hello world\nf('hello ','');         //hello \n```\n\n构造函数的形参\n\n``` javascript\nfunction Person(name='ziyi2',age=23){\n    this.name = name;\n    this.age = age;\n    \n    let name;       //Identifier 'name' has already been declared\n}\n\nlet p = new Person();\nconsole.log(p);     // Person { name=\"ziyi2\",  age=23}\nlet p2 = new Person('ziyi3');\nconsole.log(p2);    // Person { name=\"ziyi3\",  age=23}\n```\n\u003e提示: 函数的形参是默认声明的,所以不能在函数中再次声明,否则会报错.\n\n解构默认值,注意不是函数的形式参数的初始值\n\n``` javascript\nfunction Person({name='ziyi2',age=23}){\n    this.name = name;\n    this.age = age;\n}\n\nvar p = new Person({});\nconsole.log(p);         //Person { name=\"ziyi2\",  age=23}\nvar p1 = new Person({name:'ziyi3'});\nconsole.log(p1);        //Person { name=\"ziyi3\",  age=23}\n\nvar p2 = new Person();  //Cannot match against 'undefined' or 'null'.\n```\n\n``` javascript\nfunction ajax(url, {method = 'GET', data = {}, type = 'text/plain'}){\n    console.log(method);\n}\n\najax('/add',{});    //GET\najax('/add');       //Cannot match against 'undefined' or 'null'.\n```\n\n\u003e提示: 这种情况下不能省略解构需要的第二个参数,否则就会报错,如果结合解构默认值和函数形式参数的初始值,就可以忽略这个参数了.\n \n解构默认值和函数形式参数初始值结合使用\n\n``` javascript\nfunction f({x=0,y=0} = {}) {        //函数的形参初始值是空对象,而结构的默认值是0,0\n    console.log([x,y]);\n}\n\nfunction f1({x,y} = {x:1,y:1}) {    //函数的形参的初始值是对象{x:1,y:1},而结构没有默认值\n    console.log([x,y]);\n}\n\nfunction f2({x=0,y=0} = {x:1,y:1}) {    \n    console.log([x,y]);\n}\n\n\nf();        //[0, 0]    生效的是解构的默认值\nf1();       //[1, 1]    生效的是形参的初始值\nf2();       //[1, 1]    生效的是形参的初始值,因为这个时候没有解构赋值,所以形参初始值生效\n\nf(2,2);     //[0, 0] 生效的是解构的默认值,传入的参数因为不是对象而无效\nf1(2,2);    //[undefined, undefined] 解构没有赋默认值,传入的参数也不是对象\nf2(2,2);    //[undefined, undefined] 解构没有赋默认值,传入的参数也不是对象\n\nf({x:2});   //[2, 0] 覆盖了形参的初始值{},同时解构的默认值生效了,y为默认值0\nf1({x:2});  //[2, undefined],因为传入的是{x:2}对象,覆盖了默认的初始对象{x:1,y:1},同时因为解构没有初始值所以y是undefined     \nf2({x:2});  //[2, 0] 覆盖了形参初始值{x:1,y:1},同时解构的默认值生效了\n\nf({});      //[0, 0] 生效的是解构的默认值\nf1({});     //[undefined, undefined] {}覆盖了默认的初始对象{x:1,y:1}\nf2({});     //[0, 0] 解构的默认值生效了,形参的初始对象也被覆盖了\n```\n\n默认的参数最好放在最后,方便赋值\n\n``` javascript\nfunction f(x=1,y){\n    console.log([x,y]);\n}\n\nf();            //[1, undefined]\nf(5);           //[5, undefined]\nf(undefined,1); //[1, 1]    \n//f(,1);        //Unexpected token ,\n\nfunction f1(x,y=1){\n    console.log([x,y]);\n}\n\nf1();           //[undefined, 1]\nf1(1);          //[1, 1]\n```\n\n### 函数的length属性\n\n``` javascript\nfunction f(x=1,y){\n    console.log([x,y]);\n}\n\nfunction f1(x,y=1){\n    console.log([x,y]);\n}\n\nfunction f2(x=1,y,z){\n    console.log([x,y]);\n}\n\nfunction f3(x,y,z=1){\n    console.log([x,y]);\n}\n\nfunction f4(...args){\n\n}\n\nconsole.log(f.length);  //0 如果默认参数没放在末尾,那么没有指定默认值得参数个数将会出错\nconsole.log(f1.length); //1 函数未指定默认值得参数个数为1\nconsole.log(f2.length); //0\nconsole.log(f3.length); //2\nconsole.log(f4.length); //0 不计算的\n```\n\n### 作用域\n\n``` javascript\nlet x=1;\n\nfunction f(x=2,y=x){\n    console.log(y);\n}\n\nf();    //2\n```\n\n``` javascript\nlet x=1;\n\nfunction f(y=x){\n    console.log(y);\n}\n\nf();    //1\n```\n\n``` javascript\nfunction f1(f0 = x =\u003e a){\n    let a = 'inner';\n    console.log(f0());\n}\n\nf1();   //inner\n```\n\n\n``` javascript\nvar a = 'a_global';\nvar b = 'b_global';\n\nfunction f(a,c = () =\u003e console.log(a + b)){\n    var a = 'a_inner';\n    var b = 'b_inner';\n    c();                //a_innerb_inner\n    console.log(a);     //a_inner\n}\n\nf();\n```\n\n### 应用\n\n指定函数必须有参数\n\n``` javascript\nfunction throwArgsError(){\n    throw new Error('Missing parameter!');\n}\n\nfunction f(a = throwArgsError()){\n    console.log(a);\n}\n    \nf(1);   //1\nf();    //Error: Missing parameter!\n```\n\u003e提示: 如果传入参数,形参初始值就生效了,动态运行函数抛出`error`.\n\n指定函数的参数可以省略\n``` javascript\nfunction f(a = undefined){\n    console.log(a);\n}\n    \nf();    //undefined\n```\n\n### rest参数\n`ES6`引入了`rest`参数, 以`...变量名`的形式获取函数的多余参数,这样就不需要使用`arguments`对象了.`rest`参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量.\n``` javascript\nfunction f(...args){\n    let sum = 0;\n\n    for(let arg of args){\n        sum += arg;\n    }\n\n    \n    console.log(sum);   //15\n}\n\nf(1,2,3,4,5);\n```\n\n\u003e提示: `rest`参数之后不能再有其他参数,否则会报错.同时函数的`length`属性不包括`rest`参数.\n\n### (...)扩展运算符\n\n`...数组`扩展运算符可以认为是rest参数的逆运算,将一个数组转为用逗号分隔的参数序列. 注意与`rest`参数的区别.\n\n``` javascript\nfunction sum(x,y,z){\n    console.log(x+y+z);\n}\n\nfunction sum1(...parms){\n    console.log(parms[0] + parms[1] + parms[2]);\n}\n\nlet arr = [1,2,3];\nconsole.log(...arr);    //1 2 3\nsum(...arr);            //6\nsum1(1,2,3);            //6\n```\n\n### 替代数组的apply方法\n扩展运算符`...`可以展开数组,所以不需要`apply`方法将数组转为函数的参数,例如`ES5`中的写法\n\n``` javascript\nfunction f(x,y,z){\n    console.log(x + y + z);\n}\n\nlet arr = [1,2,3];\n\nf.apply(null,arr);  //6\n```\n\u003e提示: `apply`和`call`作用相同,只是`apply`的第二个参数传入的是数组,而`call`从第二个开始是要传入函数的实际参数,所以`call`第二个参数开始后面可以有很多参数.两个函数的第一个参数都是传递`this`的指向对象.\n\n\n`ES6`中的写法\n\n``` javascript\nfunction f(x,y,z){\n    console.log(x + y + z);\n}\n\nlet arr = [1,2,3];\n\nf(...arr);  //6\n```\n\n一个实际的例子\n\n``` javascript\nlet arr = [1,2,2,3,4,5,6,7,7,9];\nconsole.log(Math.max(1,2,2,3,4,5,6,7,7,9)); //9\nconsole.log(Math.max.apply(null,arr));      //9\nconsole.log(Math.max(...arr));              //9\n```\n\u003e提示: `Math.max`方法传入的参数不能是数组.\n\n``` javascript\nlet arr = [1,2];\nlet arr1 = [3,4];\nArray.prototype.push.apply(arr,arr1);\nconsole.log(arr);   //[1, 2, 3, 4]\n\narr.push.apply(arr,arr1);\nconsole.log(arr);   //[1, 2, 3, 4, 3, 4]\n\narr.push(...arr1);\nconsole.log(arr);   //[1, 2, 3, 4, 3, 4, 3, 4]\n```\n\n### 扩展运算符的应用\n\n- 数组合并新写法\n\n``` javascript\nlet arr = [1,2];\nlet arr1 = [3,4];\nvar arr2 = arr.concat(arr1);\nconsole.log(arr2);              //ES5 [1, 2, 3, 4]\nconsole.log([...arr,...arr1]);  //ES6 [1, 2, 3, 4]\n```\n\n- 与解构赋值结合\n\n``` javascript\nlet list = [1,2,3,4,5];\nlet [a,...rest] = list;\n\n//ES6\nconsole.log(a);     //1\nconsole.log(rest);  //[2, 3, 4, 5]\n\n//ES5\nlet b = list[0];\nlet c = list.slice(1);\nconsole.log(b);     //1\nconsole.log(c);     //[2, 3, 4, 5]\n```\n\n- 实现了`Iterator`接口的对象\n详细的查看`Array.from`内容.\n\n- `Map`和`Set`,`Generator`\n\n``` javascript\nlet map = new Map([\n    [1,'ziyi2'],\n    [2,'ziyi3'],\n]);\n\nlet arr = [...map.keys()];\nconsole.log(arr);           //[1, 2]\n\n\nvar go = function* (){\n    yield 1;\n    yield 2;\n    yield 3;\n};\n\nconsole.log([...go()]);     //[1, 2, 3]\n\nlet person = {\n    name: 'ziyi2',\n    age: 34\n};\n\nconsole.log([...person]);   //person is not iterable\n```\n\n- name属性\n\n### `=\u003e` 函数\n\n``` javascript\nlet f= v =\u003e v;\nconsole.log(f(1));  //1\n\n//等同于\nfunction f(v){\n    return v;\n}\n```\n\n如果不需要形参或者多个参数,就用`()`代替参数部分\n\n``` javascript\nlet f= () =\u003e 5;\nconsole.log(f());       //5\n\nlet f1= (x,y) =\u003e x+y;\nconsole.log(f1(3,4));   //7\n```\n\n\u003e提示: 单条语句时省略了`return`关键字.\n\n如果代码块多余一条语句,就要用`{}`包裹起来,并且不能省略返回时的`return`关键字\n\n``` javascript\nlet f= (x) =\u003e {\n    let a = 1;\n    return a + x;\n};\n\nconsole.log(f(5));  //6\n```\n\n注意事项\n- `this`体内的`this`对象就是定义时所在的对象(死固定的),而不是运行时那个调用此方法的对象\n\n``` javascript\nfunction f(){\n    setTimeout(() =\u003e {\n        console.log(this.name);\n    },100);\n}\n\nfunction f1(){\n    setTimeout(function(){\n        console.log(this.name);\n    },100);\n}\n\n\nvar name = 'window';\nf.call({name:'ziyi2'});     //ziyi2\nf1.call({name:'ziyi2'});    //window\n```\n\u003e提示: 100ms后执行的函数,在`f`中使用了箭头函数后,`this`就指向了`f`中绑定的对象`{name:'ziyi2'}`,而不使用箭头函数则是全局对象.\n\n在箭头函数中不存在`this,arguments,super,new.target`.都是指向包含它的函数对象的相应的该值!\n\n``` javascript\nfunction f(){\n    setTimeout(() =\u003e {\n        console.log(arguments);\n    });\n}\n\nfunction f1(){\n    setTimeout(function () {\n        console.log(arguments);\n    });\n}\n\nf(1,23,3,4);    //[1, 23, 3, 4]\nf1(1,23,3,4);   //[] \n```\n\u003e提示: 箭头函数本身不存在变量`arguments`,所以该变量是`f`函数的属性.由于箭头函数没有`this`,所以也不能使用`call,apply,bind`等方法改变`this`的指向.\n\n- 不可以当做构造函数\n- 不可以使用`arguments`对象\n- 不可以使用`yield`命令,不能用作`Generator`函数\n\n### 嵌套的箭头函数\n\n``` javascript\nconst plus = a =\u003e a + 1;\nconst mult = a =\u003e a * a;\nconsole.log(mult(plus(5))); //36\n```\n\n### 函数绑定\n\n箭头函数可以绑定this对象,而不会随着运行时调用对象的变化而变化.`ES7`为了减小显示绑定(`call,apply,bind`)的用法,提出了**函数绑定**.\n\n### 尾调用优化\n略\n\n\n## 对象的扩展\n\n### 属性简写\n\n``` javascript\nlet a = '1';\nlet b = {a};\nconsole.log(b); //Object { a=\"1\"}\n```\n\n\n``` javascript\nfunction f(x,y){\n    console.log({x,y}); // console.log({x:x,y:y});\n}\nf(1,2); //Object { x=1,  y=2}\n```\n\n\u003e提示: `{}` 内的是属性名,同时这个属性名作为变量的值作为属性值.需要注意的是`{}`的属性名总是字符串.\n\n\n### 方法简写\n\n``` javascript\nvar o = {\n\n    sum(x,y) {  //sum: function(x,y) {\n        console.log(x + y);\n    },\n\n    mult(x,y) {\n        console.log(x * y);\n    }\n};\n\no.sum(1,2); //3\n```\n\n如果是`Generator`函数\n\n``` javascript\nvar gen = {\n    *m() {\n        yield 'hello world';\n    }\n};\n```\n\n对象字面量的属性名可以是`JavaScript`表达式\n\n``` javascript\nvar prop = 'ziyi2';\nfunction f(){\n    return 'prop';\n}\n\nlet obj = {\n    [prop + '1']: 'ziyi21',\n    [f() + '2']: 'prop2'\n};\n\nconsole.log(obj);   //Object { ziyi21=\"ziyi21\",  prop2=\"prop2\"}\n```\n\n\u003e提示:  方法作为一个变量的属性也可以使用`JavaScript`表达式.\n\n\n### 方法的name属性\n\n### Object.is\n与 `===` 的作用基本一致,同时使`NaN`等于自身, `+0`和`-0`不相等.\n\n``` javascript\nconsole.log(+0 === -0);         //true\nconsole.log(NaN === NaN);       //false\nconsole.log(Object.is(+0,-0));  //false\nconsole.log(Object.is(NaN,NaN));//true\n```\n\n### Object.assign\n\n第一个参数是目标对象,之后的参数都是源对象\n``` javascript\nlet target = {a:1};\nlet source1 = {b:2};\nlet source2 = {c:3};\nObject.assign(target,source1,source2);\nconsole.log(target);    //Object { a=1,  b=2,  c=3}\n```\n\u003e提示: 类似于`extend`函数的作用,如果有同名属性,目标对象的属性值会被覆盖.\n\n``` javascript\nlet target = {a:1};\nlet source1 = {a:2};\nlet source2 = {a:3};\nObject.assign(target,source1,source2);\nconsole.log(target);    //Object {a=3}\n```\n\n\u003e提示: 该方法只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性. \n\n该方法实现的是浅拷贝,而不是深拷贝,例如如果源对象某个属性的值是对象,那么目标对象拷贝得到的这个是这个对象的引用\n\n``` javascript\nlet x = {a:{b:2}, c:3};\nlet y = {};\n\nObject.assign(y,x);\n\nx.a.b = 111;\nx.c = 222;\nconsole.log(y.a.b); //111 变了\nconsole.log(y.c);   //3\n```\n\n补充一下深拷贝和浅拷贝\n``` javascript\n//浅拷贝\nlet x = {a:1,b:2};\n\nlet y = x;\nx.a = 2;            //x对象变了y对象也变了\nconsole.log(y.a);   //2 x对象和y对象引用同一个地址\n\n\n//深拷贝\nlet c = {};\n\nfor(let key in x){\n    c[key] = x[key];\n}\n\nx.a = 3;\nx.b = 4;            //x和c对象不是引用同一个地址空间\nconsole.log(c);     //Object { a=2,  b=2}\n```\n\n当处理数组时,会把数组的索引当成属性来执行\n\n``` javascript\nlet arr = [1,2,3,4,5];\nlet arr1 = [2,1];\nObject.assign(arr,arr1);\nconsole.log(arr);   //[2, 1, 3, 4, 5]\n```\n### 用途\n- 为对象添加属性\n\n``` javascript\nclass Person {\n    constructor(name,age) {\n        Obeject.assign(this,{name,age});\n    }\n}\n```\n\n- 为对象添加方法\n\n``` javascript\nclass Person {\n    constructor(name,age) {\n        Obeject.assign(this,{name,age});\n    }\n}\n\n\nObject.assign(Person.prototype, {\n    fun1() {\n\n    },\n    fun2() {\n\n    }\n});\n\n//类似于\nPerson.prototype.fun1 = function() {};\nPerson.prototype.fun2 = function() {};\n```\n\n- 克隆对象\n\n``` javascript\nfunction clone(obj){\n    return Object.assign({},obj);\n}\n\n//拷贝带源对象的继承值\nfunction clone1(obj){\n    let proto = Object.getPrototypeOf(obj);\n    return Object.assign(Object.create(proto),obj);\n}\n```\n\n合并返回新对象\n\n``` javascript\nlet merge = (...args) =\u003e Object.assign({},...args);\n```\n\n### 属性的可枚举性\n-  `for...in`循环：只遍历对象自身的和继承的可枚举的属性\n-  `Object.keys()`：返回对象自身的所有可枚举的属性的键名\n-  `JSON.stringify()`：只串行化对象自身的可枚举的属性\n\n`Object.assign()`会忽略不可枚举属性.操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性.所以,尽量不要用`for...in`循环,而用`Object.keys()`代替.\n\n### 属性的遍历\n- `for...in`\n只遍历对象自身的和继承的可枚举的属性（不含`Symbol`属性）.\n- `Object.keys()`\n包括对象自身的（不含继承的）所有可枚举属性（不含`Symbol`属性）. `Object.getOwnPropertyNames`\n包含对象自身的所有属性（不含`Symbol`属性,但是包括不可枚举属性).\n`Object.getOwnPropertySymbols(obj)`\n包含对象自身的所有`Symbol`属性.\n- `Reflect.ownKeys(obj)`\n返回所有的属性,不管属性名是`Symbol`还是字符串,不管是否可枚举.\n\n遍历的次序规则\n - 首先遍历所有属性名为数值的属性,按照数字排序\n - 其次遍历所有属性名为字符串的属性,按照生成时间排序\n - 最后遍历所有属性名为Symbol值的属性,按照生成时间排序\n\n### `__proto__`,Object.setPrototypeOf,Object.getPrototypeOf\n### `__proto__`\n`__proto__`属性（前后各两个下划线）,用来读取或设置当前对象的prototype对象.目前,所有浏览器（包括IE11）都部署了这个属性.\n\n### Object.setPrototypeOf\n用来设置一个对象的`prototype`对象.它是`ES6`正式推荐的设置原型对象的方法.格式如下\n\n```\nObject.setPrototypeOf(obj,prototype)\n```\n\n### Object.getPrototypeOf\n\n### Objetct.keys,Objetct.values,Objetct.entries\n\n- `Objetct.keys`\n遍历对象的（不含继承的）可遍历的属性名,返回的是数组\n- `Objetct.values (ES7)`\n遍历对象的（不含继承的）可遍历的属性值,返回的是数组\n- `Objetct.entries (ES7)`\n遍历对象的（不含继承的）可遍历的属性的键值对,返回的是数组\n\n## Symbol\n\n### 概述\n`ES5`中的属性只能是字符串,在别人封装的对象的基础上,增加新的方法可能会出现重复,所以在`ES6`中新增了一种数据类型`Symbol`,也就是说在`ES6`中对象的属性可以是字符串也可以是`Symbol`类型.\n\n\u003e提示: `ES5`中的数据类型有`Undefined`,`Null`,`Boolean`,`String`,`Number`,`Object`,在`ES6`中新增了`Symbol`.\n\n凡是属性名是Symbol类型,这些属性就都是独一无二的.\n\n``` javascript\nlet s = Symbol();\nlet s1 = Symbol();\n\nobj = {};\nobj[s] = '1';\nobj[s1] = '2';\n\nconsole.log(s === s1);  //false\n```\n\n\u003e提示: `Symbol`和`String`一样.由于它是基本数据类型,所以不需要使用`new`关键字.\n\n即使传入的参数一样,也还是唯一的\n\n``` javascript\nlet s = Symbol('ziyi2');\nlet s1 = Symbol('ziyi2');\n\nconsole.log(s === s1);  //false\n```\n`Symbol`类型的数据不能隐式转换为字符串,所以在对象中使用属性的时候不能使用`.`运算符,因为`.`运算符后面跟的默认都是字符串.\n\n``` javascript\nlet s = Symbol('ziyi2');\nlet s1 = Symbol('ziyi2');\n \nvar obj = {};\nobj.s = '1';    //.后面的s其实是字符串\nconsole.log(obj['s']);  \n\nobj[s] = '2';\n\nfor(let key in obj){    //不能遍历Symbol属性\n    console.log(key);   //s 这个是字符串属性\n}\n\nconsole.log(Object.getOwnPropertySymbols(obj));     //[Symbol(ziyi2)] 这个是Symbol属性\nconsole.log(s + '12334');   //can't convert symbol to string\nconsole.log(s.toString() + '1234'); //Symbol(ziyi2)1234\n```\n\n\u003e提示: 只能显示转化为字符串,由于`.`属性后面跟的是字符串,所以不能用`Symbol`变量用作对象的`.`属性.所以`Symbol`值必须放在方括号之中.\n\n``` javascript\nlet s = Symbol('ziyi2');\n \nobj = {\n\n    [s](arg) {\n        console.log('1');\n    }\n};\n\nobj[s]('1');    //1\n```\n\u003e提示:  `Symbol`用来表示对象的属性只能采用`[]`表示法.\n\n可以用来定义不同的常量\n\n``` javascript\nconst status = {\n    success: Symbol('success'),\n    error: Symbol('error')\n};\n\nfunction f(sta){\n    switch(sta){\n        case status.success:\n            console.log('1');\n            break;\n\n        case status.error:\n            console.log('2');\n            break;\n\n        default:\n            break;      \n    }\n}\n\nf(status.success);  //1\nf(status.error);    //2 \n```\n\n### 属性遍历\n\n- `Object.getOwnPropertySymbols`\n- `for...in`\n- `Object.getOwnPropertyNames`\n- `Reflect.ownKeys`\n- `Object.keys`\n\u003e提示: 注意`Object.keys`和`for...in`的区别.\n\n###  Symbol.for,Symbol.keyFor\n如果想使用同一个`Symbol`值,`Symbol.for`方法接受一个字符串作为参数,然后先搜索有没有该参数作为名称的`Symbol`值,有则返回,没有则创建一个该字符串为名称的`Symbol`值.\n\n```javascript\nlet s = Symbol('foo');\nlet s1 = Symbol.for('foo');\nconsole.log(s === s1);  //false\nlet s2 = Symbol.for('foo');\nconsole.log(s1 === s2); //true\n```\n\n`Symbol.for()`与`Symbol()`这两种写法,都会生成新的`Symbol`.它们的区别是,前者会被登记在全局环境中供搜索,后者不会.`Symbol.keyFor`用于返回登记的值\n\n``` javascript\nlet s = Symbol('foo');\nlet s1 = Symbol.for('foo');\nconsole.log(s === s1);  //false\nlet s2 = Symbol.for('foo');\nconsole.log(s1 === s2); //true\n\nconsole.log(Symbol.keyFor(s));  //undefined\nconsole.log(Symbol.keyFor(s1)); //foo\n```\n\n防止导出的对象的属性被覆盖\n\n``` javascript\nconst s = Symbol.for('foo');\n\nfunction F(name){\n    this.name = name;\n}\n\nif(!global[s]){\n    global[s] = new F('ziyi2');\n}\n\nmodule.exports = global[s]; //导出的全局变量的该属性不会被修改\n```\n\n### 其他方法\n- Symbol.hasInstance \n- Symbol.isConcatSpreadable\n- Symbol.species\n- Symbol.match\n- Symbol.replace\n- Symbol.search\n- Symbol.split\n- Symbol.iterator\n- Symbol.toPrimitive \n- Symbol.toStringTag\n- Symbol.unscopables\n\n## Proxy和Reflect\n\n### Proxy\n用于修改某系操作的默认行为,可以理解为对目标对象做了拦截,访问对象之前,先执行拦截,所以可以对外界的访问行文进行过滤和改写.\n\n生成`Proxy`实例,`target`表示要拦截的目标对象,`hander`用来定制拦截行为,注意`hander`也是一个对象\n\n``` javascript\nlet proxy = new Proxy(target,handler);\n```\n\n``` javascript\nlet proxy = new Proxy({},{  //{}是对象\n    get: function(target,property){\n        return 11;\n    }\n});\n\nconsole.log(proxy.name);    //11\nconsole.log(proxy.age);     //11\n```\n\n\u003e提示: `hanlder`对象里的拦截函数都有两个参数,一个是目标对象`target`,一个是所要访问的属性`property`. 以上的拦截函数`get`总是返回11,所要访问任何属性都得到11.要使得`Proxy`起作用,必须针对`Proxy`实例（上例是`proxy`对象）进行操作,而不是针对目标对象（上例是空对象）进行操作.\n\n\n如果`handler`没有设置任何拦截,那就等同于直接通向原对象.\n\n``` javascript\nlet person = {\n    name:'ziyi2',\n    age: 11\n};\n\nlet proxy = new Proxy(person,{});   //handler是一个空对象\n\nproxy.name = 'ziyi3';       //相当于修改了person对象的name属性\nproxy.home = 'Hangzhou';    //相当于给person对象创建了一个home属性并赋值\n\nconsole.log(person.name);   //ziyi3\nconsole.log(person.home);   //Hangzhou\n```\n\u003e提示:  此时访问`proxy`就等同于访问`person`.\n\nProxy实例也可以作为其他对象的原型对象\n\n``` javascript\nlet proxy = new Proxy({},{\n    get(target,property) {\n        return 35;\n    }\n}); \n\nlet obj = Object.create(proxy); //proxy是obj的原型对象\n\nconsole.log(obj.name);  //35 \n```\n\n\n### 拦截方法\n\n- `get(target,prop,receiver)`\n拦截对象属性的读取,最后一个参数后续说明.\n- `set(target,prop,value,receiver)`\n拦截对象属性的设置,返回一个布尔值.\n...\n\n- `construct(target,args)`\n拦截`Proxy`实例作为构造函数调用的操作,比如`new proxy(...args)`.\n\n\n``` javascript\nfunction createArray(...elements) {\n  let handler = {\n    get(target, propKey, receiver) {\n      let index = Number(propKey);\n      if (index \u003c 0) {\n        propKey = String(target.length + index);\n      }\n      return Reflect.get(target, propKey, receiver);\n    }\n  };\n\n  let target = [];\n  target.push(...elements);\n  return new Proxy(target, handler);\n}\n\nlet arr = createArray('a', 'b', 'c');\nconsole.log(arr[-1]); //c\n```\n\n`construct`方法用于拦截`new`命令\n\n``` javascript\nvar p = new Proxy(function(){},{\n    construct(target,args) {\n        return {value:args[0]*10};\n    }\n});\n\nconsole.log(new p(2).value);    //20\n```\n\n\u003e提示:`construct`方法返回的必须是一个对象,否则会报错\n\n### Reflect\n将`Object`对象的一些明显属于语言内部的方法（比如`Object.defineProperty`）,放到`Reflect`对象上.未来的新方法将只部署在Reflect对象上.\n\n修改某些`Object`方法的返回结果,让其变得更合理.比如,`Object.defineProperty(obj, name, desc)`在无法定义属性时,会抛出一个错误,而`Reflect.defineProperty(obj, name, desc)`则会返回`false`\n\n 让`Object`操作都变成函数行为.某些`Object`操作是命令式,比如`name in obj`和`delete obj[name]`,而`Reflect.has(obj, name)`和`Reflect.deleteProperty(obj, name)`让它们变成了函数行为.\n\n``` javascript\nlet obj = {\n    name: 'ziyi2'\n};\n\nconsole.log('name' in obj);             //true\nconsole.log(Reflect.has(obj,'name'));   //true\n```\n\n``` javascript\nlet obj = {\n    name: 'ziyi2'\n};\n\n\nconsole.log('name' in obj);             //true\nconsole.log(Reflect.has(obj,'name'));   //true\n\n\nReflect.set(obj,'age',23);\nconsole.log(obj.age);                   //23\n```\n\n`Reflect`对象的方法与`Proxy`对象的方法一一对应,只要是`Proxy`对象的方法,就能在`Reflect`对象上找到对应的方法.这就让`Proxy`对象可以方便地调用对应的`Reflect`方法,完成默认行为,作为修改行为的基础.也就是说,不管`Proxy`怎么修改默认行为,你总可以在`Reflect`上获取默认行为\n\n``` javascript\nProxy(target, {\n  set: function(target, name, value, receiver) {\n    var success = Reflect.set(target,name, value, receiver);\n    if (success) {\n      log('property ' + name + ' on ' + target + ' set to ' + value);\n    }\n    return success;\n  }\n});\n```\n\n上面代码中,`Proxy`方法拦截`target`对象的属性赋值行为.它采用`Reflect.set`方法将值赋值给对象的属性,然后再部署额外的功能\n\n### Reflect对象的方法\n- `Reflect.get(target, name, receiver)`\n查找并返回`target`对象的`name`属性,如果没有该属性,则返回`undefined`.如果`name`属性部署了读取函数,则读取函数的`this`绑定`receiver`\n\n``` javascript\nvar obj = {\n  get foo() { return this.bar(); },\n  bar: function() { ... }\n};\n\n// 下面语句会让 this.bar()\n// 变成调用 wrapper.bar()\nReflect.get(obj, \"foo\", wrapper);\n```\n\n- `Reflect.set(target, name, value, receiver)`\n...\n\n## Set和Map\n### Set\n\nES6提供了新的数据结构`Set`.它类似于数组,但是成员的值都是唯一的,没有重复的值.`Set`本身是一个构造函数,用来生成`Set`数据结构.\n\n``` javascript\nlet s = new Set();\nlet arr = [1,2,3,3,3,3,3,3,4,5,6,7,8,9];\narr.map(x =\u003e s.add(x)); //add方法向Set结构加入成员\nfor(let i of s){\n    console.log(i);     //1,2,3,4,5,6,7,8,9 不会加入重复的值\n}\nconsole.log([...s]);    //[1,2,3,4,5,6,7,8,9]\nlet s1 = new Set(arr);  //接受一个数组参数\nconsole.log([...s1]);   //[1,2,3,4,5,6,7,8,9]\nconsole.log(s1.size);   //9\n\n//数组去重\n[...new Set(arr)];\n```\n\n\u003e提示: `Set`不能加重复的`NaN`,`Set`不能加入重复的值,加入的值不会发生类型转换.\n\n判断是否重复,也就是是否相等,使用`===`\n``` javascript\nlet s = new Set();\ns.add('5');\ns.add(5);\nconsole.log([...s]);    //[\"5\", 5]\nconsole.log(s.size);    //2\n```\n\n####  Set的方法和属性\n属性\n- `size` \n成员总数\n- `constructor`\n构造函数\n\n方法\n- `add`\n添加Set成员,返回Set结构本身\n- `delete`\n返回一个布尔值,表示是否删除成功\n- `has`\n判断是否是Set的成员,返回布尔值\n- `clear` \n清除所有成员,没有返回值\n\n``` javascript\nlet s = new Set();\ns.add('5');\ns.add(5);\n\nconsole.log([...s]);    //[\"5\", 5] 转化为数组\nconsole.log(s.size);    //2\n\nlet arr = Array.from(s);\nconsole.log(arr);       //[\"5\", 5] 转化为数组\n\n//数组去重方法二\n\nfunction dedupe(arr){\n    return Array.from(new Set(arr));\n}\n\nconsole.log(dedupe([1,1,2,3,4,5,5]));   //[1, 2, 3, 4, 5]\n```\n\n#### 遍历\n - `keys()`\n - `vaules()`\n - `entries()`\n - `forEach()`\n\n``` javascript\nlet set = new Set(['red', 'green', 'blue']);\n\nfor (let item of set.keys()) {\n  console.log(item);\n}\n// red\n// green\n// blue\n\nfor (let item of set.values()) {\n  console.log(item);\n}\n// red\n// green\n// blue\n\nfor (let item of set.entries()) {\n  console.log(item);\n}\n// [\"red\", \"red\"]\n// [\"green\", \"green\"]\n// [\"blue\", \"blue\"]\n```\n\n其实还可以使用`for...of`遍历`Set`.`Set`结构的实例的`forEach`方法,用于对每个成员执行某种操作,没有返回值.\n\n### WeakSet\n类似于`Set`略.\n\n### Map\nMap是比对象更灵活的一种数据结构,对象本身就就是由键值对组成的,但是属性只能是字符串,Map的键则可以用对象等更复杂方式来表示.\n\n把对象`o`当做`m`的一个键\n\n``` javascript\nlet m = new Map();\nlet o = {};\n\nm.set(o,'o object');\nconsole.log(m.get(o));  //o object\nconsole.log(m.has(o));  //true\n\nconsole.log(m.delete(o));   //true\nconsole.log(m.has(o));  //false\n```\n\n接受一个数组作为参数,该数组的成员仍然是一个个数组,每个数组中都有两个元素表示键和值\n\n``` javascript\nlet m = new Map([['name','ziyi2'],['age','23']]);\nconsole.log(m.size);        //2\nconsole.log(m.get('name')); //ziyi2\nconsole.log(m.has('name')); //true\n```\n\n如果键相同,那么后面的就会把前面的覆盖掉\n``` javascript\nlet m = new Map([['name','ziyi2'],['name','23']]);\nconsole.log(m.size);        //1\nconsole.log(m.get('name')); //23\nconsole.log(m.has('name')); //true\n\nm.set('b','ziyi2');\nconsole.log(m.get('b'));    //ziyi2\n\nm.set(['a'],\"ziyi2\");\nconsole.log(m.get(['a']));  //undefined\n```\n\n\u003e提示: 最后一个是数组,其实是两个不同的地址,所以没办法获取值,就跟对象是一样的\n\n#### 方法和属性\n类似于`Set`.\n\n#### 遍历方法\n类似于`Set`.也可以使用`...`扩展运算符.\n\n### WeakMap\n略.\n\n## Iterator和for...of循环\n`ES5`中的表示集合的属性类型只有`Array`和`Object`,`ES6`中又新增了`Map`和`Set`.需要一种统一的接口机制,来处理所有不同的数据结构,遍历器（`Iterator`）就是这样一种机制.它是一种接口,为各种不同的数据结构提供统一的访问机制.任何数据结构只要部署`Iterator`接口,就可以完成遍历操作（即依次处理该数据结构的所有成员）.\n\n`Iterator`的作用有三个：一是为各种数据结构,提供一个统一的、简便的访问接口；二是使得数据结构的成员能够按某种次序排列；三是`ES6`创造了一种新的遍历命令`for...of`循环,`Iterator`接口主要供`for...of`消费.\n\n`Iterator`的遍历过程:\n\n（1）创建一个指针对象,指向当前数据结构的起始位置.也就是说,遍历器对象本质上,就是一个指针对象.\n（2）第一次调用指针对象的`next`方法,可以将指针指向数据结构的第一个成员.\n（3）第二次调用指针对象的`next`方法,指针就指向数据结构的第二个成员.\n（4）不断调用指针对象的`next`方法,直到它指向数据结构的结束位置.\n\n每一次调用`next`方法,都会返回数据结构的当前成员的信息.具体来说,就是返回一个包含`value`和`done`两个属性的对象.其中,`value`属性是当前成员的值,`done`属性是一个布尔值,表示遍历是否结束.\n\n``` javascript\nfunction createIterator(array){\n    let nextIndex = 0;\n    return {\n        next () {\n            return nextIndex \u003c array.length ? {value: array[nextIndex ++], done: false} :\n                                              {value: undefined, done: true};\n        }\n    };\n}\n\n\nlet it = createIterator([1,2]);\nconsole.log(it.next());     //Object { value=1,  done=false}\nconsole.log(it.next());     //Object { value=2,  done=false}\nconsole.log(it.next());     // Object { value=undefined, done=true}\n```\n\n上面的函数的作用就是返回一个遍历器对象,对数组执行这个函数,就会返回该数组的遍历器对象(指针对象)`it`.\n\n在`ES6`中,有些数据结构原生具备`Iterator`接口（比如数组）,即不用任何处理,就可以被`for...of`循环遍历,有些就不行（比如对象）.原因在于,这些数据结构原生部署了`Symbol.iterator`属性（详见下文）,另外一些数据结构没有.凡是部署了`Symbol.iterator`属性的数据结构,就称为部署了遍历器接口.调用这个接口,就会返回一个遍历器对象.\n\n### 数据结构的默认Iterator接口\n`ES6`规定,默认的`Iterator`接口部署在数据结构的`Symbol.iterator`属性,或者说,一个数据结构只要具有`Symbol.iterator`属性,就可以认为是“可遍历的”（`iterable`）.**调用`Symbol.iterator`**方法**,就会得到当前数据结构默认的遍历器生成函数.**\n\n在`ES6`中,有三类数据结构原生具备`Iterator`接口：`Array`、类数组对象、`Set`和`Map`结构.\n\n\n``` javascript\nlet arr = ['a', 'b', 'c'];\nlet iter = arr[Symbol.iterator]();\n\nconsole.log(iter.next()); // { value: 'a', done: false }\nconsole.log(iter.next()); // { value: 'b', done: false }\nconsole.log(iter.next()); // { value: 'c', done: false }\nconsole.log(iter.next()); // { value: undefined, done: true }\n```\n上面代码中,变量`arr`是一个数组,原生就具有遍历器接口,部署在`arr`的`Symbol.iterator`属性上面.所以,调用这个属性,就得到遍历器对象.\n\n上面提到,原生就部署`Iterator`接口的数据结构有三类,对于这三类数据结构,不用自己写遍历器生成函数,`for...of`循环会自动遍历它们.除此之外,其他数据结构（主要是对象）的`Iterator`接口,都需要自己在`Symbol.iterator`属性上面部署,这样才会被`for...of`循环遍历.\n\n对象（`Object`）之所以没有默认部署`Iterator`接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定.\n\n\n类似数组的对象调用数组的`Symbol.iterator`方法\n\n``` javascript\nlet iterable = {\n    0: 'a',\n    1: 'b',\n    2: 'c',\n    length: 3,\n    [Symbol.iterator]: Array.prototype[Symbol.iterator] //Symbol.iterator方法直接引用数组的Iterator接口\n};\n\n\nfor(let item of iterable){\n    console.log(item);  //a b c\n}\n\nconsole.log([...iterable]); //[\"a\", \"b\", \"c\"]\n```\n\n需要注意的是普通对象部署数组的`Symbol.iterator`方法并没有效果.\n\n``` javascript\nlet iterable = {\n    a: 'a',\n    b: 'b',\n    c: 'c',\n    length: 3,\n    [Symbol.iterator]: Array.prototype[Symbol.iterator] //Symbol.iterator方法直接引用数组的Iterator接口\n};\n\n\nfor(let item of iterable){\n    console.log(item);  //undefined undefined undefined\n}\n\nconsole.log([...iterable]); //[undefined, undefined, undefined]\n\nlet  iter = iterable[Symbol.iterator]();\nconsole.log(iter.next());   //Object { value=undefined,  done=false}\nconsole.log(iter.next());   //Object { value=undefined,  done=false}\nconsole.log(iter.next());   //Object { value=undefined,  done=false}\nconsole.log(iter.next());   //Object { done=true,  value=undefined}\n```\n\n如果`Symbol.iterator`方法对应的不是遍历器生成函数（即会返回一个遍历器对象）,解释引擎将会报错\n\n``` javascript\nvar obj = {};\n\nobj[Symbol.iterator] = () =\u003e 1;\n\n[...obj] //TypeError: (intermediate value).next is not a function\n```\n\n有了遍历器接口(`Iterator`接口),数据结构就可以用`for...of`循环遍历,也可以使用`while`循环遍历\n\n\n``` javascript\nlet iterable = {\n    0: 'a',\n    1: 'b',\n    2: 'c',\n    length: 3,\n    [Symbol.iterator]: Array.prototype[Symbol.iterator] //Symbol.iterator方法直接饮用数组的Iterator接口\n};\n\n\nfor(let item of iterable){\n    console.log(item);  //a b c\n}\n\nconsole.log([...iterable]); //[\"a\", \"b\", \"c\"]\n\nlet  iter = iterable[Symbol.iterator]();\n\nlet result = iter.next();\n\nwhile(!result.done){\n    let x = result.value;\n    console.log(x);   //a b c\n    result = iter.next();\n}\n```\n\n### 调用Iterator接口的场合\n有一些场合会默认调用`Iterator`接口（即`Symbol.iterator`方法）,例如`for...of`循环\n\n#### 解构赋值\n\n对数组和`Set`结构进行解构赋值时,会默认调用`Symbol.iterator`方法\n\n``` javascript\nlet set = new Set().add('a').add('b').add('c');\nlet [x,y,z] = set;\nlet [first,...rest] = set;\nconsole.log(rest);  //[\"b\", \"c\"]\n```\n\n#### `...`扩展运算符\n该运算符也会调用默认的`iterator`接口\n\n``` javascript\nlet str = 'hello';\nconsole.log([...str]);  //[\"h\", \"e\", \"l\", \"l\", \"o\"]\n\nlet arr = ['r','e'];\nconsole.log([...str,...arr]);   //[\"h\", \"e\", \"l\", \"l\", \"o\", \"r\", \"e\"]\n```\n\n\u003e提示: 只要某个数据接口部署了`iterator`接口,就可以对它使用扩展运算符,将其转为数组. `let arr = [...iterable];`\n\n#### `yield*`\n\n`yield*`后面跟的是一个可遍历的结构,会调用该结构的遍历器接口\n\n``` javascript\nlet generator = function* (){\n    yield 1;\n    yield* [2,3,4];\n    yield 5;\n};\n\nlet iterator = generator();\n\nconsole.log(iterator.next());   //Object { value=1,  done=false}\nconsole.log(iterator.next());   //Object { value=2,  done=false}\nconsole.log(iterator.next());   //Object { value=3,  done=false}\nconsole.log(iterator.next());   //Object { value=4,  done=false}\nconsole.log(iterator.next());   //Object { value=5,  done=false}\nconsole.log(iterator.next());   //Object { value=undefined,  done=true}\n```\n\n#### 其他场合\n\n数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口\n- `for...of`\n- `Array.from`\n- `Map,Set,WeakMap,WeakSet`\n- `Promise.all`\n- `Promise.race`\n\n### 字符串的Iterator接口\n字符串也是一个类数组对象,也原生具有`Iterator`接口\n\n``` javascript\nlet str = 'hello';\n\nlet iterator = str[Symbol.iterator]();\n\nconsole.log(iterator.next());   //Object { value=h,  done=false}\nconsole.log(iterator.next());   //Object { value=e,  done=false}\nconsole.log(iterator.next());   //Object { value=l,  done=false}\nconsole.log(iterator.next());   //Object { value=l,  done=false}\nconsole.log(iterator.next());   //Object { value=o,  done=false}\nconsole.log(iterator.next());   //Object { value=undefined,  done=true}\n```\n\n\u003e提示:调用`Symbol.iterator`方法返回一个遍历器对象,该对象具有`next`方法,从而实现遍历.\n\n### Iterator接口与Generator函数\n`Symbol.iterator`方法的最简单实现\n\n``` javascript\nlet iterable = {};\n\niterable[Symbol.iterator] = function* () {\n    yield 1;\n    yield 2;\n    yield 3;\n};\n\nconsole.log([...iterable]); //[1,2,3]\n\n//简洁写法\n\n\nlet obj = {\n    *[Symbol.iterator]() {\n        yield 'hello',\n        yield 'world'\n    }\n};\n\nfor(let value of obj){\n    console.log(value); //hello world\n}\n\nlet objIter = obj[Symbol.iterator]();\nconsole.log(objIter.next());    //Object { value=\"hello\",  done=false}\nconsole.log(objIter.next());    //Object { value=\"world\",  done=false}\nconsole.log(objIter.next());    //Object { done=true,  value=undefined} \n```\n\n### 遍历器对象的方法\n\n- next\n- return\n- throw\n\n\u003e提示: 在`Generator`中讲解.\n\n\n### `for...of`\n\n#### Array\n\n`for...of`循环内部调用的是数据接口的`Symbol.iterator`方法.\n\n``` javascript\nlet arr = [1,2];\nlet iterator = arr[Symbol.iterator]();\n\nfor(let value of arr){  //原生调用iterator接口\n    console.log(value); //1 2\n}\n\nfor(let value of iterator){\n    console.log(value); //1 2\n}\n```\n\n以上两种写法相等,需要注意的是,`for...of`可以替代数组中的`forEach`方法.\n\n同时,在`ES5`中的`for...in`方法可以获取键名,但是没办法获取键值,在ES6中使用`for...of`方法可以获取键值.\n\n\n``` javascript\nlet arr = [1,2];\n\nfor(let value of arr){  //获取键值\n    console.log(value); //1 2\n}\n\n\nfor(let key in arr){    //获取键名(数组中的索引,对象中的属性名)\n    console.log(key);   //0 1\n}\n```\n同时,`for...of`在数组的遍历器接口中只返回具有数字索引的属性,这一点跟`for...in`循环也有差异\n\n``` javascript\nlet arr = [1,2];\n\narr.name = 'arr';\n\n\nfor(let value of arr){  //只遍历数组的索引\n    console.log(value); //1 2\n}\n\n\nfor(let key in arr){    //当然是遍历所有属性名\n    console.log(key);   //0 1 name\n}\n```\n\n#### Set和Map\n`Set`和`Map`和`Array`一样,原生具有`Iterator`接口,可以直接使用`for...of`循环.\n\n\n``` javascript\nlet name = new Set(['ziyi2','ziyi3']);\n\nfor(let e of name){ \n    console.log(e);  //ziyi2 ziyi3\n}\n\nlet person = new Map();\n\nperson.set('name','ziyi2');\nperson.set('age',23);\n\nfor(let [key,value] of person){\n    console.log(key + ':' + value); //name:ziyi2 age:23\n}\n\n```\n\n\u003e提示: Set结构遍历时,返回的是一个值,而Map结构遍历时,返回的是一个具有键值对的数组.\n\n#### 计算生成的数据结构\n以下三个方法调用后返回遍历器对象\n\n- entries \n遍历`[键名,键值]`组成的数组.`Map`结构的`Iterator`接口,默认就是调用`entries`方法.\n- keys\n遍历所有的键名\n- values\n遍历所有的键值\n\n``` javascript\nlet arr = [1,2];\narr.name = 'arr';\n\nfor(let key of arr.keys()){ //arr.keys()返回的是数组的遍历器对象\n    console.log(key);       //0 1\n}\n\nconsole.log(arr.keys().next()); //Object { value=0,  done=false}\n\nfor(let value of arr){\n    console.log(value);     //1,2\n}   \n\n//该方法类似于以上遍历\nfor(let value of arr.values()){ //arr.values is not a function\n    console.log(value);\n}\n\n\nfor(let pair of arr.entries()){\n    console.log(pair);  //[0, 1] [1, 2]\n}\n```\n\n#### 类数组对象\n\n`DOM NodeList`对象,`arguments`对象都是类数组对象.\n\n```javascript\nlet ps = document.querySelectorAll('p');\n\n\nfor(let p of ps){   //给所有的p元素添加class属性\n    p.classList.add('test');    \n}\n\nfor(let key in ps){\n    console.log(key);   //0,1,2,3,4,item,length\n}\n```\n\n当然并不是所有的类数组对象都具有`iterator`接口\n\n``` JavaScript\nlet obj = {\n    0: 'a',\n    1: 'b',\n    length:2\n};\n\n\nfor(let key in obj){\n    console.log(key);   //0,1,length\n}\n\nfor(let value of obj){\n    console.log(value); //obj is not iterable\n}\n```\n\n此时有两种方法可以使它拥有`iterator`接口\n\n``` JavaScript\nlet obj = {\n    0: 'a',\n    1: 'b',\n    length:2,\n    [Symbol.iterator]: Array.prototype[Symbol.iterator] //部署Symbol.iterator属性\n};\n\n\nfor(let value of obj){\n    console.log(value); //a b\n}\n\n\nlet arr1 = {\n    0: 'a',\n    1: 'b',\n    length: 2\n};\n\nfor(let value of Array.from(arr1)){ //将类数组对象转化为数组\n    console.log(value); //a b\n}\n```\n#### 比较\n\n`for...in`循环的缺点\n- 遍历数组的键名时以字符串'0','1'.'2'作为键名\n- 不仅遍历数字键名,还会遍历其他键,甚至包括原型链上的键(此时使用Object.keys()比较合适).\n- 以任意顺序遍历键名\n\n`for...of`优点\n\n- 可以与`break`,`continue`,`return`配合使用\n- 提供了遍历所有数据结构的统一操作接口\n\n``` JavaScript\nlet obj = {\n    0: 'a',\n    1: 'b',\n    length:2\n};\n\nfor(let value in obj){\n    console.log(value); //0\n    break;              //中断遍历,也可以! \n}\n\nfor(let value of Array.from(obj)){\n    console.log(value); //a\n\n    if(value === 'a'){\n        break;          //中断遍历\n    }\n}\n```\n\n## Generator\n\n`Generator`函数是`ES6`提供的一种异步编程解决方案,语法行为与传统函数完全不同.`Generator`函数是一个状态机,封装了多个内部状态.执行`Generator`函数会返回一个遍历器对象,也就是说,`Generator`函数除了状态机,还是一个遍历器对象生成函数.返回的遍历器对象,可以依次遍历`Generator`函数内部的每一个状态.\n形式上,`Generator`函数是一个普通函数,但是有两个特征.一是,`function`关键字与函数名之间有一个星号；二是,函数体内部使用`yield`语句,定义不同的内部状态（`yield`语句在英语里的意思就是“产出”）.\n\n\n``` JavaScript\nfunction* personGen(){\n    yield 'ziyi2';      //此时有ziyi2,xx3和ending三个状态\n    yield 'xx3';\n    return 'ending';\n}\n\nlet p = personGen();    //Generator函数返回的是一个遍历器对象\n\nconsole.log(p.next());  //Object { value=\"ziyi2\",  done=false}\nconsole.log(p.next());  //Object { value=\"xx3\",  done=false}\nconsole.log(p.next());  //Object { value=\"ending\",  done=true}\nconsole.log(p.next());  //Object { value=undefined,  done=true}\n```\n调用`Generator`函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一节介绍的遍历器对象`Iterator Object`.必须调用遍历器对象的`next`方法,使得指针移向下一个状态,每次调用`next`方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个`yield`语句（或`return`语句）为止.换言之,`Generator`函数是分段执行的,`yield`语句是暂停执行的标记,而`next`方法可以恢复执行.\n\n第一次调用,`Generator`函数开始执行,直到遇到第一个`yield`语句为止.`next`方法返回一个对象,它的`value`属性就是当前`yield`语句的值`ziyi2`,`done`属性的值`false`,表示遍历还没有结束.\n\n第二次调用,`Generator`函数从上次`yield`语句停下的地方,一直执行到下一个`yield`语句.`next`方法返回的对象的`value`属性就是当前`yield`语句的值`xx3`,`done`属性的值`false`,表示遍历还没有结束.\n\n一直执行到`return`语句（如果没有`return`语句,就执行到函数结束）.`next`方法返回的对象的`value`属性,就是紧跟在`return`语句后面的表达式的值（如果没有`return`语句,则`value`属性的值为`undefined`）,`done`属性的值`true`,表示遍历已经结束.\n\n`ES6`没有规定,`function`关键字与函数名之间的星号,写在哪个位置.下面写法都是可以的,一般采用第二种\n\n``` JavaScript\nfunction * f(){};\nfunction* f(){};\nfunction *f(){};\nfunction*f(){};\n```\n\n### yield\n由于`Generator`函数返回的遍历器对象,只有调用`next`方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数.`yield`语句就是暂停标志.\n\n- 遇到`yield`语句,就暂停执行后面的操作(比如有一个加法操作,没有遇到该`yield`之前是不会执行该语句的,等于手动的'惰性求值'),并将紧跟在`yield`后面的那个表达式的值,作为返回的对象的`value`属性值.\n- 下一次调用`next`方法时,再继续往下执行,直到遇到下一个`yield`语句.\n- 如果没有再遇到新的`yield`语句,就一直运行到函数结束,直到`return`语句为止,并将`return`语句后面的表达式的值,作为返回的对象的`value`属性值.\n- 如果该函数没有`return`语句,则返回的对象的`value`属性值为`undefined`.\n\n`yield`语句与`return`语句既有相似之处,也有区别.相似之处在于,都能返回紧跟在语句后面的那个表达式的值.区别在于每次遇到`yield`,函数暂停执行,下一次再从该位置继续向后执行,而`return`语句不具备位置记忆的功能.一个函数里面,只能执行一次（或者说一个）`return`语句,但是可以执行多次（或者说多个）`yield`语句.正常函数只能返回一个值,因为只能执行一次`return`；`Generator`函数可以返回一系列的值,因为可以有任意多个`yield`.从另一个角度看,也可以说`Generator`生成了一系列的值,这也就是它的名称的来历（在英语中,`generator`这个词是“生成器”的意思）.\n\n`Generator`函数可以不用`yield`语句,这时就变成了一个单纯的暂缓执行函数\n\n``` JavaScript\nfunction * fGen(){\n    console.log('此时执行');\n}\n\nlet f = fGen();     //此时并没有输出\nf.next();           //此时执行\n```\n\n`yield`语句不能用在普通函数里,否则会报错\n\n\n```JavaScript\nfunction * fGen(){\n    (function(){        //不能把yield放在普通函数里\n        yield 'ziyi2';\n        yield 'xx3';\n        return 'ending'\n    })();\n}\n\n\nlet f = fGen(); //error 报错\n```\n\n### Iterator接口关系\n对象的`Symbol.iterator`方法相当于一个遍历器生成函数,调用该函数会返回该对象的遍历器对象. 由于`Generator`函数就是遍历器生成函数,因此把`Generator`赋值给对象的`Symbol.iterator`属性,从而使得该对象具有`Iterator`接口.\n\n``` javascript\nlet myIterable = {};\n\nmyIterable[Symbol.iterator] = function* (){\n    yield 1;\n    yield 2;\n    yield 3;\n};\n\nconsole.log([...myIterable]);   //[1, 2, 3]\n```\n\n当然`Generator`函数执行后返回的遍历器对象本身也具有`Symbol.iterator`属性,执行后和自身相同\n\n``` javascript\nfunction* gen(){\n    yield 1;\n    yield 2;\n};\n\nlet g = gen();\n\nlet s = g[Symbol.iterator]();\n\nlet bool = s ===  g;\nconsole.log(bool);  //true\n\nconsole.log(g.next());  //Object { value=1,  done=false}\nconsole.log(s.next());  //Object { value=2,  done=false}\n```\n\n### next方法的参数\n### for...of循环\n一旦`next`方法的返回对象的`done`属性为`true`,`for...of`循环就会中止,且不包含该返回的对象,所以`return`语句返回的值就不会被遍历\n\n``` javascript\nfunction *f(){\n    yield 1;\n    yield 2;\n    yield 3;\n    yield 4;\n    return 6;\n}\n\nfor(let value  of f()){\n    console.log(value); //1 2 3 4\n}\n```\n\n\u003e提示: 使用`for...of`语句时不需要再使用`next`方法.\n\n`for...of`,扩展运算符`...`,解构赋值和`Array.from`方法内部调用的,都是遍历器接口,所以可以将Generator函数返回的Iterator对象作为参数.\n\n``` javascript\nfunction *f(){\n    yield 1;\n    yield 2;\n    yield 3;\n    return 4;\n    yield 5;\n\n}\n\n\nconsole.log([...f()]);          //[1, 2, 3]\nconsole.log(Array.from(f()));   //[1, 2, 3]\n\nlet [...arr] = f();\nconsole.log(arr);               //[1, 2, 3]\n\nlet [x,y,z] = f();\nconsole.log(x);                 //1\nconsole.log(y);                 //2\nconsole.log(z);                 //3\n\n\nfor(let value of f()){\n    console.log(value);         //1 2 3\n}   \n\n```\n\n通过Generator函数为原生的对象加上`Iterator`接口\n\n``` JavaScript\nfunction* objEntries(obj){\n    let keys = Reflect.ownKeys(obj);    //返回所有的属性,不管属性名是`Symbol`还是字符串,不管是否可枚举.\n\n    for(let key of keys){\n        yield [key,obj[key]];\n    }\n}\n\n\nlet person = {one:'ziyi2',two:'ziyi3',three:'ziyi4'};\n\nfor(let [key,value] of objEntries(person)){\n    console.log(`${key}:${value}`);\n}\n\n//one:ziyi2\n//two:ziyi3\n//three:ziyi4\n```\n\n以上原生的`person`对象是不具备`Iterator`接口的,通过`Generator`函数为它加上遍历器接口,就可以使用`for...of`遍历了.当然加上遍历器的另外一个方法是将`Generator`函数加到对象`Symbol.iterator`属性上面.\n\n``` javascript\nfunction* objEntries(){\n    //let keys = Reflect.ownKeys(this); //返回所有的属性,不管属性名是`Symbol`还是字符串,不管是否可枚举.\n\n    let keys = Object.keys(this);       //返回对象自身的所有可枚举的属性的键名\n\n    for(let key of keys){\n        yield [key,this[key]];\n    }\n}\n\n\nlet person = {one:'ziyi2',two:'ziyi3',three:'ziyi4'};\n\nperson[Symbol.iterator] = objEntries;\n\nfor(let [key,value] of person){\n    console.log(`${key}:${value}`);\n}\n\n//one:ziyi2\n//two:ziyi3\n//three:ziyi4\n```\n####  Generator.prototype.throw\n `Generator`函数返回的遍历器对象,都有一个`throw`方法,可以在函数体外抛出错误,然后在`Generator`函数体内捕获.\n\n``` javascript\nvar g = function* () {\n  try {\n    yield;\n  } catch (e) {\n    console.log('内部捕获', e);\n  }\n};\n\nvar i = g();\nconsole.log(i.next());  //Object {value: undefined, done: false}\n\ntry {\n  i.throw('a');\n  i.throw('b');\n} catch (e) {\n  console.log('外部捕获', e);\n}\n// 内部捕获 a\n// 外部捕获 b\n```\n\n上面代码中,遍历器对象`i`连续抛出两个错误.第一个错误被`Generator`函数体内的`catch`语句捕获.`i`第二次抛出错误,由于`Generator`函数内部的`catch`语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了`Generator`函数体,被函数体外的`catch`语句捕获.\n\n`throw`方法可以接受一个参数,该参数会被`catch`语句接收,建议抛出`Error`对象的实例\n\n``` javascript\nvar g = function* () {\n  try {\n    yield;\n  } catch (e) {\n    console.log( e);    //Error: 出错了!\n  }\n};\n\n\nlet i = g();\ni.next();\ni.throw(new Error('出错了!'));\n```\n\n\u003e提示: 不要混淆遍历器对象的`throw`方法和全局的`throw`命令.上面代码的错误,是用遍历器对象的`throw`方法抛出的,而不是用`throw`命令抛出的.后者只能被函数体外的`catch`语句捕获.\n\n\n如果`Generator`函数内部没有部署`try...catch`代码,那么遍历器对象抛出的异常将会被外部的`catch`代码块捕获\n\n``` javascript\nvar g = function* () {\n  try {\n    yield;\n  } catch (e) {\n    console.log('内部捕获',e);  \n  }\n};\n\nlet i = g();\ni.next();\n\ntry {\n    throw new Error('a');   \n    throw new Error('b');\n} catch(e){\n    console.log('外部捕获',e);\n}\n\n//外部捕获 Error: a\n```\n函数体外的`catch`语句块捕获了抛出`a`错误以后,就不会在继续`try`代码块里面剩余的语句了.\n\n``` javascript\nlet g = function* () {\n    while(true) {\n        yield;\n        console.log('内部捕获',e);\n    }\n};\n\n\nlet i = g();\nconsole.log(i.next());  //Object { done=false,  value=undefined}\n\ntry {\n    i.throw('a');\n    i.throw('b');\n} catch (e) {\n    console.log('外部捕获',e);\n}\n\n//外部捕获 a\n\n```\n\n\u003e提示: 如果外部和函数内部都没有部署`try...catch`代码块,那么程序将会报错,中断执行.\n\n``` javascript\nlet gen = function* () {\n    try {\n        yield console.log('a');\n    } catch (e) {\n        console.log('error');\n    }\n\n    yield console.log('b');\n    yield console.log('c');\n};\n\n\nlet g = gen();\n\ng.next();       //a\ng.throw();      //error \ng.next();       //b\ng.next();       //c\nconsole.log(g.next());  //Object { done=true,  value=undefined}\ng.throw();      //uncaught exception: undefined\n```\n\n```javascript\nlet gen = function* () {\n    try {\n        yield console.log('a');\n    } catch (e) {\n        console.log('error');\n    }\n\n    yield console.log('b');\n    yield console.log('c');\n};\n\n\nlet g = gen();\n\ng.next();       //a\ng.next();       //b\n\ntry {\n    throw new Error();\n} catch(e) {\n    g.next();   //c\n}\n\n```\n\n`Generator`函数体外抛出的错误,可以在函数体内捕获,反过来,`Generator`函数体内抛出的错误,也可以被函数体外的`catch`捕获\n\n``` javascript\nlet gen = function* () {\n    let x = yield 3;\n    let y = x.toUpperCase();\n    yield y;\n};\n\nlet it = gen();\n\nconsole.log(it.next());   //Object { value=3,  done=false}\n\ntry {\n    it.next(43);\n} catch (err) {\n    console.log(err);     //x.toUpperCase is not a function\n}\n\n```\n\n`yield`句本身没有返回值,或者说总是返回`undefined`.`next`方法可以带一个参数,该参数就会被当作上一个`yield`语句的返回值.\n\n``` javascript\nlet gen = function* () {\n    let x = yield 3;        //本身没有返回值,返回的总是undefined\n    let y = x.toUpperCase();\n    yield y;\n};\n\nlet it = gen();\n\nconsole.log(it.next());     //Object { value=3,  done=false}\nconsole.log(it.next());     //TypeError: x is undefined \n```\n\n一旦`Generator`执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了,如果还调用`next`,将返回`{value:undeinfed,done:true}`\n\n``` javascript\nlet gen = function* () {\n    yield 3;\n    console.log('throw an error');\n    throw new error('error:next is done');\n    yield 2;\n    yield 3;\n}\n\nlet g = gen();\n\ntry {\n    console.log(g.next());   //Object { value=3,  done=false}\n} catch(e) {\n    console.log('first next error' );\n}\n\ntry {\n    console.log(g.next());\n} catch(e) {\n    console.log('second next error' );  //throw an error second next error\n}\n\ntry {\n    console.log(g.next());  // Object { done=true,  value=undefined}\n} catch(e) {\n    console.log('third next error' );\n}\n```\n\n####  Generator.prototype.return \n\n`return`方法用来终结遍历状态,就好像在`Generator`函数中遇到了`return`\n\n``` javascript\nlet gen = function* () {\n    yield 1;\n    yield 2;\n    yield 3;\n}\n\nlet g = gen();\n\nconsole.log(g.next());      // Object { value=1,  done=false}\nconsole.log(g.return('4')); // Object { value=\"4\",  done=true}\nconsole.log(g.next());      // Object { done=true,  value=undefined}\n```\n\n\u003e提示: 如果`return`方法不传入参数,那么返回的是`undefined`\n\n\n如果`Generator`函数内部有`try...finally`代码块\n\n``` javascript\nlet gen = function* () {\n    yield 1;\n    try {\n        yield 2;\n        yield 3;\n    } finally {\n        yield 4;\n        yield 5;\n    }\n    yield 6;\n}\n\nlet g = gen();\n\nconsole.log(g.next());      //Object { value=1,  done=false}\nconsole.log(g.next());      //Object { value=2,  done=false}\nconsole.log(g.return('7')); //Object { value=\"7\",  done=true}\nconsole.log(g.next());      //Object { value=4,  done=false}\nconsole.log(g.next());      //Object { value=5,  done=false}\n```\n\n\u003e提示:调用`return`后,先执行`finally`中的代码块,执行完后再执行`return`方法\n\n#### `yield*`\n\n在`Generator`函数内部要调用`Generator`函数是不行的,此时可以用`yield*`来代替\n\n``` javascript\nfunction* f() {\n    yield 1;\n    yield 2;\n    yield* g();\n}\n\nfunction* g() {\n    yield 3;\n    yield 4;\n}\n\n\nfor(let value of f()){\n    console.log(value); //1 2 3 4\n}\n```\n\n如果`yield`后面是一个遍历器对象,那么需要`yield`命令后面加上`*`,表明它返回的是一个遍历器对象,否则返回的不是遍历值,而是该遍历器对象\n\n``` javascript\nfunction* f() {\n    yield 1;\n    yield 2;\n    yield* g();         //返回遍历值            \n    yield g();          //这里返回的是遍历器对象\n}\n\nfunction* g() {\n    yield 3;\n    yield 4;\n}\n\n\nfor(let value of f()){\n    console.log(value); //1 2 3 4  Generator {}\n}\n```\n\n遍历递归效果\n\n``` javascript\nlet f = (function* () {\n    yield 'hello';\n    yield 'bye';\n}());\n\n\nlet g = (function* () {\n    yield 'hello forward';\n    yield* f;\n    yield 'bye end';\n}());\n\nfor(let value of g) {\n    console.log(value); //'hello forward'  'hello' 'bye' 'bye end'\n}\n```\n\n以上代码等同于\n\n``` javascript\nvar f = (function* () {\n    yield 'hello';\n    yield 'bye';\n}());\n\nlet g_copy = (function* () {\n    yield 'hello forward';\n    for(let value of f) {\n        yield value;\n    }\n    yield 'bye end';\n\n}());\n\nfor(let value of g_copy) {\n    console.log(value); //'hello forward'  'hello' 'bye' 'bye end'\n}\n```\n\n由于数组原生支持`Iterator`接口,所以可以被`yield*`遍历\n\n``` javascript\nlet g = (function* () {\n    yield 'hello forward';\n    yield* ['hello','bye'];\n    yield 'bye end';\n\n}());\n\nfor(let value of g) {\n    console.log(value); //'hello forward'  'hello' 'bye' 'bye end'\n}\n```\n\n\u003e提示: 任何具有`Iterator`接口的数据接口,都可以被`yield*`遍历\n\n如果`Generator`函数中调用的`Generator`函数具有`return`语句,那么就可以在这个被调用的`Generator`函数中返回数据给调用`Generator`的`Generator`函数\n\n``` javascript\nfunction* f() {\n    yield 2;\n    yield 3;\n    return 'return value';\n}\n\nfunction* g() {\n    yield 1;\n    let r = yield* f();     //r是f()中的返回值 注意与yield中的返回值的区别\n    console.log('r:' + r); \n    yield 4;\n}\n\nlet g1 = g();\n\n\nconsole.log(g1.next()); // Object { value=1,  done=false}\nconsole.log(g1.next()); // Object { value=2,  done=false}\nconsole.log(g1.next()); // Object { value=3,  done=false}\nconsole.log(g1.next()); // r:return value  Object { value=4,  done=false}\nconsole.log(g1.next()); // Object { done=true,  value=undefined}\n```\n\u003e提示: 注意与`yield`中的返回值的区别,`yield`句本身没有返回值,或者说总是返回`undefined`.`next`方法可以带一个参数,该参数就会被当作上一个`yield`语句的返回值.\n\n``` javascript\nfunction* iterTree(tree) {\n    if(Array.isArray(tree)) {\n        // tree.forEach(function(item,index,arr){\n        //  yield* iterTree(item);  //不能放在普通函数内!!!!\n        // })\n        \n        for(let value of tree){\n            yield* iterTree(value);\n        }\n        \n\n    } else {\n        yield tree;\n    }\n}\n\n\nconst tree = [[1,2,3],4,5,[6,[7,8]]];\n\nfor(let value of iterTree(tree)) {\n    console.log(value); //1 2 3 4 5 6 7 8\n}\n```\n\n#### Generator作为对象的属性\n\n```javascript\nlet obj = {\n\n    f: function* () {\n        yield 1;\n        yield 2;\n        yield 3;\n    },\n\n    *g() {              //简写方式 *表示这个属性是Generator函数\n        yield 1;\n        yield 2;\n    }\n}\n\n\nfor(let value of obj.f()){\n    console.log(value); //1 2 3\n}\n\nfor(let value of obj.g()){\n    console.log(value); //1 2\n}\n```\n\n`Generator`函数总是返回一个遍历器,`ES6`规定这个遍历器是`Generator`函数的实例,也继承了`Generator`函数的`prototype`对象上的方法\n\n``` javascript\nfunction* g() {}\ng.prototype.say = () =\u003e 'hi';\nlet obj = g();  //返回的遍历器obj是g的实例\nconsole.log(obj.say()); //hi\n```\n\n\u003e提示: 不能把`Generator`函数当做普通的构造函数,并且不能和new命令结合\n\n\n#### 状态机\n\n```javascript\n//ES5\nlet ticking = true;\n\nlet clock = () =\u003e {\n    if(ticking) {\n        console.log('Tick!');\n    } else {\n        console.log('Tock!');\n    }\n\n    ticking = !ticking;\n}\n\nclock();    //'Tick!'\nclock();    //'Tock!'\nclock();    //'Tick!'\n\n//ES6\nlet status = function*() {\n    while(true) {\n        console.log('Tick!');\n        yield;\n        console.log('Tock!');\n        yield;\n    }\n}\n\n\nlet x = status();\n\nx.next();   //'Tick!'\nx.next();   //'Tock!'\nx.next();   //'Tick!'\n```\n\n\n#### 协程\n\n协程（coroutine）是一种程序运行的方式,可以理解成“协作的线程”或“协作的函数”.协程既可以用单线程实现,也可以用多线程实现.前者是一种特殊的子例程,后者是一种特殊的线程.\n\n（1）协程与子例程的差异\n\n传统的“子例程”（subroutine）采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数.协程与其不同,多个线程（单线程情况下,即多个函数）可以并行执行,但是只有一个线程（或函数）处于正在运行的状态,其他线程（或函数）都处于暂停态（suspended）,线程（或函数）之间可以交换执行权.也就是说,一个线程（或函数）执行到一半,可以暂停执行,将执行权交给另一个线程（或函数）,等到稍后收回执行权的时候,再恢复执行.这种可以并行执行、交换执行权的线程（或函数）,就称为协程.\n\n从实现上看,在内存中,子例程只使用一个栈（stack）,而协程是同时存在多个栈,但只有一个栈是在运行状态,也就是说,协程是以多占用内存为代价,实现多任务的并行.\n\n（2）协程与普通线程的差异\n\n不难看出,协程适合用于多任务运行的环境.在这个意义上,它与普通的线程很相似,都有自己的执行上下文、可以分享全局变量.它们的不同之处在于,同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态.此外,普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配.\n\n由于ECMAScript是单线程语言,只能保持一个调用栈.引入协程以后,每个任务可以保持自己的调用栈.这样做的最大好处,就是抛出错误的时候,可以找到原始的调用栈.不至于像异步操作的回调函数那样,一旦出错,原始的调用栈早就结束.\n\nGenerator函数是ECMAScript 6对协程的实现,但属于不完全实现.Generator函数被称为“半协程”（semi-coroutine）,意思是只有Generator函数的调用者,才能将程序的执行权还给Generator函数.如果是完全执行的协程,任何函数都可以让暂停的协程继续执行.\n\n如果将Generator函数当作协程,完全可以将多个需要互相协作的任务写成Generator函数,它们之间使用yield语句交换控制权.\n\n#### 应用\n- 异步操作的同步化表达\n\n```javascript\nfunction* ajax() {\n    let result = yield request('/url'); //result就是next传入的参数response\n    let resp = JSON.parse(result);\n    console.log(resp.value);\n}\n\n\nfunction request(url) {\n    makeAjax(url,(response =\u003e {\n        it.next(response);\n    }))\n}\n\nlet it = ajax();\nit.next();\n```\n\n使用`yield`语句可以手动逐行读取文件\n\n```javascript\nfunction* numbers() {\n  let file = new FileReader(\"numbers.txt\");\n  try {\n    while(!file.eof) {\n      yield parseInt(file.readLine(), 10);  //转换为十进制\n    }\n  } finally {\n    file.close();\n  }\n}\n```\n\n- 解决回调金字塔\n\n回调金字塔\n```javascript\nstep1(function (value1) {\n  step2(value1, function(value2) {\n    step3(value2, function(value3) {\n      step4(value3, function(value4) {\n        // Do something with value4\n      });\n    });\n  });\n});\n```\n\n采用`Promise`写法\n\n```javascript\nQ.fcall(step1)\n  .then(step2)\n  .then(step3)\n  .then(step4)\n  .then(function (value4) {\n    // Do something with value4\n  }, function (error) {\n    // Handle any error from step1 through step4\n  })\n  .done();\n  ```\n\n`Generator`写法\n\n```javascript\nfunction* longRunningTask() {\n  try {\n    var value1 = yield step1();\n    var value2 = yield step2(value1);\n    var value3 = yield step3(value2);\n    var value4 = yield step4(value3);\n    // Do something with value4\n  } catch (e) {\n    // Handle any error from step1 through step4\n  }\n}\n```\n\n然后,使用一个函数,按次序自动执行所有步骤\n\n```javascript\nscheduler(longRunningTask());\n\nfunction scheduler(task) {\n  setTimeout(function() {\n    var taskObj = task.next(task.value);\n    // 如果Generator函数未结束,就继续调用\n    if (!taskObj.done) {\n      task.value = taskObj.value\n      scheduler(task);\n    }\n  }, 0);\n}\n```\n\n注意,`yield`语句是同步运行,不是异步运行（否则就失去了取代回调函数的设计目的了）.实际操作中,一般让`yield`语句返回`Promise`对象.\n\n```javascript\nvar Q = require('q');\n\nfunction delay(milliseconds) {\n  var deferred = Q.defer();\n  setTimeout(deferred.resolve, milliseconds);\n  return deferred.promise;\n}\n\nfunction* f(){\n  yield delay(100);\n};\n```\n\n- 作为数据结构\n\n``` javascript\nfunction *doStuff() {\n  yield fs.readFile.bind(null, 'hello.txt');\n  yield fs.readFile.bind(null, 'world.txt');\n  yield fs.readFile.bind(null, 'and-such.txt');\n}\n\nfor (task of doStuff()) {\n  // task是一个函数,可以像回调函数那样使用它\n}\n\n```\n\n## Promise\n### Promise概述\n- 对象的状态不受外界影响\n`Promise`对象代表一个异步操作,有三种状态,`Pending(待决)`,`Resolved(成功)`和 `Rejected(失败)`.只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变.\n\n- 一旦状态改变,就不会再变,任何时候都可以得到这个结果\n\n`Promise`对象的状态改变,只有两种可能：从`Pending`变为`Resolved`和从`Pending`变为`Rejected`,只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果\n\n\n`Promise`也有一些缺点.首先,无法取消`Promise`,一旦新建它就会立即执行,无法中途取消.其次,如果不设置回调函数,`Promise`内部抛出的错误,不会反应到外部.第三,当处于`Pending`状态时,无法得知目前进展到哪一个阶段（刚刚开始还是即将完成）.\n如果某些事件不断地反复发生,一般来说,使用`stream`模式是比部署`Promise`更好的选择\n\n### 用法\n\n\n```javascript\nlet promise = new Promise((resolve,reject) =\u003e {\n\n    //异步操作\n    \n    // f(function(){\n    //  if(err){\n    //      reject(error);\n    //  } \n    //  resolve(value);\n    // })\n    \n    //操作成功\n    resolve(value); //status:Pending -\u003e Resolved\n\n    //操作失败\n    reject(error);  //status:Pending -\u003e Rejected    \n\n});\n\n//Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数.\npromise.then(value =\u003e {\n    //success\n},error =\u003e {\n    //failed\n})\n\n```\n\n\u003e提示: 在`Promise`对象中的`resolve`和`reject`会将参数(这里是`value`和`error`)传递出去,而`then`中的成功和失败方法都会接受相应的参数\n\n\n简单的例子\n\n```javascript\nfunction timeOut(ms) {\n    return new Promise((resolve,reject) =\u003e {\n        setTimeout(resolve,ms,'done');\n    });\n}\n\ntimeOut(5000).then((value) =\u003e {\n    console.log(value);     //done\n});\n```\n新建Promise实例的时候会立即执行\n\n```javascript\nlet promise = new Promise((resolve,reject) =\u003e {\n    console.log('new promise running!');\n    resolve('resolve running!');\n});\n\npromise.then((value) =\u003e {\n    console.log(value);\n})\n\nconsole.log('global running!');\n\n\n\n//new promise running!\n//global running!\n//resolve running!\n```\n\najax实例\n\n```javascript\nvar getJSON = function(url) {\n  var promise = new Promise(function(resolve, reject){\n    var client = new XMLHttpRequest();\n    client.open(\"GET\", url);\n    client.onreadystatechange = handler;\n    client.responseType = \"json\";\n    client.setRequestHeader(\"Accept\", \"application/json\");\n    client.send();\n\n    function handler() {\n      if (this.readyState !== 4) {\n        return;\n      }\n      if (this.status === 200) {\n        resolve(this.response);\n      } else {\n        reject(new Error(this.statusText));\n      }\n    };\n  });\n\n  return promise;\n};\n\ngetJSON(\"/posts.json\").then(function(json) {\n  console.log('Contents: ' + json);\n}, function(error) {\n  console.error('出错了', error);\n});\n```\n\n\n`resolve`函数的参数除了正常的值以外,还可能是另一个`Promise实例`\n\n`p2`的`resolve`方法将`p1`作为参数,即一个异步的操作结果返回另一个异步操作,\n**这时`p1`的状态就会传递给`p2`,`p1`的状态决定了`p2`的状态**,如果`p1`状态是`Pending`,那么`p2`的回调函数就会等待`p1`的状态改变,如果`p1`的状态已经是`Resolved`或`Rejected`,那么`p2`的回调函数就会立刻执行\n\n\n此时p2会等待p1的状态执行完毕在执行\n\n``` javascript\nlet p1 = new Promise((resolve,reject) =\u003e {\n    setTimeout(() =\u003e resolve('p1 resolve!'),1000);\n});\n\nlet p2 = new Promise((resolve,reject) =\u003e {\n    setTimeout(() =\u003e resolve(p1), 500);\n});\n\np2.then(value =\u003e console.log(value));   //p1 resolve\n```\n\n\n此时p2立即执行,因为p1的状态先执行完毕\n\n```javascript\nlet p1 = new Promise((resolve,reject) =\u003e {\n    setTimeout(() =\u003e resolve('p1 resolve!'),500);\n});\n\nlet p2 = new Promise((resolve,reject) =\u003e {\n    setTimeout(() =\u003e resolve(p1), 1000);\n});\n\np2.then(value =\u003e console.log(value));   //p1 resolve\n\n```\n\n`p1`的状态是`Rejected`,所以`p2`执行后也是触发`Rejected`状态对应的回调函数\n\n```javascript\nlet p1 = new Promise((resolve,reject) =\u003e {\n    setTimeout(() =\u003e reject('p1 reject!'),500);\n});\n\nlet p2 = new Promise((resolve,reject) =\u003e {\n    setTimeout(() =\u003e resolve(p1), 1000);    //resolve接受一个Promise实例作为参数\n});\n\np2.then(value =\u003e console.log('resolve ' + value),   \n        error =\u003e console.log('reject ' + error));   //reject p1 reject\n```\n\n### Promise.prototype.then\n\n为`Promise`实例添加状态改变时对应的回调函数,第一个参数`Resolve`状态对应的回调函数,第二个参数是`Rejected`状态对应的回调函数,也可以进行链式调用,比如`ajax`请求时,连续需要发送好几个`ajax`,但是后一个`ajax`需要在前一个`ajax`请求成功时才触发,此时非常适用!\n\n采用链式`then`,前一个异步操作在执行回调函数时,如果返回的还是一个`Promise`对象(另外一个异步操作),那么后一个回调函数就会等待该`Promise`对象的状态的改变从而触发相应的回调函数.\n\n\n``` javascript\nlet p = new Promise((resolve,reject) =\u003e {\n    resolve('resolve data!');\n});\n\np.then((data) =\u003e {\n    console.log(data);  //resolve data\n    return p;\n}).then((data) =\u003e {\n    console.log(data);  //resolve data\n    return p;\n}).then((data) =\u003e {\n    console.log(data);  //resolve data;\n});\n```\n\u003e提示: 通常两个异步操作如果同时执行,它们的回调函数的触发时间的先后是不一定的,要看异步操作的执行状态时间,但是采用链式的形式可以指定一组按照顺序执行的异步操作\n\n\n### Promise.prototype.catch\n\n如果异步操作抛出错误,状态就会变为`Rejected`,就会调用`catch`方法指定的回调函数,处理这个错误\n\n``` javascript\nlet p = new Promise((resolve,reject) =\u003e {\n    throw new Error('promise Error!');\n});\n\n//方法一\np.then((val) =\u003e console.log('resolve:'))\n .catch((err) =\u003e console.log('err:' + err));\n//err:Error: promise Error!\n\n//方法二,等同于方法一\np.then((val) =\u003e console.log('resolve:'))\n .then(null,(err) =\u003e console.log('err:' + err));\n//err:Error: promise Error!\n```\n\n`Promise`一旦状态发生变化时不能被改变的\n\n```javascript\nlet promise = new Promise((resolve,reject) =\u003e {\n    resolve('resolve data!');\n    reject('reject data!');         //等于没有设置\n    throw new Error('error data!'); //等于没有抛出\n});\n\n\npromise.then((value) =\u003e console.log(value),\n             (error) =\u003e console.log(error))\n       .catch(err =\u003e console.log(err));\n\n//resolve data!\n```\n\n\n尽管返回的仍然是`promise`,只要任何一个出错,都会被最后的`catch`捕获\n\n``` javascript\nlet promise = new Promise((resolve,reject) =\u003e {\n    throw new Error('error data!');\n});\n\n\npromise.then(() =\u003e promise)\n       .then(() =\u003e promise)\n       .catch(err =\u003e console.log(err)); //Error: error data!\n```\n\n一般来说,不要在`then`方法里面定义`Rejected`状态的回调函数（即`then`的第二个参数）,总是使用`catch`方法\n\n\n``` javascript\nlet promise = new Promise((resolve,reject) =\u003e {\n    reject('reject data!');\n});\n\n//bad\npromise.then((data) =\u003e console.log(data),\n             (err)  =\u003e console.log(err));   //reject data!\n\n//good \npromise.then((data) =\u003e console.log(data))\n       .catch((err) =\u003e console.log(err));   //reject data!\n```\n\u003e提示:使用`catch`不仅能够捕获状态`Rejected`,还能捕获异常错误.因此建议使用`catch`而不是使用`then`方法的第二个参数\n\n\n`Nodejs`有一个`unhandledRejection`事件,专门监听未捕获的`reject`错误.\n\n```javascript\nprocess.on('unhandledRejection', function (err, p) {\n  console.error(err.stack)\n});\n```\n\u003e提示: `p`是报错的`Promise`实例\n\n\n`catch`方法返回的还是一个`Promise`对象\n\n``` javascript\nlet promise = new Promise((resolve,reject) =\u003e {\n    reject(x+4);\n});\n\n\npromise.then((value) =\u003e console.log('resolve1:' + value))\n       .catch((err)  =\u003e console.log('err1 ' + err))     //err1 ReferenceError: x is not defined\n       .then((value) =\u003e console.log('resolve2'))        //resolve2\n       // .catch((err)  =\u003e console.log('err2 ' + x))\n       // .catch((err)  =\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziyi2%2Fes6","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fziyi2%2Fes6","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziyi2%2Fes6/lists"}