ES6学习笔记

11.15 星期三
学习地址:ECMAScript 6 入门  http://es6.ruanyifeng.com/ 阮一峰
下载node js,Node JS环境搭建及sublime Text 3配置Node Js环境,添加前端插件。
一、es6简介:
    二者关系:ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 Jscript 和 ActionScript)。日常场合,这两个词是可以互换的。
    ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。

二、letconst命令:

    let1、不存在提升变量:所声明的变量一定要在声明后使用,否则报错。
    2、暂时性死区:在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
                   只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。在let声明变量a前,对变量a赋值会报错
                   如果一个变量根本没有被声明,使用typeof反而不会报错。
                   例如:typeof x; let x; //报错
                         typeof y;//undefined
                         let a=a;//报错
                         var b=b;//不报错
                    常见的死区:function a(x=y,y=2){};a();//y还没声明,x不能使用y;
    3、不允许重复声明:
    4、块级作用域:ES5 只有全局作用域和函数作用域。let实际上为 JavaScript 新增了块级作用域,只要let相同名字的变量不在同一层作用域就可以
    5、块级作用域与函数声明:
                            ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
                            ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。
                            ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
              (1)允许在块级作用域内声明函数。
              (2)函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
              (3)同时,函数声明还会提升到所在的块级作用域的头部。
              注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。
    6do表达式:在块级作用域之前加上do,使它变为do表达式,然后就会返回内部最后执行的表达式的值。
               例如:{let t = f(); t = t * t + 1;} //没有返回值,无法得到t的值;
                     let x = do { let t = f(); t * t + 1;}; //变量x会得到整个块级作用域的返回值(t * t + 1)。

    var1、存在提升变量:var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefinedconst1、声明一个只读的常量。一旦声明,必须立即初始化,常量的值就不能改变。
    2、不存在提升变量
    3、只在所声明的作用域里有效
    4、存在暂时性死区
    5、不可重复声明
    本质:const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。
          但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,
          保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。
          (可以给对象加属性,给数组加元素,但是不能重新指向新的地址)
    将对象冻结,应该使用Object.freeze方法。const foo = Object.freeze({});//不能给该对象添加属性,否则报错;

    声明方法:
            ES5 只有两种声明变量的方法:var命令和function命令。
            ES6 有6种声明方法:varfunctionletconstimportclass

    顶层对象:在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。
              var命令和function命令声明的全局变量,依旧是顶层对象的属性;
              另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。

    global对象:
               1、浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window2、浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
               3、Node 里面,顶层对象是global,但其他环境都不支持。

    this:
    全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
    函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
    不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content SecurityPolicy,内容安全政策),那么eval、new Function这些方法都可能无法使用。

1116 星期四:

三、变量的解构赋值:
1、基本用法:
形如let [a, b, c] = [1, 2, 3];//从数组中提取值,按照对应位置,对变量赋值。
如果对应不上,就默认为undefined;两边都要是数组的形式。否则解构失败
2、解构允许使用默认值:例如:let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
                             如果一个数组成员不严格等于undefined,默认值是不会生效的。
                       例如:let [x = 1] = [null]; x // null 如果一个数组成员是null,默认值就不会生效,因为null不严格等于undefined3、对象的解构赋值:解构不仅可以用于数组,还可以用于对象。
                       例如:let { foo, bar } = { foo: "aaa", bar: "bbb" };
                             foo // "aaa"
                             bar // "bbb"
                    数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
4、对象的解构也可以指定默认值。
5、字符串的解构赋值:
6、字符串的解构赋值:解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。
                     由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错。
7、函数参数的解构赋值:例如:function add([x, y]){ return x + y;};   add([1, 2]); // 3
8、以下三种解构赋值不得使用圆括号:(1)变量声明语句
                                   (2)函数参数
                                   (3)赋值语句的模式
9、可以使用圆括号的情况:赋值语句的非模式部分,可以使用圆括号。
                         例如:[(b)] = [3]; // 正确
                               ({ p: (d) } = {}); // 正确
                               [(parseInt.prop)] = [3]; // 正确
10、用途:(1)交换变量的值:let x = 1;let y = 2;[x, y] = [y, x];
          (2)从函数返回多个值:function example() {return [1, 2, 3];} ;   let [a, b, c] = example();
          (3)函数参数的定义:function f([x, y, z]) { ... };f([1, 2, 3]);
          (4)提取 JSON 数据:let jsonData = {id: 42,status: "OK",data: [867, 5309]}; let { id, status, data: number } = jsonData;
          (5)函数参数的默认值
          (6)遍历 Map 结构:const map = new Map(); map.set('first', 'hello'); map.set('second', 'world');
                              for (let [key, value] of map) { console.log(key + " is " + value);}
                              // first is hello   // second is world
          (7)输入模块的指定方法


11.17 星期五
四、字符串的扩展:
1、字符的 Unicode 表示法:JavaScript 允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。
           只要将码点放入大括号,就能正确解读该字符。"\u{20BB7}"// "��"
           以下有6种方法表示:
           (1)'\z' === 'z'  // true
           (2)'\172' === 'z' // true
           (3)'\x7A' === 'z' // true
           (4)'\u007A' === 'z' // true
           (5)'\u{7A}' === 'z' // true
           (6)"\u{41}\u{42}\u{43}"// "ABC"
2、codePointAt():能够正确处理 4 个字节储存的字符,返回一个字符的码点。
3、String.fromCodePoint():用于从码点返回对应字符
4、字符串的遍历器接口:字符串可以被for...of循环遍历。
5、at():可以识别 Unicode 编号大于0xFFFF的字符,返回正确的字符。
6、normalize():用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。
7、includes(), startsWith(), endsWith():传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。
                                       includes():返回布尔值,表示是否找到了参数字符串。
                                       startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
                                       endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
8、repeat(n):返回一个新字符串,表示将原字符串重复n次;参数是负数或者Infinity,会报错。参数NaN等同于 09、padStart(),padEnd():原字符串的长度,等于或大于指定的最小长度,则返回原字符串。
                        省略第二个参数,默认使用空格补全长度。
                        方法(长度,‘补全的内容’)
                        padStart()用于头部补全,'x'.padStart(5, 'ab') // 'ababx' 一共5个字符,x前面以ab补全
                        padEnd()用于尾部补全。'x'.padEnd(5, 'ab') // 'xabab' 一共5个字符,x后面以ab补全
10、模板字符串:用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
          例如:原来:
                      'There are ' + basket.count + ' ' +
                      'items in your basket, ' +
                      '' + basket.onSale +
                      ' are on sale!'
                现在:
                      `
                        There are ${basket.count} items
                         in your basket, ${basket.onSale}
                        are on sale!
                      `
          模板字符串中嵌入变量,需要将变量名写在${变量名|函数名()}之中。
          模板使用<%...%>放置 JavaScript 代码,使用<%= ... %>输出 JavaScript 表达式。
11、实例:模板编译
12、标签模板:模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。
              这被称为“标签模板”功能(tagged template)。
              标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。
                            alert`123`
                            // 等同于
                            alert(123)
              例如:
              let a = 5;
              let b = 10;
              tag`Hello ${ a + b } world ${ a * b }`;
              // 等同于
              tag(['Hello ', ' world ', ''], 15, 50);
              上面代码中,模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值。
              tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。
13String.raw():String.raw方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。
                  String.raw({ raw: 'test' }, 0, 1, 2);
                  // 't0e1s2t'

                  // 等同于
                  String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);
14、模板字符串的限制:标签模板里面,可以内嵌其他语言。但是,模板字符串默认会将字符串转义,导致无法嵌入其他语言。

五、正则的扩展:
1RegExp 构造函数:
         在 ES5 中,RegExp构造函数的参数有两种情况:
                  第一种情况是,参数是字符串,这时第二个参数表示正则表达式的修饰符(flag)。
                  var regex = new RegExp('xyz', 'i');
                  第二种情况是,参数是一个正则表示式,这时会返回一个原有正则表达式的拷贝。
                  var regex = new RegExp(/xyz/i);
                  // 它们都等价于
                  var regex = /xyz/i;
           在ES6中:
                   如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。
                   而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
                   new RegExp(/abc/ig, 'i').flags   // 在es5中报错
                   new RegExp(/abc/ig, 'i').flags   //原有正则对象的修饰符是ig,它会被第二个参数i覆盖。
2、字符串的正则方法:
                    String.prototype.match 调用 RegExp.prototype[Symbol.match]
                    String.prototype.replace 调用 RegExp.prototype[Symbol.replace]
                    String.prototype.search 调用 RegExp.prototype[Symbol.search]
                    String.prototype.split 调用 RegExp.prototype[Symbol.split]
3、u 修饰符:
            ES6 对正则表达式添加了u修饰符,含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。
            (1)点字符:点(.)字符在正则表达式中,含义是除了换行符以外的任意单个字符。对于码点大于0xFFFF的 Unicode 字符,点字符不能识别,必须加上u修饰符。
            (2)Unicode 字符表示法:ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上u修饰符,才能识别当中的大括号,否则会被解读为量词。
            (3)量词:使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的 Unicode 字符。
            (4)预定义模式:u修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的 Unicode 字符。
            (5)i 修饰符:有些 Unicode 字符的编码不同,但是字型很相近,比如,\u004B与\u212A都是大写的K。
4、y 修饰符:与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始,,g修饰符只要剩余位置中匹配就可,二y修饰符确保匹配必须从剩余的第一个位置开始,这就是“粘连”的涵义。
5、sticky 属性:与y修饰符匹配,ES6的正则对象多了sticky属性,表示是否设置了y修饰符;
6、flags 属性:返回正则表达式的修饰符
7、s 修饰符:dotAll 模式
8、后行断言
9、Unicode 属性类
10具名组匹配

六、数值的扩展:
1、二进制和八进制表示法:分别用前缀0b和0o表示;
2Number.isFinite():用来检查一个数值是否非无穷
3Number.NaN()检查一个值是否为NaN
4、es6将parseInt(),和parseFloat()一直到Number对象,行为保持不变;
5Number.isInteger()来判断一个值是否为整数。
6Number.EPSILON:是一个可以接受的误差范围;
7、安全整数和Number.isSafeInteger():判断一个整数是否落在这个范围之内;

Math对象的扩展:
1Math。trunc():去除一个数的小数部分;
2Math.sign()判断一个数到底是正数、负数、还是零;参数为正数,返回+1;参数为负数,返回-1;参数为0,返回0;参数为-0返回-0;其他值,返回NaN3Math.cbrt()技术一个数的立方根
4Math.clz32()整数使用32位二进制形式表示,返回一个32位无符号整数形式有多少个前导05Math.imul(参数1,参数2)返回两个数一32位带符号整数形式相乘的结果,返回也是一个32位的带符号整数;
6Math.fround()返回一个数的单精度浮点数形式;
7Math.hypot()返回所有参数的平方和的平方根;
es6新增了4个对数相关方法:
1Math.expm1()返回ex-1,即Math.exp(x)-1
2Math.log1p()返回1+x的自然对数,即Math.log(1+x),如果x小于-1,返回NaN3Math.log10()返回以10为底的x的对数,如果x小于0,则返回NaN4Math.log2()返回以2为底数的x的对数,如果x小于0,则返回NaN;
es新增6个三角函数方法。
Math.sinh\cosh\tanh\asinh\acosh\atanh(x):返回x的双曲正弦\双曲余弦\双曲正弦\反双曲正弦\反双曲余弦\反双曲正切;


11.21 星期二
七、函数的扩展:
1、函数参数的默认值:
        ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
        ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
        参数变量是默认声明的,所以不能用letconst再次声明。
        使用参数默认值时,函数不能有同名参数。
        参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

        1、与解构赋值默认值结合使用
        参数默认值可以与解构赋值的默认值,结合起来使用。
        2、参数默认值的位置
        通常情况下,定义了默认值的参数,应该是函数的尾参数。
        当参数对应undefined,结果触发了默认值,参数等于null,就没有触发默认值。
        3、函数的 length 属性
        指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
        如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。例如:(function (a = 0, b, c) {}).length // 0
2、rest 参数:
        ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。
        rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
        1arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。
        2、rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。
        注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
        函数的length属性,不包括 rest 参数。
3、严格模式:
        从 ES5 开始,函数内部可以设定为严格模式。
        ES2016 做了一点修改,规定只要函数参数使用了/默认值/、/解构赋值/、或者/扩展运算符/,那么函数内部就不能显式设定为严格模式,否则会报错。
        第一种是设定全局性的严格模式,这是合法的。
        'use strict';
        function doSomething(a, b = a) {
          // code
        }
        第二种是把函数包在一个无参数的立即执行函数里面。
        const doSomething = (function () {
          'use strict';
          return function(value = 42) {
            return value;
          };
        }());
4、name 属性:
        函数的name属性,返回该函数的函数名。例如:function foo() {} ;foo.name // "foo"
        1、将一个匿名函数赋值给一个变量,ES5 的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。
        var f = function () {};
          // ES5
          f.name // ""
          // ES6
          f.name // "f"
        2、将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
        Function构造函数返回的函数实例,name属性的值为anonymous。
        const bar = function baz() {};
          // ES5
          bar.name // "baz"
          // ES6
          bar.name // "baz"
        3Function构造函数返回的函数实例,name属性的值为anonymous。
          (new Function).name // "anonymous"
        4、bind返回的函数,name属性值会加上bound前缀。
          function foo() {};
          foo.bind({}).name // "bound foo"
          (function(){}).bind({}).name // "bound "
