ES2015-笔记.md

声明

本文是学习阮一峰 - ECMAScript 6 入门时的笔记,内容为用来作为备忘速查的知识点。推荐大家阅读原版,以便于对ES6有更清晰完整的认识。

版本概述

说明
    ECMAScript 2016 开始,ECMAScript将进入每年发布一次新标准的阶段。
    浏览器引擎通常6周一次版本。
    ECMA TC39 是制定ECMAScript标准的组织。主要包括各大JS引擎厂商员工。
    最后是通过 ECMA General Assembly 来表决标准是否通过。

let和const

let
    说明
        用来声明变量,和var类似,有块级作用域特性,只在代码块内有效,
        可用于循环,let声明的变量只在本次循环有效
        不允许重复声明
        不存在变量提升
        暂时性死区(temporal dead zone,简称TDZ)
    范例
        无变量提升
            console.log(foo); // 输出undefined
            console.log(bar); // 报错ReferenceError
            var foo = 2;
            let bar = 2;
        暂时性死区
            var tmp = 123;
            if (true) {
              tmp = 'abc'; // ReferenceError
              let tmp;
            }
块级作用域中函数声明
    说明
        ES5中规定函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域之中声明。
        虽然浏览器并没有遵守ES5的规定,但在严格模式下函数在块级作用域声明会报错。
        ES6引入了块级作用域,明确允许在块级作用域之中声明函数。
        ES6函数声明类似于var,即会提升到全局作用域或函数作用域的头部.
        ES6中函数特性在不同的浏览器和平台表现不同。开发时需注意平台。
    范例
        function f() { console.log('I am outside!'); }
        (function () {
          if (false) {
            // 重复声明一次函数f
            function f() { console.log('I am inside!'); }
          }
          f();
        }());
        //ES5中运行,输出 I am inside!
        //ES6中运行,会报错,f is not a function
        //ES6严格模式下中运行,会输出 I am outside!

const
    说明
        声明常量,不能改变值
        一旦声明变量,就必须立即初始化赋值
        对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。
        其他特性与let相同

全局对象的属性
    说明
        var命令和function命令声明的全局变量,依旧是全局对象的属性;
        let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。
    范例
        var a = 1;
        // 如果在Node的REPL环境,可以写成global.a
        // 或者采用通用方法,写成this.a
        window.a // 1
        let b = 1;
        window.b // undefined

变量的解构赋值

数组的解构赋值
    说明
        ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
        如果解构不成功,变量的值就等于undefined。
        等号的右边不是不是可遍历的结构,将会报错。
        只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
        解构赋值不仅适用于var命令,也适用于let和const命令。
    范例
        数组的解构赋值
            var [a, b, c] = [1, 2, 3];
            let [foo, [[bar], baz]] = [1, [[2], 3]];
            let [ , , third] = ["foo", "bar", "baz"];
            let [x, , y] = [1, 2, 3];
            let [head, ...tail] = [1, 2, 3, 4];
        不完全解构
            let [x, y] = [1, 2, 3];
            let [a, [b], d] = [1, [2, 3], 4];
        报错情况
            let [foo] = 1;
            let [foo] = false;
            let [foo] = NaN;
            let [foo] = undefined;
            let [foo] = null;
            let [foo] = {};
        对Set结构解构
            let [x, y, z] = new Set(["a", "b", "c"]);
        对提供Iterator接口的数据结构解构
            function* fibs() {
              var a = 0;
              var b = 1;
              while (true) {
                yield a;
                [a, b] = [b, a + b];
              }
            }
            var [first, second, third, fourth, fifth, sixth] = fibs();

默认值
    说明
        解构赋值允许指定默认值。
        如果解构出的值===undefined,那么变量的值将为默认值。
        默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
    范例
        指定默认值
            var [foo = true] = [];             // foo=true
            [x, y = 'b'] = ['a'];              // x='a', y='b'
            [x, y = 'b'] = ['a', undefined];   // x='a', y='b'
        undefined情况
            var [x = 1] = [undefined];         // x=1
            var [x = 1] = [null];              // x=null
        懒加载情况
            function f() { return 0; };
            let [x = f()] = [1];               // x=1
        默认值可以引用其他变量
            let [x = 1, y = x] = [];           // x=1; y=1
            let [x = 1, y = x] = [2];          // x=2; y=2
            let [x = 1, y = x] = [1, 2];       // x=1; y=2
            let [x = y, y = 1] = [];           // ReferenceError

对象的解构赋值
    说明
        对象的解构赋值没有次序,只要变量与属性同名,即可取到正确的值。
        如果变量名与属性名不一致需指定属性名,格式为 {属性名:变量名}。
        如果变量的声明和赋值是一体的,需注意重复声明会报错。
        解构也可以用于嵌套结构的对象。
        可指定默认值,默认值生效的条件是,对象的属性值严格等于undefined。
        如果解构失败,变量的值等于undefined。
        需注意不能将大括号写在行首,避免JavaScript将其解释为代码块。
        解构赋值允许,等号左边的模式之中,不放置任何变量名,虽然没有意义。
    范例
        变量名属性名一致
            var { bar, foo } = { foo: "aaa", bar: "bbb" };
            // foo="aaa"  bar="bbb"
            var { baz } = { foo: "aaa", bar: "bbb" };
            // baz=undefined
        变量名属性名不一致
            var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
            // baz="aaa"
            let obj = { first: 'hello', last: 'world' };
            let { first: f, last: l } = obj;
            // f='hello' l='world'
        重复声明
            let foo; 
            let {foo} = {foo: 1};        // SyntaxError
            let baz; 
            let {bar: baz} = {bar: 1};   // SyntaxError
            let foo;
            ({foo} = {foo: 1});          // 成功
            let baz;
            ({bar: baz} = {bar: 1});     // 成功
        嵌套
            var obj = {
              p: ['Hello', {y: 'World'}]
            };
            var { p: [x, { y }] } = obj;
            // x="Hello" y="World"
        默认值
            var {x = 3} = {};            // x=3
            var {x, y = 5} = {x: 1};     // x=1 y=5
        解构失败
            var {foo} = {bar: 'baz'};    // foo=undefined
        不放置任何变量名
            ({} = [true, false]);
            ({} = 'abc');
            ({} = []);
        对数组进行对象属性的解构
            var arr = [1, 2, 3];
            var {0 : first, [arr.length - 1] : last} = arr;
            // first=1 last=3

字符串的解构赋值
    说明
        赋值时,字符串被转换成了一个类似数组的对象
        赋值时,可对字符串的属性解构
    范例
        const [a, b, c, d, e] = 'hello';
        let {length : len} = 'hello';

数值和布尔值的解构赋值
    说明
        解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
        如果等号右边的值无法转换为对象,则报错。
    范例
        let {toString: s} = 123;
        s === Number.prototype.toString   // true
        let { prop: x } = undefined;      // TypeError
        let { prop: y } = null;           // TypeError

函数参数的解构赋值
    说明
        函数的参数可以使用解构赋值。
        函数参数的解构可以使用默认值。
    范例
        函数的参数解构赋值
            function add([x, y]){return x + y;}; add([1, 2]);  //结果为3
            [[1, 2], [3, 4]].map(([a, b]) => a + b)            // [3,7]
        函数参数的解构使用默认值
            function move({x = 0, y = 0} = {}) {return [x, y]; }
            move({x: 3, y: 8});       // [3, 8]
            move({x: 3});             // [3, 0]
            move({});                 // [0, 0]
            move();                   // [0, 0]
        函数参数的解构使用默认值另外结果
            function move({x, y} = { x: 0, y: 0 }) { return [x, y]; }
            move({x: 3, y: 8});       // [3, 8]
            move({x: 3});             // [3, undefined]
            move({});                 // [undefined, undefined]
            move();                   // [0, 0]

圆括号问题
    说明
        ES6的规则是,只要有可能导致解构的歧义,就不得使用圆括号。
        可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。
        声明语句中,不能带有圆括号。
        函数参数中,函数参数也属于变量声明,因此不能带有圆括号。
        赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中。
    范例
        声明语句中(报错)
            var [(a)] = [1];
            var {x: (c)} = {};
            var ({x: c}) = {};
            var {(x: c)} = {};
            var {(x): c} = {};}
            var { o: ({ p: p }) } = { o: { p: 2 } };
        函数参数中(报错)
            function f([(z)]) { return z; }
        赋值语句中(报错)
            ({ p: a }) = { p: 42 };
            ([a]) = [5];
            [({ p: a }), { x: c }] = [{}, {}];
        赋值语句的非模式部分,可以使用圆括号
            [(b)] = [3];
            ({ p: (d) } = {});
            [(parseInt.prop)] = [3];

用途
    范例
        交换变量的值
            [x, y] = [y, x];
        从函数返回多个值
            function example() {return [1, 2, 3]; }
            var [a, b, c] = example();
        函数参数的定义
            function f([x, y, z]) { ... }
            f([1, 2, 3])
        提取JSON数据
            var jsonData = {
                id: 42,
                status: "OK",
                data: [867, 5309]
            }
            let { id, status, data: number } = jsonData;
        函数参数的默认值
            jQuery.ajax = function (url, {
                async = true,
                beforeSend = function () {},
                cache = true,
                complete = function () {},
                crossDomain = false,
                global = true,
                // ... more config
            }) {
                // ... do stuff
            };
        遍历Map结构
            var map = new Map();
            map.set('first', 'hello');
            map.set('second', 'world');
            for (let [key, value] of map) {
                console.log(key + " is " + value);
            }
        加载模块的指定方法
            const { SourceMapConsumer, SourceNode } = require("source-map");

##字符串扩展

字符的Unicode表示法
    说明
        JavaScript允许采用\uxxxx形式表示一个字符,其中“xxxx”表示字符的码点。
        超出\u0000——\uFFFF范围的字符,将码点放入大括号,就能正确解读该字符
    范例
        大括号表示Unicode字符
            "\u{20BB7}"              // "𠮷"
            "\u{41}\u{42}\u{43}"     // "ABC"
        6种表示一个字符的方法
            '\z' === 'z'             // true
            '\172' === 'z'           // true
            '\x7A' === 'z'           // true
            '\u007A' === 'z'         // true
            '\u{7A}' === 'z'         // true

codePointAt()
    说明
        JavaScript内部,字符以UTF-16的格式储存,每个字符固定为2个字节。
        需4个字节储存的字符(码点大于0xFFFF),JavaScript会认为它们是两个字符。
        codePointAt方法能够正确处理4个字节储存的字符,返回一个字符的码点。
    范例
        大于0xFFFF的字符
            var s = "𠮷";
            s.length                       // 2
            s.charAt(0)                    // ''
            s.charAt(1)                    // ''
            s.charCodeAt(0)                // 55362
            s.charCodeAt(1)                // 57271
        codePointAt
            var s = '𠮷a';
            s.codePointAt(0)               // 134071 既十六进制的20BB7
            s.codePointAt(1)               // 57271
            s.charCodeAt(2)                // 97
            s.codePointAt(0).toString(16)  // "20bb7"
            s.charCodeAt(2).toString(16)   // "61"
        测试一个字符是否是32位字符
            function is32Bit(c) {
                return c.codePointAt(0) > 0xFFFF;
            }
            is32Bit("𠮷")   // true
            is32Bit("a")    // false

String.fromCodePoint()
    说明
        ES5中的String.fromCharCode方法,可从码点返回对应字符,无法识别32位UTF-16字符
        ES6提供了String.fromCodePoint,功能与fromCharCode类似。
        可以识别32位的UTF-16字符。
        如果String.fromCodePoint方法有多个参数,则它们会被合并成一个字符串返回。
    范例
        String.fromCharCode(0x20BB7)   // "ஷ"
        String.fromCodePoint(0x20BB7)  // "𠮷"
        String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
        // true

字符串的遍历器接口
    说明
        ES6为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历。
        除了遍历字符串,这个遍历器最大的优点是可以识别大于0xFFFF的码点。
    范例
        遍历
            for (let codePoint of 'foo') {
              console.log(codePoint)
            }
        遍历32位字符
            var text = String.fromCodePoint(0x20BB7);
            for (let i = 0; i < text.length; i++) {
              console.log(text[i]);
            }
            // " "
            // " "
            for (let i of text) {
              console.log(i);
            }
            // "𠮷"

at()
    说明
        ES5中的charAt方法,可返回字符串给定位置的字符。但不识别码点大于0xFFFF的字符。
        ES6提案中的at方法识别Unicode编号大于0xFFFF的字符。
    范例
        'abc'.charAt(0)  // "a"
        '𠮷'.charAt(0)    // "\uD842"
        'abc'.at(0)      // "a"
        '𠮷'.at(0)        // "𠮷"

normalize()
    说明
        对于欧洲语言中的带有重音符号等字符,Unicode提供了两种方法表示。
        一种是直接提供带重音符号的字符,比如Ǒ(\u01D1)。
        另一种是合成符号,即原字符与重音符号两个字符合成一个字符。
        如O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。
        这两种表示方法,在视觉和语义上都等价,但是JavaScript不能识别。
        ES6提供normalize()方法,用来将字符的不同表示方法统一为同样的形式。
        这称为Unicode正规化。
    参数
        NFC    标准等价合成,默认,指的是视觉和语义上的等价。
        NFD    标准等价分解,在标准等价的前提下,返回合成字符分解的多个简单字符。
        NFKC   兼容等价合成,指的是语义上存在等价,但视觉上不等价。
        NFKD   兼容等价分解,兼容等价的前提下,返回合成字符分解的多个简单字符。
    范例
        不带参数
            '\u01D1'==='\u004F\u030C'   //false
            '\u01D1'.length             // 1
            '\u004F\u030C'.length       // 2
            '\u01D1'.normalize() === '\u004F\u030C'.normalize()  //true
        带有参数
            '\u004F\u030C'.normalize('NFC').length // 1
            '\u004F\u030C'.normalize('NFD').length // 2

includes(), startsWith(), endsWith()
    说明
        之前JavaScript只有indexOf方法,用来确定一个字符串是否包含在另一个字符串中。
        ES6又提供了三种新方法,这三个方法都支持第二个参数,表示开始搜索的位置。
    方法
        includes()      返回布尔,表示是否找到了参数字符串。
        startsWith()    返回布尔,表示参数字符串是否在源字符串的头部。
        endsWith()      返回布尔,表示参数字符串是否在源字符串的尾部
    范例
        var s = 'Hello world!';
        s.startsWith('Hello')        // true
        s.endsWith('!')              // true
        s.includes('o')              // true
        s.startsWith('world', 6)     // true
        s.endsWith('Hello', 5)       // true
        s.includes('Hello', 6)       // false

repeat()
    说明
        repeat方法返回一个新字符串,表示将原字符串重复n次。
        参数如果是小数,会被取整。
    范例
        'x'.repeat(3)            // "xxx"
        'hello'.repeat(2)        // "hellohello"
        'na'.repeat(0)           // ""
        'na'.repeat(2.9)         // "nana"
        'na'.repeat(Infinity)    // RangeError
        'na'.repeat(-1)          // RangeError
        'na'.repeat(-0.9)        // ""
        'na'.repeat(NaN)         // ""
        'na'.repeat('na')        // ""
        'na'.repeat('3')         // "nanana"

padStart(),padEnd()
    说明
        ES7的函数,如果某个字符串不够指定长度,会在头部或尾部补全。
        padStart用于头部补全,padEnd用于尾部补全。
    范例
        'x'.padStart(5, 'ab')               // 'ababx'
        'x'.padStart(4, 'ab')               // 'abax'
        'x'.padEnd(5, 'ab')                 // 'xabab'
        'x'.padEnd(4, 'ab')                 // 'xaba'
        'xxx'.padStart(2, 'ab')             // 'xxx'
        'xxx'.padEnd(2, 'ab')               // 'xxx'
        'abc'.padStart(10, '0123456789')    // '0123456abc'
        'x'.padStart(4)                     // '   x'
        'x'.padEnd(4)                       // 'x   '

