美文网首页
ECMAScript 6学习笔记

ECMAScript 6学习笔记

作者: 写前端的大叔 | 来源:发表于2020-02-01 15:42 被阅读0次

    由于受新型冠状病毒的影响,假期又延长了,又不能出门,只好在家认真学习了,就借此机会阅读完阮一峰老师的ECMAScript 6入门教程,顺便将一些es6中常用的特性进行一个整理。相当于读书时代的划重点

    1:let和const

    用来声明变量,用法类似于var,但所声明的变量只能在变量所在的代码块内有效。
    相同点:
    都是用于块级作用域,不存在变量提升,一定要在声明后才可以使用,在声明前使用会报错。
    不同点:
    let定义的变量可以修改值。const定义的变量为常量,赋值后不能在修改。

    2:变量解构赋值

    解构是指按照一定模式,从数组和对象中提取值,对变量进行赋值。

    1.数组解构

    let [a, b, c] = [1, 2, 3];
    相当于

    let a = 1;
    let b = 2;
    let c = 3;
    

    2.对象解构

    let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
    foo // "aaa"
    bar // "bbb"
    let { baz } = { foo: 'aaa', bar: 'bbb' };
    baz // undefined
    

    3.解构用途

    交换变量的值

    let x = 1;
    let y = 2;
    [x, y] = [y, x];
    

    从函数返回多个值

    // 返回一个数组
    function example() {
      return [1, 2, 3];}
    let [a, b, c] = example();
    // 返回一个对象
    function example() {
      return {
        foo: 1,
        bar: 2
      };
    }
    let { foo, bar } = example();
    

    提取JSON数据

    let jsonData = {
      id: 42,
      status: "OK",
      data: [867, 5309]
    };
    let { id, status, data: number } = jsonData;
    console.log(id, status, number);
    // 42, "OK", [867, 5309]
    

    输入模块的指定方法

    const { SourceMapConsumer, SourceNode } = require("source-map");
    

    3.字符串扩展

    1.使用for...of遍历字符串。

          var str = 'abcd';
            for (let value of str){
                console.log(value);
            }
    

    2.模板字符串

    模板字符串是增强版的字符串,使用`来表示字符串的使用,也可以用来定义多行字符串,或者在字符串中使用变量。

    //普通字符串
            let a = `this is str`;
            console.log(a);
            //多行字符串
            let glsl = `void main(){
                var a;
            }`
            console.log(glsl);
            //包含变量的字符串
            let x = 1,y = 2;
            let b = `x:${x}\n y:${y}`
            console.log(b);
    

    3.includes(), startsWith(), endsWith()

    之前判断一个字符串中是否包含某个字符串时,使用的是indexOf方法,在es6中新增了三个方法进行判断字符串中是否包含指定的字符。

    includes():判断字符串中是否包含某个字符串。
    startsWith():判断字符串是否以某个字符串开头。
    endsWith():判断字符串是否以某个字符串结尾。

            var string = `这是一个字符串。`
            console.log(string.includes('字'));//true
            console.log(string.startsWith('这'));//true
            console.log(string.endsWith('。'));//true
    

    4.repeat(n)

    表示将原字符串复制n次,返回一个新的字符串。

    console.log(string.repeat(3));
    //这是一个字符串。这是一个字符串。这是一个字符串。
    

    5.padStart(),padEnd()

    根据指定的参数在开头或者结尾补全字符串。如:

    console.log('x'.padStart(5, 'ab')) // 'ababx'
    console.log('x'.padStart(4, 'ab')) // 'abax'
    console.log('x'.padEnd(5, 'ab')) // 'xabab'
    console.log('x'.padEnd(4, 'ab')) // 'xaba'
    

    6.trimStart(),trimEnd()

    trim()用于消除前后的空格,trimStart()用于消除字符串开头的空格。trimEnd()用于消除字符串结尾的空格。

    4.数值的扩展

    1.Number.isFinite(), Number.isNaN()

    isFinite用于判断一个数值是否是有限的(即不是Infinity)。如果参数类型不是数值,Number.isFinite一律返回false

    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
    

    Number.isNaN()用来检查一个值是否为NaN。如果参数类型不是NaNNumber.isNaN一律返回false

    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
    

    与全局的isFinite()isNaN()有什么区别?
    全局的isFinite()isNaN()会先调用Number()将非数值转成数值。而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false, Number.isNaN()只有对于NaN才返回true,非NaN一律返回false
    1.Number.isInteger()
    使用Number.isInteger()可以判断变量是否为整数类型。

    console.log(Number.isInteger(12.5));//false
    console.log(Number.isInteger(12));//true
    

    2.Math.trunc()

    Math.trunc()去除小数部分,返回整数部分。

    console.log(Math.trunc(12.33));//12
    

    5.函数的扩展

    1.函数参数的默认值

    在定义函数的时候,可以在函数中使用默认值,当调用函数时,传递的变量值为空时,如果有默认值,使用变量的时候将使用默认值,通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。如下所示:

      function init(a,b,c = 10){
                console.log(a);//12
                console.log(b);//1
                console.log(c);//10
            }
            init(12,1);
    

    2.箭头函数

    ES6 允许使用“箭头”(=>)定义函数。如下两个函数是一样的。

    var fun = function(a,b){
                console.log(a+b);
            }
            var fun1 = (a,b) => {
                console.log(a+b);
            }
            fun(1,2);
            fun1(1,2);
    

    箭头函数注意点

    • 函数体内的this对象,是定义时所在的对象,不是使用是所在的对象。
    • 不可当作构造函数来使用,也就是不能使用new来创建对象。
    • 不可使用arguments对象,如果要使用类似的功能,可以使用 rest
    • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
      关于第一点函数体内的this对象,是定义时所在的对象,示例如下所示:
          function foo() {
                setTimeout(() => {
                    console.log('id:', this.id);//42
                }, 100);
                setTimeout(function(){
                    console.log('id:', this.id);//21
                },200)
            }
            var id = 21;
            foo.call({ id: 42 });
    

    不适用场合

    • 第一个场合是定义对象的方法,且该方法内部包括this。
    • 第二个场合是需要动态this的时候,也不应使用箭头函数。

    3.尾调用

    尾调用就是指某个函数的最后一步是调用另一个函数。如下所示:

    function f(x) {
      if (x > 0) {
        return m(x)
      }
      return n(x);
    }
    

    上面代码中,函数mn都属于尾调用,因为它们都是函数f的最后一步操作。

    6.数组扩展

    1.扩展运算符

    扩展运算符是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

    扩展运算符的作用

    • 替代函数的 apply 方法
      由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。
    function fun(a,b,c){
                console.log(a+b+c);
            }
            fun.apply(null,[1,2,3]);
            fun(...[1,2,3])
    
    • 复制数组
            var a = [1,2,3];
            var b = [...a];
            b[1]= 0;
            console.log(a);//1,2,3
            console.log(b);//1,0,3
    
    • 合并数组
            var a = [1,2,3];
            var b = [...a];
            b[1]= 0;
            console.log(a);//1,2,3
            console.log(b);//1,0,3
            var c = [...a,...b];
            console.log(c);
    

    2.Array.from()

    Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。

    let arrayLike = {
        '0': 'a',
        '1': 'b',
        '2': 'c',
        length: 3
    };
    
    // ES5的写法
    var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
    
    // ES6的写法
    let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
    

    3.Array.of()

    Array.of方法用于将一组值,转换为数组。

    Array.of(3, 11, 8) // [3,11,8]
    Array.of(3) // [3]
    Array.of(3).length // 1
    

    7.对象的扩展

    1.属性的可枚举性和遍历

    对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。

    目前,有四个操作会忽略enumerable为false的属性。

    • for...in循环:只遍历对象自身的和继承的可枚举的属性。
    • Object.keys():返回对象自身的所有可枚举的属性的键名。
    • JSON.stringify():只串行化对象自身的可枚举的属性。
    • Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。

    2.属性遍历

    对象的属性遍历一共有5种方法。

    1.for...in

    for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

    var obj = {
                a:'a1',
                b:'b1',
                c:12
            }
            ergodic();
            function ergodic(){
                for(let key in obj){
                    console.log(key + '=' + obj[key]);
                }
            }
    

    2.Object.keys(obj)

    Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

    var obj = {
                a:'a1',
                b:'b1',
                c:12
            }
            ergodic();
            function ergodic(){
                var array = Object.keys(obj);
                array.forEach(key => {
                    console.log(key + '=' + obj[key]);
                })
            }
    

    3.Object.getOwnPropertyNames(obj)

    Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

    var obj = {
                a:'a1',
                b:'b1',
                c:12
            }
            ergodic();
            function ergodic(){
                var array = Object.getOwnPropertyNames(obj);
                array.forEach(key => {
                    console.log(key + '=' + obj[key]);
                })
            }
    

    4.Object.getOwnPropertySymbols(obj)

    Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

    5.Reflect.ownKeys(obj)

    Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

    var obj = {
                a:'a1',
                b:'b1',
                c:12
            }
            ergodic();
            function ergodic(){
                var array = Reflect.ownKeys(obj);
                array.forEach(key => {
                    console.log(key + '=' + obj[key]);
                })
            }
    

    3.扩展运算符

    对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。对象的扩展运算符等同于使用Object.assign()方法。

    var obj = {
                a:'a1',
                b:'b1',
                c:12,
            }
            var obj1 = {...obj};
            console.log(obj1);
    

    4.Null 判断运算符

    ES2020引入了一个新的Null判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

    5.Object.is()

    用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。但与===不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

    +0 === -0 //true
    NaN === NaN // false
    
    Object.is(+0, -0) // false
    Object.is(NaN, NaN) // true
    

    6.Object.assign()

    Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

    const target = { a: 1 };
    
    const source1 = { b: 2 };
    const source2 = { c: 3 };
    
    Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}
    

    注意点

    • Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
      -一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

    8.Symbol

    Symbol是一种新的数据类型,属性名使用Symbol类型是独一无二的,不会跟其它的属性名发生冲突。Symbol是一个原始类型的值,不是对象,不能添加给它添加属性。Symbol可以接受一个字符串作为参数,如果参数是一个对象,会先调用它的toString方法转成字符,再生成一个Symbol值。Symbol函数的参数只是当前Symbol值的一个描述,因些两个参数相同的Symbol是不相等的。Symbol值不能与其它类型的值进行运算。

    // 没有参数的情况
    let s1 = Symbol();
    let s2 = Symbol();
    
    s1 === s2 // false
    let sym = Symbol('My symbol');
    
    "your symbol is " + sym
    // TypeError: can't convert symbol to string
    `your symbol is ${sym}`
    // TypeError: can't convert symbol to string
    

    Symbol值可以转化为字符串和布尔类型的值。

    let sym = Symbol('My symbol');
    
    String(sym) // 'Symbol(My symbol)'
    sym.toString() // 'Symbol(My symbol)'
    
    let sym = Symbol();
    Boolean(sym) // true
    

    1.Symbol.for()

    Symbol.for()可以接受一个字符串作为参数,然后生成一个Symbol值,如果参数相同,生成的Symbol值也相等。

    console.log(Symbol.for('s') === Symbol.for('s'));//true
    console.log(Symbol('s') === Symbol('s'));//false
    

    通过Symbol.for()创建的Symbol值可以使用Symbol.keyFor()获取参数名。

    let s3 = Symbol.for('s3');
    console.log(Symbol.keyFor(s3));//s3
    let s4 = Symbol('s4');
    console.log(Symbol.keyFor(s4));//undefined
    

    2.Symbol.hasInstance

    对象的Symbol.hasInstance属性指向一个内部方法,当其它对象在使用instanceof时,会调用这个方法。

           const Even = {
                [Symbol.hasInstance](obj){
                    return Number(obj) % 2 === 0;
                }
            }
            console.log(1 instanceof Even) // false
            console.log(2 instanceof Even) // true
    

    3.Symbol.match

    对象的Symbol.match属性指向一个函数,当执行str.match(myObject)时,如果该属性存在时,将返回该方法的返回值。

            const MyMatcher = {
                [Symbol.match](str){
                    return 'haha'
                }
            }
            console.log('test'.match(MyMatcher));//haha
    

    9.Set 和 Map 数据结构

    1.Set

    Set类似于数组,但成员的值是唯一的,没有重复的值。

    Set常用方法

    • Set.prototype.constructor:造函数,默认就是Set函数。
    • Set.prototype.size:返回成员的数量。
    • Set.prototype.add(value):往Set中添加成员。
    • Set.prototype.delete(value):删除指定的成员。
    • Set.prototype.has(value):判断是否包含value
    • Set.prototype.clear():清空所有成员。
    • Set.prototype.keys():返回键名的遍历器
    • Set.prototype.values():返回键值的遍历器
    • Set.prototype.entries():返回键值对的遍历器
    • Set.prototype.forEach():使用回调函数遍历每个成员

    2.WeakSet

    WeakSetSet类似,成员的值是唯一的,没有重复的值。但WeakSet只能存储对象。并且WeakSet中存储的是弱引用,当其它对象不再引用该对象时,垃圾回收器将自动回收该对象的内存。WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。WeakSet不能被遍历。

    常用方法

    • WeakSet.prototype.add(value):向 WeakSet实例添加一个新成员。
    • WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
    • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet实例之中。

    3.Map

    标准对象的键值是字符串或者Symbol,不能使用对象作为键,如果使用对象作为键,将自动转成字符串。如果需要以对象作为键,将会用到Map。如果对同一个键多次赋值,后面的值将覆盖前面的值。

            var map = new Map();
            var obj = {a:'a'};
            map.set(obj,'这是对象作为key');
            map.set('key','haha');
            map.set('key','haha1');
            console.log(map.get('key'));//haha1
            console.log(map.get(obj));//这是对象作为key
    

    常用方法

    • Map.prototype.size():返回Map的长度。
    • Map.prototype.set(key, value)set方法设置键名key对应的键值为value,然后返回整个 Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。
    • Map.prototype.get(key)get方法读取key对应的键值,如果找不到key,返回undefined
    • Map.prototype.has(key)has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
    • Map.prototype.delete(key)delete方法删除某个键,返回true。如果删除失败,返回false
    • Map.prototype.clear():清空所有值。
    • Map.prototype.keys():返回键名的遍历器。
    • Map.prototype.values():返回键值的遍历器。
    • Map.prototype.entries():返回所有成员的遍历器。
    • Map.prototype.forEach():遍历 Map 的所有成员。

    WeakMap

    WeakMap结构与Map结构类似,也是用于生成键值对的集合。

    WeakMap与Map的区别

    • 首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
    • 其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。一旦不再需要时,我们就必须手动删除引用。

    10.Proxy

    Proxy是一种代理,在目标对象之前设置一层‘拦截’,在访问该对象时,都要先访问这个代理对象,通过这个代理对象可以进行过滤和修改等操作。

    var proxy = new Proxy(target, handler);
    

    new Proxy生成一个Proxy实例,target表示需要拦截的目标对象,handler用于定制拦截行为。如下所示为一个拦截get方法的代码。

            var o = new Proxy({},{
                get:function(target,key){
                    return 'haha'
                }
            })
            console.log(o.name);//haha
            console.log(o['type']);//haha
    

    注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

    Proxy 可拦截的13个方法

    • get(target, propKey, receiver):拦截对象属性的读取,比如proxy.fooproxy['foo']
    • set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
    • has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
    • deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
    • ownKeys(target):拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
    • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
    • defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
    • preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
    • getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
    • isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
    • setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
    • apply(target, object, args):拦截Proxy实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
    • construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

    11.Reflect

    ReflectProxy一样都可以操作对象。使用Reflect可以拿到对象原始的方法。

    // 老写法
    'assign' in Object // true
    
    // 新写法
    Reflect.has(Object, 'assign') // true
    

    静态方法

    • 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)

    12.Promise

    Promise是一种异步编程的解决方案,相对传统的回调函数和事件要强大。Promise是一个对象,它可以获取异步操作的消息。

    Promise特点

    • Promise的状态不受外界的影响。Promise对象代表一个操作,它包含三种状态:pending(进行中),fulfilled(已成功)和rejected(已失败)。只有异步操作的结果才能决定是哪种状态,其它操作无法改变Promise的状态。
    • 一旦状态改变,就不会再变, 任何时候都可以获取到这个结果。Promise的状态变化只有两种,从
      pending(进行中)变成fulfilled(已成功),或者从pending(进行中)变成rejected(已失败)。
      Promise缺点
    • Promise创建后会立即执行,无法中途取消。
    • 如果不设置回调函数,Promise内部发生错误时无法反应到外部。
    • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

    1.基本用法

    Promise对象是一个构造函数,通过构造函数来创建Promise实例。如下所示:

            let promise = new Promise(function(resolve,reject){
                setTimeout(resolve,500);
            });
            promise.then(function(value){
                console.log('promise执行结束')
            },function(error){
                console.log('promise执行时发生了错误')
            })
    

    Promise构造函数接受一个函数作为参数,函数包括resolvereject两个函数,resolve的作用是将Promise对象的状态从pending(进行中)变成fulfilled(已成功)。在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

    注意
    因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。,如下所示:

          let p = new Promise(function(resolve,reject){
                console.log(1);
                resolve(2);
                console.log(3);
            })
            p.then(function(value){
                console.log(value);
            })
            //1
            //3
            //2
    

    resolve函数的参数除了正常的值以外,还可能是另一个 Promise实例,比如像下面这样。

    let p1 = new Promise(function(resolve,reject){
                setTimeout(() => {
                    reject('执行错误')
                }, 3000);
            })
            
            let p2 = new Promise(function(resolve,reject){
                setTimeout(() => {
                    resolve(p1);
                }, 1500);
            })
            p2.then(function(value){
                console.log(value)
            }).catch(error =>{
                console.log(error)
            })
    

    2.Promise.prototype.then()

    then方法是Promise原型对象上的一个方法,该方法的作用是Promise在发生状态改变后的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。then方法将返回一个Promise对象,因此可以采用链式写法。如下所示:

            let p3 = new Promise(function(resolve,reject){
                resolve('p3');
            })
            p3.then(function(value){
                console.log(value);
                return new Promise(function(resolve,reject){
                    setTimeout(() => {
                        resolve('p3返回的Promise');
                    }, 1000);
                })
            }).then(function(value){
                console.log(value)
            })
            //p3
            //p3返回的Promise'
    

    3.Promise.prototype.catch()

    Promise.prototype.catch方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。如下所示:

    let p3 = new Promise(function(resolve,reject){
                resolve('p3');
            })
            p3.then(function(value){
                console.log(value);
                return new Promise(function(resolve,reject){
                    setTimeout(() => {
                        reject(new Error('p3的Promise返回错误信息'));
                    }, 1000);
                })
            }).then(function(value){
                console.log('result'+value)
            }).catch(function(error){
                console.log(error);
            })
            //p3
            //p3的Promise返回错误信息'
    

    如果Promise的状态已经变成了resolved,再抛出异常是无效的。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。如下所示:

          const promise = new Promise(function(resolve, reject) {
                resolve('ok');
                throw new Error('test');
            });
            promise.then(function(value) { 
                console.log(value) 
            })
            .catch(function(error) { 
                console.log(error) 
            });
    

    Promise对象的错误具有‘冒泡’性质,它会一直向后传递,直到捕获为止。错误总是被下一个catch捕获,一般来说,不要在then方法里面定义 Reject状态的回调函数(即then的第二个参数),总是使用catch方法。如下所示:

    const promise = new Promise(function(resolve, reject) {
                reject('发生了错误');
            });
            promise.then(function(value) { 
                console.log("success-"+value) 
            })
            .catch(function(error) { 
                console.log("error="+error) 
            });
            //error=发生了错误
    

    4.Promise.prototype.finally()

    finally方法是不论Promise的状态是什么样的,都会执行finally方法,如下所示:

    const promise = new Promise(function(resolve, reject) {
                reject('发生了错误');
            });
            promise.then(function(value) { 
                console.log("success-"+value) 
            })
            .catch(function(error) { 
                console.log("error="+error) 
            }).finally(function(value){
                console.log('finally');
            });
            //error=发生了错误
            //finally
    

    finally方法不接受任何参数。finally方法总是会返回原来的值。

    5.Promise.all()

    Promise.all()将多个Promise包装成一个Promise实例。

    let p1 = new Promise(function(resolve,reject){
                resolve('p1');
            })
            let p2 = new Promise(function(resolve,reject){
                resolve('p2');
            })
            let p3 = new Promise(function(resolve,reject){
                resolve('p3');
            })
            let p= Promise.all([p1,p2,p3]).then(function(value){
                console.log('success:' + value)
            }).catch(function(error){
                console.log('fail='+error);
            })
    

    p的状态由p1p2p3决定,分成两种情况。

    • 只有p1p2p3的状态全为fulfilled时,p的状态才为fulfilled。此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
    • 只要p1p2p3中有一个状态为rejected时,p的状态就为rejected。此时第一个被reject的实例的返回值,会传递给p的回调函数。

    注意,如果作为参数的 Promise实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。如下所示:

    let p1 = new Promise(function(resolve,reject){
                resolve('p1');
            })
            let p2 = new Promise(function(resolve,reject){
                reject('p2');
            }).catch(function(error){
    
            })
            let p3 = new Promise(function(resolve,reject){
                resolve('p3');
            })
            let promise = Promise.all([p1,p2,p3]).then(function(value){
                console.log('success:' + value)
            }).catch(function(error){
                console.log('fail='+error);
            })
    

    6.Promise.race()

    Promise.race()Promise.all()类似,都可以将多个Promise包装成一个Promise实例。 不同之处是只要有一个实例率先改变状态,Promise.race()实例的结果也将改变状态,如下所示:

    let p1 = new Promise(function(resolve,reject){
                resolve('p1');
            })
            let p2 = new Promise(function(resolve,reject){
                reject('p2');
            }).catch(function(error){
    
            })
            let p3 = new Promise(function(resolve,reject){
                resolve('p3');
            })
            let promise = Promise.race([p1,p2,p3]).then(function(value){
                console.log('success:' + value)
            }).catch(function(error){
                console.log('fail='+error);
            })
            //success:p1
    

    7.Promise.allSettled()

    Promise.allSettled()也是接受一组 Promise 实例作为参数,包装成一个新的 Promise实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020。如下所示:

          let p1 = new Promise(function(resolve,reject){
                resolve('p1');
            })
            let p2 = new Promise(function(resolve,reject){
                reject('p2');
            })
            let p3 = new Promise(function(resolve,reject){
                resolve('p3');
            })
            let promise = Promise.allSettled([p1,p2,p3]).then(function(value){
                debugger
                console.log('success:' + value)
            }).catch(function(error){
                console.log('fail='+error);
            })
    

    该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()Promise实例。

    8.Promise.resolve()

    Promise.resolve()将现有对象转化为Promise对象。如下所示:

    const jsPromise = Promise.resolve($.ajax('/whatever.json'));
    

    Promise.resolve方法的参数分成四种情况。

    • 参数是一个 Promise 实例,如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
    • 参数是一个thenable对象。thenable对象指的是具有then方法的对象,Promise.resolve方法会将这个对象转为 Promise对象,然后就立即执行thenable对象的then方法。
    • 参数不是具有then方法的对象,或根本就不是对象。如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved
    • 不带有任何参数。Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise对象。

    9.Promise.reject()

    Promise.reject()返回一个新的Promise对象,该对象的状态为rejected

    13.Iterator 和 for...of 循环

    Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据类型只要部署了Iterator接口,就能完成遍历操作。

    Iterator作用

    • 为各种数据类型提供一个统一的,简便的访问接口。
    • 使用数据结构的成员按照某种次序进行排列。
    • 使用 for...of进行遍历。

    1.默认的Iterator 接口

    凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。

    原生具备 Iterator 接口的数据结构如下。

    • Array
    • Map
    • Set
    • String
    • TypedArray
    • 函数的 arguments 对象
    • NodeList 对象

    如下所示:

           var array = [1,2,3,4];
            var iterator = array[Symbol.iterator]();
            console.log(iterator.next());
            console.log(iterator.next());
            console.log(iterator.next());
            console.log(iterator.next());
            console.log(iterator.next());
    

    14.Generator

    Generator是一种异步编程的解决方案,语法行为与传统的函数不同。Generator是一种状态机函数,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,可依次遍历Generator函数内部的每一个状态。

    Generator 特征

    • function与函数名之间有一个*。如function* test(){}
    • 函数内部可以使用yield表达式,定义不同的内部状态。
              function* helloGenerator(){
                    yield 'hello';
                    yield 'Generator';
                    return 'end'
                }
                var it = helloGenerator();
                for(var item of it){
                    console.log(item);
                }
    

    Generator函数的调用跟调用普通函数一样,直接在函数名后面加一个括号,但与普通函数不一样的是,Generator函数执行后返回的是一个Iterator对象,可以调用next方法,或者使用for...of进行遍历。

    1.yield 表达式

    由于Generator函数返回的是一个Iterator对象,只有在执行next方法时才会遍历下一个状态,通过使用yield表达式可以设置一种暂停标注。当执行完next方法后,将yield后面的值作为value返回,执行完一条后并暂停,只有当调用下一个next方法才会往下执行并继续查找yield表达式,当查找不到yield时,将返回并结束遍历。yield只能在Generator函数中使用,在其它地方使用时将报错。

    2.Generator.prototype.throw()

    Generator函数返回的对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

    3.yield* 表达式

    使用yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

    function* foo() {
      yield 'a';
      yield 'b';
    }
    function* bar() {
      yield 'x';
      yield* foo();
      yield 'y';
    }
    
    // 等同于
    function* bar() {
      yield 'x';
      yield 'a';
      yield 'b';
      yield 'y';
    }
    
    // 等同于
    function* bar() {
      yield 'x';
      for (let v of foo()) {
        yield v;
      }
      yield 'y';
    }
    
    for (let v of bar()){
      console.log(v);
    }
    // "x"
    // "a"
    // "b"
    // "y"
    

    4.Generator用途

    • 异步操作的同步化表达
      通过使用Generator可以处理异步函数,用来代替回调函数。将异步操作写在yield表达式上,等调用next方法后再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。如下所示:
    function* loadUI() {
      showLoadingScreen();
      yield loadUIDataAsynchronously();
      hideLoadingScreen();
    }
    var loader = loadUI();
    // 加载UI
    loader.next()
    
    // 卸载UI
    loader.next()
    
    • 控制流程管理
      利用for...of循环会自动依次执行yield命令的特性,提供一种更一般的控制流管理的方法。如下所示:
    let steps = [step1Func, step2Func, step3Func];
    
    function* iterateSteps(steps){
      for (var i=0; i< steps.length; i++){
        var step = steps[i];
        yield step();
      }
    }
    
    • 部署 Iterator 接口
      利用 Generator 函数,可以在任意对象上部署 Iterator 接口。如下所示:
    function* iterEntries(obj) {
      let keys = Object.keys(obj);
      for (let i=0; i < keys.length; i++) {
        let key = keys[i];
        yield [key, obj[key]];
      }
    }
    
    let myObj = { foo: 3, bar: 7 };
    
    for (let [key, value] of iterEntries(myObj)) {
      console.log(key, value);
    }
    
    // foo 3
    // bar 7
    
    • 作为数据结构
      Generator可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为 Generator 函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。如下所示:
    function* doStuff() {
      yield fs.readFile.bind(null, 'hello.txt');
      yield fs.readFile.bind(null, 'world.txt');
      yield fs.readFile.bind(null, 'and-such.txt');
    }
    
    for (task of doStuff()) {
      // task是一个函数,可以像回调函数那样使用它
    }
    

    5.Generator 函数的异步应用

    异步是指一个任务不是连续完成的,可以理解成该任务被分成为两段,先执行第一段,然后再执行其它的任务,等做好准备后再执行第二段的任务。

    比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。

    6.async 函数

    async函数是Generator的方法糖。async就是将Generator函数的*换成了async,将yield换成了awaitasync函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

          let readFile = function(){
                return new Promise(function(resolve,reject){
                    setTimeout(() => {
                        resolve('文件读取成功')
                    }, 2000);
                })
            }
    
            let asyncReadFile = async function(){
                let rf1 = await readFile()
                let rf2 = await readFile();
                console.log(rf1.toString());
                console.log(rf2.toString());
            }
            asyncReadFile();
    

    7.await命令

    await命令后面是一个Promise对象,如果是其它值,将直接返回对应的值。如下所示:

    async function f() {
      // 等同于
      // return 123;
      return await 123;
    }
    
    f().then(v => console.log(v))
    // 123
    

    await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。如下所示:

    async function f() {
      await Promise.reject('出错了');
    }
    
    f()
    .then(v => console.log(v))
    .catch(e => console.log(e))
    // 出错了
    

    任何一个await语句后面的Promise 对象变为reject状态,那么整个async函数都会中断执行。

    async function f() {
      await Promise.reject('出错了');
      await Promise.resolve('hello world'); // 不会执行
    }
    

    使用try...catch结构,实现多次重复尝试。

    const superagent = require('superagent');
    const NUM_RETRIES = 3;
    
    async function test() {
      let i;
      for (i = 0; i < NUM_RETRIES; ++i) {
        try {
          await superagent.get('http://google.com/this-throws-an-error');
          break;
        } catch(err) {}
      }
      console.log(i); // 3
    }
    
    test();
    

    上面代码中,如果await操作成功,就会使用break语句退出循环;如果失败,会被catch语句捕捉,然后进入下一轮循环。

    使用注意点

    • await命令后面的Promise对象,运行结果可能是rejected,最好将 await命令放在try...catch中。
    • 多个 await命令如果不存在先后顺序,最好同时时行。
    • await命令只能在async中使用,在其它地方使用将会报错。

    15.

    • async函数可以保留运行堆栈。

    8.async函数实现原理

    async的实现原理就是将Genernator函数与自动执行器包装在一个函数里。如下所示:

    async function fn(args) {
      // ...
    }
    
    // 等同于
    
    function fn(args) {
      return spawn(function* () {
        // ...
      });
    }
    

    所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。

    16.Class

    ES6可以使用class关键字创建类,相比使用构造函数创建类更具有面向对象的思想,如下所示:

            class Point{
                constructor(x,y){
                    this.x = x;
                    this.y = y;
                }
                toString(){
                    console.log("x="+this.x + " y="+this.y);
                }
            }
            let point = new Point(12,13);
            point.toString();
    

    类的内部所有定义的方法,都是不可枚举的(non-enumerable)。如下所示:

    class Point{
                constructor(x,y){
                    this.x = x;
                    this.y = y;
                }
                toString(){
                    console.log("x="+this.x + " y="+this.y);
                }
            }
            let point = new Point(12,13);
            point.toString();
            console.log(Object.keys(Point.prototype));//[]
            console.log(Object.keys(point));// ["x", "y"]
    

    1.取值函数(getter)和存值函数(setter)

    ES5一样,在“类”的内部可以使用getset关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

    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'
    

    2.Class表达式

    函数一样,类也可以使用表达式的形式定义。如下所示:

            const Circle = class C{
                getRaidus(){
                    console.log('圆的半径为10')
                }
            }
            const circle = new Circle();
            circle.getRaidus();
    

    3.静态方法

    类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    Foo.classMethod() // 'hello'
    
    var foo = new Foo();
    foo.classMethod()
    // TypeError: foo.classMethod is not a function
    

    4.实例属性

    Class定义的类中,添加属性可以在构造函数中添加,也可以在类的顶层定义属性,如下所示:

          const Circle = class C{
                raidus = 20;
                constructor(){
                    this.x = 10;
                    this.y = 10;
                }
                getRaidus(){
                    console.log(`x=${this.x};x=${this.y};radius=${this.raidus};`)
                }
            }
            const circle = new Circle();
            circle.getRaidus();
    

    5.静态属性

    静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。

    // 老写法
    class Foo {
      // ...
    }
    Foo.prop = 1;
    
    // 新写法
    class Foo {
      static prop = 1;
    }
    

    6.私有属性

    class加了私有属性。方法是在属性名之前,使用#表示。如下所示:

            class Foo {
                #privateValue = 42;
                getPrivateValue() {
                    return this.#privateValue;
                }
            }
    
            var foo = new Foo();
            console.log(foo.getPrivateValue())
    

    7.继承

    Class可以通过extends关键字实现继承,这比 ES5的通过修改原型链实现继承,要清晰和方便很多。如下所示:

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    }
    
    class ColorPoint extends Point {
      constructor(x, y, color) {
        super(x, y); // 调用父类的constructor(x, y)
        this.color = color;
      }
    
      toString() {
        return this.color + ' ' + super.toString(); // 调用父类的toString()
      }
    }
    

    子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

    8.super

    super既可以当作函数来使用,也可以使用对象来使用。

    • super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
    class A {}
    
    class B extends A {
      constructor() {
        super();
      }
    }
    
    • super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
    class A {
      p() {
        return 2;
      }
    }
    
    class B extends A {
      constructor() {
        super();
        console.log(super.p()); // 2
      }
    }
    
    let b = new B();
    

    super指向父类的原型对象,所以定义在父类实例上的属性和方法,无法通过super来调用的。

    9.Object.getPrototypeOf()

    Object.getPrototypeOf可以用来从子类上获取父类。

    Object.getPrototypeOf(ColorPoint) === Point
    // true
    

    可以使用这个方法判断,一个类是否继承了另一个类。

    10.类的 prototype 属性和proto属性

    Class创建的类,同样有prototype__proto__属性,同样存在两条继承链。

    • 子类的__proto__属性,表示构造函数的继承,总是指向父类。。
    • 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
        class A {
    
            }
    
            class B extends A {
    
            }
            console.log(B.__proto__ === A);//true
            console.log(B.prototype.__proto__ === A.prototype);//true
    

    11.实例的 proto 属性

    子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性,也就是说子类原型的原型是父类的原型。

          class A {
    
            }
    
            class B extends A {
    
            }
            let a = new A();
            let b = new B();
            console.log(b.__proto__.__proto__ === a.__proto__);//true
    

    17.Module

    ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

    严格模式

    • 变量必须声明后再使用
    • 函数的参数不能有同名属性,否则报错
    • 不能使用with语句
    • 不能对只读属性赋值,否则报错
    • 不能使用前缀 0 表示八进制数,否则报错
    • 不能删除不可删除的属性,否则报错
    • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
    • eval不会在它的外层作用域引入变量
    • evalarguments不能被重新赋值
    • arguments不会自动反映函数参数的变化
    • 不能使用arguments.callee
    • 不能使用arguments.caller
    • 禁止this指向全局对象
    • 不能使用fn.callerfn.arguments获取函数调用的堆栈
    • 增加了保留字(比如protectedstaticinterface

    1.export 命令

    模块主要由两个命令组成,exportimport命令。export用于向外部导出接口。import用于引入外部导出的接口。一个单独的文件就是一个模块,模块类的变量和函数在外部无法获取,如果希望外部能够访问,需要使用export输入变量。export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错。

    export const url = 'http://www.gzcopright.cn';
    function test (){
    
    };
    
    function util (){
    
    }
    export {test,util as Util};
    

    2.import命令

    使用export导出的变量,在其它JS文件中使用import可以加载模块。import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。import命令具有提升效果,会提升到整个模块的头部,首先执行,这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

    import{url,Util,log} from './module.js'
    
    function test(){
        console.log(url)
    }
    
    test();
    

    如果多次执行import语句,只会执行一次,如下所示:

    import 'lodash';
    import 'lodash';
    

    3.模块的整体加载

    除了指定加载某个输出值,可以使用*来加载所有输出的变量,如下所示:

    import * as obj from './module.js'
    
    function test(){
        console.log(obj.url)
    }
    
    test();
    

    4.export default 命令

    export default用于指定模块的默认输出,一个模块只能有一个默认输出。export default输出的命令在使用import时,不需要使用{},并且名称可以随便取。如下所示:

    export default function () {
      console.log('foo');
    }
    
    // import-default.js
    import customName from './export-default';
    customName(); // 'foo'
    

    export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。

    // 正确
    export var a = 1;
    
    // 正确
    var a = 1;
    export default a;
    
    // 错误
    export default var a = 1;
    

    5.export 与 import 的复合写法

    如果在同一模块中,先输入后输出同一个模块,importexport可以写在一起。

    export { foo, bar } from 'my_module';
    
    // 可以简单理解为
    import { foo, bar } from 'my_module';
    export { foo, bar };
    

    6.模块的继承

    模块之间是可以继承的,如下所示,circleplus模块,继承了circle模块。

    // circleplus.js
    
    export * from 'circle';
    export var e = 2.71828182846;
    export default function(x) {
      return Math.exp(x);
    }
    

    上面代码中的export *,表示再输出circle模块的所有属性和方法。

    7.import()

    使用import()可以动态加载模块,使用方法跟import一样,唯一的区别就是import需要放在代码的顶层,不支持动态导入。

    适用场合

    • 按需加载,import()可以在需要的时候,再加载某个模块。
    button.addEventListener('click', event => {
      import('./dialogBox.js')
      .then(dialogBox => {
        dialogBox.open();
      })
      .catch(error => {
        /* Error handling */
      })
    });
    
    • 条件加载。import()可以放在if代码块,根据不同的情况,加载不同的模块。
    if (condition) {
      import('moduleA').then(...);
    } else {
      import('moduleB').then(...);
    }
    
    • 动态的模块路径。import()允许模块路径动态生成。
    import(f())
    .then(...);
    

    上面代码中,根据函数f的返回结果,加载不同的模块。

    18.Module 的加载实现

    加载js文件,使用的是<script>,默认是同步加载,既浏览器引擎解析dom时,如果碰到了script标签时会先加载js脚本。当js文件太大时,会阻塞线程出现卡死的情况,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。

    <script src="path/to/myModule.js" defer></script>
    <script src="path/to/myModule.js" async></script>
    

    deferasync都支持异步加载脚本。但两者有一定的区别

    • defer加载的脚本需要等面布渲染完成后再运行,如果有多个script使用defer时,每个文件是按顺序执行了。
    • async是加载完后就执行,如果有多个script使用async时,顺序是不固定的。

    浏览器加载ES6模块,也使用<script>标签,但是要加入type="module"属性。浏览器对于带有type="module"<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。

    1.ES6 模块与 CommonJS 模块的差异

    • ES6 模块输出的是值的引用,而CommonJS 模块输出的是值拷贝。
    • ES6 模块是编译时加载接口,而CommonJS 模块是运行时加载。

    19.编程风格

    • let 取代 var,常量使用const
    • 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
    • 使用数组成员对变量赋值时,函数的参数如果是对象的成员,如果函数返回多个值,优先使用解构赋值。
    • 单行定义的对象,最后一个成员不要以逗号结尾,多行定义的对象,最后一个成员以逗号结尾。对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。
    • 使用扩展运算符(...)拷贝数组。使用 Array.from方法,将类似数组的对象转为数组。
    • 立即执行函数可以写成箭头函数的形式。那些使用匿名函数当作参数的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了 this
    • 注意区分 ObjectMap,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要key: value的数据结构,使用 Map结构。因为 Map有内建的遍历机制。
    • 总是用 Class,取代需要 prototype的操作。因为 Class 的写法更简洁,更易于理解。
    • 使用import取代require。使用export取代module.exports

    相关文章

      网友评论

          本文标题:ECMAScript 6学习笔记

          本文链接:https://www.haomeiwen.com/subject/tpavthtx.html