5、箭头函数:var 函数名=(参数s)=>返回值
        var f = v => v;等同于 var f = function(v) { return v;};
        1、如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
        2、如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
        var sum = (num1, num2) => { return num1 + num2; }
        3、所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
        箭头函数有几个使用注意点。

        (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

        (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

        (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

        (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
        箭头函数里面根本没有自己的this,而是引用外层的this6、双冒号运算符:箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代callapplybind调用。
        函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。
        例如:foo::bar;// 等同于bar.bind(foo);
        1、如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
        2、双冒号运算符的运算结果,还是一个对象,因此可以采用链式写法。
7、尾调用优化:某个函数的最后一步是调用另一个函数。
        例如:function f(x){ return g(x);}//一定要有一个return,不加return 就默认为return undefined,就不属于尾调用;
        1、尾调用优化:只保留内层函数的调用帧。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。
        2、尾递归:函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
        3、递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。
           但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
        ES6 中只要使用尾递归,就不会发生栈溢出,相对节省内存。
        4、ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
        这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。严格模式则不可以使用这两个变量,否则报错;
        func.arguments:返回调用时函数的参数。
        func.caller:返回调用当前函数的那个函数。
8、函数参数的尾逗号:
        ES2017 允许函数的最后一个参数有尾逗号(trailing comma)。
        这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。
9catch 语句的参数:
        1、传统:写法是catch语句必须带有参数,用来接收try代码块抛出的错误。
        try {
          //  ···
        } catch (error) {
          //  ···
        }
        2、新的写法允许省略catch后面的参数,而不报错。
        try {
          //  ···
        } catch {
          //  ···
        }


八、数组的扩展:
1Array.from()用于将两类对象转为真正的数组;类似数组的对象,和可遍历的对象;
用法:Array.from(对象名\长度,对每个参数进行处理)
Array.from(【123】,(x)=>x*x);//1、4、9、
2Array.of()将一组值转换为数组;Array.of(3, 11, 8) // [3,11,8]
3Array.prototyp.copyWithin(targer从该位开始替换,start=从第n位开始读取数据,end=到第n为停止读取)
4、find()找出第一个符合条件的数组成员,它的参数是一个回调参数,所有数组成员依次执行回调函数,知道找出第一个返回值为true的成员;
例如:【1234】.find((n)=>n<05、findIndex()返回第一个符合条件的数组成员的位置,若所有成员都不符合条件,则返回-1;
例如:【151015】.findIndex(func(value,index,arr){return value>9;})//2
6、fill(参数,起位置,止位置),将参数填充整个数组;
7、es6提供三个新方法:
                1、entries()返回键对值
                2、keys()返回键名
                3、values()返回键值
                这三个都可以用在for……of里
8、includs()返回布尔值,表示某数是否包含给定的值;
9、数组的空位:数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。
               Array(3) // [, , ,]Array(3)返回一个具有 3 个空位的数组。
              空位不是undefined,一个位置的值等于undefined,依然是有值的。空位是没有任何值,in运算符可以说明这一点。
              0 in [undefined, undefined, undefined] // true 0 号位置是有值的,
              0 in [, , ,] // false 0 号位置没有值。
              ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位:
              1、forEach(), filter(), every() 和some()都会跳过空位。
              2、map()会跳过空位,但会保留这个值
              3、join()和toString()会将空位视为undefined,而undefinednull会被处理成空字符串。

              ES6 则是明确将空位转为undefined1Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
              2、扩展运算符(...)也会将空位转为undefined3、copyWithin()会连空位一起拷贝。
              4、fill()会将空位视为正常的数组位置。
              5for...of循环也会遍历空位。
              6、entries()、keys()、values()、find()和findIndex()会将空位处理成undefined。
              由于空位的处理规则非常不统一,所以建议避免出现空位。

11.22 星期三
九、对象的扩展:
1、属性的简洁表示法:
      ES6 允许直接写入变量和函数,作为对象的属性和方法。
      ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。
      属性的赋值器(setter)和取值器(getter),事实上也是采用这种写法。
2、属性名表达式:
avaScript 定义对象的属性,有两种方法。
        // 方法一 obj.foo = true;用标识符作为属性名
        // 方法二 obj['a' + 'bc'] = 123;用表达式作为属性名,这时要将表达式放在方括号之内。
        表达式还可以用于定义方法名。
        属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串
3、方法的 name 属性:函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。
        1、对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,
        而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。
        const obj = {
          get foo() {},
          set foo(x) {}
        };

        obj.foo.name
        // TypeError: Cannot read property 'name' of undefined

        const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');

        descriptor.get.name // "get foo"
        descriptor.set.name // "set foo"
        2、有两种特殊情况:
              bind方法创造的函数,name属性返回bound加上原函数的名字;
              Function构造函数创造的函数,name属性返回anonymous。
        3、如果对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。
4Object.is():
        1、用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
        Object.is('foo', 'foo')
        // true
        Object.is({}, {})
        // false
        2、不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
        +0 === -0 //true
        NaN === NaN // false
        Object.is(+0, -0) // false
        Object.is(NaN, NaN) // true
5Object.assign():用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
       例如:
        const target = { a: 1 };
        const source1 = { b: 2 };
        const source2 = { c: 3 };
        Object.assign(target, source1, source2);
        target // {a:1, b:2, c:3}
        Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
      注意:
        如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
        如果只有一个参数,Object.assign会直接返回该参数。
        如果该参数不是对象,则会先转成对象,然后返回。
        由于undefinednull无法转成对象,所以如果它们作为参数,就会报错。
        如果undefinednull不在首参数,就不会报错。
        其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。
        Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
        属性名为 Symbol 值的属性,也会被Object.assign拷贝。

        (1)浅拷贝
         Object.assign方法实行的是浅拷贝,而不是深拷贝。
         也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
        (2)同名属性的替换
         对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
        (3)数组的处理
         Object.assign可以用来处理数组,但是会把数组视为对象。Object.assign([1, 2, 3], [4, 5])// [4, 5, 3]4)取值函数的处理
         Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。//就是返回函数的最终结果——值;

         常见用途:
         (1)为对象添加属性
         (2)为对象添加方法
         (3)克隆对象
         (4)合并多个对象
         (5)为属性指定默认值

6、属性的可枚举性和遍历:
        可枚举性
        对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
        描述对象的enumerable属性,称为”可枚举性“,如果该属性为false,就表示某些操作会忽略当前属性
        1、目前,有四个操作会忽略enumerable为false的属性。
          for...in循环:只遍历对象自身的和继承的可枚举的属性。
          Object.keys():返回对象自身的所有可枚举的属性的键名。
          JSON.stringify():只串行化对象自身的可枚举的属性。
          Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
        2、ES6 规定,所有 Class 的原型的方法都是不可枚举的。
        3、操作中引入继承的属性会让问题复杂化,大多数时候,我们只关心对象自身的属性。所以,尽量不要用for...in循环,而用Object.keys()代替。
        4、属性的遍历
              ES6 一共有 5 种方法可以遍历对象的属性。
              (1for...in
              for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
              (2Object.keys(obj)
              Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
              (3Object.getOwnPropertyNames(obj)
              Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
              (4Object.getOwnPropertySymbols(obj)
              Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
              (5)Reflect.ownKeys(obj)
              Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
              以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
              首先遍历所有数值键,按照数值升序排列。
              其次遍历所有字符串键,按照加入时间升序排列。
              最后遍历所有 Symbol 键,按照加入时间升序排列。
7Object.getOwnPropertyDescriptors():返回某个对象属性的描述对象(descriptor)。
        1、ES2017 引入了Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象。
        2Object.getOwnPropertyDescriptors方法的另一个用处,是配合Object.create方法,将对象属性克隆到一个新对象。这属于浅拷贝。
                const clone = Object.create(Object.getPrototypeOf(obj),
                Object.getOwnPropertyDescriptors(obj));

                // 或者

                const shallowClone = (obj) => Object.create(
                  Object.getPrototypeOf(obj),
                  Object.getOwnPropertyDescriptors(obj)
                );
        3Object.getOwnPropertyDescriptors方法可以实现一个对象继承另一个对象。
8、__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
        1、__proto__属性(前后各两个下划线),用来读取或设置当前对象的prototype对象。目前,所有浏览器(包括 IE11)都部署了这个属性。
            // es6 的写法
            const obj = {
              method: function() { ... }
            };
            obj.__proto__ = someOtherObj;

            // es5 的写法
            var obj = Object.create(someOtherObj);
            obj.method = function() { ... };
        2Object.setPrototypeOf方法的作用与__proto__相同,用来设置一个对象的prototype对象,返回参数对象本身。
           它是 ES6 正式推荐的设置原型对象的方法。
           // 格式
          Object.setPrototypeOf(object继承, prototype遗产)

          // 用法
          const o = Object.setPrototypeOf({}, null);
        3Object.getPrototypeOf(obj);读取一个对象的原型对象。
9、super 关键字
        this关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super,指向当前对象的原型对象。
        const proto = {
          foo: 'hello'
        };

        const obj = {
          find() {
            return super.foo;
          }
        };

        Object.setPrototypeOf(obj, proto);
        obj.find() // "hello"
        注意,super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。
        第一种写法是super用在属性里面,第二种和第三种写法是super用在一个函数里面,然后赋值给foo属性。
        目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法。
        const obj = {foo: super.foo}// 报错
        const obj = {foo: () => super.foo}// 报错
        const obj = {foo: function () {return super.foo}}// 报错
        对于 JavaScript 引擎来说,这里的super都没有用在对象的方法之中。
        JavaScript 引擎内部,super.foo等同于Object.getPrototypeOf(this).foo(属性)或Object.getPrototypeOf(this).foo.call(this)(方法)。
10Object.keys(),Object.values(),Object.entries():
        1Object.keys():ES5 引入了Object.keys方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
        例如:var obj = { foo: 'bar', baz: 42 };Object.keys(obj)// ["foo", "baz"]
        ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作为遍历一个对象的补充手段,供for...of循环使用。
        2Object.values:方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
        例如:const obj = { foo: 'bar', baz: 42 };Object.values(obj)// ["bar", 42]
        注意:Object.values会过滤属性名为 Symbol 值的属性。
              如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。
        3Object.entries方法:返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。
        例如:const obj = { foo: 'bar', baz: 42 };Object.entries(obj)// [ ["foo", "bar"], ["baz", 42] ]
        注意:Object.entries的基本用途是遍历对象的属性。
             Object.entries方法的另一个用处是,将对象转为真正的Map结构。
11、对象的扩展运算符:
        扩展运算符(...)。
        const [a, ...b] = [1, 2, 3];
        a // 1
        b // [2, 3]1)解构赋值:对象的解构赋值用于从一个对象取值,相当于将所有可遍历的、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面。
            let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
            x // 1
            y // 2
            z // { a: 3, b: 4 }2)扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
        let z = { a: 3, b: 4 };let n = { ...z }; n // { a: 3, b: 4 }
        1、这等同于使用Object.assign方法。let aClone = { ...a };// 等同于 let aClone = Object.assign({}, a);
        2、扩展运算符可以用于合并两个对象。let ab = { ...a, ...b };// 等同于let ab = Object.assign({}, a, b);
        3、与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式。const obj = {...(x > 1 ? {a: 1} : {}),b: 2,};
        4、如果扩展运算符后面是一个空对象,则没有任何效果。{...{}, a: 1}// { a: 1 }
        5、如果扩展运算符的参数是nullundefined,这两个值会被忽略,不会报错。
        6、扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的
12、Null 传导运算符:
        const firstName = message?.body?.user?.firstName || 'default';
        上面代码有三个?.运算符,只要其中一个返回nullundefined,就不再往下运算,而是返回undefined。
        “Null 传导运算符”有四种用法:
              obj?.prop // 读取对象属性
              obj?.[expr] // 同上
              func?.(...args) // 函数或对象方法的调用
              new C?.(...args) // 构造函数的调用
         例如:
          // 如果 a 是 null 或 undefined, 返回 undefined
          // 否则返回 a.b.c().d
          a?.b.c().d
          // 如果 a 是 null 或 undefined,下面的语句不产生任何效果
          // 否则执行 a.b = 42
          a?.b = 42
          // 如果 a 是 null 或 undefined,下面的语句不产生任何效果
          delete a?.b

12.2 星期四
十、Symbol
1、概述:ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。
         它是 JavaScript 语言的第七种数据类型,前六种是:undefinednull、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
         Symbol保证每个属性的名字都是独一无二的,从根本上防止属性名的冲突

         Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
         注意:Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。
               也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
         Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
         let s1 = Symbol('foo');s1 // Symbol(foo)
         s1.toString() // "Symbol(foo)"
         1、Symbol 值不能与其他类型的值进行运算,会报错。
         2、Symbol 值可以显式转为字符串。
            let sym = Symbol('My symbol');
            String(sym) // 'Symbol(My symbol)'
            sym.toString() // 'Symbol(My symbol)'
         3、Symbol 值也可以转为布尔值,但是不能转为数值。
            Boolean(sym) // true
            !sym  // false
            Number(sym) // TypeError
            sym + 2 // TypeError
2、作为属性名的 Symbol:
        由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。
        这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
        1、Symbol 值作为对象属性名时,不能用点运算符。
            a.mySymbol = 'Hello!';
            a[mySymbol] // undefined
            a['mySymbol'] // "Hello!"
            因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 Symbol 值。
            使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
        2、采用增强的对象写法,上面代码的obj对象可以写得更简洁一些。
            let obj = {
              [s](arg) { ... }
            };
        3、常量使用 Symbol 值最大的好处,就是其他任何值都不可能有相同的值了,因此可以保证上面的switch语句会按设计的方式工作。
           还有一点需要注意,Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。
3、实例:消除魔术字符串:
        魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。
        1、把Triangle写成shapeType对象的triangle属性,这样就消除了强耦合。
        如果仔细分析,可以发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可。
        因此,这里就很适合改用 Symbol 值。
            const shapeType = {
              triangle: Symbol()
            };
4、属性名的遍历:
        1、Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。
        2Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
            Object.getOwnPropertySymbols(对象名);//返回该对象的symbol值
        3for (let i in obj) {
              console.log(i); // 无输出
            }
            Object.getOwnPropertyNames(obj)
            // []
        4、另一个新的 API,Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
            Reflect.ownKeys(对象名)// 返回所有类型的键名,包括常规键名和 Symbol 键名。 ["属性名1", "属性名2", Symbol(symbol名)]
        5Object.keys(x)、Object.getOwnPropertyNames(x)都无法获取它,可以造成了一种非私有的内部方法的效果。
5、Symbol.for(),Symbol.keyFor():
         1、symbol。for()重新使用同一个 Symbol 值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
         2、symbol.for()与symbol()的区别:
                  1、都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
                  2、Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。
                  3、如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。
                  4、Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key。
                      let s1 = Symbol.for("foo");
                      Symbol.keyFor(s1) // "foo"
                      let s2 = Symbol("foo");
                      Symbol.keyFor(s2) // undefined
                      上面代码中,变量s2属于未登记的 Symbol 值,所以返回undefined5、Symbol.for为 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。
6、实例:模块的 Singleton 模式:
         Singleton 模式指的是调用一个类,任何时候返回的都是同一个实例。
         对于 Node 来说,模块文件可以看成是一个类。
7、内置的 Symbol 值:
    1、Symbol.hasInstance
    对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)。
    例如:class MyClass {
      [Symbol.hasInstance](foo) {
        return foo instanceof Array;
      }
    }

    [1, 2, 3] instanceof new MyClass() // true
    上面代码中,MyClass是一个类,new MyClass()会返回一个实例。该实例的Symbol.hasInstance方法,会在进行instanceof运算时自动调用,判断左侧的运算子是否为Array的实例。
    2、Symbol.isConcatSpreadable
    对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。
    例如:let arr1 = ['c', 'd'];
    ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
    arr1[Symbol.isConcatSpreadable] // undefined

    let arr2 = ['c', 'd'];
    arr2[Symbol.isConcatSpreadable] = false;
    ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
    上面代码说明,数组的默认行为是可以展开,Symbol.isConcatSpreadable默认等于undefined。该属性等于true时,也有展开的效果。
    类似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable属性设为true,才可以展开。
    Symbol.isConcatSpreadable属性也可以定义在类里面。
    注意,Symbol.isConcatSpreadable的位置差异
    3、Symbol.species
    对象的Symbol.species属性,指向当前对象的构造函数。创造实例时,默认会调用这个方法,即使用这个属性返回的函数当作构造函数,来创造新的实例对象。
    class MyArray extends Array {
      // 覆盖父类 Array 的构造函数
      static get [Symbol.species]() { return Array; }
    }
    上面代码中,子类MyArray继承了父类Array。创建MyArray的实例对象时,本来会调用它自己的构造函数(本例中被省略了),但是由于定义了Symbol.species属性,所以会使用这个属性返回的的函数,创建MyArray的实例。
    这个例子也说明,定义Symbol.species属性要采用get读取器。默认的Symbol.species属性等同于下面的写法。
      static get [Symbol.species]() {
        return this;
      }
    4、Symbol.match
    对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。
    String.prototype.match(regexp)
    // 等同于
    regexp[Symbol.match](this)

    class MyMatcher {
      [Symbol.match](string) {
        return 'hello world'.indexOf(string);
      }
    }

    'e'.match(new MyMatcher()) // 1
    5、Symbol.replace
    对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
    String.prototype.replace(searchValue, replaceValue)
    // 等同于
    searchValue[Symbol.replace](this, replaceValue)
    例如:
    const x = {};
    x[Symbol.replace] = (...s) => console.log(s);

    'Hello'.replace(x, 'World') // ["Hello", "World"]
    Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,上面例子是Hello,第二个参数是替换后的值,上面例子是World。
    6、Symbol.search
    对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
    String.prototype.search(regexp)
    // 等同于
    regexp[Symbol.search](this)
    6、Symbol.split
    对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。
    String.prototype.split(separator, limit)
    // 等同于
    separator[Symbol.split](this, limit)
    7、Symbol.iterator
    对象的Symbol.iterator属性,指向该对象的默认遍历器方法。
    const myIterable = {};
    myIterable[Symbol.iterator] = function* () {
      yield 1;
      yield 2;
      yield 3;
    };

    [...myIterable] // [1, 2, 3]
    8、Symbol.toPrimitive
    对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。

    Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。

    Number:该场合需要转成数值
    String:该场合需要转成字符串
    Default:该场合可以转成数值,也可以转成字符串
    9、Symbol.toStringTag
    对象的Symbol.toStringTag属性,指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object]或[object Array]中object后面的那个字符串。
    // 例一
    ({[Symbol.toStringTag]: 'Foo'}.toString())
    // "[object Foo]"

    // 例二
    class Collection {
      get [Symbol.toStringTag]() {
        return 'xxx';
      }
    }
    let x = new Collection();
    Object.prototype.toString.call(x) // "[object xxx]"
    10、Symbol.unscopables
    对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。

十一、Set 和 Map 数据结构
1、Set:
    1、基本用法:
    ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,用来生成 Set 数据结构。
    const s = new Set();
    [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
    for (let i of s) {
      console.log(i);
    }
    // 2 3 5 4 代码通过add方法向 Set 结构加入成员,结果表明 Set 结构不会添加重复的值。
    [...new Set(array)] // 去除数组的重复成员
    2、向 Set 加入值的时候,不会发生类型转换,所以5"5"是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value equality”,它类似于精确相等运算符(===),主要的区别是NaN等于自身,而精确相等运算符认为NaN不等于自身。
    3、在 Set 内部,两个NaN是相等。
    4、两个对象总是不相等的。{}和{}不相等
    5、Set 实例的属性和方法
        Set 结构的实例有以下属性。
        Set.prototype.constructor:构造函数,默认就是Set函数。
        Set.prototype.size:返回Set实例的成员总数。
        Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。
        四个操作方法。
        add(value):添加某个值,返回 Set 结构本身。
        delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
        has(value):返回一个布尔值,表示该值是否为Set的成员。
        clear():清除所有成员,没有返回值。
    6Array.from方法可以将 Set 结构转为数组。
    7、遍历操作
        Set 结构的实例有四个遍历方法,可以用于遍历成员。
        keys():返回键名的遍历器
        values():返回键值的遍历器
        entries():返回键值对的遍历器
        forEach():使用回调函数遍历每个成员
        需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
           (1)keys(),values(),entries():返回的都是遍历器对象。由于 Set 结构没有键名,只有键值,所以keys方法和values方法的行为完全一致。
                entries方法返回的遍历器,同时包括键名和键值,所以每次输出一个数组,它的两个成员完全相等。
                Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。可以直接用for...of循环遍历 Set。
                Set.prototype[Symbol.iterator] === Set.prototype.values// true2)forEach()
                Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
                forEach方法还可以有第二个参数,表示绑定处理函数内部的this对象。
           (3)遍历的应用
                扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构。
                let set = new Set(['red', 'green', 'blue']);
                let arr = [...set];
                // ['red', 'green', 'blue']
                扩展运算符和 Set 结构相结合,就可以去除数组的重复成员。

                let arr = [3, 5, 2, 2, 5, 5];
                let unique = [...new Set(arr)];
                // [3, 5, 2]
        使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。
        let a = new Set([1, 2, 3]);
        let b = new Set([4, 3, 2]);

        // 并集
        let union = new Set([...a, ...b]);
        // Set {1, 2, 3, 4}

        // 交集
        let intersect = new Set([...a].filter(x => b.has(x)));
        // set {2, 3}

        // 差集
        let difference = new Set([...a].filter(x => !b.has(x)));
        // Set {1}
2、WeakSet:
      WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
      其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
      1、语法
      WeakSet 是一个构造函数,可以使用new命令,创建 WeakSet 数据结构。
      const ws = new WeakSet();//作为构造函数,WeakSet 可以接受一个数组或类似数组的对象作为参数。数组的成员只能是对象。
      2、WeakSet 结构有以下三个方法。
      WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
      WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
      WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在
      3、WeakSet 没有size属性,没有办法遍历它的成员。
      4、WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏
3、Map:
  1、含义和基本用法
  JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
  它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。
  2、键值对”的数据结构,Map 比 Object 更合适。
  3、任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
  const set = new Set([
    ['foo', 1],
    ['bar', 2]
  ]);
  const m1 = new Map(set);
  m1.get('foo') // 1

  const m2 = new Map([['baz', 3]]);
  const m3 = new Map(m2);
  m3.get('baz') // 3
  上面代码中,我们分别使用 Set 对象和 Map 对象,当作Map构造函数的参数,结果都生成了新的 Map 对象。
  4、对同一个键多次赋值,后面的值将覆盖前面的值。
  5、如果读取一个未知的键,则返回undefined6、注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
  const map = new Map();
  map.set(['a'], 555);
  map.get(['a']) // undefined
  上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get方法无法读取该键,返回undefined7、同理,同样的值的两个实例,在 Map 结构中被视为两个键。
  8、Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
  9、若 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefinednull也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
  10、实例的属性和操作方法
      (1)size 属性size属性返回 Map 结构的成员总数。
      (2)set(key, value)set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
           set方法返回的是当前的Map对象,因此可以采用链式写法。
      (3)get(key)get方法读取key对应的键值,如果找不到key,返回undefined。
      (4)has(key)has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
      (5delete(key)delete方法删除某个键,返回true。如果删除失败,返回false。
      (6)clear()clear方法清除所有成员,没有返回值。
  11、遍历方法
  Map 结构原生提供三个遍历器生成函数和一个遍历方法。
      keys():返回键名的遍历器。
      values():返回键值的遍历器。
      entries():返回所有成员的遍历器。
      forEach():遍历 Map 的所有成员。
      需要特别注意的是,Map 的遍历顺序就是插入顺序。
      map[Symbol.iterator] === map.entries
  12、Map 结构转为数组结构,比较快速的方法是使用扩展运算符(...)。
      const map = new Map([
        [1, 'one'],
        [2, 'two'],
        [3, 'three'],
      ]);
      [...map.keys()]// [1, 2, 3]
      [...map.values()]// ['one', 'two', 'three']
      [...map.entries()]// [[1,'one'], [2, 'two'], [3, 'three']]
      [...map]// [[1,'one'], [2, 'two'], [3, 'three']]
  13、与其他数据结构的互相转换
      (1)Map 转为数组,Map 转为数组最方便的方法,就是使用扩展运算符(...)。
          const myMap = new Map()
            .set(true, 7)
            .set({foo: 3}, ['abc']);
          [...myMap]
          // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]2)数组 转为 Map,将数组传入 Map 构造函数,就可以转为 Map。new Map([ [true, 7],[{foo: 3}, ['abc']] ])
      (3)Map 转为对象,如果所有 Map 的键都是字符串,它可以转为对象。
      (4)对象转为 Map
      (5)Map 转为 JSON,Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。
           另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。
      (6JSON 转为 MapJSON 转为 Map,正常情况下,所有键名都是字符串。
4、WeakMap:WeakMap结构与Map结构类似,也是用于生成键值对的集合。
      1、区别有两点。
         首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
         其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。
      2、只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
      基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。
      WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。
      注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
      3、WeakMap 的语法
      WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操作(即没有key()、values()和entries()方法),也没有size属性。
      二是无法清空,即不支持clear方法。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。
      4、WeakMap 的用途:WeakMap 应用的典型场合就是 DOM 节点作为键名。

12.7:星期四、
十二、Proxy
1、概述:Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
    ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
    var proxy = new Proxy(target, handler);
    new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
    如果handler没有设置任何拦截,那就等同于直接通向原对象。
    Proxy 支持的拦截操作一览,一共 13 种。
        1、get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']。
        2、set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
        3、has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
        4、deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
        5、ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
        6、getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
        7、defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
        8、preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
        9、getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
        10、isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
        11、setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
        12、apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
        13、construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
2、Proxy 实例的方法:
   1、get(目标对象,属性名,实例本身)
      get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(即this关键字指向的那个对象),其中最后一个参数可选。
          var person = {
            name: "张三"
          };
          var proxy = new Proxy(person, {
            get: function(target, property) {
              if (property in target) {
                return target[property];
              } else {
                throw new ReferenceError("Property \"" + property + "\" does not exist.");
              }
            }
          });
          proxy.name // "张三"
          proxy.age // 抛出一个错误
      get方法可以继承。
      get方法的第三个参数receiver,总是为当前的 Proxy 实例。
      如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy 对象访问该属性会报错。
   2、set(目标对象,属性名,属性值, Proxy 实例本身)
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
          let validator = {
            set: function(obj, prop, value) {
              if (prop === 'age') {
                if (!Number.isInteger(value)) {
                  throw new TypeError('The age is not an integer');
                }
                if (value > 200) {
                  throw new RangeError('The age seems invalid');
                }
              }
              // 对于满足条件的 age 属性以及其他属性,直接保存
              obj[prop] = value;
            }
          };
          let person = new Proxy({}, validator);
          person.age = 100;
          person.age // 100
          person.age = 'young' // 报错
          person.age = 300 // 报错
          结合get和set方法,就可以做到防止这些内部属性被外部读写。
    3、apply(目标对象,目标对象的上下文对象(this)和目标对象的参数数组)apply方法拦截函数的调用、call和apply操作。
    apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
        var target = function () { return 'I am the target'; };
          var handler = {
            apply: function () {
              return 'I am the proxy';
            }
          };
          var p = new Proxy(target, handler);
          p()
          // "I am the proxy"
          上面代码中,变量p是 Proxy 的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。
     4、has()has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。
          虽然for...in循环也用到了in运算符,但是has拦截对for...in循环不生效。
          let stu1 = {name: '张三', score: 59};
          let stu2 = {name: '李四', score: 99};
          let handler = {
            has(target, prop) {
              if (prop === 'score' && target[prop] < 60) {
                console.log(`${target.name} 不及格`);
                return false;
              }
              return prop in target;
            }
          }

          let oproxy1 = new Proxy(stu1, handler);
          let oproxy2 = new Proxy(stu2, handler);

          'score' in oproxy1
          // 张三 不及格
          // false

          'score' in oproxy2
          // true

          for (let a in oproxy1) {
            console.log(oproxy1[a]);
          }
          // 张三
          // 59

          for (let b in oproxy2) {
            console.log(oproxy2[b]);
          }
          // 李四
          // 99
          上面代码中,has拦截只对in运算符生效,对for...in循环不生效,导致不符合要求的属性没有被排除在for...in循环之外。
     5、construct(目标对象、构建函数的参数对象)construct方法用于拦截new命令,下面是拦截对象的写法。
          var handler = {
            construct (target, args, newTarget) {
              return new target(...args);
            }
          };
          例如:
          var p = new Proxy(function () {}, {
            construct: function(target, args) {
              console.log('called: ' + args.join(', '));
              return { value: args[0] * 10 };// construct方法返回的必须是一个对象,否则会报错。
            }
          });

          (new p(1)).value
          // "called: 1"
          // 10
      6、deleteProperty()
          deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。
          var handler = {
            deleteProperty (target, key) {
              invariant(key, 'delete');
              return true;
            }
          };
          function invariant (key, action) {
            if (key[0] === '_') {
              throw new Error(`Invalid attempt to ${action} private "${key}" property`);
            }
          }
          var target = { _prop: 'foo' };
          var proxy = new Proxy(target, handler);
          delete proxy._prop
          // Error: Invalid attempt to delete private "_prop" property
          上面代码中,deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错。
          注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。
      7、defineProperty()defineProperty方法拦截了Object.defineProperty操作。
          var handler = {
            defineProperty (target, key, descriptor) {
              return false;
            }
          };
          var target = {};
          var proxy = new Proxy(target, handler);
          proxy.foo = 'bar'
          // TypeError: proxy defineProperty handler returned false for property '"foo"'
          上面代码中,defineProperty方法返回false,导致添加新属性会抛出错误。

          注意,如果目标对象不可扩展(extensible),则defineProperty不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则defineProperty方法不得改变这两个设置。
      8、getOwnPropertyDescriptor()getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefinedvar handler = {
              getOwnPropertyDescriptor (target, key) {
                if (key[0] === '_') {
                  return;
                }
                return Object.getOwnPropertyDescriptor(target, key);
              }
            };
            var target = { _foo: 'bar', baz: 'tar' };
            var proxy = new Proxy(target, handler);
            Object.getOwnPropertyDescriptor(proxy, 'wat')
            // undefined
            Object.getOwnPropertyDescriptor(proxy, '_foo')
            // undefined
            Object.getOwnPropertyDescriptor(proxy, 'baz')
            // { value: 'tar', writable: true, enumerable: true, configurable: true }
            上面代码中,handler.getOwnPropertyDescriptor方法对于第一个字符为下划线的属性名会返回undefined9、getPrototypeOf()
          getPrototypeOf方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。
          Object.prototype.__proto__
          Object.prototype.isPrototypeOf()
          Object.getPrototypeOf()
          Reflect.getPrototypeOf()
          instanceof
          下面是一个例子。
          var proto = {};
          var p = new Proxy({}, {
            getPrototypeOf(target) {
              return proto;
            }
          });
          Object.getPrototypeOf(p) === proto // true
          上面代码中,getPrototypeOf方法拦截Object.getPrototypeOf(),返回proto对象。
          注意,getPrototypeOf方法的返回值必须是对象或者null,否则报错。另外,如果目标对象不可扩展(extensible), getPrototypeOf方法必须返回目标对象的原型对象。
      10、isExtensible()
          isExtensible方法拦截Object.isExtensible操作。
          var p = new Proxy({}, {
            isExtensible: function(target) {
              console.log("called");
              return true;
            }
          });
          Object.isExtensible(p)
          // "called"
          // true
          上面代码设置了isExtensible方法,在调用Object.isExtensible时会输出called。
          注意,该方法只能返回布尔值,否则返回值会被自动转为布尔值。
      11、ownKeys()ownKeys方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。
              Object.getOwnPropertyNames()
              Object.getOwnPropertySymbols()
              Object.keys()
          下面是拦截Object.keys()的例子。
          let target = {
            a: 1,
            b: 2,
            c: 3
          };

          let handler = {
            ownKeys(target) {
              return ['a'];
            }
          };
          let proxy = new Proxy(target, handler);
          Object.keys(proxy)
          // [ 'a' ]
          上面代码拦截了对于target对象的Object.keys()操作,只返回a、b、c三个属性之中的a属性。
       12、preventExtensions()
            preventExtensions方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。
            这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错。
            var p = new Proxy({}, {
              preventExtensions: function(target) {
                return true;
              }
            });
            Object.preventExtensions(p) // 报错
            上面代码中,proxy.preventExtensions方法返回true,但这时Object.isExtensible(proxy)会返回true,因此报错。
            为了防止出现这个问题,通常要在proxy.preventExtensions方法里面,调用一次Object.preventExtensions。
            var p = new Proxy({}, {
              preventExtensions: function(target) {
                console.log('called');
                Object.preventExtensions(target);
                return true;
              }
            });
           Object.preventExtensions(p)
            // "called"
            // true
        13、setPrototypeOf()setPrototypeOf方法主要用来拦截Object.setPrototypeOf方法。
            下面是一个例子。
            var handler = {
              setPrototypeOf (target, proto) {
                throw new Error('Changing the prototype is forbidden');
              }
            };
            var proto = {};
            var target = function () {};
            var proxy = new Proxy(target, handler);
            Object.setPrototypeOf(proxy, proto);
            // Error: Changing the prototype is forbidden
            上面代码中,只要修改target的原型对象,就会报错。
            注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(extensible),setPrototypeOf方法不得改变目标对象的原型。
3、Proxy.revocable():
    Proxy.revocable方法返回一个可取消的 Proxy 实例。
    let target = {};
    let handler = {};
    let {proxy, revoke} = Proxy.revocable(target, handler);
    proxy.foo = 123;
    proxy.foo // 123
    revoke();
    proxy.foo // TypeError: Revoked
    Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
    Proxy.revocable的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
4this 问题:
       虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。
       主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。
              const target = {
                m: function () {
                  console.log(this === proxy);
                }
              };
              const handler = {};
              const proxy = new Proxy(target, handler);
              target.m() // false
              proxy.m()  // true
              上面代码中,一旦proxy代理target.m,后者内部的this就是指向proxy,而不是target。
5、实例:Web 服务的客户端

1211星期一、
十三、reflect
1、概述:
    Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。

    (1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

    (2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回falseObject.defineProperty(target, property, attributes);
      // success
    } catch (e) {
      // failure
    }// 老写法

    if (Reflect.defineProperty(target, property, attributes)) {
      // success
    } else {
      // failure
    } // 新写法3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
    'assign' in Object // true // 老写法
    Reflect.has(Object, 'assign') // true// 新写法4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
    Proxy(target, {
  set: function(target, name, value, receiver) {
    var success = Reflect.set(target,name, value, receiver);
    if (success) {
      log('property ' + name + ' on ' + target + ' set to ' + value);
    }
    return success;
  }
    });
    上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。
    有了Reflect对象以后,很多操作会更易读。
    Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1  // 老写法
    Reflect.apply(Math.floor, undefined, [1.75]) // 1 // 新写法
    2、静态方法:
    Reflect对象一共有 13 个静态方法。
      1、Reflect.apply(target, thisArg, args):
          Reflect.apply方法等同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数。
          一般来说,如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args),采用Reflect对象可以简化这种操作。
          const ages = [11, 33, 12, 54, 18, 96];

          // 旧写法
          const youngest = Math.min.apply(Math, ages);
          const oldest = Math.max.apply(Math, ages);
          const type = Object.prototype.toString.call(youngest);

          // 新写法
          const youngest = Reflect.apply(Math.min, Math, ages);
          const oldest = Reflect.apply(Math.max, Math, ages);
          const type = Reflect.apply(Object.prototype.toString, youngest, []);
      2、Reflect.construct(target, args)
          Reflect.construct方法等同于new target(...args),这提供了一种不使用new,来调用构造函数的方法。
          function Greeting(name) {
            this.name = name;
          }
          const instance = new Greeting('张三');// new 的写法
          const instance = Reflect.construct(Greeting, ['张三']);// Reflect.construct 的写法
      3、Reflect.get(target, name, receiver)
           Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefinedvar myObject = {
            foo: 1,
            bar: 2,
            get baz() {
              return this.foo + this.bar;
            },
          }

          Reflect.get(myObject, 'foo') // 1
          Reflect.get(myObject, 'bar') // 2
          Reflect.get(myObject, 'baz') // 3

          var myReceiverObject = {
            foo: 4,
            bar: 4,
          };

          Reflect.get(myObject, 'baz', myReceiverObject) // 8如果name属性部署了读取函数(getter),则读取函数的this绑定receiver。
          如果第一个参数不是对象,Reflect.get方法会报错。
      4、Reflect.set(target, name, value, receiver)
      Reflect.set方法设置target对象的name属性等于value。
          var myObject = {
            foo: 1,
            set bar(value) {
              return this.foo = value;
            },
          }
          myObject.foo // 1
          Reflect.set(myObject, 'foo', 2);
          myObject.foo // 2
          Reflect.set(myObject, 'bar', 3)
          myObject.foo // 3
          如果第一个参数不是对象,Reflect.set会报错。
          如果name属性设置了赋值函数,则赋值函数的this绑定receiver。
      5、Reflect.defineProperty(target, name, desc)
      Reflect.defineProperty方法基本等同于Object.defineProperty,用来为对象定义属性。未来,后者会被逐渐废除,请从现在开始就使用Reflect.defineProperty代替它。
            function MyDate() {
              /*…*/
            }

            // 旧写法
            Object.defineProperty(MyDate, 'now', {
              value: () => Date.now()
            });

            // 新写法
            Reflect.defineProperty(MyDate, 'now', {
              value: () => Date.now()
            });
            如果Reflect.defineProperty的第一个参数不是对象,就会抛出错误,比如Reflect.defineProperty(1, 'foo')。
      6、Reflect.deleteProperty(target, name):
          Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性。
          const myObj = { foo: 'bar' };
          delete myObj.foo;// 旧写法
          Reflect.deleteProperty(myObj, 'foo');// 新写法
          该方法返回一个布尔值。如果删除成功,或者被删除的属性不存在,返回true;删除失败,被删除的属性依然存在,返回false7、Reflect.has(target, name)
      Reflect.has方法对应name in obj里面的in运算符。
          var myObject = {
            foo: 1,
          };
          'foo' in myObject // true  // 旧写法
          Reflect.has(myObject, 'foo') // true// 新写法
          如果第一个参数不是对象,Reflect.has和in运算符都会报错。
      8、Reflect.ownKeys(target):
      Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和。
            var myObject = {
              foo: 1,
              bar: 2,
              [Symbol.for('baz')]: 3,
              [Symbol.for('bing')]: 4,
            };

            // 旧写法
            Object.getOwnPropertyNames(myObject)
            // ['foo', 'bar']

            Object.getOwnPropertySymbols(myObject)
            //[Symbol(baz), Symbol(bing)]

            // 新写法
            Reflect.ownKeys(myObject)
            // ['foo', 'bar', Symbol(baz), Symbol(bing)]
      9、Reflect.isExtensible(target):
          Reflect.isExtensible方法对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展。
          const myObject = {};
          Object.isExtensible(myObject) // true// 旧写法
          Reflect.isExtensible(myObject) // true// 新写法
          如果参数不是对象,Object.isExtensible会返回false,因为非对象本来就是不可扩展的,而Reflect.isExtensible会报错。
      10、Reflect.preventExtensions(target)
      Reflect.preventExtensions对应Object.preventExtensions方法,用于让一个对象变为不可扩展。它返回一个布尔值,表示是否操作成功。
          var myObject = {};
          Object.preventExtensions(myObject) // Object {}// 旧写法
          Reflect.preventExtensions(myObject) // true// 新写法
          如果参数不是对象,Object.preventExtensions在 ES5 环境报错,在 ES6 环境返回传入的参数,而Reflect.preventExtensions会报错。
      11、Reflect.getOwnPropertyDescriptor(target, name)
      Reflect.getOwnPropertyDescriptor基本等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象,将来会替代掉后者。
          var myObject = {};
          Object.defineProperty(myObject, 'hidden', {
            value: true,
            enumerable: false,
          });
          var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');// 旧写法
          var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden'); // 新写法
          Reflect.getOwnPropertyDescriptor和Object.getOwnPropertyDescriptor的一个区别是,如果第一个参数不是对象,Object.getOwnPropertyDescriptor(1, 'foo')不报错,返回undefined,而Reflect.getOwnPropertyDescriptor(1, 'foo')会抛出错误,表示参数非法。
      12、Reflect.getPrototypeOf(target):
            Reflect.getPrototypeOf(obj)
            Reflect.getPrototypeOf方法用于读取对象的__proto__属性,对应Object.getPrototypeOf(obj)。
            const myObj = new FancyThing();
            Object.getPrototypeOf(myObj) === FancyThing.prototype;// 旧写法
            Reflect.getPrototypeOf(myObj) === FancyThing.prototype;// 新写法
            Reflect.getPrototypeOf和Object.getPrototypeOf的一个区别是,如果参数不是对象,Object.getPrototypeOf会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf会报错。
            Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
            Reflect.getPrototypeOf(1) // 报错
      13、Reflect.setPrototypeOf(target, prototype)
      Reflect.setPrototypeOf方法用于设置对象的__proto__属性,返回第一个参数对象,对应Object.setPrototypeOf(obj, newProto)。
          const myObj = new FancyThing();
          Object.setPrototypeOf(myObj, OtherThing.prototype);// 旧写法
          Reflect.setPrototypeOf(myObj, OtherThing.prototype);// 新写法
          如果第一个参数不是对象,Object.setPrototypeOf会返回第一个参数本身,而Reflect.setPrototypeOf会报错。
          如果第一个参数是undefinednullObject.setPrototypeOf和Reflect.setPrototypeOf都会报错。

      上面这些方法的作用,大部分与Object对象的同名方法的作用都是相同的,而且它与Proxy对象的方法是一一对应的。
3、实例:使用 Proxy 实现观察者模式

12.12 星期二
十四、Promise 对象
1、Promise 的含义
    Promise 是异步编程的一种解决方案,所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

    Promise对象有以下两个特点。
    (1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

    (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

    有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
    Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
    如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise更好的选择。
2、基本用法:
    下面代码创造了一个Promise实例。
    const promise = new Promise(function(resolve, reject) {
      // ... some code

      if (/* 异步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
    resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
    Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
    promise.then(function(value) {
      // success
    }, function(error) {
      // failure
    });
    then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
    面是一个用Promise对象实现的 Ajax 操作的例子。
    const getJSON = function(url) {
      const promise = new Promise(function(resolve, reject){
        const handler = function() {
          if (this.readyState !== 4) {
            return;
          }
          if (this.status === 200) {
            resolve(this.response);
          } else {
            reject(new Error(this.statusText));
          }
        };
        const client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = handler;
        client.responseType = "json";
        client.setRequestHeader("Accept", "application/json");
        client.send();

      });

      return promise;
    };

    getJSON("/posts.json").then(function(json) {
      console.log('Contents: ' + json);
    }, function(error) {
      console.error('出错了', error);
    });
    上面代码中,getJSON是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在getJSON内部,resolve函数和reject函数调用时,都带有参数。
    如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,
    一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

    new Promise((resolve, reject) => {
      return resolve(1);
      // 后面的语句不会执行
      console.log(2);
    })
3、Promise.prototype.then():
    Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

    then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

    getJSON("/posts.json").then(function(json) {
      return json.post;
    }).then(function(post) {
      // ...
    });
    上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。
4、Promise.prototype.catch()
    Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

    getJSON('/posts.json').then(function(posts) {
      // ...
    }).catch(function(error) {
      // 处理 getJSON 和 前一个回调函数运行时发生的错误
      console.log('发生错误!', error);
    });
    上面代码中,getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
    // 写法一
    const promise = new Promise(function(resolve, reject) {
      try {
        throw new Error('test');
      } catch(e) {
        reject(e);
      }
    });
    promise.catch(function(error) {
      console.log(error);
    });

    // 写法二
    const promise = new Promise(function(resolve, reject) {
      reject(new Error('test'));
    });
    promise.catch(function(error) {
      console.log(error);
    });
    比较上面两种写法,可以发现reject方法的作用,等同于抛出错误。
    如果 Promise 状态已经变成resolved,再抛出错误是无效的。
    Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。

    Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
5、Promise.all():
    Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

    const p = Promise.all([p1, p2, p3]);
    上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise实例
    p的状态由p1、p2、p3决定,分成两种情况。
    (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
    (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
6、Promise.race():
    Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
    const p = Promise.race([p1, p2, p3]);
    上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
    Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
    如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve。

    const p = Promise.race([
      fetch('/resource-that-may-take-a-while'),
      new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
      })
    ]);
    p.then(response => console.log(response));
    p.catch(error => console.log(error));
    上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。
7、Promise.resolve():
      有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
      const jsPromise = Promise.resolve($.ajax('/whatever.json'));
      上面代码将 jQuery 生成的deferred对象,转为一个新的 Promise 对象。
      Promise.resolve等价于下面的写法。
      Promise.resolve('foo')// 等价于 new Promise(resolve => resolve('foo'))
      Promise.resolve方法的参数分成四种情况。
      (1)参数是一个 Promise 实例:如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
      (2)参数是一个thenable对象:thenable对象指的是具有then方法的对象,比如下面这个对象。
          let thenable = {
            then: function(resolve, reject) {
              resolve(42);
            }
          };
          Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
      (3)参数不是具有then方法的对象,或根本就不是对象:
           如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
            const p = Promise.resolve('Hello');
            p.then(function (s){
              console.log(s)
            });
            // Hello4)不带有任何参数: Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
            所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法。
            const p = Promise.resolve();

            p.then(function () {
              // ...
            });
            上面代码的变量p就是一个 Promise 对象。
            需要注意的是,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
8、Promise.reject():Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
      const p = Promise.reject('出错了');// 等同于 const p = new Promise((resolve, reject) => reject('出错了'))
      p.then(null, function (s) {
        console.log(s)
      });
      // 出错了
      上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。
      注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。
9、两个有用的附加方法:
      1、done()
      Promise 对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise 内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
      asyncFunc()
        .then(f1)
        .catch(r1)
        .then(f2)
        .done();
      它的实现代码相当简单。

      Promise.prototype.done = function (onFulfilled, onRejected) {
        this.then(onFulfilled, onRejected)
          .catch(function (reason) {
            // 抛出一个全局错误
            setTimeout(() => { throw reason }, 0);
          });
      };
      从上面代码可见,done方法的使用,可以像then方法那样用,提供fulfilled和rejected状态的回调函数,也可以不提供任何参数。但不管怎样,done都会捕捉到任何可能出现的错误,并向全局抛出。
      2finally():finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
      它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
      下面是一个例子,服务器使用 Promise 处理请求,然后使用finally方法关掉服务器。
      server.listen(0)
        .then(function () {
          // run test
        })
        .finally(server.stop);
      它的实现也很简单。
      Promise.prototype.finally = function (callback) {
        let P = this.constructor;
        return this.then(
          value  => P.resolve(callback()).then(() => value),
          reason => P.resolve(callback()).then(() => { throw reason })
        );
      };
      上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。
10、应用
加载图片:
    我们可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。
    const preloadImage = function (path) {
      return new Promise(function (resolve, reject) {
        const image = new Image();
        image.onload  = resolve;
        image.onerror = reject;
        image.src = path;
      });
    };
11、Promise.try():
      实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。
      因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。
      Promise.resolve().then(f)
      上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。

      const f = () => console.log('now');
      Promise.resolve().then(f);
      console.log('next');
      // next
      // now
      上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。

      那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的,并且还有两种写法。
      第一种写法是用async函数来写。
      const f = () => console.log('now');
      (async () => f())();
      console.log('next');
      // now
      // next
      上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。

      (async () => f())()
      .then(...)
      需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。

      (async () => f())()
      .then(...)
      .catch(...)
      第二种写法是使用new Promise()。

      const f = () => console.log('now');
      (
        () => new Promise(
          resolve => resolve(f())
        )
      )();
      console.log('next');
      // now
      // next
      上面代码也是使用立即执行的匿名函数,执行new Promise()。这种情况下,同步函数也是同步执行的。

      鉴于这是一个很常见的需求,所以现在有一个提案,提供Promise.try方法替代上面的写法。

      const f = () => console.log('now');
      Promise.try(f);
      console.log('next');
      // now
      // next
      事实上,Promise.try存在已久,Promise 库Bluebird、Q和when,早就提供了这个方法。

      由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。

      function getUsername(userId) {
        return database.users.get({id: userId})
        .then(function(user) {
          return user.name;
        });
      }
      上面代码中,database.users.get()返回一个 Promise 对象,如果抛出异步错误,可以用catch方法捕获,就像下面这样写。

      database.users.get({id: userId})
      .then(...)
      .catch(...)
      但是database.users.get()可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try...catch去捕获。

      try {
        database.users.get({id: userId})
        .then(...)
        .catch(...)
      } catch (e) {
        // ...
      }
      上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。

      Promise.try(database.users.get({id: userId}))
        .then(...)
        .catch(...)
      事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。

十五、Iterator 和 for...of 循环
1、Iterator(遍历器)的概念:
    遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
    Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;
                           二是使得数据结构的成员能够按某种次序排列;
                           三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
    Iterator 的遍历过程是这样的。
            (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
            (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
            (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
            (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
    每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
        var it = makeIterator(['a', 'b']);
        it.next() // { value: "a", done: false }
        it.next() // { value: "b", done: false }
        it.next() // { value: undefined, done: true }
        function makeIterator(array) {
          var nextIndex = 0;
          return {
            next: function() {
              return nextIndex < array.length ?
                {value: array[nextIndex++], done: false} :
                {value: undefined, done: true};
            }
          };
        }
2、默认 Iterator 接口
     Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(详见下文)。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
      一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是”可遍历的“(iterable)。
      ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内(参见 Symbol 一章)。
      原生具备 Iterator 接口的数据结构如下。、Array、MapSet、String、TypedArray、函数的 arguments 对象、NodeList 对象
      对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。
      一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。
      class RangeIterator {
        constructor(start, stop) {
          this.value = start;
          this.stop = stop;
        }

        [Symbol.iterator]() { return this; }

        next() {
          var value = this.value;
          if (value < this.stop) {
            this.value++;
            return {done: false, value: value};
          }
          return {done: true, value: undefined};
        }
      }
      function range(start, stop) {
        return new RangeIterator(start, stop);
      }

      for (var value of range(0, 3)) {
        console.log(value); // 0, 1, 2
      }
      上面代码是一个类部署 Iterator 接口的写法。Symbol.iterator属性对应一个函数,执行后返回当前对象的遍历器对象。
      注意,普通对象部署数组的Symbol.iterator方法,并无效果。
      如果Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。
3、调用 Iterator 接口的场合:
    有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法),除了下文会介绍的for...of循环,还有几个别的场合。
    (1)解构赋值:对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
        let set = new Set().add('a').add('b').add('c');
        let [x,y] = set;// x='a'; y='b'
        let [first, ...rest] = set;// first='a'; rest=['b','c'];2)扩展运算符:扩展运算符(...)也会调用默认的 Iterator 接口。
          // 例一
          var str = 'hello';
          [...str] //  ['h','e','l','l','o']

          // 例二
          let arr = ['b', 'c'];
          ['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']
          上面代码的扩展运算符内部就调用 Iterator 接口。
          实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。
          let arr = [...iterable];
    (3yield*:yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
          let generator = function* () {
            yield 1;
            yield* [2,3,4];
            yield 5;
          };
          var iterator = generator();
          iterator.next() // { value: 1, done: false }
          iterator.next() // { value: 2, done: false }
          iterator.next() // { value: 3, done: false }
          iterator.next() // { value: 4, done: false }
          iterator.next() // { value: 5, done: false }
          iterator.next() // { value: undefined, done: true }4)其他场合;由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
        for...of
        Array.from()
        Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
        Promise.all()
        Promise.race()
4、字符串的 Iterator 接口:字符串是一个类似数组的对象,也原生具有 Iterator 接口。
      var someString = "hi";
      typeof someString[Symbol.iterator]// "function"
      var iterator = someString[Symbol.iterator]();
      iterator.next()  // { value: "h", done: false }
      iterator.next()  // { value: "i", done: false }
      iterator.next()  // { value: undefined, done: true }
      上面代码中,调用Symbol.iterator方法返回一个遍历器对象,在这个遍历器上可以调用 next 方法,实现对于字符串的遍历。
      可以覆盖原生的Symbol.iterator方法,达到修改遍历器行为的目的。
5、Iterator 接口与 Generator 函数
6、遍历器对象的 return(),throw():
    遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。
    return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句或continue语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。
    function readLinesSync(file) {
      return {
        next() {
          return { done: false };
        },
        return() {
          file.close();
          return { done: true };
        },
      };
    }
    上面代码中,函数readLinesSync接受一个文件对象作为参数,返回一个遍历器对象,其中除了next方法,还部署了return方法。下面的三种情况,都会触发执行return方法。
    // 情况一
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      break;
    }
    // 情况二
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      continue;
    }
    // 情况三
    for (let line of readLinesSync(fileName)) {
      console.log(line);
      throw new Error();
    }
    上面代码中,情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;情况二输出所有行以后,执行return方法,关闭该文件;情况三会在执行return方法关闭文件之后,再抛出错误。
    注意,return方法必须返回一个对象,这是 Generator 规格决定的。
7for...of 循环
一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。
也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。
for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
    1、数组原生具备iterator接口(即默认部署了Symbol.iterator属性),for...of循环本质上就是调用这个接口产生的遍历器,
    for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样。
    2、Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for...of循环。
    3、计算生成的数据结构
    有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。
        entries() 返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值;
                  对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。
        keys() 返回一个遍历器对象,用来遍历所有的键名。
        values() 返回一个遍历器对象,用来遍历所有的键值。
    这三个方法调用后生成的遍历器对象,所遍历的都是计算生成的数据结构。
    4、类似数组的对象:类似数组的对象包括好几类。下面是for...of循环用于字符串、DOM NodeList 对象、arguments对象的例子。
    5、对象:对于普通的对象,for...of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。但是,这样情况下,for...in循环依然可以用来遍历键名。
    对于普通的对象,for...in循环可以遍历键名,for...of循环会报错。
    一种解决方法是,使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。
与其他遍历语法的比较:
      1、以数组为例,JavaScript 提供多种遍历语法。最原始的写法就是for循环。
          for (var index = 0; index < myArray.length; index++) {
            console.log(myArray[index]);
          }
          这种写法比较麻烦,因此数组提供内置的forEach方法。
          myArray.forEach(function (value) {
            console.log(value);
          });
          这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效。

      2for...in循环可以遍历数组的键名。
          for (var index in myArray) {
            console.log(myArray[index]);
          }
          for...in循环有几个缺点。
          数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
          for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
          某些情况下,for...in循环会以任意顺序遍历键名。
          总之,for...in循环主要是为遍历对象而设计的,不适用于遍历数组。
      3for...of循环相比上面几种做法,有一些显著的优点。
          for (let value of myArray) {
            console.log(value);
          }
          有着同for...in一样的简洁语法,但是没有for...in那些缺点。
          不同于forEach方法,它可以与breakcontinuereturn配合使用。
          提供了遍历所有数据结构的统一操作接口。
          下面是一个使用 break 语句,跳出for...of循环的例子。
          for (var n of fibonacci) {
            if (n > 1000)
              break;
            console.log(n);
          }
          上面的例子,会输出斐波纳契数列小于等于 1000 的项。如果当前项大于 1000,就会使用break语句跳出for...of循环。

十六、Generator 函数的语法
1、简介:
  1.1Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
      Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
      执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
      形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
      Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
      调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
      4种写法:function * foo(x, y) { ··· }、function *foo(x, y) { ··· }、function* foo(x, y) { ··· }、function*foo(x, y) { ··· }
  1.2yield 表达式:
      由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。
      yield表达式就是暂停标志。
      1.2.1遍历器对象的next方法的运行逻辑如下:
        (1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
        (2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
        (3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
        (4)如果该函数没有return语句,则返回的对象的value属性值为undefinedyield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
        yield表达式如果用在另一个表达式之中,必须放在圆括号里面。
        yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
  1.3、与 Iterator 接口的关系:任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
        由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
        var myIterable = {};
        myIterable[Symbol.iterator] = function* () {
          yield 1;
          yield 2;
          yield 3;
        };

        [...myIterable] // [1, 2, 3]
        上面代码中,Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了 Iterator 接口,可以被...运算符遍历了
        Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。
        function* gen(){
          // some code
        }

        var g = gen();

        g[Symbol.iterator]() === g
        // true
        上面代码中,gen是一个 Generator 函数,调用它会生成一个遍历器对象g。它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己。
2、next 方法的参数:yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
    例子1function* f() {
      for(var i = 0; true; i++) {
        var reset = yield i;
        if(reset) { i = -1; }
      }
    }

    var g = f();

    g.next() // { value: 0, done: false }
    g.next() // { value: 1, done: false }
    g.next(true) // { value: 0, done: false }
    上面代码先定义了一个可以无限运行的 Generator 函数f,如果next方法没有参数,每次运行到yield表达式,变量reset的值总是undefined。当next方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。
    例子2function* foo(x) {
      var y = 2 * (yield (x + 1));
      var z = yield (y / 3);
      return (x + y + z);
    }

    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{value:NaN, done:true}

    var b = foo(5);
    b.next() // { value:6, done:false }
    b.next(12) // { value:8, done:false }
    b.next(13) // { value:42, done:true }
    上面代码中,第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN。

    如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于423for...of 循环:
      for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。
      function *foo() {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
        yield 5;//一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象
        return 6;}//return语句返回的6,不包括在for...of循环之中。
      for (let v of foo()) { console.log(v);}// 1 2 3 4 5使用for...of语句时不需要使用next方法。
      原生的 JavaScript 对象没有遍历接口,无法使用for...of循环,通过 Generator 函数为它加上这个接口,就可以用了。
      除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。
        function* numbers () {
          yield 1
          yield 2
          return 3
          yield 4
        }

        [...numbers()] // [1, 2]// 扩展运算符
        Array.from(numbers()) // [1, 2]// Array.from 方法
        let [x, y] = numbers();// 解构赋值
        x // 1
        y // 2

        for (let n of numbers()) {
          console.log(n)
        }// for...of 循环
        // 1
        // 2
4、Generator.prototype.throw():Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

var i = g();
i.next();
//i.throw(new Error('出错了!'));// Error: 出错了!(…)throw方法可以接受一个参数,该参数会被catch语句接收,建议抛出Error对象的实例。
try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
上面代码中,遍历器对象i连续抛出两个错误。第一个错误被 Generator 函数体内的catch语句捕获。i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
5、Generator.prototype.return():Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。
    function* gen() {
      yield 1;
      yield 2;
      yield 3;
    }

    var g = gen();

    g.next()        // { value: 1, done: false }
    g.return('foo') // { value: "foo", done: true }
    g.next()        // { value: undefined, done: true }
    g.return() //undefined如果return方法调用时,不提供参数,则返回值的value属性为undefined。
    上面代码中,遍历器对象g调用return方法后,返回值的value属性就是return方法的参数foo。并且,Generator 函数的遍历就终止了,返回值的done属性为true,以后再调用next方法,done属性总是返回true。
    如果 Generator 函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。
6、next()、throw()、return() 的共同点:next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 
函数恢复执行,并且使用不同的语句替换yield表达式。
    1、next()是将yield表达式替换成一个值。
    const g = function* (x, y) {
      let result = yield x + y;
      return result;
    };
    const gen = g(1, 2);
    gen.next(); // Object {value: 3, done: false}
    gen.next(1); // Object {value: 1, done: true}
    // 相当于将 let result = yield x + y
    // 替换成 let result = 1;
    上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数,就相当于替换成undefined2throw()是将yield表达式替换成一个throw语句。
    gen.throw(new Error('出错了')); // Uncaught Error: 出错了
    // 相当于将 let result = yield x + y
    // 替换成 let result = throw(new Error('出错了'));
    3return()是将yield表达式替换成一个return语句。
    gen.return(2); // Object {value: 2, done: true}
    // 相当于将 let result = yield x + y
    // 替换成 let result = return 2;
7yield* 表达式:
    如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
    function* bar() {
      yield 'x';
      foo();//这是一个generator函数,直接调用没有效果
      yield 'y';
    }
    for (let v of bar()){
      console.log(v);
    }// "x"// "y"
    我们可以这样写:
    function* bar() {
      yield 'x';
      yield* foo();//前面加一个yield* generator函数名,如果是yield foo()返回的是对象
      yield 'y';
    }
    或者:
    function* bar() {
      yield 'x';
      for (let v of foo()) {//遍历generator函数
        yield v;
      }
      yield 'y';
    }
    如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*表达式。
    任何数据结构只要有 Iterator 接口,就可以被yield*遍历。
    let read = (function* () {
      yield 'hello';
      yield* 'hello';
    })();
    read.next().value // "hello"
    read.next().value // "h"
    上面代码中,yield表达式返回整个字符串,yield*语句返回单个字符。因为字符串具有 Iterator 接口,所以被yield*遍历。
8、作为对象属性的 Generator 函数:
    如果一个对象的属性是 Generator 函数,可以简写成下面的形式。
    let obj = {
      * myGeneratorMethod() {//等同于myGeneratorMethod: function* () 
        ···//表示这个属性是一个 Generator 函数。
      }
    };
9、Generator 函数的this:
    生成一个空对象,使用call方法绑定 Generator 函数内部的this。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。
    function* F() {
      this.a = 1;//如果let obj = F();obj.a返回是 undefined
      yield this.b = 2;//直接new F()报错;因为F不是构造函数。
      yield this.c = 3;
    }
    var obj = {};//生成一个空对象,
    var f = F.call(obj);//使用call方法绑定 Generator 函数内部的this。
    f.next();  // Object {value: 2, done: false}//这个对象执行三次next方法(因为F内部有两个yield表达式),完成 F 内部所有代码的运行。
    f.next();  // Object {value: 3, done: false}
    f.next();  // Object {value: undefined, done: true}
    obj.a // 1
    obj.b // 2
    obj.c // 3
    执行的是遍历器对象f,但是生成的对象实例是obj
    或者:
    function* F() {
      this.a = 1;
      yield this.b = 2;
      yield this.c = 3;
    }
    var f = F.call(F.prototype);//将这两个对象统一,将obj换成F.prototype。
    //或者
    再将F改成构造函数,就可以对它执行new命令了。

    function* gen() {
      this.a = 1;
      yield this.b = 2;
      yield this.c = 3;
    }

    function F() {//将F改成构造函数,就可以对它执行new命令
      return gen.call(gen.prototype);
    }

    var f = new F();
    输入下面的代码,同样有效
    f.next();  // Object {value: 2, done: false}
    f.next();  // Object {value: 3, done: false}
    f.next();  // Object {value: undefined, done: true}
    f.a // 1
    f.b // 2
    f.c // 3
    上面代码中,首先是F内部的this对象绑定obj对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次next方法(因为F内部有两个yield表达式),完成 F 内部所有代码的运行。这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例。
10、含义:
    10.1、Generator 与状态机
    Generator 是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。
    var ticking = true;
    var clock = function() {
      if (ticking)
        console.log('Tick!');
      else
        console.log('Tock!');
      ticking = !ticking;
    }
    上面代码的clock函数一共有两种状态(Tick和Tock),每运行一次,就改变一次状态。这个函数如果用 Generator 实现,就是下面这样。

    var clock = function* () {
      while (true) {
        console.log('Tick!');
        yield;
        console.log('Tock!');
        yield;
      }
    };
11、应用:Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景。
      (1)异步操作的同步化表达
      Generator 函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数。
      Ajax 是典型的异步操作,通过 Generator 函数部署 Ajax 操作,可以用同步的方式表达。
      function* main() {
        var result = yield request("http://some.url");
        var resp = JSON.parse(result);
          console.log(resp.value);
      }

      function request(url) {
        makeAjaxCall(url, function(response){
          it.next(response);
        });
      }

      var it = main();
      it.next();
      上面代码的main函数,就是通过 Ajax 操作获取数据。可以看到,除了多了一个yield,它几乎与同步操作的写法完全一样。注意,makeAjaxCall函数中的next方法,必须加上response参数,因为yield表达式,本身是没有值的,总是等于undefined。
      (2)控制流管理
      如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。

      step1(function (value1) {
        step2(value1, function(value2) {
          step3(value2, function(value3) {
            step4(value3, function(value4) {
              // Do something with value4
            });
          });
        });
      });
      采用 Promise 改写上面的代码。

      Promise.resolve(step1)
        .then(step2)
        .then(step3)
        .then(step4)
        .then(function (value4) {
          // Do something with value4
        }, function (error) {
          // Handle any error from step1 through step4
        })
        .done();
      上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量 Promise 的语法。Generator 函数可以进一步改善代码运行流程。

      function* longRunningTask(value1) {
        try {
          var value2 = yield step1(value1);
          var value3 = yield step2(value2);
          var value4 = yield step3(value3);
          var value5 = yield step4(value4);
          // Do something with value4
        } catch (e) {
          // Handle any error from step1 through step4
        }
      }
      (3)部署 Iterator 接口
      利用 Generator 函数,可以在任意对象上部署 Iterator 接口。

      function* iterEntries(obj) {
        let keys = Object.keys(obj);
        for (let i=0; i < keys.length; i++) {
          let key = keys[i];
          yield [key, obj[key]];
        }
      }

      let myObj = { foo: 3, bar: 7 };

      for (let [key, value] of iterEntries(myObj)) {
        console.log(key, value);
      }

      // foo 3
      // bar 7
      上述代码中,myObj是一个普通对象,通过iterEntries函数,就有了 Iterator 接口。也就是说,可以在任意对象上部署next方法。
      (4)作为数据结构
      Generator 可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为 Generator 函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。

      function *doStuff() {
        yield fs.readFile.bind(null, 'hello.txt');
        yield fs.readFile.bind(null, 'world.txt');
        yield fs.readFile.bind(null, 'and-such.txt');
      }
      上面代码就是依次返回三个函数,但是由于使用了 Generator 函数,导致可以像处理数组那样,处理这三个返回的函数。

12.14星期四:
十六、Generator 函数的异步应用
1、传统方法:
    ES6 诞生以前,异步编程的方法,大概有下面四种。回调函数、事件监听、发布/订阅、Promise 对象
    Generator 函数将 JavaScript 异步编程带入了一个全新的阶段。
2、基本概念:
    2.1、异步
    所谓"异步",简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
    2.2、回调函数
    JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。回调函数的英语名字callback,直译过来就是"重新调用"。
    回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是null):原因是执行分成两段,第一段执行完以后,任务所在的上下文环境就已经结束了。在这以后抛出的错误,原来的上下文环境已经无法捕捉,只能当作参数,传入第二段。
2.3、Promise
回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。假定读取A文件之后,再读取B文件,代码如下。
fs.readFile(fileA, 'utf-8', function (err, data) {
  fs.readFile(fileB, 'utf-8', function (err, data) {
    // ...
  });
});
不难想象,如果依次读取两个以上的文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,无法管理。因为多个异步操作形成了强耦合,只要有一个操作需要修改,它的上层回调函数和下层回调函数,可能都要跟着修改。这种情况就称为"回调函数地狱"(callback hell)。
Promise 对象就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。采用 Promise,连续读取多个文件,写法如下。
var readFile = require('fs-readfile-promise');

readFile(fileA)
.then(function (data) {
  console.log(data.toString());
})
.then(function () {
  return readFile(fileB);
})
.then(function (data) {
  console.log(data.toString());
})
.catch(function (err) {
  console.log(err);
});
上面代码中,我使用了fs-readfile-promise模块,它的作用就是返回一个 Promise 版本的readFile函数。Promise 提供then方法加载回调函数,catch方法捕捉执行过程中抛出的错误。
3、Generator 函数:
    3.1、协程:传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中有一种叫做"协程"(coroutine),意思是多个线程互相协作,完成异步任务。
    协程有点像函数,又有点像线程。它的运行流程大致如下:
        第一步,协程A开始执行。
        第二步,协程A执行到一半,进入暂停,执行权转移到协程B。
        第三步,(一段时间后)协程B交还执行权。
        第四步,协程A恢复执行。
    例如:读取文件的协程写法如下。
      function* asyncJob() {
        // ...其他代码
        var f = yield readFile(fileA);
        // ...其他代码
      }
    函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。
    协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。
    3.2、协程的 Generator 函数实现
    Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
    整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明
    例如:Generator 函数的执行方法如下。
        function* gen(x) {
          var y = yield x + 2;
          return y;
        }
        var g = gen(1);
        g.next() // { value: 3, done: false }
        g.next() // { value: undefined, done: true }
    上面代码中,调用 Generator 函数,会返回一个内部指针(即遍历器)g。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针g的next方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的yield语句,上例是执行到x + 2为止。
    换言之,next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象,表示当前阶段的信息(value属性和done属性)。value属性是yield语句后面表达式的值,表示当前阶段的值;done属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
    3.3、Generator 函数的数据交换和错误处理
    Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。
    next返回值的 value 属性,是 Generator 函数向外输出数据;next方法还可以接受参数,向 Generator 函数体内输入数据。
        function* gen(x){
          var y = yield x + 2;
          return y;
        }
        var g = gen(1);
        g.next() // { value: 3, done: false }
        g.next(2) // { value: 2, done: true }
    上面代码中,第一next方法的value属性,返回表达式x + 2的值3。第二个next方法带有参数2,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量y接收。因此,这一步的value属性,返回的就是2(变量y的值)。
    Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。
        function* gen(x){
          try {
            var y = yield x + 2;
          } catch (e){
            console.log(e);
          }
          return y;
        }

        var g = gen(1);
        g.next();
        g.throw('出错了');//出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。
        // 出错了
3.4、异步任务的封装
下面看看如何使用 Generator 函数,执行一个真实的异步任务。
    var fetch = require('node-fetch');

    function* gen(){
      var url = 'https://api.github.com/users/github';
      var result = yield fetch(url);
      console.log(result.bio);
    }
上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。就像前面说过的,这段代码非常像同步操作,除了加上了yield命令。
执行这段代码的方法如下。
    var g = gen();
    var result = g.next();

    result.value.then(function(data){
      return data.json();
    }).then(function(data){
      g.next(data);
    });
上面代码中,首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个 Promise 对象,因此要用then方法调用下一个next方法。

可以看到,虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。
4、Thunk 函数:
    4.1、Thunk 函数是自动执行 Generator 函数的一种方法。
        f(x + 5) 传值调用时,等同于f(6)
        f(x + 5) 传名调用时,等同于(x + 5) * 2
    4.2、Thunk 函数的含义:编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
        function f(m) {  return m * 2;}
        f(x + 5);
        // 等同于
        var thunk = function () {  return x + 5;};
        function f(thunk) { return thunk() * 2;}
    函数 f 的参数x + 5被一个函数替换了。凡是用到原参数的地方,对Thunk函数求值即可。
    这就是 Thunk 函数的定义,它是“传名调用”的一种实现策略,用来替换某个表达式。
    4.3、JavaScript 语言的 Thunk 函数
        JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数。
        // 正常版本的readFile(多参数版本)
        fs.readFile(fileName, callback);

        // Thunk版本的readFile(单参数版本)
        var Thunk = function (fileName) {
          return function (callback) {
            return fs.readFile(fileName, callback);
          };
        };

        var readFileThunk = Thunk(fileName);
        readFileThunk(callback);
        上面代码中,fs模块的readFile方法是一个多参数函数,两个参数分别为文件名和回调函数。经过转换器处理,它变成了一个单参数函数,只接受回调函数作为参数。这个单参数版本,就叫做 Thunk 函数。
        任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。
        // ES5版本
        var Thunk = function(fn){
          return function (){
            var args = Array.prototype.slice.call(arguments);
            return function (callback){
              args.push(callback);
              return fn.apply(this, args);
            }
          };
        };

        // ES6版本
        const Thunk = function(fn) {
          return function (...args) {
            return function (callback) {
              return fn.call(this, ...args, callback);
            }
          };
        };
        使用上面的转换器,生成fs.readFile的 Thunk 函数。

        var readFileThunk = Thunk(fs.readFile);
        readFileThunk(fileA)(callback);
        下面是另一个完整的例子。

        function f(a, cb) {
          cb(a);
        }
        const ft = Thunk(f);

        ft(1)(console.log) // 1
    4.4、Thunkify 模块
      生产环境的转换器,建议使用 Thunkify 模块。
      首先是安装。$ npm install thunkify
      使用方式如下:
      var thunkify = require('thunkify');
      var fs = require('fs');
      var read = thunkify(fs.readFile);
      read('package.json')(function(err, str){
        // ...
      });
    4.5、Generator 函数的流程管理: ES6 有了 Generator 函数,Thunk 函数现在可以用于 Generator 函数的自动流程管理。
    4.6、Thunk 函数的自动流程管理:Thunk 函数真正的威力,在于可以自动执行 Generator 函数。下面就是一个基于 Thunk 函数的 Generator 执行器。
5、co 模块:
    5.1、基本用法
    co 模块是著名程序员 TJ Holowaychuk 于 20136 月发布的一个小工具,用于 Generator 函数的自动执行。
    下面是一个 Generator 函数,用于依次读取两个文件。
    var gen = function* () {
      var f1 = yield readFile('/etc/fstab');
      var f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    co 模块可以让你不用编写 Generator 函数的执行器。

    var co = require('co');
    co(gen);
    上面代码中,Generator 函数只要传入co函数,就会自动执行。

    co函数返回一个Promise对象,因此可以用then方法添加回调函数。

    co(gen).then(function (){
      console.log('Generator 函数执行完成');
    });
    上面代码中,等到 Generator 函数执行结束,就会输出一行提示。
    5.2、co 模块的原理
    为什么 co 可以自动执行 Generator 函数?前面说过,Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。
    两种方法可以做到这一点。
    (1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
    (2)Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。
    co 模块其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个模块。使用 co 的前提条件是,Generator 函数的yield命令后面,只能是 Thunk 函数或 Promise 对象。如果数组或对象的成员,全部都是 Promise 对象,也可以使用 co,详见后文的例子。
    5.3、co 模块的源码:co 就是上面那个自动执行器的扩展,它的源码只有几十行,非常简单。
    首先,co 函数接受 Generator 函数作为参数,返回一个 Promise 对象。
        function co(gen) {
          var ctx = this;

          return new Promise(function(resolve, reject) {
          });
        }
        需要进行判断
        function co(gen) {
          var ctx = this;

          return new Promise(function(resolve, reject) {
            if (typeof gen === 'function') gen = gen.call(ctx);//在返回的 Promise 对象里面,co 先检查参数gen是否为 Generator 函数。如果是,就执行该函数,得到一个内部指针对象;
            if (!gen || typeof gen.next !== 'function') return resolve(gen);如果不是就返回,并将 Promise 对象的状态改为resolved。
          });
        }
     接着,co 将 Generator 函数的内部指针对象的next方法,包装成onFulfilled函数。这主要是为了能够捕捉抛出的错误。
        function co(gen) {
          var ctx = this;

          return new Promise(function(resolve, reject) {
            if (typeof gen === 'function') gen = gen.call(ctx);
            if (!gen || typeof gen.next !== 'function') return resolve(gen);

            onFulfilled();
            function onFulfilled(res) {
              var ret;
              try {
                ret = gen.next(res);
              } catch (e) {
                return reject(e);
              }
              next(ret);
            }
          });
        }
      最后,就是关键的next函数,它会反复调用自身。

          function next(ret) {
            if (ret.done) return resolve(ret.value);
            var value = toPromise.call(ctx, ret.value);
            if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
            return onRejected(
              new TypeError(
                'You may only yield a function, promise, generator, array, or object, '
                + 'but the following object was passed: "'
                + String(ret.value)
                + '"'
              )
            );
          }
上面代码中,next函数的内部代码,一共只有四行命令。
第一行,检查当前是否为 Generator 函数的最后一步,如果是就返回。
第二行,确保每一步的返回值,是 Promise 对象。
第三行,使用then方法,为返回值加上回调函数,然后通过onFulfilled函数再次调用next函数。
第四行,在参数不符合要求的情况下(参数非 Thunk 函数和 Promise 对象),将 Promise 对象的状态改为rejected,从而终止执行。
      5.4、处理并发的异步操作
      co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。

      这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。

      // 数组的写法
      co(function* () {
        var res = yield [
          Promise.resolve(1),
          Promise.resolve(2)
        ];
        console.log(res);
      }).catch(onerror);

      // 对象的写法
      co(function* () {
        var res = yield {
          1: Promise.resolve(1),
          2: Promise.resolve(2),
        };
        console.log(res);
      }).catch(onerror);
      下面是另一个例子。

      co(function* () {
        var values = [n1, n2, n3];
        yield values.map(somethingAsync);
      });

      function* somethingAsync(x) {
        // do something async
        return y
      }
      上面的代码允许并发三个somethingAsync异步操作,等到它们全部完成,才会进行下一步。
      5.6、实例:处理 Stream
      Node 提供 Stream 模式读写数据,特点是一次只处理数据的一部分,数据分成一块块依次处理,就好像“数据流”一样。这对于处理大规模数据非常有利。Stream 模式使用 EventEmitter API,会释放三个事件:data事件:下一块数据块已经准备好了;end事件:整个“数据流”处理“完了;error事件:发生错误。
      使用Promise.race()函数,可以判断这三个事件之中哪一个最先发生,只有当data事件最先发生时,才进入下一个数据块的处理。从而,我们可以通过一个while循环,完成所有数据的读取。
      const co = require('co');
      const fs = require('fs');

      const stream = fs.createReadStream('./les_miserables.txt');
      let valjeanCount = 0;

      co(function*() {
        while(true) {
          const res = yield Promise.race([
            new Promise(resolve => stream.once('data', resolve)),
            new Promise(resolve => stream.once('end', resolve)),
            new Promise((resolve, reject) => stream.once('error', reject))
          ]);
          if (!res) {
            break;
          }
          stream.removeAllListeners('data');
          stream.removeAllListeners('end');
          stream.removeAllListeners('error');
          valjeanCount += (res.toString().match(/valjean/ig) || []).length;
        }
        console.log('count:', valjeanCount); // count: 1120
      });
      上面代码采用 Stream 模式读取《悲惨世界》的文本文件,对于每个数据块都使用stream.once方法,在data、end、error三个事件上添加一次性回调函数。变量res只有在data事件发生时才有值,然后累加每个数据块之中valjean这个词出现的次数。

12.15星期五
十八、async 函数
1、含义:是 Generator 函数的语法糖。
    1.1、例如:有一个 Generator 函数,依次读取两个文件。
    const fs = require('fs');
    const readFile = function (fileName) {
      return new Promise(function (resolve, reject) {
        fs.readFile(fileName, function(error, data) {
          if (error) return reject(error);
          resolve(data);
        });
      });
    };

    const gen = function* () {
      const f1 = yield readFile('/etc/fstab');
      const f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    写成async函数,就是下面这样。

    const asyncReadFile = async function () {//async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await。
      const f1 = await readFile('/etc/fstab');
      const f2 = await readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };

    1.2、async函数对 Generator 函数的改进,体现在以下四点。
    (1)内置执行器。
    Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
    asyncReadFile();
    上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。
    (2)更好的语义。
    async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
    (3)更广的适用性。
    co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
    (4)返回值是 Promise。
    async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
2、基本用法:async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
    2.1、async 函数有多种使用形式。
    // 函数声明:async function foo() {}
    // 函数表达式:const foo = async function () {};
    // 对象的方法:let obj = { async foo() {} };obj.foo().then(...)
    // Class 的方法:
    class Storage {
      constructor() {
        this.cachePromise = caches.open('avatars');
      }

      async getAvatar(name) {
        const cache = await this.cachePromise;
        return cache.match(`/avatars/${name}.jpg`);
      }
    }

    const storage = new Storage();
    storage.getAvatar('jake').then(…);
    // 箭头函数:const foo = async () => {};
3、语法:async函数的语法规则总体上比较简单,难点是错误处理机制。
    3.1、返回 Promise 对象:async函数返回一个 Promise 对象。
    async函数内部return语句返回的值,会成为then方法回调函数的参数。
    async function f() {
      return 'hello world';
    }
    f().then(v => console.log(v))
    // "hello world"
    上面代码中,函数f内部return命令返回的值,会被then方法回调函数接收到。
    async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
    async function f() {
      throw new Error('出错了');
    }

    f().then(
      v => console.log(v),
      e => console.log(e)
    )
    // Error: 出错了
    3.2、Promise 对象的状态变化
    async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
    例如:下面是一个例子。
    async function getTitle(url) {
      let response = await fetch(url);
      let html = await response.text();
      return html.match(/([\s\S]+)<\/title>/i</span>)[<span class="hljs-number">1</span>];
    }
    getTitle(<span class="hljs-string">'https://tc39.github.io/ecma262/'</span>).then(console.log)
    <span class="hljs-comment">// "ECMAScript 2017 Language Specification"</span>
    上面代码中,函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log。
    <span class="hljs-number">3.3</span>、await 命令:await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">f</span><span class="hljs-params">()</span> {</span>
      <span class="hljs-keyword">return</span> await <span class="hljs-number">123</span>;
    }

    f().then(v => console.log(v))<span class="hljs-comment">// 123,await命令的参数是数值123,它被转成 Promise 对象,并立即resolve。</span>
    await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被<span class="hljs-keyword">catch</span>方法的回调函数接收到。
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">f</span><span class="hljs-params">()</span> {</span>
      await Promise.reject(<span class="hljs-string">'出错了'</span>);
    }

    f()
    .then(v => console.log(v))
    .catch(e => console.log(e))
    <span class="hljs-comment">// 出错了</span>
    注意,上面代码中,await语句前面没有<span class="hljs-keyword">return</span>,但是reject方法的参数依然传入了<span class="hljs-keyword">catch</span>方法的回调函数。这里如果在await前面加上<span class="hljs-keyword">return</span>,效果是一样的。
    只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">f</span><span class="hljs-params">()</span> {</span>
      await Promise.reject(<span class="hljs-string">'出错了'</span>);
      await Promise.resolve(<span class="hljs-string">'hello world'</span>); <span class="hljs-comment">// 不会执行,因为第一个await语句状态变成了reject。</span>
    }

    有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。
    第一种方法:这时可以将第一个await放在<span class="hljs-keyword">try</span>...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">f</span><span class="hljs-params">()</span> {</span>
      <span class="hljs-keyword">try</span> {
        await Promise.reject(<span class="hljs-string">'出错了'</span>);
      } <span class="hljs-keyword">catch</span>(e) {
      }
      <span class="hljs-keyword">return</span> await Promise.resolve(<span class="hljs-string">'hello world'</span>);
    }

    f()
    .then(v => console.log(v))<span class="hljs-comment">// hello world</span>
    第二种方法是await后面的 Promise 对象再跟一个<span class="hljs-keyword">catch</span>方法,处理前面可能出现的错误。
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">f</span><span class="hljs-params">()</span> {</span>
      await Promise.reject(<span class="hljs-string">'出错了'</span>)
        .catch(e => console.log(e));
      <span class="hljs-keyword">return</span> await Promise.resolve(<span class="hljs-string">'hello world'</span>);
    }

    f()
    .then(v => console.log(v))
    <span class="hljs-comment">// 出错了</span>
    <span class="hljs-comment">// hello world</span>
    <span class="hljs-number">3.3</span>、错误处理:如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。
    防止出错的方法,也是将其放在<span class="hljs-keyword">try</span>...catch代码块之中。
    第一种:如果有多个await命令,可以统一放在<span class="hljs-keyword">try</span>...catch结构中。
    第二种:实现多次重复尝试。<span class="hljs-keyword">for</span>(){<span class="hljs-keyword">try</span>{…… <span class="hljs-keyword">break</span>}<span class="hljs-keyword">catch</span>(err){}}<span class="hljs-comment">//如果await操作成功,就会使用break语句退出循环;如果失败,会被catch语句捕捉,然后进入下一轮循环。</span>
    <span class="hljs-number">3.4</span>、使用注意点
       第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在<span class="hljs-keyword">try</span>...catch代码块中。
       async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">myFunction</span><span class="hljs-params">()</span> {</span>
          <span class="hljs-keyword">try</span> {
            await somethingThatReturnsAPromise();
          } <span class="hljs-keyword">catch</span> (err) {
            console.log(err);
          }
        }

        <span class="hljs-comment">// 另一种写法</span>

        async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">myFunction</span><span class="hljs-params">()</span> {</span>
          await somethingThatReturnsAPromise()
          .catch(<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(err)</span> {</span>
            console.log(err);
          });
        }
       第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
          <span class="hljs-comment">// 写法一</span>
          <span class="hljs-keyword">let</span> [foo, bar] = await Promise.all([getFoo(), getBar()]);

          <span class="hljs-comment">// 写法二</span>
          <span class="hljs-keyword">let</span> fooPromise = getFoo();
          <span class="hljs-keyword">let</span> barPromise = getBar();
          <span class="hljs-keyword">let</span> foo = await fooPromise;
          <span class="hljs-keyword">let</span> bar = await barPromise;
       第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。
<span class="hljs-number">4</span>、async 函数的实现原理:就是将 Generator 函数和自动执行器,包装在一个函数里。
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fn</span><span class="hljs-params">(args)</span> {</span> <span class="hljs-comment">// ...} 等同于 function fn(args) { return spawn(function* () {  // ... });}</span>
    所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。
<span class="hljs-number">5</span>、与其他异步处理方法的比较:
    <span class="hljs-number">5.1</span>、首先是 Promise 的写法。
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">chainAnimationsPromise</span><span class="hljs-params">(elem, animations)</span> {</span>
      <span class="hljs-comment">// 变量ret用来保存上一个动画的返回值</span>
      <span class="hljs-keyword">let</span> ret = <span class="hljs-literal">null</span>;
      <span class="hljs-comment">// 新建一个空的Promise</span>
      <span class="hljs-keyword">let</span> p = Promise.resolve();
      <span class="hljs-comment">// 使用then方法,添加所有动画</span>
      <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> anim of animations) {
        p = p.then(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(val)</span> {</span>
          ret = val;
          <span class="hljs-keyword">return</span> anim(elem);
        });
      }
      <span class="hljs-comment">// 返回一个部署了错误捕捉机制的Promise</span>
      <span class="hljs-keyword">return</span> p.catch(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(e)</span> {</span>
        <span class="hljs-comment">/* 忽略错误,继续执行 */</span>
      }).then(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> {</span>
        <span class="hljs-keyword">return</span> ret;
      });
    }
    虽然 Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(then、<span class="hljs-keyword">catch</span>等等),操作本身的语义反而不容易看出来。
    <span class="hljs-number">5.2</span>、接着是 Generator 函数的写法。
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">chainAnimationsGenerator</span><span class="hljs-params">(elem, animations)</span> {</span>
      <span class="hljs-keyword">return</span> spawn(<span class="hljs-function"><span class="hljs-keyword">function</span>*<span class="hljs-params">()</span> {</span>
        <span class="hljs-keyword">let</span> ret = <span class="hljs-literal">null</span>;
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> anim of animations) {
            ret = <span class="hljs-keyword">yield</span> anim(elem);
          }
        } <span class="hljs-keyword">catch</span>(e) {
          <span class="hljs-comment">/* 忽略错误,继续执行 */</span>
        }
        <span class="hljs-keyword">return</span> ret;
      });
    }
    上面代码使用 Generator 函数遍历了每个动画,语义比 Promise 写法更清晰,用户定义的操作全部都出现在spawn函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的spawn函数就是自动执行器,它返回一个 Promise 对象,而且必须保证<span class="hljs-keyword">yield</span>语句后面的表达式,必须返回一个 Promise。
    <span class="hljs-number">5.3</span>、最后是 async 函数的写法。
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">chainAnimationsAsync</span><span class="hljs-params">(elem, animations)</span> {</span>
      <span class="hljs-keyword">let</span> ret = <span class="hljs-literal">null</span>;
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> anim of animations) {
          ret = await anim(elem);
        }
      } <span class="hljs-keyword">catch</span>(e) {
        <span class="hljs-comment">/* 忽略错误,继续执行 */</span>
      }
      <span class="hljs-keyword">return</span> ret;
    }
    可以看到 Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。
<span class="hljs-number">6</span>、实例:按顺序完成异步操作
<span class="hljs-number">7</span>、异步遍历器:Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的next方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。next方法返回的对象的结构是{value, done},其中value表示当前的数据的值,done是一个布尔值,表示遍历是否结束。
    <span class="hljs-number">7.1</span>、异步遍历的接口
    异步遍历器的最大的语法特点,就是调用遍历器的next方法,返回的是一个 Promise 对象。
    asyncIterator
      .next()
      .then(
        ({ value, done }) => <span class="hljs-comment">/* ... */</span>
      );
      上面代码中,asyncIterator是一个异步遍历器,调用next方法以后,返回一个 Promise 对象。因此,可以使用then方法指定,这个 Promise 对象的状态变为resolve以后的回调函数。回调函数的参数,则是一个具有value和done两个属性的对象,这个跟同步遍历器是一样的。
    对象的异步遍历器接口,部署在Symbol.asyncIterator属性上面。不管是什么样的对象,只要它的Symbol.asyncIterator属性有值,就表示应该对它进行异步遍历。
    <span class="hljs-number">7.2</span>、<span class="hljs-keyword">for</span> await...of:前面介绍过,<span class="hljs-keyword">for</span>...of循环用于遍历同步的 Iterator 接口。新引入的<span class="hljs-keyword">for</span> await...of循环,则是用于遍历异步的 Iterator 接口。
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">f</span><span class="hljs-params">()</span> {</span>
      <span class="hljs-keyword">for</span> await (<span class="hljs-keyword">const</span> x of createAsyncIterable([<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>])) {
        console.log(x);
      }
    }<span class="hljs-comment">// a// b</span>
    上面代码中,createAsyncIterable()返回一个异步遍历器,<span class="hljs-keyword">for</span>...of循环自动调用这个遍历器的next方法,会得到一个 Promise 对象。await用来处理这个 Promise 对象,一旦resolve,就把得到的值(x)传入<span class="hljs-keyword">for</span>...of的循环体。<span class="hljs-keyword">for</span> await...of循环的一个用途,是部署了 asyncIterable 操作的异步接口,可以直接放入这个循环。
    <span class="hljs-keyword">let</span> body = <span class="hljs-string">''</span>;
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">f</span><span class="hljs-params">()</span> {</span>
      <span class="hljs-keyword">for</span> await(<span class="hljs-keyword">const</span> data of req) body += data;
      <span class="hljs-keyword">const</span> parsed = <span class="hljs-built_in">JSON</span>.parse(body);
      console.log(<span class="hljs-string">'got'</span>, parsed);
    }
    上面代码中,req是一个 asyncIterable 对象,用来异步读取数据。可以看到,使用<span class="hljs-keyword">for</span> await...of循环以后,代码会非常简洁。
    如果next方法返回的 Promise 对象被reject,<span class="hljs-keyword">for</span> await...of就会报错,要用<span class="hljs-keyword">try</span>...catch捕捉。
    async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> {</span>
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">for</span> await (<span class="hljs-keyword">const</span> x of createRejectingIterable()) {
          console.log(x);
        }
      } <span class="hljs-keyword">catch</span> (e) {
        console.error(e);
      }
    }
    注意,<span class="hljs-keyword">for</span> await...of循环也可以用于同步遍历器。
    (async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> {</span>
      <span class="hljs-keyword">for</span> await (<span class="hljs-keyword">const</span> x of [<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>]) {
        console.log(x);
      }
    })();<span class="hljs-comment">// a// b</span>
    <span class="hljs-number">7.3</span>、异步 Generator 函数:就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。
    在语法上,异步 Generator 函数就是async函数与 Generator 函数的结合。
    async <span class="hljs-function"><span class="hljs-keyword">function</span>* <span class="hljs-title">gen</span><span class="hljs-params">()</span> {</span>
      <span class="hljs-keyword">yield</span> <span class="hljs-string">'hello'</span>;
    }
    <span class="hljs-keyword">const</span> genObj = gen();
    genObj.next().then(x => console.log(x));
    <span class="hljs-comment">// { value: 'hello', done: false }</span>
    上面代码中,gen是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用next方法,返回一个 Promise 对象。
    异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。
    <span class="hljs-comment">// 同步 Generator 函数</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span>* <span class="hljs-title">map</span><span class="hljs-params">(iterable, func)</span> {</span>
      <span class="hljs-keyword">const</span> iter = iterable[Symbol.iterator]();
      <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
        <span class="hljs-keyword">const</span> {value, done} = iter.next();
        <span class="hljs-keyword">if</span> (done) <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">yield</span> func(value);
      }
    }
    <span class="hljs-comment">// 异步 Generator 函数</span>
    async <span class="hljs-function"><span class="hljs-keyword">function</span>* <span class="hljs-title">map</span><span class="hljs-params">(iterable, func)</span> {</span>
      <span class="hljs-keyword">const</span> iter = iterable[Symbol.asyncIterator]();
      <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>) {
        <span class="hljs-keyword">const</span> {value, done} = await iter.next();
        <span class="hljs-keyword">if</span> (done) <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">yield</span> func(value);
      }
    }
    上面代码中,可以看到有了异步遍历器以后,同步 Generator 函数和异步 Generator 函数的写法基本上是一致的。
    定义的异步 Generator 函数的用法如下。
    (async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> {</span>
      <span class="hljs-keyword">for</span> await (<span class="hljs-keyword">const</span> line of readLines(filePath)) {
        console.log(line);
      }
    })()
    异步 Generator 函数可以与<span class="hljs-keyword">for</span> await...of循环结合起来使用。

    async <span class="hljs-function"><span class="hljs-keyword">function</span>* <span class="hljs-title">prefixLines</span><span class="hljs-params">(asyncIterable)</span> {</span>
      <span class="hljs-keyword">for</span> await (<span class="hljs-keyword">const</span> line of asyncIterable) {
        <span class="hljs-keyword">yield</span> <span class="hljs-string">'> '</span> + line;
      }
    }
    异步 Generator 函数的返回值是一个异步 Iterator,即每次调用它的next方法,会返回一个 Promise 对象,也就是说,跟在<span class="hljs-keyword">yield</span>命令后面的,应该是一个 Promise 对象。
    async <span class="hljs-function"><span class="hljs-keyword">function</span>* <span class="hljs-title">asyncGenerator</span><span class="hljs-params">()</span> {</span>
  console.log(<span class="hljs-string">'Start'</span>);
  <span class="hljs-keyword">const</span> result = await doSomethingAsync(); <span class="hljs-comment">// (A)</span>
  <span class="hljs-keyword">yield</span> <span class="hljs-string">'Result: '</span>+ result; <span class="hljs-comment">// (B)</span>
  console.log(<span class="hljs-string">'Done'</span>);
  }

  <span class="hljs-keyword">const</span> ag = asyncGenerator();
  ag.next().then({value, done} => {
    <span class="hljs-comment">// ...</span>
  })
  上面代码中,ag是asyncGenerator函数返回的异步 Iterator 对象。调用ag.next()以后,asyncGenerator函数内部的执行顺序如下。

  打印出Start。
  await命令返回一个 Promise 对象,但是程序不会停在这里,继续往下执行。
  程序在B处暂停执行,<span class="hljs-keyword">yield</span>命令立刻返回一个 Promise 对象,该对象就是ag.next()的返回值。
  A处await命令后面的那个 Promise 对象 resolved,产生的值放入result变量。
  B处的 Promise 对象 resolved,then方法指定的回调函数开始执行,该函数的参数是一个对象,value的值是表达式<span class="hljs-string">'Result: '</span> + result的值,done属性的值是<span class="hljs-literal">false</span>。
  <span class="hljs-number">7.4</span>、<span class="hljs-keyword">yield</span>* 语句
  <span class="hljs-keyword">yield</span>*语句也可以跟一个异步遍历器。
  async <span class="hljs-function"><span class="hljs-keyword">function</span>* <span class="hljs-title">gen1</span><span class="hljs-params">()</span> {</span>
    <span class="hljs-keyword">yield</span> <span class="hljs-string">'a'</span>;
    <span class="hljs-keyword">yield</span> <span class="hljs-string">'b'</span>;
    <span class="hljs-keyword">return</span> <span class="hljs-number">2</span>;
  }

  async <span class="hljs-function"><span class="hljs-keyword">function</span>* <span class="hljs-title">gen2</span><span class="hljs-params">()</span> {</span>
    <span class="hljs-comment">// result 最终会等于 2</span>
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">yield</span>* gen1();
  }
  上面代码中,gen2函数里面的result变量,最后的值是<span class="hljs-number">2</span>。

  与同步 Generator 函数一样,<span class="hljs-keyword">for</span> await...of循环会展开<span class="hljs-keyword">yield</span>*。

  (async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> {</span>
    <span class="hljs-keyword">for</span> await (<span class="hljs-keyword">const</span> x of gen2()) {
      console.log(x);
    }
  })();<span class="hljs-comment">// a// b</span>

