J'Blog

ES6笔记

ES6笔记

let和const

var是在当前函数内,let和const是在代码块内有效,所谓代码块就是用{}包裹起来的。var存在“变量提升“现象,即变量可以在声明之前使用,值为undefined。而let只能在声明变量之后使用。let和const存在暂时性死区,不允许重复声明

块级作用域:ES5只有全局作用于和函数作用域,没有块级作用域,这带来很多不合理的场景。第一种场景,内层变量可能会覆盖外层变量。第二种场景,用来计数的循环变量泄漏为全局变量。

块级作用域和函数声明:ES5规定,函数只能在顶层作用域和函数作用域中声明,不能在块级作用域声明。但ES6允许,但应避免在块级作用域内声明函数,如果确实需要,也应该写成函数表达式,而不是函数声明语句。ES6块级作用域必须有大括号,如果没有就不存在块级作用域。

const声明一个只读的常量,一旦声明,常量的值就不会改变。const实际上保证的不是变量的值不得改动,而是变量指向的内存地址所保存的数据不得改动,对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于符合类型的数据(主要是对象和数组)变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

顶层对象的属性:顶层对象在浏览器指的是window对象,在Node指的是global对象,顶层对象的属性和全局变量挂钩带来的问题是首先没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次程序员很容易不知不觉地创建了全局变量,最后顶层对象的属性是导出可以读写的,这非常不利于模块化编程。ES6为了改变这一点,var命令和function命令声明的全局变量依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量不属于顶层对象的属性。

解构赋值

解构赋值可以运用到数组、对象、字符串、数值和布尔值、函数参数上。以上情况如果结构不成功变量的值就等于undefined,如果右边的值不具备Iterator接口(不可迭代)会报错。ES6内部使用严格相等运算符(===),判断一个位置是否有值,只有当一个数组成员严格等于undefined,默认值才会生效。对象的结构复制与数组有一个重要的不同,数组的元素是按次序排列的,变量的取值有它的位置决定,而对象的属性没有次序,变量必须与属性同名,才能取到正确的值,取不到undefined。字符串的结构复制是将字符串转换成一个类似数组的对象,这样就可以获取字符串的length属性。结构数值和布尔值的时候是将其先转为对象。

解构赋值的用途:1. 变换变量的值 2. 从函数返回多个值 3. 函数参数的定义 4. 提取Json数据 5. 遍历map结构,可以通过for(let [key, value] of map) 6. 输入模块的指定方法

字符串的扩展

ES6 为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历。实例方法:includes(),startsWith(),endsWith(),传统上,JavaScript只有indexOf方法,以上三个方法都支持第二个参数,表示开始索引的位置。endsWith的第二个参数表示针对前n个字符,其他两个方法针对从第n个位置知道字符串结束。trimStart(),trimEnd(),他们的行为与trm()一致,消除头部或者尾部的空格,不会修改原字符串。matchAll()返回一个正则表达式在当前字符串的所有匹配。replaceAll(searchValue,replacement)替换所有的匹配,searchValue是搜索模式,可以是一个字符串也可以是一个全局的正则表达式(必须带有g修饰符不然报错)。

函数的扩展

rest参数:(...变量名)用于获取函数的多余参数,这样就不用使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

    //arguments变量的写法
    function sortNumbers() {
        return Array.from(arguments).sort();
        //arguments对象不是数组,二十一个类似数组的对象,所以为了使用数组的方法,必须使用Array.from先将其转为数组。
    }
    
    //rest参数的写法
    const sortNumbers = (...numbers) => numbers.sort();

箭头函数:使用注意点:1. 箭头函数没有自己的this对象 2. 不可以当做构造函数 3. 不可以使用arguments对象,可以用...rest替代 4. 不可以使用yield命令。对于普通函数来说,内部的this指向函数运行时所在的对象,但是对于箭头函数,内部的this就是定义时上层作用域中的this,箭头函数的this指向是固定的,普通函数的this指向是可变的。箭头函数没有自己的this,所以不能用call(),apply(),bind()这些方法去改变this的指向。不适用场合:对象的属性,需要动态this的时候,函数体很复杂建议使用普通函数,提高代码可读性。

Function.prototype.toString()方法返回函数代码本身

数组的扩展

