ES6相关

133 阅读4分钟

Let、Const、Var

1. 区别

区别varletconst
是否有块级作用域×✔️✔️
是否存在变量提升✔️××
是否添加全局属性✔️××
能否重复声明变量✔️××
是否存在暂时性死区×✔️✔️
是否必须设置初始值××✔️
能否改变指针指向✔️✔️×

2. 带来的问题

  • 使用var声明变量的问题
    • 允许重复的变量声明会导致数据被覆盖
    • 变量提升:怪异的数据访问、闭包问题
    • 全局变量挂载到全局对象:全局对象成员污染问题
  • 使用let声明变量
    • 会有块级作用域:内层变量可能覆盖外层变量;用来计数的循环变量泄露为全局变量。
    • 声明变量的问题
      • let声明的变量不会挂载到全局对象
      • let声明的变量,不允许当前作用域范围内重复声明
      • 在块级作用域中用let定义的变量,在作用域外不能访问
      • 使用let不会有变量提升,因此,不能在定义let变量之前使用它
      • 底层实现上,let声明的变量实际上也会有提升,但是,提升后会将其放入到“暂时性死区”,如果访问的变量位于暂时性死区,则会报错:“Cannot access 'a' before initialization”。
      • 当代码运行到该变量的声明语句时,会将其从暂时性死区中移除。
      • 在循环中,用let声明的循环变量,会特殊处理,每次进入循环体,都会开启一个新的作用域,并且将循环变量绑定到该作用域(每次循环,使用的是一个全新的循环变量)
      • 在循环中使用let声明的循环变量,在循环结束后会销毁
  • 使用const声明常量
    • const和let完全相同,仅在于用const声明的变量,必须在声明时赋值,而且不可以重新赋值。实际上,在开发中,应该尽量使用const来声明变量,以保证变量的值不会随意篡改,原因如下:
    • 根据经验,开发中的很多变量,都是不会更改,也不应该更改的。后续的很多框架或者是第三方JS库,都要求数据不可变,使用常量可以一定程度上保证这一点。
    • 注意的细节:
      • 常量不可变,是指声明的常量的内存空间不可变,并不保证内存空间中的地址指向的其他空间不可变。
      • 常量的命名
      • 特殊的常量:该常量从字面意义上,一定是不可变的,比如圆周率、月地距地或其他一些绝不可能变化的配置。通常,该常量的名称全部使用大写,多个单词之间用下划线分割。
      • 普通的常量:使用和之前一样的命名即可。
      • 在for循环中,循环变量不可以使用常量,不然无法循环变化。

异步、promise

每个promise对象(异步任务)都有两个阶段,三个状态

  • 两个阶段:unsettled(未决) settled(已决)
  • 三个状态:pending(挂起状态) fulfilled(完成状态(通过resolve方法得到)) rejected(失败状态(通过reject方法得到))
  • 针对 resolved 的后续处理称为thenable
  • 针对rejected 的后续处理称为 catchable

1. 类:构造函数的语法糖

  • 传统的构造函数的问题
    • 属性和原型方法定义分离,降低了可读性
    • 原型成员可以被枚举
    • 默认情况下,构造函数仍然可以被当作普通函数使用
  • 类的特点
    • 类声明不会被提升,与 let 和 const 一样,存在暂时性死区
    • 中的所有代码均在严格模式下执行
    • 类的所有方法都是不可枚举的
    • 类的所有方法都无法被当作构造函数使用
    • 类的构造器必须使用 new 来调用
  • 新旧对比:
        // function Animal(type,name,age,sex){
        //     this.type = type,
        //     this.name = name,
        //     this.age = age,
        //     this.sex = sex
        // }
        // Animal.prototype.print = function(){
        //     console.log(`种类:${this.type}`);
        //     console.log(`名字:${this.name}`);
        //     console.log(`年龄:${this.age}`);
        //     console.log(`性别:${this.sex}`);
        // }
        // const a = new Animal('狗','旺财',3,'男');
        // a.print();
        // for(const prop in a){
        //     console.log(prop);
        // }
    
        // class Animal{
        //     constructor(type,name,age,sex){
        //         this.type = type,
        //         this.name = name,
        //         this.age = age,
        //         this.sex = sex
        //     }
        //     print(){
        //         console.log(`种类:${this.type}`);
        //         console.log(`名字:${this.name}`);
        //         console.log(`年龄:${this.age}`);
        //         console.log(`性别:${this.sex}`);
        //     }
        // }
        // const a = new Animal('狗','旺财',3,'男');
        // a.print();
        // for(const prop in a){
        //     console.log(prop);
        // }
    
  • 类的其他书写方式
    • 匿名类
          const A = class {
             constructor(){}
             say(){}
          }
          const a = new A();
      