<span class="hljs-number">12.18</span> 星期一
十九、Class 的基本语法
<span class="hljs-number">1</span>、简介:
    JavaScript 语言中,生成实例对象的传统方法是通过构造函数
    ES6 的<span class="hljs-keyword">class</span>可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的<span class="hljs-keyword">class</span>写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
    ES6 的类,完全可以看作构造函数的另一种写法。
    <span class="hljs-keyword">class</span> Point { <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">aa</span>(){</span>console.log(<span class="hljs-string">'stuff'</span>);}}<span class="hljs-comment">//等同于 Point.prototype = {aa() {console.log('stuff')}};</span>
    <span class="hljs-comment">//类的所有方法都定义在类的prototype属性上面。</span>
    <span class="hljs-keyword">typeof</span> Point <span class="hljs-comment">// "function"</span>
    Point === Point.prototype.constructor <span class="hljs-comment">// true</span>
    <span class="hljs-keyword">var</span> b = <span class="hljs-keyword">new</span> Point();<span class="hljs-comment">//使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。</span>
    Point.aa() <span class="hljs-comment">// "stuff"</span>
    b.aa === Ponit.prototype.aa <span class="hljs-comment">// true在类的实例上面调用方法,其实就是调用原型上的方法。</span>
    上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
    由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。<span class="hljs-built_in">Object</span>.assign方法可以很方便地一次向类添加多个方法。
    <span class="hljs-keyword">class</span> Point {
      constructor(){
        <span class="hljs-comment">// ...</span>
      }
    }
    <span class="hljs-built_in">Object</span>.assign(Point.prototype, {
      toString(){},
      toValue(){}
    });
   Point.prototype.constructor === Point <span class="hljs-comment">// true ,prototype对象的constructor属性,直接指向“类”的本身,这与 ES5 的行为是一致的。</span>
   另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
   采用 ES5 的写法(Point.prototype.方法(){……}),这样的方法就是可枚举的。
<span class="hljs-number">2</span>、严格模式:
    类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
    考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
<span class="hljs-number">3</span>、constructor 方法:
    constructor方法是类的默认方法,通过<span class="hljs-keyword">new</span>命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
    <span class="hljs-keyword">class</span> Point {}  等同于  <span class="hljs-keyword">class</span> Point { constructor() {}}
    上面代码中,定义了一个空的类Point,JavaScript 引擎会自动为它添加一个空的constructor方法。
    constructor方法默认返回实例对象(即<span class="hljs-keyword">this</span>),完全可以指定返回另外一个对象。
    <span class="hljs-keyword">class</span> Foo {
      constructor() {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.create(<span class="hljs-literal">null</span>);
      }
    }

    <span class="hljs-keyword">new</span> Foo() <span class="hljs-keyword">instanceof</span> Foo
    <span class="hljs-comment">// false</span>
    上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例。
    类必须使用<span class="hljs-keyword">new</span>调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用<span class="hljs-keyword">new</span>也可以执行。
<span class="hljs-number">4</span>、类的实例对象:
    与 ES5 一样,类的所有实例共享一个原型对象。
    <span class="hljs-keyword">var</span> p1 = <span class="hljs-keyword">new</span> Point(<span class="hljs-number">2</span>,<span class="hljs-number">3</span>);
    <span class="hljs-keyword">var</span> p2 = <span class="hljs-keyword">new</span> Point(<span class="hljs-number">3</span>,<span class="hljs-number">2</span>);
    p1.__proto__ === p2.__proto__<span class="hljs-comment">//true</span>
    p1.__proto__.printName = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> {</span> <span class="hljs-keyword">return</span> <span class="hljs-string">'Oops'</span> };<span class="hljs-comment">//在p1的原型上添加了一个printName方法,由于p1的原型就是p2的原型,因此p2也可以调用这个方法。</span>
    使用实例的__proto__属性改写原型,必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。
    再来一个例子:
    <span class="hljs-keyword">class</span> Point {
      constructor(x, y) {
        <span class="hljs-keyword">this</span>.x = x;
        <span class="hljs-keyword">this</span>.y = y;
      }

      toString() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">'('</span> + <span class="hljs-keyword">this</span>.x + <span class="hljs-string">', '</span> + <span class="hljs-keyword">this</span>.y + <span class="hljs-string">')'</span>;
      }

    }
    <span class="hljs-keyword">var</span> point = <span class="hljs-keyword">new</span> Point(<span class="hljs-number">2</span>, <span class="hljs-number">3</span>);
    point.toString() <span class="hljs-comment">// (2, 3)</span>
    point.hasOwnProperty(<span class="hljs-string">'x'</span>) <span class="hljs-comment">// true</span>
    point.hasOwnProperty(<span class="hljs-string">'y'</span>) <span class="hljs-comment">// true</span>
    point.hasOwnProperty(<span class="hljs-string">'toString'</span>) <span class="hljs-comment">// false</span>
    point.__proto__.hasOwnProperty(<span class="hljs-string">'toString'</span>) <span class="hljs-comment">// true</span>
    point.hasOwnProperty(<span class="hljs-string">'y'</span>) <span class="hljs-comment">// true ,x和y都是实例对象point自身的属性(因为定义在this变量上),所以hasOwnProperty方法返回true,</span>
    point.hasOwnProperty(<span class="hljs-string">'toString'</span>) <span class="hljs-comment">// false,而toString是原型对象的属性(因为定义在Point类上),所以hasOwnProperty方法返回false。</span>
    point.__proto__.hasOwnProperty(<span class="hljs-string">'toString'</span>) <span class="hljs-comment">// true,point的原型有string方法</span>