扩展运算符(spread)是三个点(...)它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。Array.from方法用于将两类对象转为真正的数组:类似数组的对象和可遍历对象。对于还没有部署该方法的浏览器,可以用Array.prototype.slice方法替代。

    //类似数组的特征必须有length属性的对象
    ES5: var arr1 = [].slice.call(arrayLike);
    ES6: let arr = Array.from(arrayLike);
    
    //Array.from开可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
    Array.from(arrayLike, x => x * x);
    //如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this。
    //Array.of方法用于将一组值转换为数组
    Array.of(3,11,8);
    
    //这个方法主要的目的是弥补数组构造函数Array()的不足,因为参数个数的不同会导致Array()的行为有差异
    Array()    //[]
    Array(3)   //[,,,]
    Array(3,11,8)    //[3,11,8]
    //数组实例的find()和findIndex()
    //find()返回符合条件的数组成员,没找到则返回undefined,findIndex返回符合条件的成员位置,没找到返回-1,回调函数可以接受三个参数(value,index,arr)
    [1,5,10,15].find(function(value,index,arr){
        return value > 9;
    })
    
    //两个方法都可以接受第二个参数,用来绑定回调函数的this对象
    function f(v) {
        return v > this.age;
    }
    
    let person = { name: 'John', age: 20 };
    [10, 12, 26, 15].find(f, person);   //26
    
    //这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。
    [NaN].indexOf(NaN)    //-1
        [NaN].findIndex(y => Object.is(NaN, y))    //0

fill()方法使用给定值填充一个数组,数组中已有的元素会被全部抹去,fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

entries(),keys(),values()用于遍历数组,他们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别就是keys()是键名的遍历,values()是对键值的遍历,entries()是对键值对的遍历。如果不使用for...of循环,可以手动调用遍历器对象的next对象,进行遍历。

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。第二个参数可选,表示搜索的起始位置。没有这个方法之前我们通常使用数组的indexOf方法,但是indexOf方法有两个缺点,一是不够语义化,他的含义是找到参数值的第一个出现位置,让所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相等运算符(===)进行判断,这会导致对NaN的误判。另外Map和Set数据结构有一个has方法。Map结构的has方法,是用来查找键名的,Map.prototype.has(key);Set结构的has方法,是用来查找值的,Set.prototype.has(value)。

flat()用于将嵌套的数组‘拉平’,百年城一维的数组,该方法返回一个新数组,不会影响原数据。可以将flat()方法的参数写成一个整数,表示想要拉平的层数。flat(Infinity)表示不管有多少层嵌套,都要转成一维数组。flatMap()方法对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()方法,该方法返回一个新数组,不会影响元数据。flatMap()只能展开一层数组,该方法的参数是一个遍历函数可以接受三个参数,分别是(value,index,arr),还可以有第二个参数,用来绑定遍历函数里面的this。

对象的扩展

对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为,Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。包括value,writable,enumerable,configurable。如果enumerable为false就表示某些操作会忽略当前属性。for...in循环:只遍历对象自身的和继承的可枚举的属性;Object.keys返回对象自身的所有可枚举的属性的键名;JSON.stringify只串行化对象自身的可枚举的属性;Object.assign忽略enumerable为false的属性只拷贝对象自身的可枚举的属性。

ES6一共有5种方法可以遍历对象的属性:for...in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性);Object.keys(obj)返回一个数组,包括对象自身(不含继承的)所有可枚举属性(不含Symbol属性)的键名;Object.getOwnPropertyNames(obj)返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)的键名;Object.getOwnPropertySymbols(obj)返回一个数组,包含对象自身的所有Symbol属性的键名;Reflect.ownKeys(obj)返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是Symbol或字符串,也不管是否枚举。

super关键字:this关键字总是指向函数所在的当前对象,super指向当前对象的原型对象。super关键字只能用在对象的方法之中,用在其他地方都会报错。

    const proto = {
        foo: 'hello'
    };
    
    const obj = {
        foo: 'world',
        find() {
            return super.foo;
            //super.foo等同于Object.getPrototypeOf(this).foo或Object.getPrototypeOf(this).foo.call(this);
        }
    }
    
    Object.setPrototypeOf(obj, proto);
    //setPrototypeOf设置对象原型
    obj.find()       //'hello'
    
    
    const proto = {
        x: 'hello',
        foo() {
            console.log(this.x);
        }
    };
    
    const obj = {
        x: 'world',
        foo() {
            super.foo();
        }
    }
    
    Object.setPrototypeOf(obj, proto);
    obj.foo()    //'world'
    //因为绑定的this还是当前对象obj