2. 类的继承

  • 概念: 如果B继承自A,A是B的父类,则B会自动拥有A中所有实例,如果定义了constructor,并且该类是子类,则必须在constructor的第一行手动调用父类的构造函数;如果子类不写constructor,则会有默认的构造器,该构造器需要的参数和父类一致,并且自动调用父类构造器。
  • 新的关键字:
    • extends: 继承,用于类的定义
    • super: 直接当作函数调用,表示父类构造函数;如果当作对象使用,则表示父类的原型
            // class Animal {
            //     constructor(type, name, age, sex) {
            //         this.type = type;
            //         this.name = name;
            //         this.age = age;
            //         this.sex = sex;
            //     }
            //     print() {
            //         console.log(`【种类】:${this.type}`);
            //         console.log(`【名字】:${this.name}`);
            //         console.log(`【年龄】:${this.age}`);
            //         console.log(`【性别】:${this.sex}`);
            //     }
            //     jiao(){
            //         throw new Error("动物怎么叫的?");
            //     }
            // }
            // class Dog extends Animal{
            //     constructor(name,age,sex){
            //         super('犬类',name,age,sex);
            //         this.loves = '吃骨头';
            //     }
            //     print(){
            //         // 调用父类的print
            //         super.print();
            //         //自己的方法
            //         console.log(`爱好:${this.loves}`);
            //     }
            //     jiao(){
            //         console.log('旺旺!');
            //     }
            // }
            // const dog = new Dog('旺财',3,'公');
            // dog.print();
            // dog.jiao();
            // console.log(dog);
    

3. 继承(六种继承方式)

  • 原型链继承: 就是让子类的原型等于父类的实例对象,这样继承的子类的实例共享了父类实例上的方法,当一个子类实例修改了父类实例的属性,其他子类实例的属性就会发生变化。但是缺点是:过多的继承了没用的属性,构造实例的时候不能向父类的构造函数传递参数。
  • 借用构造函数继承: 在创建实例的时候可以向父类构造函数传递参数,可以实现多继承,缺点:子类实例没有继承到父类原型上的属性和方法。
  • 组合式继承: 就是原型链继承和构造函数继承的结合,它的特点是,可以向父类的构造函数传递参数,也可以继承父类原型上的属性和方法,但是每次创建函数的时候,都会调用两次父类的构造函数,造成性能上和内存的损耗。
  • 原型式继承: 类似于复制一个对象,用函数包装起来,这样的继承会使所有原型都会继承原型上的属性,但是创建出来的新实例的属性都是要在后面添加的。
  • 寄生式继承: 寄生式继承相当于给原型式继承又套了一层函数传递参数,可以为原型式继承创建的对象添加属性。
  • 寄生组合式继承: 解决了组合式继承的缺点:调用两次父类的构造函数。

ESmodule common js

1. 目的:

  • 解决全局变量污染
  • 解决依赖混乱
  • 更容易细分文件