<span class="hljs-number">5</span>、Class 表达式:
    与函数一样,类也可以使用表达式的形式定义。
    <span class="hljs-keyword">const</span> MyClass = <span class="hljs-keyword">class</span> Me {
      getClassName() {
        <span class="hljs-keyword">return</span> Me.name;
      }
    };注意这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类。
    <span class="hljs-keyword">let</span> inst = <span class="hljs-keyword">new</span> MyClass();
    inst.getClassName() <span class="hljs-comment">// Me</span>
    Me.name <span class="hljs-comment">// ReferenceError: Me is not defined</span>
    上面代码表示,Me只在 Class 内部有定义。
    如果类的内部没用到的话,可以省略Me,也就是可以写成下面的形式。
    <span class="hljs-keyword">const</span> MyClass = <span class="hljs-keyword">class</span> { <span class="hljs-comment">/* ... */</span> };
    采用 Class 表达式,可以写出立即执行的 Class。
    <span class="hljs-keyword">let</span> person = <span class="hljs-keyword">new</span> <span class="hljs-keyword">class</span> {
      constructor(name) {
        <span class="hljs-keyword">this</span>.name = name;
      }

      sayName() {
        console.log(<span class="hljs-keyword">this</span>.name);
      }
    }(<span class="hljs-string">'张三'</span>);
    person.sayName(); <span class="hljs-comment">// "张三"</span>
    上面代码中,person是一个立即执行的类的实例。