模板字符串
    说明
        为解决JS的字符串拼接不便,ES6推出了模板字符串。
        模板字符串(template string)是增强版的字符串,用反引号(`)标识。
        它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
        表示多行字符串时,所有的空格和缩进都会被保留在输出之中。
        模板字符串中嵌入变量,需要将变量名写在${}之中。
    范例
        普通字符串
            `In JavaScript '\n' is a line-feed.`
        多行字符串
            `In JavaScript this is
             not legal.`
            console.log(`string text line 1
            string text line 2`);
        嵌入变量
            var name = "Bob", time = "today";
            `Hello ${name}, how are you ${time}?`
        放入表达式
            var x = 1,y = 2;
            `${x} + ${y * 2} = ${x + y * 2}`
            var obj = {x: 1, y: 2};
            `${obj.x + obj.y}`
        调用函数
            function fn() { return "Hello World";}
            `foo ${fn()} bar`
            // foo Hello World bar

标签模板
    说明
        模板字符可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。
        标签模板函数会收到三个参数,第一个参数为拆分的字符串数组,后续参数为替换的值。
        如果模板字符里面有变量,会将模板字符串先处理成多个参数,再调用函数
    范例
        无参数
            alert`123`    // 等同于alert(123)
        有参数
            var a = 5,b = 10;
            tag`Hello ${ a + b } world ${ a * b }`;
            // 等同于 tag(['Hello ', ' world ', ''], 15, 50);

String.raw()
    说明
        String.raw方法,往往用来充当模板字符串的处理函数。
        返回一个斜杠都被转义的字符串,对应于替换变量后的模板字符串.
        作为正常的函数使用时。第一个参数是一个有raw属性的对象,且raw属性的值应该是一个数组。
    范例
        直接使用
            String.raw`Hi\u000A!`;
            // 'Hi\\u000A!'
        做为函数使用
            String.raw({ raw: 'test' }, 0, 1, 2);
            // 't0e1s2t'
            // 等同于
            String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);

##正则的扩展

RegExp构造函数
    说明
        在ES6中,RegExp作为构造函数时,当第一个参数为正则时,允许第二个修饰符参数。
        第二个修饰符参数会覆盖正则中的修饰符
    范例
        new RegExp(/abc/ig, 'i')  //实际修饰符为i

字符串的正则方法
    说明
        ES6将如下4个方法,修改为在语言内部全部调用RegExp的实例方法.
        从而做到所有与正则相关的方法,全都定义在RegExp对象上。
    方法
        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]

u修饰符
    说明
        ES6对正则表达式添加了u修饰符,含义为“Unicode模式”。
        加上u修饰符,. 在正则表达式中可以识别码点大于0xFFFF的Unicode字符。
        ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上u修饰符。
        使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的Unicode字符。
        和i修饰符一起使用,可识别字形相近但编码不同的Unicode字符。
    范例
        匹配32位字符
            /^\uD83D/u.test('\uD83D\uDC2A')    // false
            /^\uD83D/.test('\uD83D\uDC2A')     // true
        点字符
            var s = '𠮷';
            /^.$/.test(s)    // false
            /^.$/u.test(s)   // true
        Unicode字符表示法
            /\u{61}/.test('a')      // false
            /\u{61}/u.test('a')     // true
            /\u{20BB7}/u.test('𠮷')  // true
        量词
            /a{2}/.test('aa')     // true
            /a{2}/u.test('aa')    // true
            /𠮷{2}/.test('𠮷𠮷')   // false
            /𠮷{2}/u.test('𠮷𠮷')  // true
        预定义模式
            /^\S$/.test('𠮷')     // false
            /^\S$/u.test('𠮷')    // true
        i修饰符
            /[a-z]/i.test('\u212A')   // false
            /[a-z]/iu.test('\u212A')  // true

y修饰符
    说明
        ES6为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。
        y修饰符的作用与g修饰符类似,也是全局匹配。
        不同的是,y修饰符确保匹配必须从剩余的第一个位置开始。
    范例
        和g修饰符区别
            var s = 'aaa_aa_a';
            var r1 = /a+/g;
            var r2 = /a+/y;
            r1.exec(s)         // ["aaa"]
            r2.exec(s)         // ["aaa"]
            r1.exec(s)         // ["aa"]
            r2.exec(s)         // null
        和g修饰符一起使用
            const REGEX = /a/gy;
            console.log('aaxa'.replace(REGEX, '-'));
            // '--xa'

sticky属性
    说明
        与y修饰符相匹配,ES6的正则对象多了sticky属性,表示是否设置了y修饰符。
    范例
        var r = /hello\d/y;
        r.sticky
        // true

flags属性
    说明
        ES6为正则表达式新增了flags属性,会返回正则表达式的修饰符
    范例
        /abc/ig.source     // "abc"
        /abc/ig.flags      // 'gi'

后行断言
    说明
        这个是ES7的提案。
        目前JavaScript只支持正向肯定预查和正向否定预查。
        不支持反向肯定预查和反向否定预查。
    表达式
        (?=pattern)     正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。
        (?!pattern)     正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。
        (?<=pattern)    反向肯定预查,与正向肯定预查类似,只是方向相反。
        (?<!pattern)    反向否定预查,与正向否定预查类似,只是方向相反。
    范例
        /x(?=y)/      //x只有在y前面才匹配
        /x(?!y)/      //x只有不在y前面才匹配
        /(?<=y)x/     //x只有在y后面才匹配
        /(?<!y)x/     //x只有不在y后面才匹配

Unicode属性类
    说明
        这是一个提案。
        引入了一种新的类的写法\p{...}和\P{...}。
        允许正则表达式匹配符合Unicode某种属性的所有字符。
    两种格式
        \p{UnicodePropertyName}
        \p{UnicodePropertyName=UnicodePropertyValue}
    范例
        匹配希腊字符
            const regexGreekSymbol = /\p{Script=Greek}/u;
            regexGreekSymbol.test('π')   // true
        匹配所有十进制字符
            const regex = /^\p{Decimal_Number}+$/u;
            regex.test('𝟏𝟐𝟑𝟜𝟝𝟞𝟩𝟪𝟫𝟬𝟭𝟮𝟯𝟺𝟻𝟼') // true
        匹配所有数字
            const regex = /^\p{Number}+$/u;
            regex.test('²³¹¼½¾')             // true
            regex.test('㉛㉜㉝')              // true
            regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ')  // true

数值的扩展

二进制和八进制表示法
    说明
        ES6提供了二进制和八进制数值的新的写法。
        分别用前缀0b(或0B)和0o(或0O)表示。
        ES5严格模式之中,八进制不允许使用前缀0表示,ES6进一步明确,要使用前缀0o表示。
    范例
        二进制和八进制表示
            0b111110111 === 503  // true
            0o767 === 503        // true
        转化为10进制
            Number('0b111')      // 7
            Number('0o10')       // 8

Number.isFinite(), Number.isNaN()
    说明
        ES6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法。
    方法
        Number.isFinite()    检查一个数值是否为有限的(finite)
        Number.isNaN()       检查一个值是否为NaN。
    范例
        检查是否有限
            Number.isFinite(15);         // true
            Number.isFinite(0.8);        // true
            Number.isFinite(NaN);        // false
            Number.isFinite(Infinity);   // false
            Number.isFinite(-Infinity);  // false
            Number.isFinite('foo');      // false
            Number.isFinite('15');       // false
            Number.isFinite(true);       // false
        检查是否为NaN
            Number.isNaN(NaN)            // true
            Number.isNaN(15)             // false
            Number.isNaN('15')           // false
            Number.isNaN(true)           // false
            Number.isNaN(9/NaN)          // true
            Number.isNaN('true'/0)       // true
            Number.isNaN('true'/'true')  // true

Number.parseInt(), Number.parseFloat()
    说明
        ES6将全局方法parseInt()和parseFloat(),移植到Number对象上面。
        行为完全保持不变。
    范例
        ES5的写法
            parseInt('12.34')               // 12
            parseFloat('123.45#')           // 123.45
        ES6的写法
            Number.parseInt('12.34')        // 12
            Number.parseFloat('123.45#')    // 123.45

Number.isInteger()
    说明
        用来判断一个值是否为整数。
        在JavaScript内部,整数和浮点数是同样的储存方法,所以3和3.0被视为同一个值。
    范例
        Number.isInteger(25)      // true
        Number.isInteger(25.0)    // true
        Number.isInteger(25.1)    // false
        Number.isInteger("15")    // false
        Number.isInteger(true)    // false

Number.EPSILON
    说明
        ES6在Number对象上面,新增一个极小的常量Number.EPSILON
        引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。
        如果误差能够小于Number.EPSILON,我们就可以认为得到了正确结果。
    范例
        极小常量
            Number.EPSILON               // 2.220446049250313e-16
            Number.EPSILON.toFixed(20)   // '0.00000000000000022204'
        判断误差是否在范围内
            0.1 + 0.2                          // 0.30000000000000004
            0.1 + 0.2 - 0.3                    // 5.551115123125783e-17
            5.551115123125783e-17.toFixed(20)  // '0.00000000000000005551'
            5.551115123125783e-17 < Number.EPSILON   // true

安全整数和Number.isSafeInteger()
    说明
        JavaScript能够准确表示的整数范围在-2^53到2^53之间(不含两个端点)。
        ES6引入了两个常量和一个方法用来判断这个范围。
    常量
        Number.MAX_SAFE_INTEGER        可表示的数的最大值
        Number.MIN_SAFE_INTEGER        可表示的数的最小值
    方法
        Number.isSafeInteger()         判断指定的值是否在-2^53到2^53之间
    范例
        常量
            Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1        // true
            Number.MAX_SAFE_INTEGER === 9007199254740991           // true
            Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER   // true
            Number.MIN_SAFE_INTEGER === -9007199254740991          // true
        范围
            Number.isSafeInteger('a')                        // false
            Number.isSafeInteger(null)                       // false
            Number.isSafeInteger(NaN)                        // false
            Number.isSafeInteger(Infinity)                   // false
            Number.isSafeInteger(-Infinity)                  // false
            Number.isSafeInteger(3)                          // true
            Number.isSafeInteger(1.2)                        // false
            Number.isSafeInteger(9007199254740990)           // true
            Number.isSafeInteger(9007199254740992)           // false
            Number.isSafeInteger(Number.MIN_SAFE_INTEGER)    // true
            Number.isSafeInteger(Number.MAX_SAFE_INTEGER)    // true
            Number.isSafeInteger(Number.MIN_SAFE_INTEGER-1)  // false
            Number.isSafeInteger(Number.MAX_SAFE_INTEGER+1)  // false

Math对象的扩展
    说明
        ES6在Math对象上新增了17个与数学相关的方法。
        所有这些方法都是静态方法,只能在Math对象上调用。
    方法
        Math.trunc        去除一个数的小数部分,返回整数部分
        Math.sign         判断一个数到底是正数、负数、还是零
        Math.cbrt         计算一个数的立方根
        Math.clz32        返回一个数的32位无符号整数形式有多少个前导0
        Math.imul         返回两个数以32位带符号整数形式相乘的结果
        Math.fround       返回一个数的单精度浮点数形式
        Math.hypot        返回所有参数的平方和的平方根
    对数方法
        Math.expm1        Math.expm1(x)返回ex-1,即Math.exp(x) - 1
        Math.log1p        Math.log1p(x)方法返回1+x的自然对数,即Math.log(1 + x)
        Math.log10        Math.log10(x)返回以10为底的x的对数
        Math.log2         Math.log2(x)返回以2为底的x的对数
    三角函数方法
        Math.sinh(x)      返回x的双曲正弦
        Math.cosh(x)      返回x的双曲余弦
        Math.tanh(x)      返回x的双曲正切
        Math.asinh(x)     返回x的反双曲正弦
        Math.acosh(x)     返回x的反双曲余弦
        Math.atanh(x)     返回x的反双曲正切

指数运算符
    说明
        ES7新增了一个指数运算符(**)
    范例
        2 ** 2                  // 4
        2 ** 3                  // 8
        let b = 3; b **= 3;     // 等同于 b = b * b * b;

数组的扩展

Array.from()
    说明
        Array.from方法用于将类似数组的对象和可遍历对象转为真正的数组。
    范例
        转换类似数组的对象
            let arrayLike = {
                '0': 'a',
                '1': 'b',
                '2': 'c',
                length: 3
            };
            [].slice.call(arrayLike)    //ES5写法  ['a', 'b', 'c']
            Array.from(arrayLike)       //ES6写法  ['a', 'b', 'c']

Array.of()
    说明
        Array.of方法用于将一组值,转换为数组。
    范例
        Array.of(3, 11, 8)       // [3,11,8]
        Array.of(3)              // [3]
        Array.of(3).length       // 1
        Array()                  // []
        Array(3)                 // [, , ,]
        Array(3, 11, 8)          // [3, 11, 8]

数组实例的copyWithin()
    说明
        在当前数组实例内部,将指定位置的成员复制(覆盖)到其他位置
    参数
        Array.prototype.copyWithin(target, start = 0, end = this.length)
            target 必需,从该位置开始替换数据。
            start  可选,从该位置开始读取数据,默认为0。如果为负值,表示倒数。
            end    可选,到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
    范例
        [1, 2, 3, 4, 5].copyWithin(0, 3)       // [4, 5, 3, 4, 5]
        [1, 2, 3, 4, 5].copyWithin(0, 3, 4)    // [4, 2, 3, 4, 5]
        [1, 2, 3, 4, 5].copyWithin(0, -2, -1)  // [4, 2, 3, 4, 5]
        [].copyWithin.call({length: 5, 3: 1}, 0, 3)
        // {0: 1, 3: 1, length: 5}

数组实例的find()和findIndex()
    说明
        数组实例的find方法,用于找出第一个符合条件的数组成员。
        它的参数是一个回调函数,对数组元素依次执行函数,如返回值为true,返回该成员。
        如果没有符合条件的成员,则返回undefined。
        findIndex用法与find类似,返回第一个符合条件的元素位置,都不符合条件,则返回-1。
        这两个方法都可以发现NaN,弥补了数组的IndexOf方法的不足。
    范例
        find
            [1, 4, -5, 10].find((n) => n < 0)  // -5
            [1, 5, 10, 15].find(function(value, index, arr) {
              return value > 9;
            }) // 10
        findIndex
            [1, 5, 10, 15].findIndex(function(value, index, arr) {
              return value > 9;
            }) // 2
        NaN的判断
            [NaN].indexOf(NaN)                         // -1
            [NaN].findIndex(y => Object.is(NaN, y))    // 0

数组实例的fill()
    说明
        fill方法使用给定值,填充一个数组
        fill方法可接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
    范例
        ['a', 'b', 'c'].fill(7)            // [7, 7, 7]
        new Array(3).fill(7)               // [7, 7, 7]
        ['a', 'b', 'c'].fill(7, 1, 2)      // ['a', 7, 'c']

数组实例的entries(),keys()和values()
    说明
        ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。
        它们都返回一个遍历器对象,可以用for...of循环进行遍历。
        keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
    范例
        for...of遍历
            for (let index of ['a', 'b'].keys()) {
              console.log(index);
            }
            for (let elem of ['a', 'b'].values()) {
              console.log(elem);
            }
            for (let [index, elem] of ['a', 'b'].entries()) {
              console.log(index, elem);
            }
        next遍历
            let letter = ['a', 'b', 'c'];
            let entries = letter.entries();
            console.log(entries.next().value);   // [0, 'a']
            console.log(entries.next().value);   // [1, 'b']
            console.log(entries.next().value);   // [2, 'c']

数组实例的includes()
    说明
        该方法属于ES7。
        Array.prototype.includes返回一个布尔值,表示某个数组是否包含给定的值。
        该方法的第二个参数表示搜索的起始位置,默认为0,负数表示倒数。
        相对于indexOf不会误判NaN,且返回的是布尔值。
    范例
        [1, 2, 3].includes(2);       // true
        [1, 2, 3].includes(4);       // false
        [1, 2, NaN].includes(NaN);   // true
        [1, 2, 3].includes(3, 3);    // false
        [1, 2, 3].includes(3, -1);   // true

数组的空位
    说明
        数组的空位指,数组的某一个位置没有任何值。
        Array构造函数返回的数组都是空位。
        空位不是undefined,空位是没有任何值,in运算符可以说明这一点。
        ES5中对不同的函数对空位的处理不一致,ES6则是明确将空位转为undefined。
    ES5中对空位的处理
        forEach(), filter(), every() 和some()都会跳过空位。
        map()会跳过空位,但会保留这个值
        join()和toString()将空位视为undefined,undefined和null会被处理成空字符串。
    ES6中对空位的处理
        Array.from方法会将数组的空位,转为undefined。
        扩展运算符(...)也会将空位转为undefined。
        copyWithin()会连空位一起拷贝。
        fill()会将空位视为正常的数组位置。
        for...of循环也会遍历空位。
        entries、keys、values、find和findIndex会将空位处理成undefined。
    范例
        ES5中对空位的处理
            [,'a'].forEach((x,i) => console.log(i));     // 1
            ['a',,'b'].filter(x => true)                 // ['a','b']
            [,'a'].every(x => x==='a')                   // true
            [,'a'].some(x => x !== 'a')                  // false
            [,'a'].map(x => 1)                           // [,1]
            [,'a',undefined,null].join('#')              // "#a##"
            [,'a',undefined,null].toString()             // ",a,,"
        ES6中对空位的处理
            Array.from(['a',,'b'])          // [ "a", undefined, "b" ]
            [...['a',,'b']]                 // [ "a", undefined, "b" ]
            [,'a','b',,].copyWithin(2,0)    // [,"a",,"a"]
            new Array(3).fill('a')          // ["a","a","a"]
            [...[,'a'].entries()]           // [[0,undefined], [1,"a"]]
            [...[,'a'].keys()]              // [0,1]
            [...[,'a'].values()]            // [undefined,"a"]
            [,'a'].find(x => true)          // undefined
            [,'a'].findIndex(x => true)     // 0

函数的扩展

函数参数的默认值
    说明
        ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
        参数变量是默认声明的,所以不能用let或const再次声明。
        参数默认值可以与解构赋值的默认值,结合起来使用。
        通常情况下,定义了默认值的参数,应该是函数的尾参数。
    函数的length属性
        指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。
        如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
    作用域
        如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一样的。
        即先是当前函数的作用域,然后才是全局作用域。
        如果参数的默认值是一个函数,该函数的作用域是其声明时所在的作用域。
    范例
        默认值
            function Point(x = 0, y = 0) {
                this.x = x;
                this.y = y;
            }
            var p = new Point();
            p  // { x: 0, y: 0 }
        与解构赋值默认值结合使用
            function foo({x, y = 5}) {
                console.log(x, y);
            }
            foo({})             // undefined, 5
            foo({x: 1})         // 1, 5
            foo({x: 1, y: 2})   // 1, 2
            foo() // TypeError: Cannot read property 'x' of undefined
        双重默认值
            function m1({x = 0, y = 0} = {}) {
                return [x, y];
            }
        函数的length属性
            (function (a) {}).length               // 1
            (function (a = 5) {}).length           // 0
            (function (a, b, c = 5) {}).length     // 2
            (function (a = 0, b, c) {}).length     // 0
            (function (a, b = 1, c) {}).length     // 1
        参数默认值不是定义时执行
            function throwIfMissing() {
              throw new Error('Missing parameter');
            }
            function foo(mustBeProvided = throwIfMissing()) {
              return mustBeProvided;
            }
            foo()
            // Error: Missing parameter

rest参数
    说明
        ES6引入rest参数(形式为“...变量名”),用于获取函数的多余参数。
        rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
        rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
        函数的length属性,不包括rest参数。
        和arguments的区别是,arguments并不是数组,无法调用数组相关的方法。
    范例
        function push(array, ...items) {
           items.forEach(function(item) {
              array.push(item);
              console.log(item);
           });
        }
        var a = [];
        push(a, 1, 2, 3);

扩展运算符
    说明
        扩展运算符(spread)是三个点(...)。
        它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
        能够正确识别32位的Unicode字符。
        任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。
    范例
        函数调用
            function add(x, y) {
                return x + y;
            }
            var numbers = [4, 38];
            add(...numbers);   // 42
        替代apply方法
            // ES5的写法
            function f(x, y, z) {
              // ...
            }
            var args = [0, 1, 2];
            f.apply(null, args);
            // ES6的写法
            function f(x, y, z) {
              // ...
            }
            var args = [0, 1, 2];
            f(...args);
        合并数组
            var arr1 = ['a', 'b'];
            var arr2 = ['c'];
            var arr3 = ['d', 'e'];
            [...arr1, ...arr2, ...arr3]
            // [ 'a', 'b', 'c', 'd', 'e' ]
        与解构赋值结合
            const [first, ...rest] = [1, 2, 3, 4, 5];
            first // 1
            rest  // [2, 3, 4, 5]
            const [first, ...rest] = [];
            first // undefined
            rest  // []:
            const [first, ...rest] = ["foo"];
            first  // "foo"
            rest   // []
        字符串
            [...'hello']                     // [ "h", "e", "l", "l", "o" ]
            'x\uD83D\uDE80y'.length          // 4
            [...'x\uD83D\uDE80y'].length     // 3 \uD83D\uDE80y 是一个32位字符
            let str = 'x\uD83D\uDE80y';
            str.split('').reverse().join('') // 'y\uDE80\uD83Dx'
            [...str].reverse().join('')      // 'y\uD83D\uDE80x'
        实现了Iterator接口的对象
            var nodeList = document.querySelectorAll('div');
            var array = [...nodeList];
        Map和Set结构,Generator函数
            let map = new Map([
                [1, 'one'],
                [2, 'two'],
                [3, 'three'],
            ]);
            let arr = [...map.keys()]; // [1, 2, 3]
            var go = function*(){
                yield 1;
                yield 2;
                yield 3;
            };
            [...go()] // [1, 2, 3]

name属性
    说明
        函数的name属性,返回该函数的函数名。
        这个属性早就被浏览器广泛支持,但是直到ES6,才将其写入了标准。
        如果将一个匿名函数赋值给一个变量,ES5中name为空字符串,ES6中name为变量名。
        Function构造函数返回的函数实例,name属性的值为“anonymous”。
        bind返回的函数,name属性值会加上“bound ”前缀。
    范例
        ES5与ES6区别
            var func1 = function () {};
            // ES5
            func1.name // ""
            // ES6
            func1.name // "func1"
        构造函数
            (new Function).name            // "anonymous"
        bind返回的函数
            function foo() {};
            foo.bind({}).name              // "bound foo"
            (function(){}).bind({}).name   // "bound "

箭头函数
    说明
        ES6允许使用“箭头”(=>)定义函数。
        如果不需要参数或需要多个参数,需使用一个圆括号代表参数部分。
        如果的代码块部分多于一条语句,需使用大括号将它们括起来,并且使用return语句返回。
        箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。
        因为没有this,所以也就不能用作构造函数。也不能用call,apply,bind这些方法。
    注意
        函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
        不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
        不可以使用arguments对象,该对象在函数体内不存在。可以用Rest参数代替。
        super、new.target 在箭头函数之中也是不存在的。
        不可以使用yield命令,因此箭头函数不能用作Generator函数。
    范例
        无参数
            var f = () => 5;
        一个参数
            var f = v => v;
        多个参数
            var sum = (num1, num2) => num1 + num2;
        多个参数且代码块多与1行
            var sum = (num1, num2) => {var sum = num1 + num2; return sum; }
        直接返回一个对象
            var getTempItem = id => ({ id: id, name: "Temp" });
        与变量解构结合使用
            const full = ({ first, last }) => first + ' ' + last;
        简化回调函数
            [1,2,3].map(x => x * x);
        与rest参数结合
            const numbers = (...nums) => nums;
            numbers(1, 2, 3, 4, 5)    // [1,2,3,4,5]
        this值
            function foo() {
              setTimeout(() => {
                console.log('id:', this.id);
              }, 100);
            }
            var id = 21;
            foo.call({ id: 42 });    // id: 42

函数绑定
    说明
        该语法是ES7的一个提案,Babel转码器已经支持。
        函数绑定(function bind)运算符,用来取代call、apply、bind调用。
        函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。
        该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
        如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
    范例
        语法
            foo::bar;                 // 等同 bar.bind(foo);
            foo::bar(...arguments);   // 等同 bar.apply(foo, arguments);
            var method = ::obj.foo;   // 等同 var method = obj::obj.foo;
        链式写法
            let { find, html } = jake;
            document.querySelectorAll("div.myClass")
            ::find("p")
            ::html("hahaha");

尾调用优化
    说明
        尾调用优化是某个函数的最后一步是调用另一个函数。
    尾调用优化
        正常函数嵌套调用会在内存中形成一个调用帧组成的调用栈。
        尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧。
        如果所有函数都是尾调用,那么每次执行时,调用帧只有一项,这将大大节省内存。
        只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用。
    尾递归
        函数调用自身,称为递归。如果尾调用自身,就称为尾递归。
        递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误。
        尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
        尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。
    严格模式
        ES6的尾调用优化只在严格模式下开启,正常模式是无效的。
        这是因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈。
        func.arguments:返回调用时函数的参数。
        func.caller:返回调用当前函数的那个函数。
    范例
        尾调用
            function f(x){
                return g(x);
            }
        不属于尾调用
            // 情况一
            function f(x){
              let y = g(x);
              return y;
            }
            // 情况二
            function f(x){
              return g(x) + 1;
            }
            // 情况三
            function f(x){
              g(x);
            }
        尾调用优化
            function f() {
              let m = 1;
              let n = 2;
              return g(m + n);
            }
            f();
            // 等同于
            function f() {
              return g(3);
            }
            f();
            // 等同于
            g(3);
        非尾递归
            function Fibonacci (n) {
                if ( n <= 1 ) {return 1};
                return Fibonacci(n - 1) + Fibonacci(n - 2);
            }
            Fibonacci(10); // 89
            // Fibonacci(100)
            // Fibonacci(500)
            // 堆栈溢出了
        尾递归
            function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
                if( n <= 1 ) {return ac2};
                return Fibonacci2 (n - 1, ac2, ac1 + ac2);
            }
            Fibonacci2(100) // 573147844013817200000
            Fibonacci2(1000) // 7.0330367711422765e+208
            Fibonacci2(10000) // Infinity

函数参数的尾逗号
    说明
        ECMAScript 2017将允许函数的最后一个参数有尾逗号(trailing comma)。
        此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
    范例
        function clownsEverywhere(
          param1,
          param2,
        ) { /* ... */ }
        clownsEverywhere(
          'foo',
          'bar',
        );

对象的扩展

属性的简洁表示法
    说明
        ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
        如果某个方法的值是一个Generator函数,前面需要加上星号。
    范例
        var birth = '2000/01/01';
        var Person = {
            name: '张三',
            //等同于birth: birth
            birth,
            // 等同于getPoint: function ()...
            getPoint() {
                var x = 1,y = 10; 
                return {x, y};
            }
            * generatorFun(){
                yield 'hello world';
            }
        };

属性名表达式
    说明
        ES6允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在方括号内。
        属性名表达式与简洁表示法,不能同时使用,会报错。
    范例
        let propKey = 'foo';
        let obj = {
            [propKey]: true,
            ['a' + 'bc']: 123,
            ['h'+'ello']() {
                return 'hi';
            }
        };

方法的name属性
    说明
        函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。
        如果对象的方法是一个Symbol值,那么name属性返回的是这个Symbol值的描述。
        bind方法创造的函数,name属性返回“bound”加上原函数的名字。
        Function构造函数创造的函数,name属性返回“anonymous”。
    范例
        方法的name属性
            var person = {
                sayName() {
                    console.log(this.name);
                },
                get firstName() {
                    return "Nicholas";
                }
            };
            person.sayName.name   // "sayName"
            person.firstName.name // "get firstName"
        方法是Symbol值
            const key1 = Symbol('description');
            const key2 = Symbol();
            let obj = {
                [key1]() {},
                [key2]() {},
            };
            obj[key1].name  // "[description]"
            obj[key2].name  // ""

Object.is()
    说明
        ES5中,相等(==)会自动转换类型,全等(===)NaN不等于自身。
        ES6中新增Object.is()方法,与全等类似,不同的是+0不等于-0,NaN等于自身。
    范例
        Object.is('foo', 'foo')    // true
        Object.is({}, {})          // false
        +0 === -0                  // true
        NaN === NaN                // false
        Object.is(+0, -0)          // false
        Object.is(NaN, NaN)        // true

Object.assign()
    说明
        Object.assign方法用于对象的合并。
        将源对象(source)的所有可枚举属性,复制到目标对象(target)。
        Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
        如果属性名相同,则后面的属性会覆盖前面的属性。
        如果参数不是对象,则会先转成对象。
        undefined和null无法转成对象,作为首参数会报错,作为非首参数参数跳过。
    注意
        Object.assign只拷贝源对象的自身属性,不拷贝继承属性和不可枚举的属性。
        Object.assign方法实行的是浅拷贝,而不是深拷贝。
        如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
        对于这种嵌套的对象,遇到同名属性,Object.assign的处理方法是替换,而不是添加。
    范例
        同名属性
            var target = { a: 1, b: 1 };
            var source1 = { b: 2, c: 2 };
            var source2 = { c: 3 };
            Object.assign(target, source1, source2);
            target // {a:1, b:2, c:3}
        undefined,null情况
            Object.assign(undefined)               // 报错
            Object.assign()                        // 报错
            let obj = {a: 1};
            Object.assign(obj, undefined) === obj  // true
            Object.assign(obj, null) === obj       // true
        其他类型
            var v1 = 'abc';
            var v2 = true;
            var v3 = 10;
            var obj = Object.assign({}, v1, v2, v3);
            console.log(obj); // { "0": "a", "1": "b", "2": "c" }
        属性名为Symbol值的属性
            Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
            // { a: 'b', Symbol(c): 'd' }
        嵌套对象
            var target = { a: { b: 'c', d: 'e' } }
            var source = { a: { b: 'hello' } }
            Object.assign(target, source)
            // { a: { b: 'hello' } }

属性的可枚举性
    说明
        ES6规定,所有Class的原型的方法都是不可枚举的。
        对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。
        Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。
    范例
        Class的原型的方法不可枚举
            Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable
            // false
        获取属性描述
            let obj = { foo: 123 };
            Object.getOwnPropertyDescriptor(obj, 'foo')
            // {value:123,writable:true,enumerable:true,configurable:true}

属性的遍历
    方法
        for...in
            循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。
        Object.keys(obj)
            返回数组,包括自身(不含继承)的所有可枚举属性(不含Symbol属性)。
        Object.getOwnPropertyNames(obj)
            返回数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)
        Object.getOwnPropertySymbols(obj)
            返回数组,包含对象自身的所有Symbol属性。
        Reflect.ownKeys(obj)
            返回数组,包含对象自身的所有属性,无论是否可枚举,包含Symbol属性。
    遍历顺序
        首先遍历所有属性名为数值的属性,按照数字排序。
        其次遍历所有属性名为字符串的属性,按照生成时间排序。
        最后遍历所有属性名为Symbol值的属性,按照生成时间排序
    范例
        Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
        // ['2', '10', 'b', 'a', Symbol()]

原型
    __proto__ 
        用来读取或设置当前对象的prototype对象。
        该属性没有写入ES6的正文,而是写入了附录,因浏览器广泛支持,才被加入了ES6。
        标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署。
        新的代码最好认为这个属性是不存在的。
        无论从语义的角度,还是从兼容性的角度,都不要使用这个属性。
    Object.setPrototypeOf()
        用来设置一个对象的prototype对象。
        它是ES6正式推荐的设置原型对象的方法。
    Object.getPrototypeOf()
        该方法与setPrototypeOf方法配套,用于读取一个对象的prototype对象。
    范例
        __proto__属性
            // es6的写法
            var obj = {
                method: function() { ... }
            };
            obj.__proto__ = someOtherObj;
            // es5的写法``
            var obj = Object.create(someOtherObj);
            obj.method = function() { ... };
        Object.setPrototypeOf
            let proto = {};
            let obj = { x: 10 };
            Object.setPrototypeOf(obj, proto);
            proto.y = 20;
            proto.z = 40;
            obj.x // 10
            obj.y // 20
            obj.z // 40
        Object.getPrototypeOf
            function Rectangle() {};
            var rec = new Rectangle();
            Object.getPrototypeOf(rec) === Rectangle.prototype   // true
            Object.setPrototypeOf(rec, Object.prototype);
            Object.getPrototypeOf(rec) === Rectangle.prototype   // false

Object.values(),Object.entries()
    说明
        这两个方法是ES7的提案。
        Object.values方法返回一个数组,成员是参数对象自身可遍历属性的值。
        Object.entries方法返回一个数组,成员是参数对象自身可遍历属性的键值对数组。
    范例
        Object.values
            var obj = { foo: "bar", baz: 42 };
            Object.values(obj)    // ["bar", 42]
            var obj = { 100: 'a', 2: 'b', 7: 'c' };
            Object.values(obj)    // ["b", "c", "a"]
            var obj = Object.create({}, {p: {value: 42}});
            Object.values(obj)    // []
            Object.values({ [Symbol()]: 123, foo: 'abc' });  // ['abc']
            Object.values('foo')  // ['f', 'o', 'o']
        Object.entries
            var obj = { foo: 'bar', baz: 42 };
            Object.entries(obj)
            // [ ["foo", "bar"], ["baz", 42] ]
            var map = new Map(Object.entries(obj));
            map // Map { foo: "bar", baz: 42 }

对象的扩展运算符
    说明
        这是ES7的一个提案。
        将Rest运算符(解构赋值),扩展运算符(...)引入对象。
        Babel转码器已经支持这项功能。
        如自定义属性放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
        如自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
        展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的。
    范例
        解构赋值
            let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
            x // 1
            y // 2
            z // { a: 3, b: 4 }
        解构赋值报错
            let { x, y, ...z } = null;        // 运行时错误
            let { x, y, ...z } = undefined;   // 运行时错误
            let { ...x, y, z } = obj;         // 句法错误
            let { x, ...y, ...z } = obj;      // 句法错误
        解构赋值浅拷贝
            let obj = { a: { b: 1 } };
            let { ...x } = obj;
            obj.a.b = 2;
            x.a.b // 2
        解构赋值产生的变量只读取对象自身的属性
            var o = Object.create({ x: 1, y: 2 });
            o.z = 3;
            let { x, ...{ y, z } } = o;
            x // 1
            y // undefined
            z // 3
        结构赋值用作函数扩展
            function baseFunction({ a, b }) {
              // ...
            }
            function wrapperFunction({ x, y, ...restConfig }) {
              // 使用x和y参数进行操作
              // 其余参数传给原始函数
              return baseFunction(restConfig);
            }
        扩展运算符
            let z = { a: 3, b: 4 };
            let n = { ...z };
            n // { a: 3, b: 4 }
        扩展运算符用于合并对象
            let ab = { ...a, ...b };
            // 等同于
            let ab = Object.assign({}, a, b);

Object.getOwnPropertyDescriptors()
    说明
        Object.getOwnPropertyDescriptors方法是一个ES7的提案。
        返回指定对象所有自身属性(非继承属性)的描述对象。
        目的主要是为了解决Object.assign,无法正确拷贝get属性和set属性的问题。
        ES5中的Object.getOwnPropertyDescriptor方法,是返回某个对象属性的描述对象。
    范例
        const obj = {
          foo: 123,
          get bar() { return 'abc' }
        };
        Object.getOwnPropertyDescriptors(obj)
        // { foo:
        //    { value: 123,
        //      writable: true,
        //      enumerable: true,
        //      configurable: true },
        //   bar:
        //    { get: [Function: bar],
        //      set: undefined,
        //      enumerable: true,
        //      configurable: true } }

Symbol

概述
    说明
        ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。
        它是JavaScript语言的第七种数据类型。
        前六种是:Undefined、Null、Boolean、String、Number、Object。
        Symbol值通过Symbol函数生成。
        对象的属性名现在可以有两种类型,一种是字符串,另一种就是新增的Symbol类型。
        凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
        Symbol函数前不能使用new命令,生成的Symbol是一个原始类型的值,不是对象。
        Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述。
        Symbol值不能与其他类型的值进行运算。
        Symbol值可以显式转为字符串,布尔值,但是不能转为数值。
    范例
        创建Symbol实例
            let s = Symbol();
            typeof s; // "symbol"
        描述参数
            var s1 = Symbol('foo');
            var s2 = Symbol('bar');
            s1  // Symbol(foo)
            s2  // Symbol(bar)
            s1.toString()  // "Symbol(foo)"
            s2.toString()  // "Symbol(bar)"
        唯一性
            var s1 = Symbol();
            var s2 = Symbol();
            s1 === s2 // false
            var s1 = Symbol("foo");
            var s2 = Symbol("foo");
            s1 === s2 // false
        无法参与运算
            var sym = Symbol('My symbol');
            "your symbol is " + sym   // TypeError
            `your symbol is ${sym}`   // TypeError
        转为字符串
            var sym = Symbol('My symbol');
            String(sym)    // 'Symbol(My symbol)'
            sym.toString() // 'Symbol(My symbol)'
        转为布尔值
            var sym = Symbol();
            Boolean(sym)  // true
            !sym          // false
            if (sym) {
              // ...
            }
            Number(sym)   // TypeError
            sym + 2       // TypeError

作为属性名的Symbol
    说明
        由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符。
        用于对象的属性名,可保证不会出现同名的属性。
        这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
    范例
        作为对象属性
            var mySymbol = Symbol();
            var a = {};
            a[mySymbol] = 'Hello!';
            var b = {
                [mySymbol]: 'Hello!'
            };
            var c = {};
            Object.defineProperty(c, mySymbol, { value: 'Hello!' });
            // 以上写法都得到同样结果
            a[mySymbol] // "Hello!"
        对象方法简洁写法
            let s = Symbol();
            let obj = {
                [s](arg) { ... }
            };
        定义一组常量
            log.levels = {
                DEBUG: Symbol('debug'),
                INFO: Symbol('info'),
                WARN: Symbol('warn')
            };
            log(log.levels.DEBUG, 'debug message');
            log(log.levels.INFO, 'info message');

实例:消除魔术字符串
    说明
        魔术字符串指在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。
        风格良好的代码,应该尽量消除魔术字符串,该由含义清晰的变量代替。

属性名的遍历
    说明
        Symbol作为属性名不会出现在for...in、for...of循环中。
        也不会被Object.keys()、Object.getOwnPropertyNames()返回。
        Object.getOwnPropertySymbols方法可以获取指定对象的所有Symbol属性名。
        Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和Symbol键名。
    范例
        获取Symbol属性
            var obj = {};
            var foo = Symbol("foo");
            Object.defineProperty(obj, foo, {
                value: "foobar",
            });
            for (var i in obj) {
                console.log(i); // 无输出
            }
            Object.getOwnPropertyNames(obj)    // []
            Object.getOwnPropertySymbols(obj)  // [Symbol(foo)]
        Reflect.ownKeys
            let obj = {
                [Symbol('my_key')]: 1,
                enum: 2,
                nonEnum: 3
            };
            Reflect.ownKeys(obj)
            // [Symbol(my_key), 'enum', 'nonEnum']

Symbol.for(),Symbol.keyFor()
    说明
        Symbol.for接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。
        有就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。
        Symbol.for生成的实例会被登记在全局环境中供搜索,Symbol不会。
        Symbol.keyFor方法返回一个已登记的Symbol类型值的key。
    范例
        Symbol.for
            var s1 = Symbol.for('foo');
            var s2 = Symbol.for('foo');
            s1 === s2 // true
        Symbol.keyFor
            var s1 = Symbol.for("foo");
            Symbol.keyFor(s1) // "foo"
            var s2 = Symbol("foo");
            Symbol.keyFor(s2)  // undefined

内置的Symbol值
    说明
        除了定义自己使用的Symbol值以外,ES6还提供了11个内置的Symbol值。
        这些内置的Symbol值指向语言内部使用的方法。
    内置值
        Symbol.hasInstance
            指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。
        Symbol.isConcatSpreadable
            等于一个布尔值,表示该对象使用Array.prototype.concat()时,是否可以展开。
        Symbol.species
            指向一个方法。该对象作为构造函数创造实例时,会调用这个方法。即如果this.constructor[Symbol.species]存在,就会使用这个属性作为构造函数,来创造新的实例对象。
        Symbol.match
            指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。
        Symbol.replace
            指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
        Symbol.search
            指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
        Symbol.split
            指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。
        Symbol.iterator
            指向该对象的默认遍历器方法。
            对象进行for...of循环时,会调用Symbol.iterator方法,返回该对象的默认遍历器。
        Symbol.toPrimitive
            指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
            Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式。
            一共有三种模式: Number,String,Default(可以转换字符串或数值)。
        Symbol.toStringTag
            指向一个方法。在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型。
        Symbol.unscopables
            指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。
    范例
        Symbol.hasInstance
            class Even {
              static [Symbol.hasInstance](obj) {
                return Number(obj) % 2 === 0;
              }
            }
            1 instanceof Even // false
            2 instanceof Even // true
            12345 instanceof Even // false
        Symbol.isConcatSpreadable
            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.match
            String.prototype.match(regexp)
            // 等同于
            regexp[Symbol.match](this)
            class MyMatcher {
                [Symbol.match](string) {
                    return 'hello world'.indexOf(string);
                }
            }
            'e'.match(new MyMatcher()) // 1
        Symbol.search
            String.prototype.search(regexp)
            // 等同于
            regexp[Symbol.search](this)
            class MySearch {
                constructor(value) {
                    this.value = value;
                }
                [Symbol.search](string) {
                    return string.indexOf(this.value);
                }
            }
            'foobar'.search(new MySearch('foo')) // 0
        Symbol.split
            String.prototype.split(separator, limit)
            // 等同于
            separator[Symbol.split](this, limit)
        Symbol.toPrimitive
            let obj = {
                [Symbol.toPrimitive](hint) {
                    switch (hint) {
                        case 'number':
                            return 123;
                        case 'string':
                            return 'str';
                        case 'default':
                            return 'default';
                        default:
                            throw new Error();
                    }
                }
            };
            2 * obj // 246
            3 + obj // '3default'
            obj == 'default' // true
            String(obj) // 'str'
        Symbol.toStringTag
            ({[Symbol.toStringTag]: 'Foo'}.toString())
            // "[object Foo]"
            class Collection {
                get [Symbol.toStringTag]() {
                    return 'xxx';
                }
            }
            var x = new Collection();
            Object.prototype.toString.call(x) // "[object xxx]"

Proxy和Reflect

Proxy概述
    说明
        Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改。
        属于一种“元编程”(meta programming),即对编程语言进行编程。
    范例
        var proxy = new Proxy({}, {
            get: function(target, property) {
                return 35;
            }
        });
        proxy.time // 35
        proxy.name // 35

    拦截操作
        get(target, propKey, receiver)
        set(target, propKey, value, receiver)
        has(target, propKey)
        deleteProperty(target, propKey)
        ownKeys(target)
        getOwnPropertyDescriptor(target, propKey)
        defineProperty(target, propKey, propDesc)
        preventExtensions(target)
        getPrototypeOf(target)
        isExtensible(target)
        setPrototypeOf(target, proto)
        apply(target, object, args)
        construct(target, args)

Proxy.revocable()
    说明
        返回一个可取消的Proxy实例。
        Proxy.revocable方法返回一个对象,该对象包含proxy和revoke两个属性。
        proxy是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。
    范例
        let target = {};
        let handler = {};
        let {proxy, revoke} = Proxy.revocable(target, handler);
        proxy.foo = 123;
        proxy.foo // 123
        revoke();
        proxy.foo // TypeError: Revoked

Reflect概述
    说明
        将Object对象的一些明显属于语言内部的方法,放到Reflect对象上。
        修改某些Object方法的返回结果,让其变得更合理。
        让之前某些Object操作都变成函数行为。
        Reflect对象的方法与Proxy对象的方法一一对应。
        不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
    范例
        var loggedObj = new Proxy(obj, {
            get(target, name) {
                console.log('get', target, name);
                return Reflect.get(target, name);
            },
            deleteProperty(target, name) {
                console.log('delete' + name);
                return Reflect.deleteProperty(target, name);
            },
            has(target, name) {
                console.log('has' + name);
                return Reflect.has(target, name);
            }
        });

Reflect对象的方法
    方法列表
        Reflect.apply(target,thisArg,args)
        Reflect.construct(target,args)
        Reflect.get(target,name,receiver)
        Reflect.set(target,name,value,receiver)
        Reflect.defineProperty(target,name,desc)
        Reflect.deleteProperty(target,name)
        Reflect.has(target,name)
        Reflect.ownKeys(target)
        Reflect.isExtensible(target)
        Reflect.preventExtensions(target)
        Reflect.getOwnPropertyDescriptor(target, name)
        Reflect.getPrototypeOf(target)
        Reflect.setPrototypeOf(target, prototype)

Set和Map数据结构

Set
    说明
        ES6提供了新的数据结构Set。类似于数组,但是成员的值都是唯一的,没有重复的值。
        Set本身是一个构造函数,用来生成Set数据结构。
        Set函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。
        向Set加入值的时候,不会发生类型转换,所以5和"5"是两个不同的值。
        Set的遍历顺序就是插入顺序。对Set遍历key和value方法的行为完全一致。
    实例属性
        Set.prototype.constructor     构造函数,默认就是Set函数。
        Set.prototype.size            返回Set实例的成员总数。
    实例方法
        add(value)                    添加某个值,返回Set结构本身。
        delete(value)                 删除某个值,返回一个布尔值,表示删除是否成功。
        has(value)                    返回一个布尔值,表示该值是否为Set的成员。
        clear()                       清除所有成员,没有返回值。
    范例
        构造函数
            var s = new Set();
            [2, 3, 5, 4, 5, 2, 2].map(x => s.add(x));
            for (let i of s) {
              console.log(i);
            }
            // 2 3 5 4
        通过数组初始化
            var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
            [...items]  // [1, 2, 3, 4, 5]
            items.size  // 5
        数组去重
            [...new Set(array)]
        Set转换为Array
            var items = new Set([1, 2, 3, 4, 5]);
            var array = Array.from(items);
        使用数组的map方法
            let set = new Set([1, 2, 3]);
            set = new Set([...set].map(x => x * 2));
            // 返回Set结构:{2, 4, 6}
        使用数组的filter方法
            let set = new Set([1, 2, 3, 4, 5]);
            set = new Set([...set].filter(x => (x % 2) == 0));
            // 返回Set结构:{2, 4}
        并集、交集和差集
            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}

WeakSet
    说明
        WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别。
        首先,WeakSet的成员只能是对象,而不能是其他类型的值。
        WeakSet中的对象都是弱引用,垃圾回收机制不考虑WeakSet对该对象的引用。
        WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在。
        WeakSet没有size属性,没有办法遍历它的成员。
        WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。
    方法
        WeakSet.prototype.add()     向WeakSet实例添加一个新成员
        WeakSet.prototype.delete()  清除WeakSet实例的指定成员
        WeakSet.prototype.has()     返回布尔值,表示某个值是否在实例之中
    范例
        创建
            var ws = new WeakSet();
            var obj = {};
            var foo = {};
            ws.add(window);
            ws.add(obj);
            ws.has(window); // true
            ws.has(foo);    // false
            ws.delete(window);
            ws.has(window);    // false
        遍历
            ws.size    // undefined
            ws.forEach // undefined
            ws.forEach(function(item){ console.log(item)})
            // TypeError: undefined is not a function

Map
    说明
        ES6提供了Map数据结构。它类似于对象,也是键值对的集合。
        Map的“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
        Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。
        Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。
        Map的遍历顺序就是插入顺序。
    实例属性
        size                   返回Map结构的成员总数
    实例方法
        set(key, value)        设置key所对应的值,存在则覆盖,返回整个Map结构。
        get(key)               读取key对应的键值,找不到返回undefined。
        has(key)               返回一个布尔值,表示某个键是否在Map数据结构中
        delete(key)            删除某个键,成功返回true。失败,返回false。
        clear()                清除所有成员,没有返回值。
    遍历
        keys()                 返回键名的遍历器。
        values()               返回键值的遍历器。
        entries()              返回所有成员的遍历器。
        forEach()              遍历Map的所有成员。
    范例
        基本使用
            var m = new Map();
            var o = {p: 'Hello World'};
            m.set(o, 'content')
            m.get(o)     // "content"
            m.has(o)     // true
            m.delete(o)  // true
            m.has(o)     // false
        通过数组创建
            var m = new Map([
                [true, 'foo'],
                ['true', 'bar']
            ]);
            m.get(true)    // 'foo'
            m.get('true')  // 'bar'
        覆盖
            let map = new Map();
            map.set(1, 'aaa').set(1, 'bbb');
            map.get(1) // "bbb"
        基础类型作为键
            let map = new Map();
            map.set(NaN, 123);
            map.get(NaN)  // 123
            map.set(-0, 123);
            map.get(+0)   // 123
        转换为数组
            let 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']]

WeakMap
    说明
        WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名。
        键名是对象的弱引用,键名所指向的对象,不计入垃圾回收机制。
        当对象被回收后,WeakMap自动移除对应的键值对。
        WeakMap没有遍历方法和clear方法,也没有size属性。
        基本上WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。
    范例
        var wm = new WeakMap();
        var element = document.querySelector(".element");
        wm.set(element, "Original");
        wm.get(element) // "Original"
        element.parentNode.removeChild(element);
        element = null;
        wm.get(element) // undefined

Iterator和for…of循环

Iterator(遍历器)的概念
    说明
        它是一种接口,为各种不同的数据结构提供统一的访问机制。
        任何数据结构只要部署Iterator接口,就可以完成遍历操作。
        ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of使用。
        每一次调用next方法,都会返回一个包含value和done两个属性的对象。
        value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
    Iterator的遍历过程
        1. 创建一个指针对象,指向当前数据结构的起始位置。
        2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
        3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
        4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

数据结构的默认Iterator接口
    说明
        当使用for...of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。
        ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性.
        一个数据结构只要具有Symbol.iterator属性,就可以认为是可遍历的(iterable)。
        ES6中有三类数据结构原生具备Iterator接口:数组、类数组的对象、Set和Map结构。
    范例
        数组的Iterator接口
            let arr = ['a', 'b', 'c'];
            let iter = arr[Symbol.iterator]();
            iter.next()  // { value: 'a', done: false }
            iter.next()  // { value: 'b', done: false }
            iter.next()  // { value: 'c', done: false }
            iter.next()  // { value: undefined, done: true }
        为对象添加Iterator接口
            let obj = {
                data: ['hello', 'world'],
                [Symbol.iterator]() {
                    const self = this;
                    let index = 0;
                    return {
                        next() {
                            if (index < self.data.length) {
                                return {
                                    value: self.data[index++],
                                    done: false
                                };
                            } else {
                                return {
                                    value: undefined,
                                    done: true
                                };
                            }
                        }
                    };
                }
            };
        通过原型部署Iterator接口
            function Obj(value) {
                this.value = value;
                this.next = null;
            }
            Obj.prototype[Symbol.iterator] = function() {
                var iterator = {
                    next: next
                };
                var current = this;
                function next() {
                    if (current) {
                        var value = current.value;
                        current = current.next;
                        return {
                            done: false,
                            value: value
                        };
                    } else {
                        return {
                            done: true
                        };
                    }
                }
                return iterator;
            }
            var one = new Obj(1);
            var two = new Obj(2);
            var three = new Obj(3);
            one.next = two;
            two.next = three;
            for (var i of one) {
                console.log(i);
            }
            // 1
            // 2
            // 3
        为类似数组的对象添加Iterator接口
            NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
            // 或者
            NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

调用Iterator接口的场合
    说明
        对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法。
        扩展运算符(...)也会调用默认的iterator接口。
        yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
        由于数组的遍历会调用遍历器接口,任何接受数组作为参数的场合,都调用了遍历器接口。
    范例
        解构赋值
            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'];
        扩展运算符
            var str = 'hello';
            [...str] //  ['h','e','l','l','o']
            let arr = ['b', 'c'];
            ['a', ...arr, 'd']
            // ['a', 'b', 'c', 'd'
        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 }

字符串的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 }

Iterator接口与Generator函数
    说明
        Symbol.iterator方法的最简单实现是Generator函数。
    范例
        var myIterable = {};
        myIterable[Symbol.iterator] = function*() {
            yield 1;
            yield 2;
            yield 3;
        };
        [...myIterable] // [1, 2, 3]
        // 或者采用下面的简洁写法
        let obj = { * [Symbol.iterator]() {
                yield 'hello';
                yield 'world';
            }
        };
        for (let x of obj) {
            console.log(x);
        }
        // hello

遍历器对象的return(),throw()
    说明
        遍历器对象除了具有next方法,还可以具有return方法和throw方法。
        如果for...of循环提前退出(出错,break或continue),就会调用return方法。
        throw方法主要是配合Generator函数使用,一般的遍历器对象用不到这个方法。
    范例
        function readLinesSync(file) {
            return {
                next() {
                    if (file.isAtEndOfFile()) {
                        file.close();
                        return {
                            done: true
                        };
                    }
                },
                return () {
                    file.close();
                    return {
                        done: true
                    };
                },
            };
        }
        for (let line of readLinesSync(fileName)) {
            console.log(x);
            break;
        }

for...of循环
    说明
        一个数据结构只要部署了Symbol.iterator属性,就可以用for...of循环遍历它的成员。
        for...of循环内部调用的是数据结构的Symbol.iterator方法。
        for...of可以使用的范围包括数组、Set、Map、某些类似数组对象、Generator和字符串。
    范例
        数组
            let arr = [3, 5, 7];
            arr.foo = 'hello';
            for (let i in arr) {
                console.log(i); // "0", "1", "2", "foo"
            }
            for (let i of arr) {
                console.log(i); //  "3", "5", "7"
            }
        Set和Map
            var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
            for (var e of engines) {
                console.log(e);
            }
            // Gecko
            // Trident
            // Webkit
            var es6 = new Map();
            es6.set("edition", 6);
            es6.set("committee", "TC39");
            es6.set("standard", "ECMA-262");
            for (var [name, value] of es6) {
                console.log(name + ": " + value);
            }
            // edition: 6
            // committee: TC39
            // standard: ECMA-262

Generator 函数

简介
    说明
        Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。
        Generator函数是一个状态机,封装了多个内部状态,还是一个遍历器对象生成函数。
        返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。
        Generator函数一个普通函数,function关键字与函数名之间有一个星号。
        函数体内部使用yield语句,定义不同的内部状态(yield英语意思就是“产出”)。
        Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态。
        yield语句如果用在一个表达式之中,必须放在圆括号里面。
        yield语句用作函数参数或赋值表达式的右边,可以不加括号。
    函数内执行顺序
        遇到yield语句,暂停执行后面的操作,并将yield后面表达式的值作为返回对象的value值。
        下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。
        没有yield就一直运行到函数结束,直到return语句为止,把return的值作为value值。
        如果该函数没有return语句,则返回的对象的value属性值为undefined。
    范例
        基本使用
            function* helloWorldGenerator() {
                yield 'hello';
                yield 'world';
                return 'ending';
            }
            var hw = helloWorldGenerator();
            hw.next()   // { value: 'hello', done: false }
            hw.next()   // { value: 'world', done: false }
            hw.next()   // { value: 'ending', done: true }
            hw.next()   // { value: undefined, done: true }
        赋值给Symbol.iterator
            var myIterable = {};
            myIterable[Symbol.iterator] = function*() {
                yield 1;
                yield 2;
                yield 3;
            };
            [...myIterable]  // [1, 2, 3]

next方法的参数
    说明
        yield句本身没有返回值,或者说总是返回undefined。
        next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
    范例
        function* 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 }

for...of循环
    说明
        for...of循环可以自动遍历Generator函数时生成的Iterator对象。
        一旦next方法的返回对象的done属性为true,for...of就会中止,且不包含该返回对象。
        扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。
    范例
        基本使用
            function *foo() {
                yield 1;
                yield 2;
                yield 3;
                yield 4;
                yield 5;
                return 6;
            }
            for (let v of foo()) {
                console.log(v);
            }
            // 1 2 3 4 5
        斐波那契数列
            function* fibonacci() {
                let [prev, curr] = [0, 1];
                for (;;) {
                    [prev, curr] = [curr, prev + curr];
                    yield curr;
                }
            }
            for (let n of fibonacci()) {
                if (n > 1000) break;
                console.log(n);
            }
        通过Generator为原生对象添加Iterator接口
            function* objectEntries() {
                let propKeys = Object.keys(this);
                for (let propKey of propKeys) {
                    yield [propKey, this[propKey]];
                }
            }
            let jane = {
                first: 'Jane',
                last: 'Doe'
            };
            jane[Symbol.iterator] = objectEntries;
            for (let [key, value] of jane) {
                console.log(`${key}: ${value}`);
            }
            // first: Jane
            // last: Doe
        解构赋值等
            function* numbers() {
                yield 1
                yield 2
                return 3
                yield 4
            }
            [...numbers()]            // [1, 2]
            Array.from(numbers())     // [1, 2]
            let [x, y] = numbers();   // x = 1  y = 2

Generator.prototype.throw()
    说明
        Generator函数返回的遍历器对象,都有一个throw方法。
        它可以在函数体外抛出错误,然后在Generator函数体内捕获。
        如果Generator函数内部没有部署try...catch,那么错误将被外部try...catch捕获。
        在Generator函数内部throw方法被捕获以后,会附带执行下一条yield语句。
        一旦Generator执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。
        如果此后还调用next方法,将返回 {value:undefined,done:true}。
    范例
        var g = function*() {
            try {
                yield;
            } catch (e) {
                console.log('内部捕获', e);
            }
        };
        var i = g();
        i.next();
        try {
            i.throw('a');
            i.throw('b');
        } catch (e) {
            console.log('外部捕获', e);
        }
        // 内部捕获 a
        // 外部捕获 b

Generator.prototype.return()
    说明
        Generator函数返回的遍历器对象,还有一个return方法。
        它可以返回给定的值,并且终结遍历Generator函数。
        如果return方法调用时,不提供参数,则返回值的value属性为undefined。
    范例
        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 }

yield*语句
    说明
        如果在Generater函数内部,调用另一个Generator函数,默认情况下是没有效果的。
        使用yield*语句,可以用来在一个Generator函数里面执行另一个Generator函数。
        如果yield命令后面跟的是一个遍历器对象,需要在yield命令后面加上星号。
        yield*后面的Generator函数(没有return语句时),等同for...of循环。
        任何数据结构只要有Iterator接口,就可以被yield*遍历。
    范例
        基本使用
            function* inner() {
                yield 'hello!';
            }
            function* outer2() {
                yield 'open'
                yield * inner()
                yield 'close'
            }
            var gen = outer2()
            gen.next().value // "open"
            gen.next().value // "hello!"
            gen.next().value // "close"
        相当于for...of循环
            function* concat(iter1, iter2) {
                yield * iter1;
                yield * iter2;
            }
            // 等同于
            function* concat(iter1, iter2) {
                for (var value of iter1) {
                    yield value;
                }
                for (var value of iter2) {
                    yield value;
                }
            }
        用于数组
            function* gen() {
                yield * ["a", "b", "c"];
            }
            gen().next() // { value:"a", done:false }
        被代理的对象有return的情况
            function* genFuncWithReturn() {
                yield 'a';
                yield 'b';
                return 'The result';
            }
            function* logReturned(genObj) {
                let result = yield * genObj;
                console.log(result);
            }
            [...logReturned(genFuncWithReturn())]
            // The result
            // 值为 [ 'a', 'b' ]

作为对象属性的Generator函数
    说明
        一个对象的属性可以是Generator函数。
    范例
        完整格式
            let obj = {
                myGeneratorMethod: function*() {
                    // ···
                }
            };
        简写
            let obj = {
                * myGeneratorMethod() {
                    // ···
                }
            };

Generator函数的this
    说明
        Generator函数总是返回一个遍历器。
        这个遍历器是Generator函数的实例,继承了Generator函数的prototype对象上的方法。
        Generator不是普通的构造函数,因为Generator返回的是遍历器对象,而不是this对象。
        Generator函数也不能跟new命令一起用,会报错。
    范例
        基本使用
            function* g() {}
            g.prototype.hello = function() {
                return 'hi!';
            };
            let obj = g();
            obj instanceof g // true
            obj.hello() // 'hi!'
        this无效
            function* g() {
                this.a = 11;
            }
            let obj = g();
            obj.a // undefined
        模拟构造函数
            function* gen() {
                this.a = 1;
                yield this.b = 2;
                yield this.c = 3;
            }
            function F() {
                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

含义
    说明
        Generator是实现状态机的最佳结构。
        协程(coroutine)是一种程序运行的方式,可以理解成“协作的线程”或“协作的函数”。
        协程既可以用单线程实现,也可以用多线程实现。
        前者是一种特殊的子例程,后者是一种特殊的线程。
        Generator函数是ECMAScript 6对协程的实现,但属于不完全实现。
    范例
        状态机
            // ES5实现方式
            var ticking = true;
            var clock = function() {
                if (ticking)
                    console.log('Tick!');
                else
                    console.log('Tock!');
                ticking = !ticking;
            }
            // ES6实现方式
            var clock = function*() {
                while (true) {
                    console.log('Tick!');
                    yield;
                    console.log('Tock!');
                    yield;
                }
            };

应用
    说明
        Generator可以暂停函数执行,返回任意表达式的值。
        这种特点使得Generator有多种应用场景。
    异步操作的同步化表达
        Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面。
        等到调用next方法时再往后执行。
        异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。
    范例
        同步方式的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();
        在任意对象上部署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

Promise对象

Promise的含义
    说明
        Promise是异步编程的一种解决方案,比传统的回调函数和事件更合理和更强大。
        ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
        所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。
        从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
        Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
    特点
        对象的状态不受外界影响。
        Promise对象代表一个异步操作,有三种状态:Pending、Resolved和Rejected。
        只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
        一旦状态改变,就不会再变,任何时候都可以得到这个结果。
        状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。

基本用法
    说明
        ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
        Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
        resolve和rejec是两个函数,由JavaScript引擎提供,不用自己部署。
        resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”。
        reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”。
        可以用then方法分别指定Resolved状态和Reject状态的回调函数。
        then方法可以接受两个回调函数作为参数。第二个函数是可选的。
    范例
        执行顺序
            let promise = new Promise(function(resolve, reject) {
                console.log('Promise');
                resolve();
            });
            promise.then(function() {
                console.log('Resolved.');
            });
            console.log('Hi!');
            // Promise
            // Hi!
            // Resolved
        封装Ajax
            var getJSON = function(url) {
                var promise = new Promise(function(resolve, reject) {
                    var client = new XMLHttpRequest();
                    client.open("GET", url);
                    client.onreadystatechange = handler;
                    client.responseType = "json";
                    client.setRequestHeader("Accept", "application/json");
                    client.send();
                    function handler() {
                        if (this.readyState !== 4) {
                            return;
                        }
                        if (this.status === 200) {
                            resolve(this.response);
                        } else {
                            reject(new Error(this.statusText));
                        }
                    };
                });
                    return promise;
            };
            getJSON("/posts.json").then(function(json) {
                console.log('Contents: ' + json);
            }, function(error) {
                console.error('出错了', error);
            });

Promise.prototype.then()
    说明
        Promise实例具有then方法,作用是为Promise实例添加状态改变时的回调函数。
        then方法返回的是一个新的Promise实例。
        因此可以采用链式写法,即then方法后面再调用另一个then方法。
        如果第一个then方法指定的回调函数,返回的是另一个Promise对象。
        第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。
    范例
        getJSON("/post/1.json").then(function(post) {
            return getJSON(post.commentURL);
        }).then(function funcA(comments) {
            console.log("Resolved: ", comments);
        }, function funcB(err) {
            console.log("Rejected: ", err);
        });

Promise.prototype.catch()
    说明
        Promise.prototype.catch方法是.then(null, rejection)的别名。
        用于指定发生错误时的回调函数。
        then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
        如果Promise状态已经变成Resolved,再抛出错误是无效的。
        Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。
        如果没有使用catch方法,Promise对象抛出的错误不会传递到外层代码(Chrome不遵守)。
        Node.js有一个unhandledRejection事件,专门监听未捕获的reject错误。
        catch方法返回的还是一个Promise对象,因此后面还可以接着调用then方法。
    范例
        作为Reject回调
            getJSON("/posts.json").then(function(posts) {
                // ...
            }).catch(function(error) {
                // 处理 getJSON 和 前一个回调函数运行时发生的错误
                console.log('发生错误!', error);
            });
        处理错误
            var promise = new Promise(function(resolve, reject) {
                throw new Error('test');
            });
            promise.catch(function(error) {
                console.log(error);
            });
            // Error: test
        错误冒泡
            getJSON("/post/1.json").then(function(post) {
                return getJSON(post.commentURL);
            }).then(function(comments) {
                // some code
            }).catch(function(error) {
                // 处理前面三个Promise产生的错误
            });
        Node.js的unhandledRejection事件
            process.on('unhandledRejection', function (err, promise) {
                console.error(err.stack)
            });

Promise.all()
    说明
        Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
        Promise.all方法的参数必须具有Iterator接口,且返回每个成员都是Promise实例。
        参数内的成员状态都变为Resolved,Promise.all返回的Promise的状态为Resolved。
        参数内的成员有一个状态为Rejected,Promise.all返回的Promise的状态为Rejected。
    范例
        var promises = [2, 3, 5, 7, 11, 13].map(function(id) {
            return getJSON("/post/" + id + ".json");
        });
        Promise.all(promises).then(function(posts) {
            // ...
        }).catch(function(reason) {
            // ...
        });

Promise.race()
    说明
        Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。
        参数内的成员只要有一个改变状态,Promise.race返回的状态便跟随改变。
        那个率先改变的Promise实例的返回值,就传递给p的回调函数。
    范例
        var 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))

Promise.resolve()
    说明
        有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
        参数是Promise实例,那么将不做任何修改、原封不动地返回这个实例。
        参数是thenable对象,那么将转换thenable对象为Promise,并执行对象的then方法。
        参数不是具有then方法的对象,或不是对象,则返回一个新Promise,状态为Resolved。
        不带有任何参数,直接返回一个Resolved状态的Promise对象。
    范例
        转换Jquery的Ajax
            var jsPromise = Promise.resolve($.ajax('/whatever.json'));
        参数是一个thenable对象
            let thenable = {
                then: function(resolve, reject) {
                    resolve(42);
                }
            };
            let p1 = Promise.resolve(thenable);
            p1.then(function(value) {
                console.log(value);
            });
            // 42
        参数不是具有then方法的对象,或不是对象
            var p = Promise.resolve('Hello');
            p.then(function(s) {
                console.log(s)
            });
            // Hello

Promise.reject()
    说明
        Promise.reject(reason)方法会返回一个新的Promise实例。
        该实例的状态为rejected。它的参数用法与Promise.resolve方法完全一致。
    范例
        var p = Promise.reject('出错了');
        // 等同于
        var p = new Promise((resolve, reject) => reject('出错了'))
        // 观察状态
        p.then(null, function(s) {
            console.log(s)
        });
        // 出错了

两个有用的附加方法
    说明
        ES6的Promise API提供的方法不是很多,有些有用的方法可以自己部署。
        下面是如何部署两个不在ES6之中、但很有用的方法。
        done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
        finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。
    范例
        done方法
            Promise.prototype.done = function (onFulfilled, onRejected) {
               this.then(onFulfilled, onRejected)
                 .catch(function (reason) {
                   // 抛出一个全局错误
                   setTimeout(() => { throw reason }, 0);
                 });
            };
            asyncFunc().then(f1).catch(r1).then(f2).done();
        finally方法
            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
                    })
                );
            };
            server.listen(0).then(function () {
                // run test
            }).finally(server.stop);

异步操作和Async函数

Thunk函数
    求值策略
        传值调用: 进入函数体之前,就计算参数内表达式的值,再将这个值传入函数。C采取吃策略。
        传名调用: 将参数中的表达式传入函数,在用到它的时候求值。Haskell语言采用这种策略。
    Thunk函数概念
        编译器的传名调用实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。
        这个临时函数就叫做Thunk函数。
    JavaScript中的Thunk
        JavaScript语言是传值调用,它的Thunk函数含义有所不同。
        在JavaScript语言中,Thunk函数替换的不是表达式。
        而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。
        Thunk函数真正的威力,在于可以自动执行Generator函数。
    范例
        Thunk函数的编译器实现
            function f(m) {
                return m * 2;
            }
            f(x + 5);
            // 等同于
            var thunk = function() {
                return x + 5;
            };
            function f(thunk) {
                return thunk() * 2;
            }
        JavaScript的Thunk实现
            // 正常版本的readFile(多参数版本)
            fs.readFile(fileName, callback);
            // Thunk版本的readFile(单参数版本)
            var readFileThunk = Thunk(fileName);
            readFileThunk(callback);
            var Thunk = function(fileName) {
                return function(callback) {
                    return fs.readFile(fileName, callback);
                };
            };

co模块
    说明
        co模块是著名程序员TJ Holowaychuk于2013年6月发布的一个小工具。
        用于Generator函数的自动执行。
        co模块其实就是将两种自动执行器(Thunk函数和Promise对象),包装成一个模块。
        使用co的前提是,Generator函数的yield命令后面,只能是Thunk函数或Promise对象。
        co支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。
    范例
        基于Promise的Generator自动执行器
            var fs = require('fs');
            var readFile = function(fileName) {
                return new Promise(function(resolve, reject) {
                    fs.readFile(fileName, function(error, data) {
                        if (error) return reject(error);
                        resolve(data);
                    });
                });
            };
            var gen = function*() {
                var f1 = yield readFile('/etc/fstab');
                var f2 = yield readFile('/etc/shells');
                console.log(f1.toString());
                console.log(f2.toString());
            };
            // 手动执行
            var g = gen();
            g.next().value.then(function(data) {
                g.next(data).value.then(function(data) {
                    g.next(data);
                });
            });
            // 自动执行
            function run(gen) {
                var g = gen();
                    function next(data) {
                    var result = g.next(data);
                    if (result.done) return result.value;
                    result.value.then(function(data) {
                        next(data);
                    });
                }
                    next();
            }
            run(gen);
        co处理并发的异步操作
            // 数组的写法
            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);

async函数
    说明
        ES7提供了async函数,使得异步操作变得更加方便。它是Generator函数的语法糖。
        async函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
        async语法是将Generator函数的星号(*)替换成async,将yield替换成await。
        async返回一个Promise对象。
        async内部return语句返回的值,会成为then方法回调函数的参数。
        async内部抛出错误,会导致返回的Promise对象变为reject状态。
        async返回的Promise对象,等到内部所有await命令的Promise对象执行完,才改变状态。
        如果await命令后面不是一个Promise对象,会被转成一个立即resolve的Promise对象。
        只要一个await语句后面的Promise变为reject,那么整个async函数都会中断执行。
        如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject。
    范例
        返回值
            async function f() {
                return 'hello world';
            }
            f().then(v => console.log(v))
            // "hello world"
        抛出错误
            async function f() {
                throw new Error('出错了');
            }
            f().then(
                v => console.log(v),
                e => console.log(e)
            )
            // Error: 出错了
        状态改变
            async function getTitle(url) {
                let response = await fetch(url);
                let html = await response.text();
                return html.match(/<title>([\s\S]+)<\/title>/i)[1];
            }
            getTitle('https://tc39.github.io/ecma262/').then(console.log)
            // "ECMAScript 2017 Language Specification"
        await后不是Promise对象
            async function f() {
                return await 123;
            }
            f().then(v => console.log(v))
            // 123
        await后面的异步操作出错
            async function f() {
                await new Promise(function(resolve, reject) {
                    throw new Error('出错了');
                });
            }
            f().then(v => console.log(v)).catch(e => console.log(e))
            // Error:出错了
        async函数的实现
            async function fn(args) {
                // ...
            }
            // 等同于
            function fn(args) {
                return spawn(function*() {
                    // ...
                });
            }
            function spawn(genF) {
                return new Promise(function(resolve, reject) {
                    var gen = genF();
                    function step(nextF) {
                        try {
                            var next = nextF();
                        } catch (e) {
                            return reject(e);
                        }
                        if (next.done) {
                            return resolve(next.value);
                        }
                        Promise.resolve(next.value).then(function(v) {
                            step(function() {
                                return gen.next(v);
                            });
                        }, function(e) {
                            step(function() {
                                return gen.throw(e);
                            });
                        });
                    }
                    step(function() {
                        return gen.next(undefined);
                    });
                });
            }
        async用法
            function timeout(ms) {
                return new Promise((resolve) => {
                    setTimeout(resolve, ms);
                });
            }
            async function asyncPrint(value, ms) {
                await timeout(ms);
                console.log(value)
            }
            asyncPrint('hello world', 50);
        不同写法对比
            // Promise写法
            function chainAnimationsPromise(elem, animations) {
                // 变量ret用来保存上一个动画的返回值
                var ret = null;
                // 新建一个空的Promise
                var p = Promise.resolve();
                // 使用then方法,添加所有动画
                for (var anim of animations) {
                    p = p.then(function(val) {
                        ret = val;
                        return anim(elem);
                    });
                }
                // 返回一个部署了错误捕捉机制的Promise
                return p.catch(function(e) {
                    /* 忽略错误,继续执行 */
                }).then(function() {
                    return ret;
                });
            }
            // Generator写法
            function chainAnimationsGenerator(elem, animations) {
                return spawn(function*() {
                    var ret = null;
                    try {
                        for (var anim of animations) {
                            ret = yield anim(elem);
                        }
                    } catch (e) {
                        /* 忽略错误,继续执行 */
                    }
                    return ret;
                });
            }
            // Async写法
            async function chainAnimationsAsync(elem, animations) {
                var ret = null;
                try {
                    for (var anim of animations) {
                        ret = await anim(elem);
                    }
                } catch (e) {
                    /* 忽略错误,继续执行 */
                }
                return ret;
            }

异步遍历器
    说明
        Iterator中next方法是同步的,只要调用就必须立刻返回值。
        对于异步操作解决方法是,Generator函数里面的异步操作返回Thunk函数或者Promise。
        即value属性是一个Thunk函数或者Promise对象,但done属性则还是同步产生的。
        目前,有一个提案,为异步操作提供原生的遍历器接口。
        即value和done这两个属性都是异步产生,这称为”异步遍历器“(Async Iterator)。
        异步遍历器的最大的语法特点,就是调用遍历器的next方法,返回的是一个Promise对象。
        对象的异步遍历器接口,部署在Symbol.asyncIterator属性上面。
        for await...of 则是用于遍历异步的Iterator接口。
        异步Generator函数可返回一个异步遍历器对象。
    范例
        语法
            asyncIterator.next().then(({value,done}) => {/* ... */});
        遍历
            for await (const line of readLines(filePath)) {
                console.log(line);
            }
        异步Generator
            async function* readLines(path) {
                let file = await fileOpen(path);
                try {
                    while (!file.EOF) {
                        yield await file.readLine();
                    }
                } finally {
                    await file.close();
                }
            }

Class

Class基本语法
    说明
        ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。
        通过class关键字,可以定义类。
        类中定义的方法不需要function关键字,也不需要逗号分隔,类中定义的方法是不可枚举的。
        使用时,直接对类使用new命令,不使用new是没法当作函数调用,会报错。
        类的所有方法都定义在类的prototype属性上面,新方法可以添加在prototype对象上面。
        类的属性名,可以采用表达式。
        Class不存在变量提升(hoist),这一点与ES5完全不同。
        类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式
    constructor方法
        constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
        类必须有constructor方法,如果没有显式定义,会默认添加一个空的constructor方法。
        constructor方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
    范例
        基本使用
            let methodName = "methodName";
            class Point {
                constructor() {
                    this.x = x;
                    this.y = y;
                }
                toString() {
                    return '(' + this.x + ', ' + this.y + ')';
                }
                toValue() {
                    // ...
                }
                [methodName]() {
                    // ...
                }
            }
            Object.assign(Point.prototype, {
                otherMethodName(){}
            });
            let point = new Point(2,3);
            console.log(Object.keys(Point.prototype));
            // [ 'otherMethodName' ]
            var p1 = new Point(2,3);
            var p2 = new Point(3,2);
            p1.__proto__ === p2.__proto__
            //true
            p1.__proto__.printName = function () { return 'Oops' };
            p1.printName() // "Oops"
            p2.printName() // "Oops"
        不存在变量提升
            new Foo(); // ReferenceError
            class Foo {}
        Class表达式(Me只能在类的内部使用)
            const MyClass = class Me {
                getClassName() {
                    return Me.name;
                }
            };
            let inst = new MyClass();
            inst.getClassName() // Me
            Me.name // ReferenceError: Me is not defined
        Class表达式省略类名
            const MyClass = class { /* ... */ };
        立即执行的class
            let person = new class {
                constructor(name) {
                    this.name = name;
                }
                sayName() {
                    console.log(this.name);
                }
            }('张三');
            person.sayName(); // "张三"
        利用外部函数实现私有方法
            class Widget {
                foo(baz) {
                    bar.call(this, baz);
                }
            }
            function bar(baz) {
                return this.snaf = baz;
            }
        利用Symbol实现私有方法
            const bar = Symbol('bar');
            const snaf = Symbol('snaf');
            export default class myClass {
                // 公有方法
                foo(baz) {
                    this[bar](baz);
                }
                // 私有方法
                [bar](baz) {
                    return this[snaf] = baz;
                }
            };

Class的继承
    说明
        Class之间可以通过extends关键字实现继承,这比通过修改原型链实现继承更加清晰方便。
        在类中super指向父类的构造函数,如果不调用super方法,子类就得不到this对象。
        子类必须在constructor方法中调用super方法,否则新建实例时会报错。
        因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。
        在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。
        ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面。
        ES6的继承,实质是先创造父类的实例对象this,然后再用子类的构造函数修改this。
        如果子类没有定义constructor方法,这个方法会被默认添加。。
    super
        作为函数调用时(即super(...args)),super代表父类的构造函数。
        作为对象调用时(即super.prop或super.method()),super代表父类。
        作为对象调用时super即可以引用父类实例的属性和方法,也可以引用父类的静态方法。
    原型
        子类的__proto__属性,表示构造函数的继承,总是指向父类。
        子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype。
        作为一个对象,子类的原型(__proto__属性)是父类;
        作为一个构造函数,子类的原型(prototype属性)是父类的实例。
    Extends的继承目标
        extends关键字后面可以跟多种类型的值。
        只要目标是一个有prototype属性的函数,就能被B继承。
    范例
        基本使用
            class Point {
                constructor(x, y) {
                    this.x = x;
                    this.y = y;
                }
            }
            class ColorPoint extends Point {
                constructor(x, y, color) {
                    super(x, y);
                    this.color = color;
                }
            }
            let cp = new ColorPoint(25, 8, 'green');
            cp instanceof ColorPoint // true
            cp instanceof Point      // true
        __proto__属性
            class A {}
            class B extends A {}
            B.__proto__ === A // true
            B.prototype.__proto__ === A.prototype // true
        继承
            // 继承自Object
            class A extends Object {}
            A.__proto__ === Object // true
            A.prototype.__proto__ === Object.prototype // true
            // A作为一个基类(即不存在任何继承)
            class A {}
            A.__proto__ === Function.prototype // true
            A.prototype.__proto__ === Object.prototype // true
            // 子类继承null
            class A extends null {}
            A.__proto__ === Function.prototype // true
            A.prototype.__proto__ === undefined // true

原生构造函数的继承
    说明
        生构造函数是指语言内置的构造函数,通常用来生成数据结构。
        之前,这些原生构造函数是无法继承的。
        因为ES5是先新建子类的实例对象this,再将父类的属性添加到子类上。
        由于父类的内部属性无法获取,导致无法继承原生的构造函数。
        ES6允许继承原生构造函数定义子类。
        因为ES6是先新建父类的实例对象this,然后再用子类的构造函数修饰this。
        ES6中,如果Object方法不是通过new Object形式调用,Object构造函数会忽略参数。
    原生构造函数
        Boolean(), Number(), String(), Array(), Date(), Function(), RegExp(), Error(), Object()
    范例
        ES5无法继承原生构造函数
            function MyArray() {
                Array.apply(this, arguments);
            }
            MyArray.prototype = Object.create(Array.prototype, {
                constructor: {
                    value: MyArray,
                    writable: true,
                    configurable: true,
                    enumerable: true
                }
            });
            var colors = new MyArray();
            colors[0] = "red";
            colors.length // 0
            colors.length = 0;
            colors[0] // "red"
        ES6继承原生构造函数定义子类
            class MyArray extends Array {
                constructor(...args) {
                    super(...args);
                }
            }
            var arr = new MyArray();
            arr[0] = 12;
            arr.length // 1
            arr.length = 0;
            arr[0] // undefined
        自定义数据结构
            class VersionedArray extends Array {
                constructor() {
                    super();
                    this.history = [[]];
                }
                commit() {
                    this.history.push(this.slice());
                }
                revert() {
                    this.splice(0, this.length, ...this.history[this.history.length - 1]);
                }
            }
            var x = new VersionedArray();
            x.push(1);
            x.push(2);
            x               // [1, 2]
            x.history       // [[]]
            x.commit();
            x.history       // [[], [1, 2]]
            x.push(3);
            x               // [1, 2, 3]
            x.revert();
            x               // [1, 2]
        继承Object的子类的行为差异
            class NewObj extends Object {
                constructor() {
                    super(...arguments);
                }
            }
            var o = new NewObj({
                attr: true
            });
            console.log(o.attr === true); // false

Class的取值函数(getter)和存值函数(setter)
    说明
        在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数。
        存值函数和取值函数是设置在属性的descriptor对象上的。
    范例
        class MyClass {
            constructor() {
            }
            get prop() {
                return 'getter';
            }
            set prop(value) {
                console.log('setter: ' + value);
            }
        }
        let inst = new MyClass();
        inst.prop = 123;
        // setter: 123
        inst.prop
        // 'getter'
        var descriptor = Object.getOwnPropertyDescriptor(MyClass.prototype, "prop");
        "get" in descriptor   // true
        "set" in descriptor   // true

Class的Generator方法
    说明
        如果某个方法之前加上星号(*),就表示该方法是一个Generator函数。
    范例
        class Foo {
            constructor(...args) {
                this.args = args;
            }
            * [Symbol.iterator]() {
                for (let arg of this.args) {
                    yield arg;
                }
            }
        }
        for (let x of new Foo('hello', 'world')) {
            console.log(x);
        }
        // hello
        // world

Class的静态方法
    说明
        类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
        如果在一个方法前,加上static关键字,表示该方法为静态方法。
        静态方法不会被实例继承,而是直接通过类来调用。
        父类的静态方法,可以被子类继承。
    范例
        class Foo {
            static classMethod() {
                return 'hello';
            }
        }
        class Bar extends Foo {
            static classMethodToo() {
                return super.classMethod() + ', too';
            }
        }
        Foo.classMethod()      // 'hello'
        Bar.classMethod()      // 'hello'
        console.log(Bar.classMethodToo());  // 'hello, too'
        var foo = new Foo()
        foo.classMethod()
        // TypeError: foo.classMethod is not a function

Class的静态属性和实例属性
    说明
        静态属性指的是Class本身的属性,即Class.propname。
        而不是定义在实例对象(this)上的属性。
        ES6明确规定,Class内部只有静态方法,没有静态属性。
        要实现静态属性只能手动给类添加属性。
        ES7有一个静态属性的提案,目前Babel转码器支持。
    范例
        ES6静态属性
            class Foo {}
            Foo.prop = 1;
            Foo.prop // 1
        ES7实例属性
            class MyClass {
                myProp = 42;
                constructor() {
                    console.log(this.myProp); // 42
                }
            }
        ES7静态属性
            class MyClass {
                static myStaticProp = 42;
                constructor() {
                    console.log(MyClass.myProp); // 42
                }
            }

new.target属性
    说明
        ES6为new命令引入了一个new.target属性,返回new命令作用于的那个构造函数。
        如果构造函数不是通过new命令调用的,new.target会返回undefined。
        这个属性可以用来确定构造函数是怎么调用的。
        子类继承父类时,new.target会返回子类。
    范例
        构造函数中使用
            function Person(name) {
                if (new.target === Person) {
                    this.name = name;
                } else {
                    throw new Error('必须使用new生成实例');
                }
            }
            var person = new Person('张三'); // 正确
            var notAPerson = Person.call(person, '张三'); // 报错
        类中使用
            class Rectangle {
                constructor(length, width) {
                    console.log(new.target === Rectangle);
                    this.length = length;
                    this.width = width;
                }
            }
            var obj = new Rectangle(3, 4); // 输出 true

Mixin模式的实现
    说明
        Mixin模式指的是,将多个类的接口“混入”(mix in)另一个类。它在ES6的实现如下。
    范例
        function mix(...mixins) {
            class Mix {}
            for (let mixin of mixins) {
                copyProperties(Mix, mixin);
                copyProperties(Mix.prototype, mixin.prototype);
            }
            return Mix;
        }
        function copyProperties(target, source) {
            for (let key of Reflect.ownKeys(source)) {
                if (key !== "constructor" && key !== "prototype" && key !== "name") {
                    let desc = Object.getOwnPropertyDescriptor(source, key);
                    Object.defineProperty(target, key, desc);
                }
            }
        }
        class DistributedEdit extends mix(Loggable, Serializable) {
          // ...
        }

修饰器

类的修饰
    说明
        修饰器(Decorator)是一个函数,用来修改类的行为。
        这是ES7的一个提案,目前Babel转码器已经支持。
        修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。
        这意味着,修饰器能在编译阶段运行代码。
        修饰器函数的第一个参数,就是所要修饰的目标类。
    范例
        基本使用
            function testable(target) {
                target.isTestable = true;
            }
            @testable
            class MyTestableClass {}
            console.log(MyTestableClass.isTestable) // true
        改变修饰器行为
            function testable(isTestable) {
                return function(target) {
                    target.isTestable = isTestable;
                }
            }
            @testable(true)
            class MyTestableClass {}
            MyTestableClass.isTestable // true
            @testable(false)
            class MyClass {}
            MyClass.isTestable // false
        添加实例属性
            function testable(target) {
                target.prototype.isTestable = true;
            }
            @testable
            class MyTestableClass {}
            let obj = new MyTestableClass();
            obj.isTestable // true

方法的修饰
    说明
        修饰器不仅可以修饰类,还可以修饰类的属性。
        此时,修饰器函数一共可以接受三个参数。
        参数分别为: 修饰的目标对象,所要修饰的属性名,该属性的描述对象。
        如果同一个方法有多个修饰器,先从外到内进入,然后由内向外执行。
        修饰器还能用来类型检查。从长期来看,它将是JavaScript代码静态分析的重要工具。
    范例
        修饰为只读
            function readonly(target, name, descriptor) {
                descriptor.writable = false;
                return descriptor;
            }
            readonly(Person.prototype, 'name', descriptor);
            class Person {
                @readonly
                name() {
                    return `${this.first} ${this.last}`
                }
            }
        添加日志
            class Math {
                @log
                add(a, b) {
                    return a + b;
                }
            }
            function log(target, name, descriptor) {
                var oldValue = descriptor.value;
                descriptor.value = function() {
                    console.log(`Calling "${name}" with`, arguments);
                    return oldValue.apply(null, arguments);
                };
                return descriptor;
            }
            const math = new Math();
            math.add(2, 4);

为什么修饰器不能用于函数?
    说明
        修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。

core-decorators.js
    说明
        core-decorators.js是一个第三方模块,提供了几个常见的修饰器。
        @autobind修饰器使得方法中的this对象,绑定原始对象。
        @readonly修饰器使得属性或方法不可写。
        @override修饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。
        @deprecate或@deprecated修饰器在控制台显示一条警告,表示该方法将废除。
        @suppressWarnings修饰器抑制decorated修饰器导致的console.warn()调用。

Mixin
    说明
        在修饰器的基础上,可以实现Mixin模式。
        Mixin模式就是对象继承的一种替代方案,意为在一个对象之中混入另外一个对象的方法。
    范例
        通过原型实现
            export function mixins(...list) {
                return function (target) {
                    Object.assign(target.prototype, ...list);
                };
            }
            import { mixins } from './mixins';
            const Foo = {
                foo() { console.log('foo') }
            };
            @mixins(Foo)
            class MyClass {}
            let obj = new MyClass();
            obj.foo() // "foo"
        通过类继承实现
            let Mixin1 = (superclass) => class extends superclass {
                foo() {
                    console.log('foo from Mixin1');
                    if (super.foo) super.foo();
                }
            };
            let Mixin2 = (superclass) => class extends superclass {
                foo() {
                    console.log('foo from Mixin2');
                    if (super.foo) super.foo();
                }
            };
            class S {
                foo() {
                    console.log('foo from S');
                }
            }
            class C extends Mixin1(Mixin2(S)) {
                foo() {
                    console.log('foo from C');
                    super.foo();
                }
            }
            new C().foo()
            // foo from C
            // foo from Mixin1
            // foo from Mixin2
            // foo from S

Trait
    说明
        Trait也是一种修饰器,效果与Mixin类似,但是提供更多功能。
        如防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等等。
        traits-decorator这个第三方模块提供的traits修饰器。可接受对象和类作为参数。
        混入时出现同名方法会报错,可通过别名和排除方法来解决,具体可参照模块的说明文档。
    范例
        import { traits } from 'traits-decorator';
        class TFoo {
            foo() { console.log('foo') }
        }
        const TBar = {
            bar() { console.log('bar') }
        };
        @traits(TFoo, TBar)
        class MyClass { }
        let obj = new MyClass();
        obj.foo() // foo
        obj.bar() // bar

Babel转码器的支持
    说明
        目前,Babel转码器已经支持Decorator。
        首先,安装babel-core和babel-plugin-transform-decorators。
        由于后者包括在babel-preset-stage-0之中,所以安装babel-preset-stage-0亦可。
    范例
        $ npm install babel-core babel-plugin-transform-decorators
        // 配置文件改为
        {
          "plugins": ["transform-decorators"]
        }
        // 脚本中打开的命令为
        babel.transform("code", {plugins: ["transform-decorators"]})

Module

概述
    说明
        在ES6之前,社区制定了一些模块加载方案,最主要的有CommonJS和AMD两种。
        CommonJS用于服务器,AMD用于浏览器。
        ES6在语言规格的层面上,实现了模块功能,可以取代现有的CommonJS和AMD规范。
        ES6模块的设计思想是尽量的静态化,使编译时就能确定模块的依赖关系和输入和输出的变量。
        CommonJS和AMD模块,都只能在运行时确定这些东西。
    ES6模块的优点
        静态分析,能进一步拓宽JavaScript的语法,比如引入宏(macro)和类型检验。
        不再需要UMD模块格式了,将来服务器和浏览器都会支持ES6模块格式。
        将来浏览器的新API就能用模块格式提供,不需做成全局变量或者navigator对象的属性。
        不再需要对象作为命名空间(比如Math对象),未来这些功能可以通过模块提供。
    范例
        CommonJS模块
            let { stat, exists, readFile } = require('fs');
            // 等同于
            let _fs = require('fs');
            let stat = _fs.stat, exists = _fs.exists, readfile = _fs.readfile;
        ES6模块
            import { stat, exists, readFile } from 'fs';
        浏览器使用ES6模块
            <script type="module" src="foo.js"></script>

严格模式
    说明  
        ES6的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
    严格模式限制
        变量必须声明后再使用
        函数的参数不能有同名属性,否则报错
        不能使用with语句
        不能对只读属性赋值,否则报错
        不能使用前缀0表示八进制数,否则报错
        不能删除不可删除的属性,否则报错
        不能删除变量delete prop,会报错,只能删除属性delete global[prop]
        eval不会在它的外层作用域引入变量
        eval和arguments不能被重新赋值
        arguments不会自动反映函数参数的变化
        不能使用arguments.callee
        不能使用arguments.caller
        禁止this指向全局对象
        不能使用fn.caller和fn.arguments获取函数调用的堆栈
        增加了保留字(比如protected、static和interface)

export命令
    说明
        export命令用于规定模块的对外接口。
        一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。
        如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
        通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。
        export命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
        export命令可以出现在模块的任何位置,只要处于模块顶层就可以。
    范例
        输出变量
            export var firstName = 'Michael';
            export var lastName = 'Jackson';
            export var year = 1958;
        统一输出
            var firstName = 'Michael';
            var lastName = 'Jackson';
            var year = 1958;
            export {firstName, lastName, year};
        输出函数或类
            export function multiply(x, y) {
                return x * y;
            };
        重新命名
            function v1() { ... }
            function v2() { ... }
            export {
                v1 as streamV1,
                v2 as streamV2,
                v2 as streamLatestVersion
            };
        动态更新(500ms后foo的值变化)
            export var foo = 'bar';
            setTimeout(() => foo = 'baz', 500);

import命令
    说明
        import命令用于输入其他模块提供的功能。
        import命令会执行所加载的模块。
        import命令接受一个对象(用大括号表示),里面指定要从其他模块导入的变量名。
        大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同。
        as关键字可将输入的变量重命名。
        import命令具有提升效果,会提升到整个模块的头部,首先执行。
        ES7有一个提案,简化先输入后输出的写法,拿掉输出时的大括号。
    范例
        导入模块中的变量
            import {firstName, lastName, year} from './profile';
        重命名
            import { lastName as surname } from './profile';
        ES7简写
            // 提案的写法
            export v from 'mod';
            // 现行的写法
            export {v} from 'mod';
        先输入后输出同一个模块
            export { es6 as default } from './someModule';
            // 等同于
            import { es6 } from './someModule';
            export default es6;

模块的整体加载
    说明
        除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象。
        所有输出值都加载在这个对象上面。
    范例
        // circle.js
        export function area(radius) {
            return Math.PI * radius * radius;
        }
        export function circumference(radius) {
            return 2 * Math.PI * radius;
        }
        // 逐一加载
        import { area, circumference } from './circle';
        console.log('圆面积:' + area(4));
        console.log('圆周长:' + circumference(14));
        // 整体加载
        import * as circle from './circle';
        console.log('圆面积:' + circle.area(4));
        console.log('圆周长:' + circle.circumference(14));

export default命令
    说明
        export default命令用于指定模块的默认输出。
        一个模块只能有一个默认输出,因此export deault命令只能使用一次。
        他模块加载该模块时,import命令可以为该默认加载的变量指定任意名字。
        导入带有默认输出的模块时,import命令后面,不使用大括号。
        export default其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。
    范例
        基本使用
            // export-default.js
            export default function() {
                console.log('foo');
            }
            // import-default.js
            import customName from './export-default';
            customName(); // 'foo'
        用于非匿名函数
            function foo() {
                console.log('foo');
            }
            export default foo;
        后面不能跟变量声明语句
            // 正确
            export var a = 1;
            // 正确
            var a = 1;
            export default a;
            // 错误
            export default var a = 1;
        同时输入默认方法和其他变量
            import customName, { otherMethod } from './export-default';
        输出类
            // MyClass.js
            export default class { ... }
            // main.js
            import MyClass from 'MyClass';
            let o = new MyClass();

模块的继承
    说明
        模块之间也可以继承。
        export * 表示再输出指定模块的所有属性和方法。
        export * 会忽略指定模块的default方法。
    范例
        circleplus模块继承circle模块
            // circleplus.js
            export * from 'circle';
            export var e = 2.71828182846;
            export default function(x) {
                return Math.exp(x);
            }
            // main.js
            import * as math from 'circleplus';
            import exp from 'circleplus';
            console.log(exp(math.e));
        改名再输出
            export { area as circleArea } from 'circle';

ES6模块加载的实质
    说明
        ES6模块加载的机制,与CommonJS模块完全不同。
        CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。
        CommonJS一旦输出一个值,模块内部的变化就影响不到这个值。
        ES6模块的运行机制遇到import命令时,只生成一个动态的只读引用。
        ES6的输入有点像Unix系统的“符号连接”,原始值变了,import输入的值也会跟着变。
        ES6输入的模块变量是“符号连接”,这个变量指向的地址是只读的,对它进行重新赋值会报错。
        export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
    范例
        CommonJS模块机制
            // lib.js
            var counter = 3;
            function incCounter() {
                counter++;
            }
            module.exports = {
                counter: counter,
                incCounter: incCounter,
            };
            // main.js
            var mod = require('./lib');
            console.log(mod.counter); // 3
            mod.incCounter();
            console.log(mod.counter); // 3
        ES6模块机制
            // lib.js
            export let counter = 3;
            export function incCounter() {
                counter++;
            }
            // main.js
            import { counter, incCounter } from './lib';
            console.log(counter); // 3
            incCounter();
            console.log(counter); // 4
        对变量赋值
            // lib.js
            export let obj = {};
            // main.js
            import { obj } from './lib';
            obj.prop = 123; // OK
            obj = {}; // TypeError
        不同的脚本加载同一模块
            // mod.js
            function C() {
                this.sum = 0;
                this.add = function() {
                    this.sum += 1;
                };
                this.show = function() {
                    console.log(this.sum);
                };
            }
            export let c = new C();
            // x.js
            import {c} from './mod';
            c.add();
            // y.js
            import {c} from './mod';
            c.show();
            // main.js
            import './x';
            import './y';
            // 输出1

循环加载
    说明
        循环加载指的是,a脚本的执行依赖b脚本,而b脚本的执行又依赖a脚本。
        通常,循环加载表示存在强耦合,如果处理不好,还可能导致递归加载,使得程序无法执行。
    CommonJS加载原理
        CommonJS的一个模块,就是一个脚本文件。
        require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
        即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。
        CommonJS模块无论加载多少次,都只会在第一次加载时运行一次。
    CommonJS循环加载
        CommonJS模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行。
        一旦出现某个模块被循环加载,就只输出已经执行的部分,还未执行的部分不会输出。
    ES6循环加载
        ES6处理“循环加载”与CommonJS有本质的不同,ES6模块是动态引用。
        使用import从一个模块加载变量,变量不会被缓存,而是成为一个指向被加载模块的引用。
        需要开发者自己保证,真正取值的时候能够取到值。
    范例
        CommonJS循环加载
            // a.js如下
            exports.done = false;
            var b = require('./b.js');
            console.log('在 a.js 之中,b.done = %j', b.done);
            exports.done = true;
            console.log('a.js 执行完毕');
            // b.js
            exports.done = false;
            var a = require('./a.js');
            console.log('在 b.js 之中,a.done = %j', a.done);
            exports.done = true;
            console.log('b.js 执行完毕');
            // main.js
            var a = require('./a.js');
            var b = require('./b.js');
            console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
            // 命令行执行main.js
            在 b.js 之中,a.done = false
            b.js 执行完毕
            在 a.js 之中,b.done = true
            a.js 执行完毕
            在 main.js 之中, a.done=true, b.done=true
        ES6循环加载
            // a.js如下
            import {bar} from './b.js';
            console.log('a.js');
            console.log(bar);
            export let foo = 'foo';
            // b.js
            import {foo} from './a.js';
            console.log('b.js');
            console.log(foo);
            export let bar = 'bar';
            // 命令行执行a.js
            $ babel-node a.js
            b.js
            undefined
            a.js
            bar

跨模块常量
    说明
        const声明的常量只在当前代码块有效。
        如果想设置跨模块的常量(即跨多个文件),可以采用下面的写法。
    范例
        // constants.js 模块
        export const A = 1;
        export const B = 3;
        export const C = 4;
        // test1.js 模块
        import * as constants from './constants';
        console.log(constants.A); // 1
        console.log(constants.B); // 3
        // test2.js 模块
        import {A, B} from './constants';
        console.log(A); // 1
        console.log(B); // 3

ES6模块的转码
    说明
        浏览器目前还不支持ES6模块,为了现在就能使用,可以将转为ES5的写法。
        除了Babel可以用来转码之外,ES6 module transpiler和SystemJS也可以用来转码。
    ES6 module transpiler
        ES6 module transpiler是square公司开源的一个转码器。
        可以将ES6模块转为CommonJS模块或AMD模块的写法,从而在浏览器中使用。
    SystemJS
        SystemJS是一个垫片库(polyfill),可以在浏览器内加载ES6、AMD和CommonJS模块。
        SystemJS将模块转为ES5格式。它在后台调用的是Google的Traceur转码器。
        System.import使用异步加载,返回一个Promise对象,可以针对这个对象编程。
    范例
        ES6 module transpiler
            // 安装
            $ npm install -g es6-module-transpiler
            // 将ES6模块文件转码
            $ compile-modules convert file1.js file2.js
            // -o参数可以指定转码后的文件名
            $ compile-modules convert -o out.js file1.js
        SystemJS
            // 先在网页内载入system.js文件。
            <script src="system.js"></script>
            // app/es6-file.js:
            export class q {
              constructor() {
                this.es6 = 'hello';
              }
            }
            // 使用System.import方法加载模块文件。
            <script>
            System.import('app/es6-file').then(function(m) {
                console.log(new m.q().es6); // hello
            });
            </script>

二进制数组

ArrayBuffer对象
    说明
        ArrayBuffer对象代表储存二进制数据的一段内存。
        它不能直接读写,只能通过视图(TypedArray视图和DataView视图)来读写。
        视图的作用是以指定格式解读二进制数据。
        ArrayBuffer也是一个构造函数,可以分配一段可以存放数据的连续内存区域。
        ArrayBuffer实例的byteLength属性,返回所分配的内存区域的字节长度。
        如果要分配的内存区域很大,有可能分配失败(因为没有那么多的连续空余内存)。
        ArrayBuffer实例的slice方法,可内存区域的一部分,拷贝生成一个新的ArrayBuffer。
        ArrayBuffer有一个静态方法isView,返回指定参数是否为ArrayBuffer的视图实例。
    范例
        生成32位内存区域
            var buf = new ArrayBuffer(32);
            buffer.byteLength
            // 32
        通过DataView读取ArrayBuffer
            var buf = new ArrayBuffer(32);
            var dataView = new DataView(buf);
            dataView.getUint8(0) // 0
        检查是否分配成功
            if (buffer.byteLength === n) {
                // 成功
            } else {
                // 失败
            }
        slice
            var buffer = new ArrayBuffer(8);
            var newBuffer = buffer.slice(0, 3);
        ArrayBuffer.isView
            var buffer = new ArrayBuffer(8);
            ArrayBuffer.isView(buffer) // false
            var v = new Int32Array(buffer);
            ArrayBuffer.isView(v) // true

TypedArray视图
    说明
        ArrayBuffer对象作为内存区域,可以存放多种类型的数据。
        同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)。
        ArrayBuffer有两种视图,一种是TypedArray视图,另一种是DataView视图。
        前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。
        目前,TypedArray视图一共包括9种类型,每一种视图都是一种构造函数。
    类型
        Int8Array          (1 byte)     8位有符号整数
        Uint8Array         (1 bytes)    8位无符号整数
        Uint8ClampedArray  (1 bytes)    8位无符号整数,溢出处理不同
        Int16Array         (2 bytes)    16位有符号整数
        Uint16Array        (2 bytes)    16位无符号整数
        Int32Array         (4 bytes)    32位有符号整数
        Uint32Array        (4 bytes)    32位无符号整数
        Float32Array       (4 bytes)    32位浮点数
        Float64Array       (8 bytes)    64位浮点数
    和数组相同点
        这9个构造函数生成的数组,统称为TypedArray视图。
        它们很像普通数组,都有length属性,都能用方括号运算符([])获取单个元素。
        所有数组的方法,在它们上面都能使用。
        TypedArray部署了Iterator接口,所以可以被遍历。
    和数组差异
        TypedArray数组的所有成员,都是同一种类型。
        TypedArray数组的成员是连续的,不会有空位。
        TypedArray数组成员的默认值为0。
        TypedArray数组只是一层视图,本身不储存数据,数据都储存在底层的ArrayBuffer中。
        TypedArray要获取底层对象必须使用buffer属性。
    字节序
        字节序指的是数值在内存中的表示方式,分为小端字节序和大端字节序。
        所有个人电脑几乎都是小端字节序,所以TypedArray数组内部也采用小端字节序读取数据。
        很多网络设备和特定的操作系统采用的是大端字节序。
        有一个占据四个字节的16进制数0x12345678。
        小端字节序将最不重要的字节排在前面,储存顺序就是78563412。
        大端字节序则完全相反,将最重要的字节排在前面,储存顺序就是12345678。
        如果一段数据是大端字节序,TypedArray数组将无法正确解析,因为它只能处理小端字节序。
        为了解决这个问题,JavaScript引入DataView对象,可以设定字节序,下文会详细介绍。
    属性和
        BYTES_PER_ELEMENT             构造器属性,表示所占据的字节数
        .prototype.BYTES_PER_ELEMENT  表示所占据的字节数
        .prototype.byteLength         返回TypedArray数组占据的内存长度(字节)
        .prototype.byteOffset         返回TypedArray在ArrayBuffer对象的偏移
        .prototype.length             表示TypedArray数组含有多少个成员
    实例方法
        .prototype.set()              用于复制数组,将一段内容完全复制到另一段内存。
        .prototype.subarray()         对于TypedArray数组的一部分,新建一个新视图。
        .prototype.slice()            返回一个指定位置的新的TypedArray实例。
        TypedArray.of()
    静态方法
        TypedArray.of()               将参数转为一个TypedArray实例。
        TypedArray.from()             把一个可遍历的数据结构转换为TypedArray。
    范例
        构造函数生成视图
            // 创建一个8字节的ArrayBuffer
            var b = new ArrayBuffer(8);
            // 创建一个指向b的Int32视图,开始于字节0,直到缓冲区的末尾
            var v1 = new Int32Array(b);
            // 创建一个指向b的Uint8视图,开始于字节2,直到缓冲区的末尾
            var v2 = new Uint8Array(b, 2);
            // 创建一个指向b的Int16视图,开始于字节2,长度为2
            var v3 = new Int16Array(b, 2, 2);
        不通过ArrayBuffer对象生成视图
            //生成包含8个成员的Float64Array数组,长度为64位
            var f64a = new Float64Array(8);
            f64a[0] = 10;
            f64a[1] = 20;
            f64a[2] = f64a[0] + f64a[1];
        复制其他视图(内存不同)
            var x = new Int8Array([1, 1]);
            var y = new Int8Array(x);
            x[0] // 1
            y[0] // 1
            x[0] = 2;
            y[0] // 1
        复制其他视图(内存相同)
            var x = new Int8Array([1, 1]);
            var y = new Int8Array(x.buffer);
            x[0] // 1
            y[0] // 1
            x[0] = 2;
            y[0] // 2
        转换为普通数组
            var normalArray = Array.prototype.slice.call(typedArray);
        字节序
            // 假定某段buffer包含如下字节 [0x02, 0x01, 0x03, 0x07]
            var buffer = new ArrayBuffer(4);
            var v1 = new Uint8Array(buffer);
            v1[0] = 2;
            v1[1] = 1;
            v1[2] = 3;
            v1[3] = 7;
            var uInt16View = new Uint16Array(buffer);
            // 计算机采用小端字节序
            // 所以头两个字节等于258
            if (uInt16View[0] === 258) {
                console.log('OK'); // "OK"
            }
            // 赋值运算
            uInt16View[0] = 255;    // 字节变为[0xFF, 0x00, 0x03, 0x07]
            uInt16View[0] = 0xff05; // 字节变为[0x05, 0xFF, 0x03, 0x07]
            uInt16View[1] = 0x0210; // 字节变为[0x05, 0xFF, 0x10, 0x02]
        字符串转换
            // ArrayBuffer转为字符串,参数为ArrayBuffer对象
            function ab2str(buf) {
                return String.fromCharCode.apply(null, new Uint16Array(buf));
            }
            // 字符串转为ArrayBuffer对象,参数为字符串
            function str2ab(str) {
                var buf = new ArrayBuffer(str.length * 2); // 每个字符占用2个字节
                var bufView = new Uint16Array(buf);
                for (var i = 0, strLen = str.length; i < strLen; i++) {
                    bufView[i] = str.charCodeAt(i);
                }
                return buf;
            }
        溢出
            var uint8 = new Uint8Array(1);
            uint8[0] = 256;  // 100000000
            uint8[0]         // 0
            uint8[0] = -1;   // 11111111
            uint8[0]         // 255

复合视图
    说明
        在同一段内存之中,可以依次存放不同类型的数据,这叫做“复合视图”。
    范例  
        var buffer = new ArrayBuffer(24);
        var idView = new Uint32Array(buffer, 0, 1);
        var usernameView = new Uint8Array(buffer, 4, 16);
        var amountDueView = new Float32Array(buffer, 20, 1);

DataView视图
    说明
        如果一段数据包括多种类型,除复合视图以外,还可以通过DataView视图进行操作。
        DataView视图提供更多操作选项,而且支持设定字节序。
        TypedArray视图是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序。
        DataView视图是用来处理网络设备传来的数据,所以可以自行设定字节序。
        DataView视图本身也是构造函数,接受一个ArrayBuffer对象作为参数,生成视图
        读取内存和写入内存的方法可在第二个参数指定字节序。
    格式
        DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]);
    实例属性
        DataView.prototype.buffer        返回对应的ArrayBuffer对象
        DataView.prototype.byteLength    返回占据的内存字节长度
        DataView.prototype.byteOffset    返回当前视图从对应的ArrayBuffer对象
    实例方法-读取内存
        getInt8                          读取1个字节,返回一个8位整数
        getUint8                         读取1个字节,返回一个无符号的8位整数
        getInt16                         读取2个字节,返回一个16位整数
        getUint16                        读取2个字节,返回一个无符号的16位整数
        getInt32                         读取4个字节,返回一个32位整数
        getUint32                        读取4个字节,返回一个无符号的32位整数
        getFloat32                       读取4个字节,返回一个32位浮点数
        getFloat64                       读取8个字节,返回一个64位浮点数
    实例方法-写入内存
        setInt8                          写入1个字节的8位整数
        setUint8                         写入1个字节的8位无符号整数
        setInt16                         写入2个字节的16位整数
        setUint16                        写入2个字节的16位无符号整数
        setInt32                         写入4个字节的32位整数
        setUint32                        写入4个字节的32位无符号整数
        setFloat32                       写入4个字节的32位浮点数
        setFloat64                       写入8个字节的64位浮点数
    范例
        创建视图
            var buffer = new ArrayBuffer(24);
            var dv = new DataView(buffer);
        读取内存
            var buffer = new ArrayBuffer(24);
            var dv = new DataView(buffer);
            // 从第1个字节读取一个8位无符号整数
            var v1 = dv.getUint8(0);
            // 从第2个字节读取一个16位无符号整数
            var v2 = dv.getUint16(1);
            // 从第4个字节读取一个16位无符号整数
            var v3 = dv.getUint16(3);
        写入内存
            // 在第1个字节,以大端字节序写入值为25的32位整数
            dv.setInt32(0, 25, false);
            // 在第5个字节,以大端字节序写入值为25的32位整数
            dv.setInt32(4, 25);
            // 在第9个字节,以小端字节序写入值为2.5的32位浮点数
            dv.setFloat32(8, 2.5, true);

二进制数组的应用
    说明
        大量的Web API用到了ArrayBuffer对象和它的视图对象。
    AJAX
        XMLHttpRequest第二版XHR2允许服务器返回二进制数据,这时分成两种情况。
        如果明确知道返回的二进制数据类型,可以把esponseType设为arraybuffer。
        如果不知道,就设为blob。
    Canvas
        网页Canvas元素输出的二进制像素数据,就是TypedArray数组。
        uint8ClampedArray的视图类型是一种针对Canvas元素的专有类型。
        这个视图类型的特点,就是专门针对颜色,把每个字节解读为无符号的8位整数(0~255)。
        而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。
    WebSocket
        WebSocket可以通过ArrayBuffer,发送或接收二进制数据。
    File API
        如果知道一个文件的二进制数据类型,也可以将这个文件读取为ArrayBuffer对象。
    范例
        Ajax
            var xhr = new XMLHttpRequest();
            xhr.open('GET', someUrl);
            xhr.responseType = 'arraybuffer';
            xhr.send();
            xhr.onreadystatechange = function() {
                if (req.readyState === 4) {
                    var arrayResponse = xhr.response;
                    var dataView = new DataView(arrayResponse);
                    var ints = new Uint32Array(dataView.byteLength / 4);
                }
            }
        Canvas
            var canvas = document.getElementById('myCanvas');
            var ctx = canvas.getContext('2d');
            var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            var uint8ClampedArray = imageData.data;
        WebSocket
            var socket = new WebSocket('ws://127.0.0.1:8081');
            socket.binaryType = 'arraybuffer';
            // Wait until socket is open
            socket.addEventListener('open', function(event) {
                // Send binary data
                var typedArray = new Uint8Array(4);
                socket.send(typedArray.buffer);
            });
            // Receive binary data
            socket.addEventListener('message', function(event) {
                var arrayBuffer = event.data;
                // ···
            });
        Fetch API
            fetch(url)
            .then(function(request) {
                return request.arrayBuffer()
            })
            .then(function(arrayBuffer) {
                // ...
            });
        File API
            var fileInput = document.getElementById('fileInput');
            var file = fileInput.files[0];
            var reader = new FileReader();
            reader.readAsArrayBuffer(file);
            reader.onload = function () {
                var arrayBuffer = reader.result;
                // ···
            };

SIMD

概述
    说明
        SIMD是(Single Instruction/Multiple Data)的缩写,意为“单指令,多数据”。
        它是JavaScript操作CPU对应指令的接口,你可以看做这是一种不同的运算执行模式。
        与它相对的是SISD(Single Instruction/Single Data),即“单指令,单数据”。
        SIMD的含义是使用一个指令,完成多个数据的运算。
        SISD的含义是使用一个指令,完成单个数据的运算,这是JavaScript的默认运算模式。
        SIMD的执行效率要高于SISD,被广泛用于3D图形运算、物理模拟等运算量超大的项目之中。
        一次SIMD运算,可以处理多个数据,这些数据被称为“通道”(lane)。
    范例
        SISD
            var a = [1, 2, 3, 4];
            var b = [5, 6, 7, 8];
            var c = [];
            c[0] = a[0] + b[0];
            c[1] = a[1] + b[1];
            c[2] = a[2] + b[2];
            c[3] = a[3] + b[3];
            c // Array[6, 8, 10, 12]
        SIMD
            var a = SIMD.Float32x4(1, 2, 3, 4);
            var b = SIMD.Float32x4(5, 6, 7, 8);
            var c = SIMD.Float32x4.add(a, b); // Float32x4[6, 8, 10, 12]

数据类型
    说明
        SIMD提供12种数据类型,总长度都是128个二进制位。
        每种数据类型被x符号分隔成两部分,前面表示通道的宽度和类型,后面表示通道数。
        每个通道之中,可以放置四种数据。Float,Int,Uint(无符号整数),Bool。
        每种SIMD的数据类型都是一个函数方法,可以传入参数,生成对应的值。
        注意,这些数据类型方法都不是构造函数,前面不能加new,否则会报错。
    数据类型
        Float32x4      四个32位浮点数
        Float64x2      两个64位浮点数
        Int32x4        四个32位整数
        Int16x8        八个16位整数
        Int8x16        十六个8位整数
        Uint32x4       四个无符号的32位整数
        Uint16x8       八个无符号的16位整数
        Uint8x16       十六个无符号的8位整数
        Bool32x4       四个32位布尔值
        Bool16x8       八个16位布尔值
        Bool8x16       十六个8位布尔值
        Bool64x2       两个64位布尔值
    范例
        var a = SIMD.Float32x4(1.0, 2.0, 3.0, 4.0);

静态方法:数学运算
    实例方法
        abs()           将它的每个通道都转成绝对值,返回一个新的SIMD值
        neg()           将它的每个通道都转成负值,返回一个新的SIMD值
        add()           将两个SIMD值的每个通道相加,返回一个新的SIMD值
        addSaturate()   和add作用一致,但溢出时返回该数据类型的最大值,add遗弃溢出值
        sub()           将两个SIMD值的每个通道相减,返回一个新的SIMD值。
        subSaturate()   和sub作用一致,但溢出时返回该数据类型的最小值,sub遗弃溢出值
        mul()           将两个SIMD值的每个通道相乘,返回一个新的SIMD值。
        div()           将两个SIMD值的每个通道相除,返回一个新的SIMD值。
        sqrt()          求出每个通道的平方根,返回一个新的SIMD值。
        reciprocalApproximation()      求出每个通道的倒数,返回一个新的SIMD值。
        reciprocalSqrtApproximation()  求出每个通道的平方根的倒数(1 / (x^0.5))

静态方法:通道处理
    方法
        check()         检查一个值是否为当前类型的SIMD值。是则返回这个值,否则报错
        extractLane()   用于返回给定通道的值,它接受两个参数,分别是SIMD值和通道编号
        replaceLane()   替换指定通道的值,并返回一个新的SIMD值
        load()          从二进制数组读入数据,生成一个新的SIMD值
        load1()         load方法的变种,只加载一个通道
        load2()         load方法的变种,加载两个通道
        load3()         load方法的变种,加载三个通道
        store()         将一个SIMD值,写入一个二进制数组
        splat()         返回一个新的SIMD值,该值的所有通道都会设成同一个预先给定的值
        swizzle()       重新排列原有的SIMD值的通道顺序
        shuffle()       从两个SIMD值之中取出指定通道,返回一个新的SIMD值

静态方法:比较运算
    方法
        equal()              比较每个通道是否相等,返回一个新的Bool32x4
        notEqual()           比较每个通道是否不相等,返回一个新的Bool32x4
        greaterThan()        比较每个通道的大小,判断是否大于,返回一个新的Bool32x4
        greaterThanOrEqual() 比较每个通道的大小,判断是否大于等于,返回新的Bool32x4
        lessThan()           比较每个通道的大小,判断是否小于,返回一个新的Bool32x4
        lessThanOrEqual()    比较每个通道的大小,判断是否小于等于,返回新的Bool32x4
        select()             通过掩码生成一个新的SIMD值。
        allTrue()            返回一个布尔值,表示该SIMD值的所有通道是否都为true
        anyTrue()            只要有一个通道为true,就返回true,否则返回false
        min()                将两个SIMD的每个通道的最小值组成一个新的SIMD返回
        minNum()             与min区别是,一个数为NaN,min返回NaN,minNum返回另外值
        max()                将两个SIMD的每个通道的最大值组成一个新的SIMD返回
        maxNum()             与max区别是,一个数为NaN,max返回NaN,maxNum返回另外值

静态方法:位运算
    方法
        and()       返回两个SIMD每个通道进行二进制AND运算(&)后得到的新的SIMD值
        or()        返回两个SIMD每个通道进行二进制OR运算(|)后得到的新的SIMD值
        xor()       返回两个SIMD每个通道进行二进制异或运算(^)后得到的新的SIMD值
        not()       返回两个SIMD每个通道进行二进制否运算(~)后得到的新的SIMD值

静态方法:数据类型转换
    说明
        SIMD提供以下方法,用来将一种数据类型转为另一种数据类型。
        带有Bits后缀的方法,会原封不动地将二进制位拷贝到新的数据类型。
        不带后缀的方法,则会进行数据类型转换。
    方法
        fromFloat32x4()
        fromFloat32x4Bits()
        fromFloat64x2Bits()
        fromInt32x4()
        fromInt32x4Bits()
        fromInt16x8Bits()
        fromInt8x16Bits()
        fromUint32x4()
        fromUint32x4Bits()
        fromUint16x8Bits()
        fromUint8x16Bits()

实例方法
    说明
        toString方法返回一个SIMD值的字符串形式。
    范例
        var a = SIMD.Float32x4(11, 22, 33, 44);
        a.toString() // "SIMD.Float32x4(11, 22, 33, 44)"