2. 导入导出方式

  • commonJs: 是一个社区规范,出现的时间较早,目前仅node环境支持,
    • 导入: 函数require("要导入的模块路径"),可以省略.js,必须以./或../开头
    • 有缓存: 一个模块在被导入时会运行一次,node会缓存导出结果,重新导入时不会再运行该模块
    • 导出: 在模块中给module.exports赋值,赋什么类型则导出什么类型
  • Es Module: ES6发布的官方模块化标准,目前浏览器和新版本node环境均支持
    • 导入: 使用关键字import,同时可以更改导入的变量的名称。
    • 导出: 使用关键字export,分为具名导出(普通导出),可以导出多个;默认导出,只能导出一个;
    • 静态导入的代码绑定的符号是常量,不可更改
            //      //仅运行一次该模块,不导入任何内容
            //          import "模块路径"
            //      // 常用,导入属性 a、b,放到变量a、b中。a->a, b->b
            //          import { a, b } from "模块路径"   
            //      // 常用,导入属性 default,放入变量c中。default->c
            //          import c from "模块 路径"  
            //      // 常用,default->c,a->a, b->b
            //          import c, { a, b } from "模块路径" 
            //      // 常用,将模块对象放入到变量obj中
            //          import * as obj from "模块路径" 
            //      // 导入属性a、b,放到变量temp1、temp2 中
            //          import {a as temp1, b as temp2} from "模块路径" 
            //      // 导入属性default,放入变量a中,default是关键字,不能作为变量名,必须定义别名
            //          import {default as a} from "模块路径" 
            //      //导入属性default、b,放入变量a、b中
            //          import {default as a, b} from "模块路径" 
            //      // 以上均为静态导入
            //          import("模块路径") // 动态导入,返回一个Promise,完成时的数据为模块对象
            //导出:具名导出(普通导出),可以导出多个;默认导出,只能导出一个;一个模块可以同时存在两种导出方式,最终会合并为一个「对象」导出
            //          export const a = 1; // 具名,常用
            //          export function b() {} // 具名,常用
            //          export const c = () => {}  // 具名,常用
            //              const d = 2;
            //          export { d } // 具名
            //              const k = 10
            //          export { k as temp } // 具名
            //          export default 3 // 默认,常用
            //          export default function() {} // 默认,常用
            //              const e = 4;
            //          export { e as default } // 默认
            //              const f = 4, g = 5, h = 6
            //          export { f, g, h as default} // 基本 + 默认
                        // 以上代码将导出下面的对象
                        /*
                        {
                            a: 1,
                            b: fn,
                            c: fn,
                            d: 2,
                            temp: 10,
                            f: 4,
                            g: 5,
                            default: 6
                        }
                        */
                // export {xx}
                // import {xx} from './'    
                // export default xx    
                // import xx from './'   
    

3. commonJS和ES Module的区别

  • 导入时文件后缀:
    • ommonJS在导入时可以省略文件后缀.js,而ES Module不可以省略
  • 依赖类型
    • commonJS为"动态依赖",在运行到代码块的时候才导入其他东西,导出可以写在其他代码块中
    • ES Module为"静态依赖",在代码运行之前分析出依赖关系,导出时必须为顶级导出,不能放在代码块中
    • ES Module中出现import('模块路径')函数时才可以动态依赖
  • 运行环境
    • commonJs可以直接在node上面运行,而Es Module则要么加上type="module"后在浏览器运行,要么在node运行时需要在package.json里面加上type="module"

ES6新增API

1. Object.is 用于判断两个数据是否相等,基本上跟严格相等(===)是一致的,除了以下两点

  • NaN和NaN相等 +0和-0不相等
  • console.log(Object.is(NaN,NaN));

2. object.assign 用于混合对象,进行覆盖重整

  •     // const obj1 = {
        //     a:1,
        //     b:2,
        //     c:3
        // }
        // const obj2 = {
        //     c:0,
        //     d:4,
        // }
        // const obj3 = Object.assign({},obj1,obj2);
        // console.log(obj1);
        // console.log(obj2);
        // console.log(obj3);
        // const obj = Object.assign(obj1,obj2);
        // console.log(obj);
    

4. Object.getOwnPropertyNames 的枚举顺序,ES6规定了该方法返回的数组的排序方式如下:

  • 先排数字,并按照升序排序
  • 再排其他,按照书写顺序排序
  • 返回排好序的属性名,不返回属性值 // const obj = { // d:1, // b:2, // a:3, // 0:6, // 5:2, // 4:1 // } // const props = Object.getOwnPropertyNames(obj); // console.log(props);

5. Object.setPrototypeOf 该函数用于设置某个对象的隐式原型

  • 比如: Object.setPrototypeOf(obj1, obj2), 相当于: obj1._proto = obj2
            // const obj1 = {
            //     a:1
            // }
            // const obj2 = {
            //     b:2
            // }
            // Object.setPrototypeOf(obj1,obj2);
            // console.log(obj1);
        //比如:
        // function Animal(type, name, age, sex) {
        //     this.type = type;
        //     this.name = name;
        //     this.age = age;
        //     this.sex = sex;
        // }
        // Animal.prototype.print = function () {
        //     console.log(`【种类】:${this.type}`);
        //     console.log(`【名字】:${this.name}`);
        //     console.log(`【年龄】:${this.age}`);
        //     console.log(`【性别】:${this.sex}`);
        // }
        // function Dog(name, age, sex) {
        //     //借用父类的构造函数
        //     Animal.call(this, "犬类", name, age, sex);
        // }
        // Object.setPrototypeOf(Dog.prototype, Animal.prototype);
        // const d = new Dog("旺财", 3, "公");
        // d.print();
        // console.log(d);