<span class="hljs-number">6</span>、不存在变量提升:
    类不存在变量提升(hoist),这一点与 ES5 完全不同。
    <span class="hljs-keyword">new</span> Foo(); <span class="hljs-comment">// ReferenceError</span>
    <span class="hljs-keyword">class</span> Foo {}
<span class="hljs-number">7</span>、私有方法:
    私有方法是常见需求,但 ES6 不提供,只能通过变通方法模拟实现。
    <span class="hljs-number">7.1</span>、一种做法是在命名上加以区别。这种命名是不保险的,在类的外部,还是可以调用到这个方法。
    <span class="hljs-keyword">class</span> Widget {
      <span class="hljs-comment">// 公有方法</span>
      foo (baz) {
        <span class="hljs-keyword">this</span>._bar(baz);
      }
      <span class="hljs-comment">// 私有方法</span>
      _bar(baz) {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.snaf = baz;
      }
      <span class="hljs-comment">// ...</span>
    }
    <span class="hljs-number">7.2</span>、另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
    <span class="hljs-keyword">class</span> Widget {
      foo (baz) {
        bar.call(<span class="hljs-keyword">this</span>, baz);
      }
      <span class="hljs-comment">// ...</span>
    }
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bar</span><span class="hljs-params">(baz)</span> {</span>
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.snaf = baz;
    }
    <span class="hljs-number">7.3</span>、还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
    <span class="hljs-keyword">const</span> bar = Symbol(<span class="hljs-string">'bar'</span>);
    <span class="hljs-keyword">const</span> snaf = Symbol(<span class="hljs-string">'snaf'</span>);
    export <span class="hljs-keyword">default</span> <span class="hljs-keyword">class</span> myClass{

      <span class="hljs-comment">// 公有方法</span>
      foo(baz) {
        <span class="hljs-keyword">this</span>[bar](baz);
      }
      <span class="hljs-comment">// 私有方法</span>
      [bar](baz) {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>[snaf] = baz;
      }
      <span class="hljs-comment">// ...</span>
    };
    上面代码中,bar和snaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。