对象的新增方法

Object.is():ES5比较两个值是否相等,只有两个运算符,相等运算符(==)和严格相等运算符(===)。他们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。ES6 Object.is用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身。

Object.assign()方法用于对象的合并,将元对象(对象本身不包含继承属性)的所有可枚举属性,复制到目标对象(浅拷贝,只是拷贝对象的引用,修改原对象属性值会影响目标对象)。传一个参数直接返回该参数,如果参数不是对象,则会转成对象,然后返回。由于undefined和null无法转成对象,所以如果他们作为第一个参数就会报错,但是作为非第一个参数,就会跳过,不会报错。其他类型的值(即数值、字符串和布尔值)不在首参数,也不会报错,但是,除了字符串会以数组形式,拷贝入目标对象,其他值都不会产生效果。

    //同名属性会被替换
    const target = { a: { b: 'c', d: 'e' } }
    const source = { a: { b: 'hello' } }
    Object.assign(target, source);
    //{ a: { b: 'hello' } }
    
    //数组的处理
    Object.assign([1,2,3], [4,5]);
    //[4,5,3]
    
    //取值函数的处理
    //Object.assign()只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制
    const source = {
        get foo() { return 1 }
    };
    const target = {};
    Object.assign(targe, source);
    // { foo: 1 }
    //Object.assign()方法很多用处
    //1. 为对象添加属性
    class Point {
        constructor(x, y) {
            Object.assign(this, {x,y});
        }
    }
    
    //2. 为对象添加方法
    Object.assign(SomeClass.prototype, {
        someMethod(arg1, arg2) {
            ...
        },
        anotherMethod() {
            ...
        }
    });
    
    //3. 克隆对象
    function clone(origin) {
        return Object.assign({}, origin);
    }
    //如果想要保持继承链,可以采用下面的代码
    function clone(origin) {
        let originProto = Object.getPrototypeOf(origin);
        return Object.assign(Object.create(originProto), origin);
    }
    
    //4. 合并多个对象
    const merge = (target, ...sources) => Object.assign(target, ...sources);
    
    //5. 为属性指定默认值
    const DEFAULTS = {
        logLevel: 0,
        outputFormat: 'html'
    };
    function processContent(options) {
        options = Object.assign({}, DEFAULTS, options);
        //如果两者有同名属性,则options的实行值会覆盖FEFAULTS的属性值
     }

ES5的Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述对象(descriptor),ES2017引入了Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象。该方法主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。该方法配合Object.defineProperties()方法,就可以实现正确拷贝。

    const source = {
        set foo(value) {
            console.log(value);
        }
    };
    
    const shallowMerge = (target, source) => Object.defineProperties(
        target,
        Object.getOwnPropertyDescriptors(source)
    );
    
    //Object.getOwnPropertyDescriptors()方法的另一个用处,是配合Object.create()方法,将对象属性克隆到一个新对象。
    const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnProPertyDescriptors(obj));

JavaScript语言的对象继承是通过原型链实现的,ES6提供了更多原型对象的操作方法。

    //__proto__属性,用来读取或设置当前对象的原型对象(prototype)
    //ES5的写法
    const obj = {
        method: function() { ... }
    };
    obj.__proto__ = somOtherObj;
    
    //ES6的写法
    var obj = Object.create(someOtherObj);
    obj.method = function() { ... };
    //__proto__是个内部属性,所以不要使用这个属性,而是使用Object.setPrototypeOf()(写操作)、Object.setPrototypeOf()(读操作)、Object.create()(生成操作)代替。

Object.keys():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键名。Object.values():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。Object.entries():返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。Object.entries()的基本用途是遍历对象的属性,另一个用处是,将对象转为真正的Map结构,const map = new Map(Object.entries(obj));

    //自己实现Object.entries方法
    //Generator函数的版本
    function* entries(obj) {
        for( let key of Object.keys(obj) ) {
            yield [key, obj[key]];
        }
    }
    
    //非Generator函数的版本
    function entries(obj) {
        let arr = [];
        for ( let key of Object.keys(obj) ) {
            arr.push([key, obj[key]]);
        }
        return arr;
    }

Object.fromEntries()方法是Object.entries()的逆操作,用于将一个键值对数组转为对象,因此特别适合将Map结构转为对象。