<span class="hljs-number">8</span>、私有属性:
<span class="hljs-number">8.1</span>、与私有方法一样,ES6 不支持私有属性。目前,有一个提案,为<span class="hljs-keyword">class</span>加了私有属性。方法是在属性名之前,使用#表示。
    <span class="hljs-keyword">class</span> Point {
      #x;
      constructor(x = <span class="hljs-number">0</span>) {
        #x = +x; <span class="hljs-comment">// 写成 this.#x 亦可</span>
      }
      get x() { <span class="hljs-keyword">return</span> #x }
      set x(value) { #x = +value }
    }
    上面代码中,#x就表示私有属性x,在Point类之外是读取不到这个属性的。还可以看到,私有属性与实例的属性是可以同名的(比如,#x与get x())。
    <span class="hljs-number">8.2</span>、私有属性可以指定初始值,在构造函数执行时进行初始化。
    <span class="hljs-keyword">class</span> Point {
      #x = <span class="hljs-number">0</span>;
      constructor() {
        #x; <span class="hljs-comment">// 0</span>
      }
    }
    要引入一个新的前缀#表示私有属性,而没有采用private关键字,是因为 JavaScript 是一门动态语言,使用独立的符号似乎是唯一的可靠方法,能够准确地区分一种属性是否为私有属性。
    <span class="hljs-number">8.3</span>、它也可以用来写私有方法。
    <span class="hljs-keyword">class</span> Foo {
      #a;
      #b;
      #sum() { <span class="hljs-keyword">return</span> #a + #b; }
      printSum() { console.log(#sum()); }
      constructor(a, b) { #a = a; #b = b; }
    }
<span class="hljs-number">9</span>、<span class="hljs-keyword">this</span> 的指向:
类的方法内部如果含有<span class="hljs-keyword">this</span>,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
    <span class="hljs-keyword">class</span> Logger {
      printName(name = <span class="hljs-string">'there'</span>) {
        <span class="hljs-keyword">this</span>.print(</span>`Hello ${name}`<span class="javascript">);
      }

      print(text) {
        console.log(text);
      }
    }
    <span class="hljs-keyword">const</span> logger = <span class="hljs-keyword">new</span> Logger();
    <span class="hljs-keyword">const</span> { printName } = logger;
    printName(); <span class="hljs-comment">// TypeError: Cannot read property 'print' of undefined</span>
    上面代码中,printName方法中的<span class="hljs-keyword">this</span>,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,<span class="hljs-keyword">this</span>会指向该方法运行时所在的环境,因为找不到print方法而导致报错。
    <span class="hljs-number">9.1</span>、一个比较简单的解决方法是,在构造方法中绑定<span class="hljs-keyword">this</span>,这样就不会找不到print方法了。
    <span class="hljs-keyword">class</span> Logger {
      constructor() {
        <span class="hljs-keyword">this</span>.printName = <span class="hljs-keyword">this</span>.printName.bind(<span class="hljs-keyword">this</span>);
      }

      <span class="hljs-comment">// ...</span>
    }
    <span class="hljs-number">9.2</span>、另一种解决方法是使用箭头函数。
    <span class="hljs-keyword">class</span> Logger {
      constructor() {
        <span class="hljs-keyword">this</span>.printName = (name = <span class="hljs-string">'there'</span>) => {
          <span class="hljs-keyword">this</span>.print(</span>`Hello ${name}`<span class="javascript">);
        };
      }

      <span class="hljs-comment">// ...</span>
    }
    <span class="hljs-number">9.3</span>、还有一种解决方法是使用Proxy,获取方法的时候,自动绑定<span class="hljs-keyword">this</span>。
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">selfish</span> <span class="hljs-params">(target)</span> {</span>
      <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">new</span> WeakMap();
      <span class="hljs-keyword">const</span> handler = {
        get (target, key) {
          <span class="hljs-keyword">const</span> value = Reflect.get(target, key);
          <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> value !== <span class="hljs-string">'function'</span>) {
            <span class="hljs-keyword">return</span> value;
          }
          <span class="hljs-keyword">if</span> (!cache.has(value)) {
            cache.set(value, value.bind(target));
          }
          <span class="hljs-keyword">return</span> cache.get(value);
        }
      };
      <span class="hljs-keyword">const</span> proxy = <span class="hljs-keyword">new</span> Proxy(target, handler);
      <span class="hljs-keyword">return</span> proxy;
    }
    <span class="hljs-keyword">const</span> logger = selfish(<span class="hljs-keyword">new</span> Logger());
<span class="hljs-number">10</span>、name 属性:
    <span class="hljs-keyword">class</span> Point {}
    Point.name <span class="hljs-comment">// "Point"</span>
    name属性总是返回紧跟在<span class="hljs-keyword">class</span>关键字后面的类名。
<span class="hljs-number">11</span>、Class 的取值函数(getter)和存值函数(setter):
    与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
   <span class="hljs-number">11.1</span>、 <span class="hljs-keyword">class</span> MyClass {
      constructor() {
        <span class="hljs-comment">// ...</span>
      }
      get prop() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">'getter'</span>;
      }
      set prop(value) {
        console.log(<span class="hljs-string">'setter: '</span>+value);
      }
    }
    <span class="hljs-keyword">let</span> inst = <span class="hljs-keyword">new</span> MyClass();
    inst.prop = <span class="hljs-number">123</span>;
    <span class="hljs-comment">// setter: 123</span>
    inst.prop
    <span class="hljs-comment">// 'getter'</span>
    上面代码中,prop属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。

    <span class="hljs-number">11.2</span>、存值函数和取值函数是设置在属性的 Descriptor 对象上的。
    <span class="hljs-keyword">class</span> CustomHTMLElement {
      constructor(element) {
        <span class="hljs-keyword">this</span>.element = element;
      }

      get html() {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.element.innerHTML;
      }

      set html(value) {
        <span class="hljs-keyword">this</span>.element.innerHTML = value;
      }
    }

    <span class="hljs-keyword">var</span> descriptor = <span class="hljs-built_in">Object</span>.getOwnPropertyDescriptor(
      CustomHTMLElement.prototype, <span class="hljs-string">"html"</span>
    );

    <span class="hljs-string">"get"</span> <span class="hljs-keyword">in</span> descriptor  <span class="hljs-comment">// true</span>
    <span class="hljs-string">"set"</span> <span class="hljs-keyword">in</span> descriptor  <span class="hljs-comment">// true</span>
    上面代码中,存值函数和取值函数是定义在html属性的描述对象上面,这与 ES5 完全一致。
<span class="hljs-number">12</span>、Class 的 Generator 方法:
    如果某个方法之前加上星号(*),就表示该方法是一个 Generator 函数。
    <span class="hljs-keyword">class</span> Foo {
      constructor(...args) {
        <span class="hljs-keyword">this</span>.args = args;
      }
      * [Symbol.iterator]() {
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> arg of <span class="hljs-keyword">this</span>.args) {
          <span class="hljs-keyword">yield</span> arg;
        }
      }
    }

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> x of <span class="hljs-keyword">new</span> Foo(<span class="hljs-string">'hello'</span>, <span class="hljs-string">'world'</span>)) {
      console.log(x);
    }
    <span class="hljs-comment">// hello</span>
    <span class="hljs-comment">// world</span>
    上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个 Generator 函数。Symbol.iterator方法返回一个Foo类的默认遍历器,<span class="hljs-keyword">for</span>...of循环会自动调用这个遍历器。
<span class="hljs-number">13</span>、Class 的静态方法:
    <span class="hljs-number">13.1</span>、类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
    <span class="hljs-keyword">class</span> Foo {
      static classMethod() {<span class="hljs-comment">//Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">'hello'</span>;
      }
    }
    Foo.classMethod() <span class="hljs-comment">// 'hello'可以直接在Foo类上调用(Foo.classMethod())</span>
    <span class="hljs-keyword">var</span> foo = <span class="hljs-keyword">new</span> Foo();
    foo.classMethod()<span class="hljs-comment">// TypeError: foo.classMethod is not a function</span>
    如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

    <span class="hljs-number">13.2</span>、如果静态方法包含<span class="hljs-keyword">this</span>关键字,这个<span class="hljs-keyword">this</span>指的是类,而不是实例。
    <span class="hljs-keyword">class</span> Foo {
      static bar () {
        <span class="hljs-keyword">this</span>.baz();<span class="hljs-comment">//静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例</span>
      }
      static baz () {
        console.log(<span class="hljs-string">'hello'</span>);
      }
      baz () {<span class="hljs-comment">//静态方法可以与非静态方法重名。</span>
        console.log(<span class="hljs-string">'world'</span>);
      }
    }
    Foo.bar() <span class="hljs-comment">// hello  等同于调用Foo.baz。</span>
    父类的静态方法,可以被子类继承。父类Foo有一个静态方法,子类Bar可以调用这个方法。
    静态方法也是可以从super对象上调用的。
    <span class="hljs-keyword">class</span> Foo {
      static classMethod() {
        <span class="hljs-keyword">return</span> <span class="hljs-string">'hello'</span>;
      }
    }
    <span class="hljs-keyword">class</span> Bar extends Foo {
      static classMethod() {
        <span class="hljs-keyword">return</span> super.classMethod() + <span class="hljs-string">', too'</span>;
      }
    }
    Bar.classMethod() <span class="hljs-comment">// "hello, too"</span>
<span class="hljs-number">14</span>、Class 的静态属性和实例属性:
    静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(<span class="hljs-keyword">this</span>)上的属性。
    <span class="hljs-keyword">class</span> Foo {}
    Foo.prop = <span class="hljs-number">1</span>;
    Foo.prop <span class="hljs-comment">// 1</span>
    上面的写法为Foo类定义了一个静态属性prop。
    目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。
    <span class="hljs-keyword">class</span> Foo {
      prop: <span class="hljs-number">2</span><span class="hljs-comment">// 写法一</span>
      static prop: <span class="hljs-number">2</span><span class="hljs-comment">// 写法二 }</span>
    Foo.prop <span class="hljs-comment">// undefined// 以下两种写法都无效</span>
    目前有一个静态属性的提案,对实例属性和静态属性都规定了新的写法。
        (<span class="hljs-number">1</span>)类的实例属性(需要用到等式)
        <span class="hljs-keyword">class</span> MyClass {
          myProp = <span class="hljs-number">42</span>;<span class="hljs-comment">//类的实例属性可以用等式,写入类的定义之中。</span>
          constructor() {
            console.log(<span class="hljs-keyword">this</span>.myProp); <span class="hljs-comment">// 42</span>
          }
        }
        上面代码中,myProp就是MyClass的实例属性。在MyClass的实例上,可以读取这个属性。
        以前,我们定义实例属性,只能写在类的constructor方法里面。
        <span class="hljs-keyword">class</span> ReactCounter extends React.Component {
          constructor(props) {
            super(props);
            <span class="hljs-keyword">this</span>.state = {
              count: <span class="hljs-number">0</span><span class="hljs-comment">//构造方法constructor里面,定义了this.state属性。</span>
            };
          }
        }
        有了新的写法以后,可以不在constructor方法里面定义。
        <span class="hljs-keyword">class</span> ReactCounter extends React.Component {
          state = {
            count: <span class="hljs-number">0</span>
          };
        }
        这种写法比以前更清晰。
        为了可读性的目的,对于那些在constructor里面已经定义的实例属性,新写法允许直接列出。
        <span class="hljs-keyword">class</span> ReactCounter extends React.Component {
          state;
          constructor(props) {
            super(props);
            <span class="hljs-keyword">this</span>.state = {
              count: <span class="hljs-number">0</span>
            };
          }
        }
        (<span class="hljs-number">2</span>)类的静态属性
        类的静态属性只要在上面的实例属性写法前面,加上static关键字就可以了。
        <span class="hljs-keyword">class</span> MyClass {
          static myStaticProp = <span class="hljs-number">42</span>;
          constructor() {
            console.log(MyClass.myStaticProp); <span class="hljs-comment">// 42</span>
          }
        }
        同样的,这个新写法大大方便了静态属性的表达。
        <span class="hljs-keyword">class</span> Foo {  <span class="hljs-comment">// ...}</span>
        Foo.prop = <span class="hljs-number">1</span>;<span class="hljs-comment">// 老写法</span>
        <span class="hljs-keyword">class</span> Foo {  static prop = <span class="hljs-number">1</span>;}<span class="hljs-comment">// 新写法</span>
        上面代码中,老写法的静态属性定义在类的外部。整个类生成以后,再生成静态属性。这样让人很容易忽略这个静态属性,也不符合相关代码应该放在一起的代码组织原则。另外,新写法是显式声明(declarative),而不是赋值处理,语义更好。
<span class="hljs-number">15</span>、<span class="hljs-keyword">new</span>.target 属性:
    <span class="hljs-keyword">new</span>是从构造函数生成实例对象的命令。ES6 为<span class="hljs-keyword">new</span>命令引入了一个<span class="hljs-keyword">new</span>.target属性,该属性一般用在构造函数之中,返回<span class="hljs-keyword">new</span>命令作用于的那个构造函数。如果构造函数不是通过<span class="hljs-keyword">new</span>命令调用的,<span class="hljs-keyword">new</span>.target会返回<span class="hljs-literal">undefined</span>,因此这个属性可以用来确定构造函数是怎么调用的。

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Person</span><span class="hljs-params">(name)</span> {</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-keyword">new</span>.target !== <span class="hljs-literal">undefined</span>) {
        <span class="hljs-keyword">this</span>.name = name;
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'必须使用 new 命令生成实例'</span>);
      }
    }

    <span class="hljs-comment">// 另一种写法</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Person</span><span class="hljs-params">(name)</span> {</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-keyword">new</span>.target === Person) {
        <span class="hljs-keyword">this</span>.name = name;
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'必须使用 new 命令生成实例'</span>);
      }
    }

    <span class="hljs-keyword">var</span> person = <span class="hljs-keyword">new</span> Person(<span class="hljs-string">'张三'</span>); <span class="hljs-comment">// 正确</span>
    <span class="hljs-keyword">var</span> notAPerson = Person.call(person, <span class="hljs-string">'张三'</span>);  <span class="hljs-comment">// 报错</span>
    上面代码确保构造函数只能通过<span class="hljs-keyword">new</span>命令调用。

    Class 内部调用<span class="hljs-keyword">new</span>.target,返回当前 Class。

    <span class="hljs-keyword">class</span> Rectangle {
      constructor(length, width) {
        console.log(<span class="hljs-keyword">new</span>.target === Rectangle);
        <span class="hljs-keyword">this</span>.length = length;
        <span class="hljs-keyword">this</span>.width = width;
      }
    }

    <span class="hljs-keyword">var</span> obj = <span class="hljs-keyword">new</span> Rectangle(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>); <span class="hljs-comment">// 输出 true</span>
    需要注意的是,子类继承父类时,<span class="hljs-keyword">new</span>.target会返回子类。

    <span class="hljs-keyword">class</span> Rectangle {
      constructor(length, width) {
        console.log(<span class="hljs-keyword">new</span>.target === Rectangle);
        <span class="hljs-comment">// ...</span>
      }
    }

    <span class="hljs-keyword">class</span> Square extends Rectangle {
      constructor(length) {
        super(length, length);
      }
    }

    <span class="hljs-keyword">var</span> obj = <span class="hljs-keyword">new</span> Square(<span class="hljs-number">3</span>); <span class="hljs-comment">// 输出 false</span>
    上面代码中,<span class="hljs-keyword">new</span>.target会返回子类。

    利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。

    <span class="hljs-keyword">class</span> Shape {
      constructor() {
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">new</span>.target === Shape) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'本类不能实例化'</span>);
        }
      }
    }

    <span class="hljs-keyword">class</span> Rectangle extends Shape {
      constructor(length, width) {
        super();
        <span class="hljs-comment">// ...</span>
      }
    }

    <span class="hljs-keyword">var</span> x = <span class="hljs-keyword">new</span> Shape();  <span class="hljs-comment">// 报错</span>
    <span class="hljs-keyword">var</span> y = <span class="hljs-keyword">new</span> Rectangle(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>);  <span class="hljs-comment">// 正确</span>
    上面代码中,Shape类不能被实例化,只能用于继承。

    注意,在函数外部,使用<span class="hljs-keyword">new</span>.target会报错。

<span class="hljs-number">12.18</span> 星期一
二十、Class 的继承
<span class="hljs-number">1</span>、简介:
    Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
    <span class="hljs-keyword">class</span> Point {}
    <span class="hljs-keyword">class</span> ColorPoint extends Point {}<span class="hljs-comment">//定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。</span>
    super关键字,它在这里表示父类的构造函数,用来新建父类的<span class="hljs-keyword">this</span>对象。
    子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的<span class="hljs-keyword">this</span>对象,而是继承父类的<span class="hljs-keyword">this</span>对象,然后对其进行加工。如果不调用super方法,子类就得不到<span class="hljs-keyword">this</span>对象。
    <span class="hljs-keyword">class</span> Point { <span class="hljs-comment">/* ... */</span> }
    <span class="hljs-keyword">class</span> ColorPoint extends Point {
      constructor() {
      }
    }
    <span class="hljs-keyword">let</span> cp = <span class="hljs-keyword">new</span> ColorPoint(); <span class="hljs-comment">// ReferenceError</span>
<span class="hljs-number">2</span>、<span class="hljs-built_in">Object</span>.getPrototypeOf():可以用来从子类上获取父类。
    <span class="hljs-built_in">Object</span>.getPrototypeOf(ColorPoint) === Point<span class="hljs-comment">// true可以判断,一个类是否继承了另一个类。</span>
<span class="hljs-number">3</span>、super 关键字
<span class="hljs-number">4</span>、类的 prototype 属性和__proto__属性
<span class="hljs-number">5</span>、原生构造函数的继承:
原生构造函数是指语言内置的构造函数,通常用来生成数据结构。ECMAScript 的原生构造函数大致有下面这些:
<span class="hljs-built_in">Boolean</span>()、<span class="hljs-built_in">Number</span>()、<span class="hljs-built_in">String</span>()、<span class="hljs-built_in">Array</span>()、<span class="hljs-built_in">Date</span>()、<span class="hljs-built_in">Function</span>()、<span class="hljs-built_in">RegExp</span>()、<span class="hljs-built_in">Error</span>()、<span class="hljs-built_in">Object</span>()
<span class="hljs-number">6</span>、Mixin 模式的实现:
    Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。它的最简单实现如下。
    <span class="hljs-keyword">const</span> a = {  a: <span class="hljs-string">'a'</span>};
    <span class="hljs-keyword">const</span> b = {  b: <span class="hljs-string">'b'</span>};
    <span class="hljs-keyword">const</span> c = {...a, ...b}; <span class="hljs-comment">// {a: 'a', b: 'b'}</span>
    上面代码中,c对象是a对象和b对象的合成,具有两者的接口。
    下面是一个更完备的实现,将多个类的接口“混入”(mix <span class="hljs-keyword">in</span>)另一个类。
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mix</span><span class="hljs-params">(...mixins)</span> {</span>
      <span class="hljs-keyword">class</span> Mix {}

      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> mixin of mixins) {
        copyProperties(Mix, mixin); <span class="hljs-comment">// 拷贝实例属性</span>
        copyProperties(Mix.prototype, mixin.prototype); <span class="hljs-comment">// 拷贝原型属性</span>
      }

      <span class="hljs-keyword">return</span> Mix;
    }
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">copyProperties</span><span class="hljs-params">(target, source)</span> {</span>
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key of Reflect.ownKeys(source)) {
        <span class="hljs-keyword">if</span> ( key !== <span class="hljs-string">"constructor"</span>
          && key !== <span class="hljs-string">"prototype"</span>
          && key !== <span class="hljs-string">"name"</span>
        ) {
          <span class="hljs-keyword">let</span> desc = <span class="hljs-built_in">Object</span>.getOwnPropertyDescriptor(source, key);
          <span class="hljs-built_in">Object</span>.defineProperty(target, key, desc);
        }
      }
    }
    上面代码的mix函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可。

    <span class="hljs-keyword">class</span> DistributedEdit extends mix(Loggable, Serializable) {
      <span class="hljs-comment">// ...</span>
    }</span></code></pre> 
 </div> 
</div>
                            </div>
                        </div>
                    </div>
                    <!--PC和WAP自适应版-->
                    <div id="SOHUCS" sid="1283462677284274176"></div>
                    <script type="text/javascript" src="/views/front/js/chanyan.js"></script>
                    <!-- 文章页-底部 动态广告位 -->
                    <div class="youdao-fixed-ad" id="detail_ad_bottom"></div>
                </div>
                <div class="col-md-3">
                    <div class="row" id="ad">
                        <!-- 文章页-右侧1 动态广告位 -->
                        <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_1"> </div>
                        </div>
                        <!-- 文章页-右侧2 动态广告位 -->
                        <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_2"></div>
                        </div>
                        <!-- 文章页-右侧3 动态广告位 -->
                        <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_3"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="container">
        <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(入门)</h4>
        <div id="paradigm-article-related">
            <div class="recommend-post mb30">
                <ul class="widget-links">
                    <li><a href="/article/1950233072825856000.htm"
                           title="三菱PLC全套学习资料及应用手册" target="_blank">三菱PLC全套学习资料及应用手册</a>
                        <span class="text-muted">good2know</span>

                        <div>本文还有配套的精品资源,点击获取简介:三菱PLC作为工业自动化领域的核心设备,其系列产品的学习和应用需要全面深入的知识。本次资料包为学习者提供从基础到进阶的全方位学习资源,包括各种型号PLC的操作手册、编程指南、软件操作教程以及实际案例分析,旨在帮助用户系统掌握PLC的编程语言、指令系统及在各类工业应用中的实施。1.三菱PLC基础知识入门1.1PLC的基本概念可编程逻辑控制器(PLC)是工业自动化</div>
                    </li>
                    <li><a href="/article/1950232316408295424.htm"
                           title="9、汇编语言编程入门:从环境搭建到简单程序实现" target="_blank">9、汇编语言编程入门:从环境搭建到简单程序实现</a>
                        <span class="text-muted">神经网络酱</span>
<a class="tag" taget="_blank" href="/search/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/1.htm">汇编语言</a><a class="tag" taget="_blank" href="/search/MEPIS/1.htm">MEPIS</a><a class="tag" taget="_blank" href="/search/GNU%E5%B7%A5%E5%85%B7%E9%93%BE/1.htm">GNU工具链</a>
                        <div>汇编语言编程入门:从环境搭建到简单程序实现1.数据存储介质问题解决在处理数据存储时,若要使用MEPIS系统,需确保有其可访问的存储介质。目前,MEPIS无法向采用NTFS格式(常用于Windows2000和XP工作站)的硬盘写入数据。不过,若硬盘采用FAT32格式,MEPIS就能进行写入操作。此外,MEPIS还能将文件写入软盘和大多数USB闪存驱动器。若工作站连接到局域网,还可通过FTP协议或挂载</div>
                    </li>
                    <li><a href="/article/1950217304876838912.htm"
                           title="STM32入门之TIM基本定时器" target="_blank">STM32入门之TIM基本定时器</a>
                        <span class="text-muted">嵌入式白话</span>
<a class="tag" taget="_blank" href="/search/STM32%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0/1.htm">STM32入门学习</a><a class="tag" taget="_blank" href="/search/stm32/1.htm">stm32</a><a class="tag" taget="_blank" href="/search/%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%A1%AC%E4%BB%B6/1.htm">嵌入式硬件</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E7%89%87%E6%9C%BA/1.htm">单片机</a>
                        <div>一、定时器简介定时器是嵌入式系统中的关键外设之一,它可以用于生成精确的延时、周期性中断、PWM波形生成等功能。在STM32F1系列单片机中,定时器不仅能为系统提供精确的时钟,还支持外部事件的捕获以及信号输出。对于定时器的功能,我们可以通过一个生活中非常常见的例子来形象地描述:微波炉的定时器。想象你正在使用微波炉加热食物。在微波炉里,定时器的作用就是帮助你控制食物加热的时间。当你设置了加热时间后,定</div>
                    </li>
                    <li><a href="/article/1950199910724857856.htm"
                           title="机器学习必备数学与编程指南:从入门到精通" target="_blank">机器学习必备数学与编程指南:从入门到精通</a>
                        <span class="text-muted">a小胡哦</span>
<a class="tag" taget="_blank" href="/search/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%9F%BA%E7%A1%80/1.htm">机器学习基础</a><a class="tag" taget="_blank" href="/search/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/1.htm">机器学习</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a>
                        <div>一、机器学习核心数学基础1.线性代数(神经网络的基础)必须掌握:矩阵运算(乘法、转置、逆)向量空间与线性变换特征值分解与奇异值分解(SVD)为什么重要:神经网络本质就是矩阵运算学习技巧:用NumPy实际操作矩阵运算2.概率与统计(模型评估的关键)核心概念:条件概率与贝叶斯定理概率分布(正态、泊松、伯努利)假设检验与p值应用场景:朴素贝叶斯、A/B测试3.微积分(优化算法的基础)重点掌握:导数与偏导</div>
                    </li>
                    <li><a href="/article/1950194868303228928.htm"
                           title="免费编程课程大汇总:从入门到精通的一站式资源" target="_blank">免费编程课程大汇总:从入门到精通的一站式资源</a>
                        <span class="text-muted">大力出奇迹985</span>
<a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/%E5%A4%A7%E6%95%B0%E6%8D%AE/1.htm">大数据</a>
                        <div>在数字化时代,编程已成为一项至关重要的技能,无论是为了职业发展还是个人兴趣,学习编程都极具价值。本文精心汇总了丰富的免费编程课程资源,涵盖从基础入门到精通的各个阶段。通过全面介绍如Coursera、edX等在线学习平台,Codecademy、freeCodeCamp等交互式学习网站,以及B站、网易云课堂等视频课程平台的免费课程,为编程学习者提供了一站式的资源指南,帮助读者轻松开启编程学习之旅,逐步</div>
                    </li>
                    <li><a href="/article/1950194363237724160.htm"
                           title="用 Python 开发小游戏:零基础也能做出《贪吃蛇》" target="_blank">用 Python 开发小游戏:零基础也能做出《贪吃蛇》</a>
                        <span class="text-muted"></span>

                        <div>本文专为零基础学习者打造,详细介绍如何用Python开发经典小游戏《贪吃蛇》。无需复杂编程知识,从环境搭建到代码编写、功能实现,逐步讲解核心逻辑与操作。涵盖Pygame库的基础运用、游戏界面设计、蛇的移动与食物生成规则等,让新手能按步骤完成开发,同时融入SEO优化要点,帮助读者轻松入门Python游戏开发,体验从0到1做出游戏的乐趣。一、为什么选择用Python开发《贪吃蛇》对于零基础学习者来说,</div>
                    </li>
                    <li><a href="/article/1950190146074767360.htm"
                           title="大数据技术笔记—spring入门" target="_blank">大数据技术笔记—spring入门</a>
                        <span class="text-muted">卿卿老祖</span>

                        <div>篇一spring介绍spring.io官网快速开始Aop面向切面编程,可以任何位置,并且可以细致到方法上连接框架与框架Spring就是IOCAOP思想有效的组织中间层对象一般都是切入service层spring组成前后端分离已学方式,前后台未分离:Spring的远程通信:明日更新创建第一个spring项目来源:科多大数据</div>
                    </li>
                    <li><a href="/article/1950187554129113088.htm"
                           title="Django学习笔记(一)" target="_blank">Django学习笔记(一)</a>
                        <span class="text-muted"></span>

                        <div>学习视频为:pythondjangoweb框架开发入门全套视频教程一、安装pipinstalldjango==****检查是否安装成功django.get_version()二、django新建项目操作1、新建一个项目django-adminstartprojectproject_name2、新建APPcdproject_namedjango-adminstartappApp注:一个project</div>
                    </li>
                    <li><a href="/article/1950183016382918656.htm"
                           title="大学生入门:初识方法及其易踩坑的点" target="_blank">大学生入门:初识方法及其易踩坑的点</a>
                        <span class="text-muted"></span>

                        <div>在java学习过程中,我们不难发现有很多重复使用的功能代码块,每次使用如果都要重新写一遍,岂不是很麻烦,就算是“cv”大法,感觉也不是很方便,那么,有什么办法可以解决这个问题呢?方法!java中,一段可重用的,用于执行特定功能的代码块叫做方法,它可以接收参数、返回结果,并且可以被多次使用。一、方法的基本结构[修饰符]返回值类型方法名([参数列表])[throws异常类型]{//方法体}[throw</div>
                    </li>
                    <li><a href="/article/1950182890033704960.htm"
                           title="大学生入门:分支结构及其易踩坑的点" target="_blank">大学生入门:分支结构及其易踩坑的点</a>
                        <span class="text-muted">山中月侣</span>
<a class="tag" taget="_blank" href="/search/java%E5%A4%A7%E5%AD%A6%E7%94%9F%E5%85%A5%E9%97%A8/1.htm">java大学生入门</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/1.htm">经验分享</a>
                        <div>循环和分支在我们的日常代码中出现频率很高,无论是简单的数据处理还是复杂的业务逻辑,都会经常用到,它们看似简单,但是使用起来还是有很多隐藏的问题的,接下来我们一起学习或者复习一下:一、分支结构1、if语句主要包括三种:if语句、if-else语句、if-elseif-else语句if语句:if(条件表达式){//条件为真时执行的代码块}如果要执行的语句只有一句,可以省略“{}”,但是不建议if-el</div>
                    </li>
                    <li><a href="/article/1950153639523840000.htm"
                           title="Qt 下拉框QComboBox控件:从入门到实战" target="_blank">Qt 下拉框QComboBox控件:从入门到实战</a>
                        <span class="text-muted"></span>

                        <div>一、QComboBox核心功能解析1.核心属性属性说明当前示例场景count列表项总数统计学历下拉框中的选项数量editable是否允许用户编辑学历选择时可输入自定义学历currentText当前选中项的文本获取用户选择的"硕士"文本currentData当前选中项的附加数据获取太原对应的区号"0351"currentIndex当前选中项的索引位置(从0开始)确定"硕士"在列表中的位置2.核心方法</div>
                    </li>
                    <li><a href="/article/1950141538352820224.htm"
                           title="2025.07 Java入门笔记01" target="_blank">2025.07 Java入门笔记01</a>
                        <span class="text-muted">殷浩焕</span>
<a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a>
                        <div>一、熟悉IDEA和Java语法(一)LiuCourseJavaOOP1.一直在用C++开发,python也用了些,Java是真的不熟,用什么IDE还是问的同事;2.一开始安装了jdk-23,拿VSCode当编辑器,在cmd窗口编译运行,也能玩;但是想正儿八经搞项目开发,还是需要IDE;3.安装了IDEA社区版:(1)IDE通常自带对应编程语言的安装包,例如IDEA自带jbr-21(和jdk是不同的</div>
                    </li>
                    <li><a href="/article/1950141156172034048.htm"
                           title="Spring AI Alibaba 快速入门指南(适合初学者)" target="_blank">Spring AI Alibaba 快速入门指南(适合初学者)</a>
                        <span class="text-muted">会飞的架狗师</span>
<a class="tag" taget="_blank" href="/search/AI/1.htm">AI</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                        <div>如果你是刚接触AI开发或Spring框架的初学者,不用担心,本指南会用简单易懂的语言带你一步步了解并使用SpringAIAlibaba。一、什么是SpringAIAlibaba(小白也能懂)简单来说,SpringAIAlibaba就是一个“工具包”,它把阿里巴巴的AI技术(比如通义千问大模型、向量数据库等)和大家常用的Spring框架“打包”到了一起。**打个比方:**就像你想做蛋糕(开发AI应用</div>
                    </li>
                    <li><a href="/article/1950140903616212992.htm"
                           title="Java朴实无华按天计划从入门到实战(强化速战版-66天)" target="_blank">Java朴实无华按天计划从入门到实战(强化速战版-66天)</a>
                        <span class="text-muted">岫珩</span>
<a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/%E6%97%B6%E9%97%B4%E5%AE%89%E6%8E%92/1.htm">时间安排</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/1.htm">学习计划</a>
                        <div>致敬读者感谢阅读笑口常开生日快乐⬛早点睡觉博主相关博主信息博客首页专栏推荐活动信息文章目录Java朴实无华按天计划从入门到实战(强化速战版-66天)1.基础(18)1.1JavaSE核心(5天)1.2数据库与SQL(5天)1.3前端基础(8天)2.进阶(17天)2.1JavaWeb核心(5天)2.2Mybatis与Spring全家桶(6天)2.3中间件入门(4天)2.4实践项目(2天)3.高阶(1</div>
                    </li>
                    <li><a href="/article/1950120484314083328.htm"
                           title="【人工智能入门必看的最全Python编程实战(1)】" target="_blank">【人工智能入门必看的最全Python编程实战(1)】</a>
                        <span class="text-muted">DFCED</span>
<a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/1.htm">深度学习</a><a class="tag" taget="_blank" href="/search/%E6%89%BE%E5%B7%A5%E4%BD%9C/1.htm">找工作</a><a class="tag" taget="_blank" href="/search/%E5%B0%B1%E4%B8%9A/1.htm">就业</a>
                        <div>--------------------------------------------------------------------------------------------------------------------1.AIGC未来发展前景未完持续…1.1人工智能相关科研重要性拥有一篇人工智能科研论文及专利软著竞赛是保研考研留学深造以及找工作的关键门票!!!拥有一篇人工智能科研论文</div>
                    </li>
                    <li><a href="/article/1950113424281235456.htm"
                           title="2025 最强 Agent 智能体 学习笔记 (71)" target="_blank">2025 最强 Agent 智能体 学习笔记 (71)</a>
                        <span class="text-muted">一刀7段</span>
<a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a>
                        <div>Agent智能体的系统学习与职业发展核心内容概览本集是《2025最强Agent智能体全套教程》的第72集,聚焦Agent智能体领域的系统学习方法与职业发展路径,系统梳理了从入门到专家的能力体系、关键学习资源、职业方向选择及行业发展机遇。内容结合技术趋势与职场需求,为不同背景的学习者(学生、开发者、转行人士)提供清晰的成长蓝图,帮助其在Agent智能体领域高效成长,实现职业目标。系统学习的能力体系与</div>
                    </li>
                    <li><a href="/article/1950106242563239936.htm"
                           title="我的ThinkPHP5框架开发22讲:从入门到实践的探索之旅" target="_blank">我的ThinkPHP5框架开发22讲:从入门到实践的探索之旅</a>
                        <span class="text-muted">a1237567892</span>
<a class="tag" taget="_blank" href="/search/%E5%AE%89%E5%85%A8/1.htm">安全</a>
                        <div>我的ThinkPHP5框架开发22讲:从入门到实践的探索之旅在编程的世界里,框架就像是一座座灯塔,照亮着开发者前行的道路。而当我第一次接触到ThinkPHP5这个PHP框架时,它就像是我探索路上的那颗最亮的星。今天,我想和大家分享我的ThinkPHP5框架开发22讲,这是我从一个初学者到逐渐熟练的实践过程。第1讲:初识ThinkPHP5记得那是一个阳光明媚的周末,我在网上浏览PHP框架的资料时,偶</div>
                    </li>
                    <li><a href="/article/1950105738256904192.htm"
                           title="环境艺术设计必学的“3D建模与渲染软件”指南" target="_blank">环境艺术设计必学的“3D建模与渲染软件”指南</a>
                        <span class="text-muted"></span>

                        <div>在环境艺术设计领域,掌握高效的设计软件是学生入门阶段普遍关注的核心问题。优秀的软件如同设计师的得力助手,能够精准表达设计创意、显著提升工作效率,在设计流程的各个环节都发挥着不可或缺的作用。根据功能划分,环艺设计常用软件主要涵盖建模、渲染、后期处理及辅助工具四大类。本文将为您梳理环艺设计中必学的核心软件。一、核心建模软件建模是将设计构思转化为三维模型的关键步骤。以下两款软件在环艺设计中应用最为广泛:</div>
                    </li>
                    <li><a href="/article/1950105514574671872.htm"
                           title="配音怎么学,能赚钱吗?" target="_blank">配音怎么学,能赚钱吗?</a>
                        <span class="text-muted">配音新手圈</span>

                        <div>配音也分很多方面,可以从最基础的先练习。像如果大学生的话时间比较多可以随时练习。在读的时候可以把硬腭隆起来的声音抛出去舌根稍微收一点。在录音前也可以吃几片苹果,避免录音时发出口水声。很多干货技巧很多可以自己搜索练习。这种兼职网上还是很多的。1、配音新手圈这是一个公众号配音新手圈里面每天更新配音任务,都是适合没有基础的人去做的,每天都有任务。适合新入门的小白练手,也是一个从业余到专业的过度期接单的平</div>
                    </li>
                    <li><a href="/article/1950102838667440128.htm"
                           title="开启 PHP 初阶之旅:解锁高效入门之道" target="_blank">开启 PHP 初阶之旅:解锁高效入门之道</a>
                        <span class="text-muted">API_Zevin</span>
<a class="tag" taget="_blank" href="/search/php/1.htm">php</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/%E5%A4%A7%E6%95%B0%E6%8D%AE/1.htm">大数据</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a>
                        <div>在当今动态网页开发领域,PHP作为一门久经沙场的服务器端脚本语言,以其强大功能与广泛适用性,持续吸引着无数初学者踏入编程殿堂。若你决心攻克PHP,为Web开发世界添砖加瓦,一系列精准策略与实用方法将成为你加速入门的得力伙伴,引领你穿越初期懵懂,迈向熟练驾驭的新征程。一、筑牢基础:语法地基稳扎稳打PHP语法虽具灵活性,初学者仍需系统研习,构建扎实根基。从变量声明起步,领会PHP变量“$”符号前缀特色</div>
                    </li>
                    <li><a href="/article/1950100058422702080.htm"
                           title="从0到1学PHP(一):PHP 基础入门:开启后端开发之旅" target="_blank">从0到1学PHP(一):PHP 基础入门:开启后端开发之旅</a>
                        <span class="text-muted"></span>

                        <div>目录一、PHP简介与发展历程1.1PHP定义与特点1.2在后端开发中的地位1.3发展阶段及重要版本更新二、PHP开发环境搭建2.1Windows系统下搭建步骤2.2Mac系统下搭建方法及常用工具2.3适合初学者的集成开发环境三、第一个PHP程序3.1编写"HelloWorld"程序3.2程序基本结构和执行过程3.3PHP代码的嵌入方式(在HTML中)一、PHP简介与发展历程1.1PHP定义与特点P</div>
                    </li>
                    <li><a href="/article/1950095270544994304.htm"
                           title="从零入门:云迁移原理详解与华为Rainbow实战指南" target="_blank">从零入门:云迁移原理详解与华为Rainbow实战指南</a>
                        <span class="text-muted">来自于狂人</span>
<a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a><a class="tag" taget="_blank" href="/search/%E4%BA%91%E8%AE%A1%E7%AE%97/1.htm">云计算</a>
                        <div>(全文较长,建议收藏后分段阅读)一、云迁移基础:新手必懂的10个核心概念1.云迁移的定义与战略价值权威定义:Gartner将云迁移定义为"将企业IT资产、应用和工作负载从传统本地环境(物理服务器、私有数据中心)迁移到云平台(公有云/混合云)的过程"。其本质是通过云技术实现资源弹性扩展、成本优化和业务创新。典型应用场景:业务系统上云:ERP(如SAP)、CRM(如Salesforce)等核心系统迁移</div>
                    </li>
                    <li><a href="/article/1950091615854981120.htm"
                           title="从零开始构建深度学习环境:基于Pytorch、CUDA与cuDNN的虚拟环境搭建与实践(适合初学者)" target="_blank">从零开始构建深度学习环境:基于Pytorch、CUDA与cuDNN的虚拟环境搭建与实践(适合初学者)</a>
                        <span class="text-muted">荣华富贵8</span>
<a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E7%9F%A5%E8%AF%86%E5%82%A8%E5%A4%872/1.htm">程序员的知识储备2</a><a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E7%9F%A5%E8%AF%86%E5%82%A8%E5%A4%873/1.htm">程序员的知识储备3</a><a class="tag" taget="_blank" href="/search/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/1.htm">深度学习</a><a class="tag" taget="_blank" href="/search/pytorch/1.htm">pytorch</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a>
                        <div>摘要:深度学习正在引领人工智能技术的革新,而对于初学者来说,正确搭建深度学习环境是迈向AI研究与应用的第一步。本文将为读者提供一套详尽的教程,指导如何在本地环境中搭建Pytorch、CUDA与cuDNN,以及如何利用Anaconda和PyCharm进行高效开发。内容涵盖从环境配置、常见错误修正,到基础的深度学习模型构建及训练。我们旨在为深度学习零基础的入门者提供一个全面且易于理解的“保姆级”教程,</div>
                    </li>
                    <li><a href="/article/1950090733188870144.htm"
                           title="Python入门第二课:核心编程概念:控制结构与逻辑" target="_blank">Python入门第二课:核心编程概念:控制结构与逻辑</a>
                        <span class="text-muted">小菜同学爱学习</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a>
                        <div>1.条件语句(if/elif/else)#成绩评级系统score=85ifscore>=90:print("优秀!")elifscore>=80:print("良好!")elifscore>=60:print("及格!✅")else:print("不及格,需要努力!")2.循环结构for循环#遍历列表fruits=["苹果","香蕉","橙子","草莓"]print("水果列表:")forfrui</div>
                    </li>
                    <li><a href="/article/1950088204879196160.htm"
                           title="阿里云个人用户可优惠购买的云服务器配置及价格汇总(最新版)" target="_blank">阿里云个人用户可优惠购买的云服务器配置及价格汇总(最新版)</a>
                        <span class="text-muted">阿里云最新优惠和活动汇总</span>

                        <div>阿里云活动中的云服务器分为新用户专享和企业新用户专享,其实新用户专享主要就是针对阿里云个人用户的,活动中的一些入门级云服务器就是主要为个人用户准备的,尤其是1核2G、2核4G这些配置,下面是阿里云个人用户可优惠购买云服务器配置及价格汇总。目前阿里云活动中可购买的云服务器时长为1个月、2个月、3个月、6个月和1年,不同活动中的云服务器购买时长有所不同,下面是个人用户可个人用户可优惠购买的月付和年付云</div>
                    </li>
                    <li><a href="/article/1950074471545106432.htm"
                           title="C++-coroutines协程 将自定义类型转为awaitable(可等待)类型的两种方法" target="_blank">C++-coroutines协程 将自定义类型转为awaitable(可等待)类型的两种方法</a>
                        <span class="text-muted">mrbone11</span>
<a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/Coroutines/1.htm">Coroutines</a><a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%8D%8F%E7%A8%8B/1.htm">协程</a><a class="tag" taget="_blank" href="/search/coroutines/1.htm">coroutines</a>
                        <div>文章目录前言重载operatorco_await定义promise_type::await_transform函数前言阅读本篇文章时,已经假定你有对协程的基本概念,如果没有,可以阅读我写的这篇协程入门文章我们已经知道,对于co_await\,expression的返回值必须是awaitable类型,即正确实现了如下三个函数:await_readyawait_suspendawait_resume对</div>
                    </li>
                    <li><a href="/article/1950057579757498368.htm"
                           title="零基础入门uniapp Vue3组合式API版本" target="_blank">零基础入门uniapp Vue3组合式API版本</a>
                        <span class="text-muted">鹤早早</span>
<a class="tag" taget="_blank" href="/search/uni-app/1.htm">uni-app</a>
                        <div>前言:小程序学习笔记,课程来源up主咸虾米_。仅记录笔记,大家想学习可以去关注他。1.已安装HBuiderX(目前是4.36版本),微信开发者工具(但还没注册小程序码),相关配置OK了。1.16相关架构学习1.pages-index-index.vuebox1box2.layout{border:1pxsolidred;.box1{border:1pxsolidgreen;}.box2{borde</div>
                    </li>
                    <li><a href="/article/1950053798433058816.htm"
                           title="页面开发样式和布局入门:Vite + Vue 3 + Less" target="_blank">页面开发样式和布局入门:Vite + Vue 3 + Less</a>
                        <span class="text-muted"></span>

                        <div>页面开发样式和布局入门:Vite+Vue3+Less引言在现代前端开发中,样式和布局是页面开发的核心部分。随着技术的不断发展,Vite、Vue3和Less等工具和框架的出现,使得前端开发变得更加高效和灵活。然而,尽管这些工具和框架提供了强大的功能,但在实际开发中仍然会遇到各种样式和布局的问题。本文将结合Vite、Vue3和Less,详细介绍在页面开发中常见的样式和布局问题,并提供解决方案和最佳实践</div>
                    </li>
                    <li><a href="/article/1950052562644299776.htm"
                           title="还在为月光族发愁吗?《小狗钱钱》:一本让你立刻就能行动并学会的理财书" target="_blank">还在为月光族发愁吗?《小狗钱钱》:一本让你立刻就能行动并学会的理财书</a>
                        <span class="text-muted">彪姐说事</span>

                        <div>我选择了《小狗钱钱》。选择这本书的理由:一、为什么会选择这本书?答:以前投资和理财思维错误,导致今天的败局,给自己人生带来了很大的打击。第一次读这本书非常有趣,这本是写给儿童的理财书却对成人有着很大的启发作用,很多成功人士都推荐这本书,认为这是理财入门的一本非常适合的书。我很喜欢里面的理财思路:1.写成功日记,也是一种为自己定投的方式。2.一切学习的结果,都建立在践行的基础上。3.正确的事情,简单</div>
                    </li>
                    <li><a href="/article/1950044088598327296.htm"
                           title="学习Java项目--尚庭公寓--第7天" target="_blank">学习Java项目--尚庭公寓--第7天</a>
                        <span class="text-muted">流萤老公学Java</span>
<a class="tag" taget="_blank" href="/search/Java%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%90%8E%E7%AB%AF%E9%A1%B9%E7%9B%AE%E5%AD%A6%E4%B9%A0/1.htm">Java第一个后端项目学习</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a>
                        <div>一、开发准备:Redis、knife4j入门1、Redis客户端使用redisInsight官方开源的图形化客户端。也可以使用命令行模式2、常用数据类型及命令通用命令(各种数据类型都可以使用的命令):keys*:查看所有键dbsize:查看键个数exists:判断key是否存在del:删除keyttl:查看key剩余过期时间String类型命令:setkeyvalue:getkeyincr:自增操</div>
                    </li>
                                <li><a href="/article/96.htm"
                                       title="怎么样才能成为专业的程序员?" target="_blank">怎么样才能成为专业的程序员?</a>
                                    <span class="text-muted">cocos2d-x小菜</span>
<a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B/1.htm">编程</a><a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a>
                                    <div>  
如何要想成为一名专业的程序员?仅仅会写代码是不够的。从团队合作去解决问题到版本控制,你还得具备其他关键技能的工具包。当我们询问相关的专业开发人员,那些必备的关键技能都是什么的时候,下面是我们了解到的情况。 
  
关于如何学习代码,各种声音很多,然后很多人就被误导为成为专业开发人员懂得一门编程语言就够了?!呵呵,就像其他工作一样,光会一个技能那是远远不够的。如果你想要成为</div>
                                </li>
                                <li><a href="/article/223.htm"
                                       title="java web开发 高并发处理" target="_blank">java web开发 高并发处理</a>
                                    <span class="text-muted">BreakingBad</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/Web/1.htm">Web</a><a class="tag" taget="_blank" href="/search/%E5%B9%B6%E5%8F%91/1.htm">并发</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91/1.htm">开发</a><a class="tag" taget="_blank" href="/search/%E5%A4%84%E7%90%86/1.htm">处理</a><a class="tag" taget="_blank" href="/search/%E9%AB%98/1.htm">高</a>
                                    <div>java处理高并发高负载类网站中数据库的设计方法(java教程,java处理大量数据,java高负载数据) 一:高并发高负载类网站关注点之数据库 没错,首先是数据库,这是大多数应用所面临的首个SPOF。尤其是Web2.0的应用,数据库的响应是首先要解决的。 一般来说MySQL是最常用的,可能最初是一个mysql主机,当数据增加到100万以上,那么,MySQL的效能急剧下降。常用的优化措施是M-S(</div>
                                </li>
                                <li><a href="/article/350.htm"
                                       title="mysql批量更新" target="_blank">mysql批量更新</a>
                                    <span class="text-muted">ekian</span>
<a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a>
                                    <div>mysql更新优化: 
一版的更新的话都是采用update set的方式,但是如果需要批量更新的话,只能for循环的执行更新。或者采用executeBatch的方式,执行更新。无论哪种方式,性能都不见得多好。 
三千多条的更新,需要3分多钟。 
查询了批量更新的优化,有说replace into的方式,即: 
 
 
replace into tableName(id,status) values</div>
                                </li>
                                <li><a href="/article/477.htm"
                                       title="微软BI(3)" target="_blank">微软BI(3)</a>
                                    <span class="text-muted">18289753290</span>
<a class="tag" taget="_blank" href="/search/%E5%BE%AE%E8%BD%AFBI+SSIS/1.htm">微软BI SSIS</a>
                                    <div>1) 
Q:该列违反了完整性约束错误;已获得 OLE DB 记录。源:“Microsoft SQL Server Native Client 11.0” Hresult: 0x80004005 说明:“不能将值 NULL 插入列 'FZCHID',表 'JRB_EnterpriseCredit.dbo.QYFZCH';列不允许有 Null 值。INSERT 失败。”。 
A:一般这类问题的存在是 </div>
                                </li>
                                <li><a href="/article/604.htm"
                                       title="Java中的List" target="_blank">Java中的List</a>
                                    <span class="text-muted">g21121</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                                    <div>        List是一个有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。 
        与 set 不同,列表通常允许重复</div>
                                </li>
                                <li><a href="/article/731.htm"
                                       title="读书笔记" target="_blank">读书笔记</a>
                                    <span class="text-muted">永夜-极光</span>
<a class="tag" taget="_blank" href="/search/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/1.htm">读书笔记</a>
                                    <div>   1.  K是一家加工厂,需要采购原材料,有A,B,C,D 4家供应商,其中A给出的价格最低,性价比最高,那么假如你是这家企业的采购经理,你会如何决策? 
    
     传统决策: A:100%订单  B,C,D:0% 
  
  &nbs</div>
                                </li>
                                <li><a href="/article/858.htm"
                                       title="centos 安装 Codeblocks" target="_blank">centos 安装 Codeblocks</a>
                                    <span class="text-muted">随便小屋</span>
<a class="tag" taget="_blank" href="/search/codeblocks/1.htm">codeblocks</a>
                                    <div>1.安装gcc,需要c和c++两部分,默认安装下,CentOS不安装编译器的,在终端输入以下命令即可yum install gccyum install gcc-c++ 
  
2.安装gtk2-devel,因为默认已经安装了正式产品需要的支持库,但是没有安装开发所需要的文档.yum install gtk2* 
3. 安装wxGTK 
   yum search w</div>
                                </li>
                                <li><a href="/article/985.htm"
                                       title="23种设计模式的形象比喻" target="_blank">23种设计模式的形象比喻</a>
                                    <span class="text-muted">aijuans</span>
<a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a>
                                    <div>1、ABSTRACT FACTORY—追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯德基,只管向服务员说“来四个鸡翅”就行了。麦当劳和肯德基就是生产鸡翅的Factory    工厂模式:客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:</div>
                                </li>
                                <li><a href="/article/1112.htm"
                                       title="开发管理 CheckLists" target="_blank">开发管理 CheckLists</a>
                                    <span class="text-muted">aoyouzi</span>
<a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E7%AE%A1%E7%90%86+CheckLists/1.htm">开发管理 CheckLists</a>
                                    <div>开发管理 CheckLists(23) -使项目组度过完整的生命周期 
开发管理 CheckLists(22) -组织项目资源 
开发管理 CheckLists(21) -控制项目的范围开发管理 CheckLists(20) -项目利益相关者责任开发管理 CheckLists(19) -选择合适的团队成员开发管理 CheckLists(18) -敏捷开发 Scrum Master 工作开发管理 C</div>
                                </li>
                                <li><a href="/article/1239.htm"
                                       title="js实现切换" target="_blank">js实现切换</a>
                                    <span class="text-muted">百合不是茶</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/%E6%A0%8F%E7%9B%AE%E5%88%87%E6%8D%A2/1.htm">栏目切换</a>
                                    <div>js主要功能之一就是实现页面的特效,窗体的切换可以减少页面的大小,被门户网站大量应用思路: 
   1,先将要显示的设置为display:bisible  否则设为none
    2,设置栏目的id  ,js获取栏目的id,如果id为Null就设置为显示
    3,判断js获取的id名字;再设置是否显示
 
  
代码实现: 
  
html代码: 
  <di</div>
                                </li>
                                <li><a href="/article/1366.htm"
                                       title="周鸿祎在360新员工入职培训上的讲话" target="_blank">周鸿祎在360新员工入职培训上的讲话</a>
                                    <span class="text-muted">bijian1013</span>
<a class="tag" taget="_blank" href="/search/%E6%84%9F%E6%82%9F/1.htm">感悟</a><a class="tag" taget="_blank" href="/search/%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86/1.htm">项目管理</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E7%94%9F/1.htm">人生</a><a class="tag" taget="_blank" href="/search/%E8%81%8C%E5%9C%BA/1.htm">职场</a>
                                    <div>        这篇文章也是最近偶尔看到的,考虑到原博客发布者可能将其删除等原因,也更方便个人查找,特将原文拷贝再发布的。“学东西是为自己的,不要整天以混的姿态来跟公司博弈,就算是混,我觉得你要是能在混的时间里,收获一些别的有利于人生发展的东西,也是不错的,看你怎么把握了”,看了之后,对这句话记忆犹新。  &</div>
                                </li>
                                <li><a href="/article/1493.htm"
                                       title="前端Web开发的页面效果" target="_blank">前端Web开发的页面效果</a>
                                    <span class="text-muted">Bill_chen</span>
<a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/Web/1.htm">Web</a><a class="tag" taget="_blank" href="/search/Microsoft/1.htm">Microsoft</a>
                                    <div>1.IE6下png图片的透明显示: 
<img src="图片地址" border="0" style="Filter.Alpha(Opacity)=数值(100),style=数值(3)"/> 
或在<head></head>间加一段JS代码让透明png图片正常显示。 
 
2.<li>标</div>
                                </li>
                                <li><a href="/article/1620.htm"
                                       title="【JVM五】老年代垃圾回收:并发标记清理GC(CMS GC)" target="_blank">【JVM五】老年代垃圾回收:并发标记清理GC(CMS GC)</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/1.htm">垃圾回收</a>
                                    <div>  CMS概述 
并发标记清理垃圾回收(Concurrent Mark and Sweep GC)算法的主要目标是在GC过程中,减少暂停用户线程的次数以及在不得不暂停用户线程的请夸功能,尽可能短的暂停用户线程的时间。这对于交互式应用,比如web应用来说,是非常重要的。 
  
CMS垃圾回收针对新生代和老年代采用不同的策略。相比同吞吐量垃圾回收,它要复杂的多。吞吐量垃圾回收在执</div>
                                </li>
                                <li><a href="/article/1747.htm"
                                       title="Struts2技术总结" target="_blank">Struts2技术总结</a>
                                    <span class="text-muted">白糖_</span>
<a class="tag" taget="_blank" href="/search/struts2/1.htm">struts2</a>
                                    <div>  
 
 必备jar文件 
   
 早在struts2.0.*的时候,struts2的必备jar包需要如下几个: 
commons-logging-*.jar   Apache旗下commons项目的log日志包 
freemarker-*.jar          </div>
                                </li>
                                <li><a href="/article/1874.htm"
                                       title="Jquery easyui layout应用注意事项" target="_blank">Jquery easyui layout应用注意事项</a>
                                    <span class="text-muted">bozch</span>
<a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a><a class="tag" taget="_blank" href="/search/%E6%B5%8F%E8%A7%88%E5%99%A8/1.htm">浏览器</a><a class="tag" taget="_blank" href="/search/easyui/1.htm">easyui</a><a class="tag" taget="_blank" href="/search/layout/1.htm">layout</a>
                                    <div>在jquery easyui中提供了easyui-layout布局,他的布局比较局限,类似java中GUI的border布局。下面对其使用注意事项作简要介绍: 
     如果在现有的工程中前台界面均应用了jquery easyui,那么在布局的时候最好应用jquery eaysui的layout布局,否则在表单页面(编辑、查看、添加等等)在不同的浏览器会出</div>
                                </li>
                                <li><a href="/article/2001.htm"
                                       title="java-拷贝特殊链表:有一个特殊的链表,其中每个节点不但有指向下一个节点的指针pNext,还有一个指向链表中任意节点的指针pRand,如何拷贝这个特殊链表?" target="_blank">java-拷贝特殊链表:有一个特殊的链表,其中每个节点不但有指向下一个节点的指针pNext,还有一个指向链表中任意节点的指针pRand,如何拷贝这个特殊链表?</a>
                                    <span class="text-muted">bylijinnan</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                                    <div>
public class CopySpecialLinkedList {

	/**
	 * 题目:有一个特殊的链表,其中每个节点不但有指向下一个节点的指针pNext,还有一个指向链表中任意节点的指针pRand,如何拷贝这个特殊链表?
拷贝pNext指针非常容易,所以题目的难点是如何拷贝pRand指针。
假设原来链表为A1 -> A2 ->... -> An,新拷贝</div>
                                </li>
                                <li><a href="/article/2128.htm"
                                       title="color" target="_blank">color</a>
                                    <span class="text-muted">Chen.H</span>
<a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a>
                                    <div><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"  "http://www.w3.org/TR/html4/loose.dtd">    <HTML>    <HEAD>&nbs</div>
                                </li>
                                <li><a href="/article/2255.htm"
                                       title="[信息与战争]移动通讯与网络" target="_blank">[信息与战争]移动通讯与网络</a>
                                    <span class="text-muted">comsci</span>
<a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a>
                                    <div>      两个坚持:手机的电池必须可以取下来 
               光纤不能够入户,只能够到楼宇 
 
      建议大家找这本书看看:<&</div>
                                </li>
                                <li><a href="/article/2382.htm"
                                       title="oracle flashback query(闪回查询)" target="_blank">oracle flashback query(闪回查询)</a>
                                    <span class="text-muted">daizj</span>
<a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/flashback+query/1.htm">flashback query</a><a class="tag" taget="_blank" href="/search/flashback+table/1.htm">flashback table</a>
                                    <div>在Oracle 10g中,Flash back家族分为以下成员: 
Flashback Database 
Flashback Drop 
Flashback Table 
Flashback Query(分Flashback Query,Flashback Version Query,Flashback Transaction Query) 
下面介绍一下Flashback Drop 和Flas</div>
                                </li>
                                <li><a href="/article/2509.htm"
                                       title="zeus持久层DAO单元测试" target="_blank">zeus持久层DAO单元测试</a>
                                    <span class="text-muted">deng520159</span>
<a class="tag" taget="_blank" href="/search/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/1.htm">单元测试</a>
                                    <div>zeus代码测试正紧张进行中,但由于工作比较忙,但速度比较慢.现在已经完成读写分离单元测试了,现在把几种情况单元测试的例子发出来,希望有人能进出意见,让它走下去. 
本文是zeus的dao单元测试: 
1.单元测试直接上代码 
  
package com.dengliang.zeus.webdemo.test;


import org.junit.Test;
import o</div>
                                </li>
                                <li><a href="/article/2636.htm"
                                       title="C语言学习三printf函数和scanf函数学习" target="_blank">C语言学习三printf函数和scanf函数学习</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/c/1.htm">c</a><a class="tag" taget="_blank" href="/search/printf/1.htm">printf</a><a class="tag" taget="_blank" href="/search/scanf/1.htm">scanf</a><a class="tag" taget="_blank" href="/search/language/1.htm">language</a>
                                    <div>printf函数 
/*
	2013年3月10日20:42:32
	地点:北京潘家园
	功能:
	目的:
		测试%x %X %#x %#X的用法
 */

# include <stdio.h>

int main(void)
{

	printf("哈哈!\n");  // \n表示换行

	int i = 10;
	printf</div>
                                </li>
                                <li><a href="/article/2763.htm"
                                       title="那你为什么小时候不好好读书?" target="_blank">那你为什么小时候不好好读书?</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/life/1.htm">life</a>
                                    <div>dady, 我今天捡到了十块钱, 不过我还给那个人了 
good girl! 那个人有没有和你讲thank you啊 
没有啦....他拉我的耳朵我才把钱还给他的, 他哪里会和我讲thank you 
  
爸爸, 如果地上有一张5块一张10块你拿哪一张呢.... 
当然是拿十块的咯... 
爸爸你很笨的, 你不会两张都拿 
  
爸爸为什么上个月那个人来跟你讨钱, 你告诉他没</div>
                                </li>
                                <li><a href="/article/2890.htm"
                                       title="iptables开放端口" target="_blank">iptables开放端口</a>
                                    <span class="text-muted">Fanyucai</span>
<a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/iptables/1.htm">iptables</a><a class="tag" taget="_blank" href="/search/%E7%AB%AF%E5%8F%A3/1.htm">端口</a>
                                    <div>1,找到配置文件 
vi /etc/sysconfig/iptables   
  
2,添加端口开放,增加一行,开放18081端口 
-A INPUT -m state --state NEW -m tcp -p tcp --dport 18081 -j ACCEPT 
  
3,保存 
ESC
:wq! 
  
4,重启服务 
service iptables </div>
                                </li>
                                <li><a href="/article/3017.htm"
                                       title="Ehcache(05)——缓存的查询" target="_blank">Ehcache(05)——缓存的查询</a>
                                    <span class="text-muted">234390216</span>
<a class="tag" taget="_blank" href="/search/%E6%8E%92%E5%BA%8F/1.htm">排序</a><a class="tag" taget="_blank" href="/search/ehcache/1.htm">ehcache</a><a class="tag" taget="_blank" href="/search/%E7%BB%9F%E8%AE%A1/1.htm">统计</a><a class="tag" taget="_blank" href="/search/query/1.htm">query</a>
                                    <div>缓存的查询 
目录 
1.    使Cache可查询 
1.1     基于Xml配置 
1.2     基于代码的配置 
2     指定可搜索的属性 
2.1     可查询属性类型 
2.2 &</div>
                                </li>
                                <li><a href="/article/3144.htm"
                                       title="通过hashset找到数组中重复的元素" target="_blank">通过hashset找到数组中重复的元素</a>
                                    <span class="text-muted">jackyrong</span>
<a class="tag" taget="_blank" href="/search/hashset/1.htm">hashset</a>
                                    <div>  如何在hashset中快速找到重复的元素呢?方法很多,下面是其中一个办法: 
 
 


 int[] array = {1,1,2,3,4,5,6,7,8,8};
         
        Set<Integer> set = new HashSet<Integer>();
         
        for(int i = 0</div>
                                </li>
                                <li><a href="/article/3271.htm"
                                       title="使用ajax和window.history.pushState无刷新改变页面内容和地址栏URL" target="_blank">使用ajax和window.history.pushState无刷新改变页面内容和地址栏URL</a>
                                    <span class="text-muted">lanrikey</span>
<a class="tag" taget="_blank" href="/search/history/1.htm">history</a>
                                    <div>后退时关闭当前页面 
<script type="text/javascript"> 
 jQuery(document).ready(function ($) { 
        if (window.history && window.history.pushState) {</div>
                                </li>
                                <li><a href="/article/3398.htm"
                                       title="应用程序的通信成本" target="_blank">应用程序的通信成本</a>
                                    <span class="text-muted">netkiller.github.com</span>
<a class="tag" taget="_blank" href="/search/%E8%99%9A%E6%8B%9F%E6%9C%BA/1.htm">虚拟机</a><a class="tag" taget="_blank" href="/search/%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">应用服务器</a><a class="tag" taget="_blank" href="/search/%E9%99%88%E6%99%AF%E5%B3%B0/1.htm">陈景峰</a><a class="tag" taget="_blank" href="/search/netkiller/1.htm">netkiller</a><a class="tag" taget="_blank" href="/search/neo/1.htm">neo</a>
                                    <div>应用程序的通信成本  
什么是通信 
一个程序中两个以上功能相互传递信号或数据叫做通信。  
什么是成本 
这是是指时间成本与空间成本。 时间就是传递数据所花费的时间。空间是指传递过程耗费容量大小。  
都有哪些通信方式 
 
 全局变量 
 线程间通信 
 共享内存 
 共享文件 
 管道 
 Socket 
 硬件(串口,USB) 等等 
  
全局变量 
全局变量是成本最低通信方法,通过设置</div>
                                </li>
                                <li><a href="/article/3525.htm"
                                       title="一维数组与二维数组的声明与定义" target="_blank">一维数组与二维数组的声明与定义</a>
                                    <span class="text-muted">恋洁e生</span>
<a class="tag" taget="_blank" href="/search/%E4%BA%8C%E7%BB%B4%E6%95%B0%E7%BB%84/1.htm">二维数组</a><a class="tag" taget="_blank" href="/search/%E4%B8%80%E7%BB%B4%E6%95%B0%E7%BB%84/1.htm">一维数组</a><a class="tag" taget="_blank" href="/search/%E5%AE%9A%E4%B9%89/1.htm">定义</a><a class="tag" taget="_blank" href="/search/%E5%A3%B0%E6%98%8E/1.htm">声明</a><a class="tag" taget="_blank" href="/search/%E5%88%9D%E5%A7%8B%E5%8C%96/1.htm">初始化</a>
                                    <div>/**  *   */ package test20111005; /**  * @author FlyingFire  * @date:2011-11-18 上午04:33:36  * @author :代码整理  * @introduce :一维数组与二维数组的初始化  *summary:  */ public c</div>
                                </li>
                                <li><a href="/article/3652.htm"
                                       title="Spring Mybatis独立事务配置" target="_blank">Spring Mybatis独立事务配置</a>
                                    <span class="text-muted">toknowme</span>
<a class="tag" taget="_blank" href="/search/mybatis/1.htm">mybatis</a>
                                    <div>在项目中有很多地方会使用到独立事务,下面以获取主键为例      
(1)修改配置文件spring-mybatis.xml    <!-- 开启事务支持 -->    <tx:annotation-driven transaction-manager="transactionManager" />       &n</div>
                                </li>
                                <li><a href="/article/3779.htm"
                                       title="更新Anadroid SDK Tooks之后,Eclipse提示No update were found" target="_blank">更新Anadroid SDK Tooks之后,Eclipse提示No update were found</a>
                                    <span class="text-muted">xp9802</span>
<a class="tag" taget="_blank" href="/search/eclipse/1.htm">eclipse</a>
                                    <div>使用Android SDK Manager 更新了Anadroid SDK Tooks 之后, 
打开eclipse提示 This Android SDK requires Android Developer Toolkit version 23.0.0 or above, 点击Check for Updates  
检测一会后提示 No update were found  </div>
                                </li>
                </ul>
            </div>
        </div>
    </div>

<div>
    <div class="container">
        <div class="indexes">
            <strong>按字母分类:</strong>
            <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a
                href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a
                href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a
                href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a
                href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a
                href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a
                href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a
                href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a
                href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a>
        </div>
    </div>
</div>
<footer id="footer" class="mb30 mt30">
    <div class="container">
        <div class="footBglm">
            <a target="_blank" href="/">首页</a> -
            <a target="_blank" href="/custom/about.htm">关于我们</a> -
            <a target="_blank" href="/search/Java/1.htm">站内搜索</a> -
            <a target="_blank" href="/sitemap.txt">Sitemap</a> -
            <a target="_blank" href="/custom/delete.htm">侵权投诉</a>
        </div>
        <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved.
<!--            <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>-->
        </div>
    </div>
</footer>
<!-- 代码高亮 -->
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script>
<link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/>
<script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script>





</body>

</html>