我对ES6的整理,以及衍生相关知识

66 阅读11分钟

let const

var

在let和const出来之前,函数作用域分为全局作用域跟函数作用域。用var先声明,不赋值,会造成一个变量提升的问题。比如说我用一个if循环包裹住一段函数,里面用var定义了一个参数,其实这个var不会形成一个作用域,这个作用域是全局的。 var的特点如下:

  • var 在全局作用域定义时,还会挂载到window(浏览器环境)上,生成全局变量
  • 可以重复声明,后面申明覆盖前面声明
  • 存在变量提升(自动把声明提到作用域顶部),不规范写法,易导致可读性差

let

  • 除了全局作用域和函数作用域外,还新增了块级作用域( { } )
  • let和const都不能重复声明
  • 作用域内,声明语句前使用会报错(暂时性死区 temporal dead zone TDZ)

const

  • let 基础上,规定了申明后不可修改,用于声明常量
  • 声明后不可修改,必须声明时候赋值
  • 当使用const声明一个对象和数组时后,此变量的引用便不可修改,但是此变量内部属性还是可以进行修改的。

Set

Set 只有键值,没有键名,类似于一个数组,但数组可以允许元素重复,Set 不允许元素重复,用这个可以解决数组去重,求并集,求交集等问题。

数组去重与字符串去重

// Set可以用于字符串或者数组的去重
    let arr = [3, 4, 3, 5, 6, 5, 6]
    let str = "23234545"
    let uniqueArr = new Set([...arr])
    console.log(uniqueArr)

    let uniqueStr=new Set([...str].join(""))
    console.log(uniqueStr)

image.png

求并集

    let a=[1,2,3]
    let b=[2,3,4]
    let union=new Set([...a,...b])
    console.log(union)
    
    

image.png

求交集

    let bb=new Set(b)
    let intersect=new Set([...a].filter(e=>
       bb.has(e)
    ))
    console.log(intersect)

image.png

Map

Map

Map主要就是键值对的存储,类似于对象,但是对象的键名只能是字符串或者是数组,键名可以是任意类型的值,相同键名只能出现一次。

  • 一个键名只能出现一次;
  • 有set、get、has、delete、clear等增删改查方法,有size属性;
  • 与Set系列不同,它有keys、values、entries等方法;

WeakMap

WeakMap :WeakMap 是JavaScript的一个内置对象,是一种数据结构。WeakMap对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

  • 因为 WeakMap 实例不会妨碍垃圾回收,所以非常适合保存关联元数据(比如深拷贝解决循环引用问题)。

  • WeakMap 跟Map相似,也是生成键值对的集合,但是键名只能是对象,null除外。

  • WeakMap不可枚举!没有遍历操作,也就是 keys() ,values(), entries() 也没有size

  • 只有四个方法,get set has delete

WeakSet

WeakSet 中的对象都是弱引用即垃圾回收机制不考虑WeakSet对其内部对象的引用,也就是说,在WeakSet中的对象当没有外界其他对象引用它时,会被垃圾回收机制回收,并不考虑WeakSet中还有它。实际用处的话,就比如我们要在一个设置一个按钮的禁用列表,我们可以用weakSet把他们存起来,通过has查询一下是否被禁用了,如果我们用普通的数组存储他们,会造成一定的性能损耗,不利于垃圾回收机制。那这个时候就可以用WeakSet存储要被禁用的Dom节点,不必担心后续这个Dom节点从文档中被移除后造成的内存泄露,WeakSet是弱引用的,经典用法就是存放Dom节点。

  • WeakSet 的成员只能是对象,不能是其他类型的值, WeakSet 里面的引用,都不计入垃圾回收机制, WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息

  • 不可遍历

  • 有三个方法: add, has, delete

  • 没有size属性,没办法遍历他的成员

  • WeakSet 的一个作用是存储DOM节点,不用担心这些节点从文档移除时候,会引发内存泄漏

类的继承extends

js的继承

原型继承

原型链继承的特点是对象实例可以共享所有继承的属性和方法。但是父类的引用类型数据会被子类共享,因为两者指向同一个内存空间,且子类实例不能给父类构造函数传参。比如说我new两个实例用原型继承Person1,这两个实例的引用类型是共享的。

    function Person1() {
        this.name = "小名";
        this.eats = ["苹果"]
        this.getName = function () {
            console.log(this.name);
        }
    }
    Person1.prototype.get = () => {
        console.log("Person1.prototype上的方法")
    }

    function student2() {}
    student2.prototype = new Person1()



    const stu2 = new student2();
    stu2.name = "消化";
    stu2.eats.push("香蕉");



    console.log(stu2.name)
    console.log(stu2.eats);
    // stu2.getName();
    stu2.get();


    const per1=new Person1()
    per1.getName()
    console.log(per1.eats)

构造函数继承

构造函数配合call继承,call改变指针方向,子类不能继承父类原型(prototype)上的方法

    function Person3() {
        this.name = "小名";
        this.eats = ["苹果"]
        this.getName = function () {
            console.log(this.name);
        }
    }
    Person3.prototype.get = () => {
        console.log("Person3.prototype上的方法")
    }

    function Student3(){
        Person3.call(this)
    }

    const stu3=new Student3();
    stu3.name="小构造函数继承"
    stu3.eats.push("构造函数")

    console.log(stu3.name)
    console.log(stu3.eats);

    const per3=new Person3()
    per3.getName()

组合继承

组合继承,用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

    function Person4() {
        this.name = "小名";
        this.eats = ["苹果"]
        this.getName = function () {
            console.log(this.name);
        }
    }
    Person4.prototype.get = () => {
        console.log("Person4.prototype上的方法")
    }

    function Student4(){
        Person4.call(this)
    }

    Student4.prototype=new Person4()

    const stu4=new Student4();
    console.log(stu4)
    stu4.name="组合继承"
    stu4.eats.push("组合函数")

    console.log(stu4.name)
    console.log(stu4.eats);

    stu4.getName()
    stu4.get()

寄生组合继承

寄生组合继承,通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。其实就是设置了一个fn来中转一下原型上的继承。

    function Person5() {
        this.name = "小名";
        this.eats = ["苹果"]
        this.getName = function () {
            console.log(this.name);
        }
    }

    Person5.prototype.get = () => {
        console.log("Person5.prototype上的方法")
    }

    function Student5(){
        Person5.call(this)
    };

    function fn(){}
    fn.prototype=new Person5()
    Student5.prototype=new fn()

    const stu5=new Student5()
    




    console.log(stu5)
    stu5.name="消化";
    stu5.eats.push("香蕉");
    console.log(stu5.name)
    console.log(stu5.eats);
    stu5.getName();
    stu5.get()

extends继承

个人比较喜欢这种,简单透彻,像c++语法。

    class parent {
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
    }
    class child extends parent {
        constructor(name, age, color) {
            super(name, age);
            this.color = color;
        }
    }

Promise

个人总结:promise是异步编程的一种解决方案,promise出现就是为了解决回调地狱的问题,开一个构造器excutor,传入resolve与reject,要注意promise是同步的,立即执行的。三种状态在代码中可以体现。主要用处就是axios是基于promise封装的,我们在axios响应拦截器那里获取到数据之后可以用.then接受成功后的回调。.then的话是异步的,处于微任务队列,他一定要等到promise返回改变后的状态才会执行,这里就需要一个#run的数组来保存上一个promise的信息,等到他的状态改变之后才会执行.then里的回调,又因为.then支持链式调用,所以.then包的一定会返回一个promise。.then会接受两个参数,onFullfilled与onRejected,在他们内部会判断一下他们的状态如果是pending,把他们放进onFullfilledCallback与onRejectedCallback。那么这儿会有一个问题, .catch和.then是同级的吗?

  • Promise.prototype.catch()方法 Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数,.catch其实就是.then的第二个参数

那么还会涉及到promise的三个常用api:all,race,allsettled。

  • all的话是将一堆promise实例放在一个数组里,用all调用,成功后返回一个promise,如果这个过程有一个失败,就立即返回这个失败的原因,后续不会再执行了。
  • race的话是返回第一个响应的promise,无论成功还是失败。
  • allsettled的 执行结果不会失败,返回一个数组,分别对应参数数组中各个Promise实例的状态。

简单promise手写

 function MyPromise(excuor){
    
    let self=this;
      self.status="pending";
      // 成功的原因
      self.value=null;
      // 失败的理由
      self.reason=null;
      self.onFullfilledCallback=[];
      self.onRejectedCallback=[];
    

    function resolve(value){
          if(self.status='pending'){

              self.value=value;

              self.status='fullfilled';

              self.onFullfilledCallback.forEach(item=>item(value))
          }
      }
    function reject(reason){
          if(self.status='pending'){
              self.reason=reason;
              self.status='rejected';
              self.onFullfilledCallback.forEach(item=>item(reason))
          }
      }
      try{
          excuor(resolve,reject)
      }
      catch(err){
          reject(err)
      }
}

MyPromise.prototype.then=function(onFullfilled,onRejected){
      // 容错处理
      onFullfilled=typeof onFullfilled==='function'?onFullfilled:function(data){resolve(data)}
      onRejected=typeof onRejected==='function'?onRejected:function(err){throw(err)}
      // 支持异步处理
      let self=this;
      // debugger

      if(self.status='pending'){
          self.onFullfilledCallback.push(onFullfilled)
          self.onRejectedCallback.push(onRejected)
      }     
    }

    let demo=new MyPromise((resolve,reject)=>{
      console.log("我是涵大帅")
      setTimeout(()=>{
          console.log("1222")
          resolve(1)

      },1000)

    })

Async Await

个人总结:async,await是让异步操作变得更加简便,一般用async是把他放在一个函数之前,这个被放置了async的函数会返回一个promise的回调,await则是等待这个promise对象的状态变为fullfilled,这个await后面跟的是异步的操作。一般来说await后面是跟着promise的,如果是promise的话,是会阻塞后续代码执行的,但是如果是函数或者是其他,就不会等待这个await。下面有道面试题可以看看。如果async包的那个函数抛出错误了怎么办呢?可以加个try和catch的代码块捕捉成功和错误。这部分参考了这位作者juejin.cn/post/712165…

    (async function () {
        try {
            const a = await new Promise((resolve, reject) => reject(123))
            console.log("我成功了!")
        } catch (err) {
            console.log('我出错了,但我不想管!', err);
        }
        console.log(456);

    })()
    
    

面试题

    function log(time) {
        setTimeout(function () {
            console.log(time);
            return 1;
        }, time)
    }
    async function funccc() {
        let a = await log(1000);
        let b = await log(3000);
        let c = log(2000);
        console.log(a);
        console.log(1)
    }
    funccc();

这个题里面的await就是来误导的,在await后面不是一个promise的时候,是不会去等待其执行完成的. 所以其实这个问题就很简单,前面介个setTimeout存入队列,先执行,a,1,这个时候,a已经定义,但是没有初始值,所以就是undefined,执行完后,再执行队列中的值,也是不会相互等待哦,依次执行下来,就是1000,2000,3000。

async/await实际上是对Generator(生成器)的封装,是一个语法糖。ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,通过next()方法可以切换到下一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方案。

    function* generate() {
        const res = yield request('1111');
        const res2 = yield request(res);
        console.log(res2)
    }

    function run() {
        const g = generate(); //拿到句柄
        function exec(params) {
            const {
                value,
                done
            } = g.next(params);
            if (!done) {
                value.then(res => exec(res))
            }
        }
        exec()
    }
    run()

扩展运算符[...arg]

扩展运算符格式很简单,就是三个点(...)

  • 链接:juejin.cn/post/684490…
    扩展运算符可以使用的点有很多,比如数组的浅拷贝,数组拼接,字符串转数组...

箭头函数

普通this指向

链接:juejin.cn/post/703525…

this的绑定有四种:默认绑定;显示绑定;隐式绑定;new绑定;

默认绑定

  • 绑定全局的window

        `console.log(this === window);   //true`
    
  • 全局作用域下独立调用函数,this指向window

      function test() {
            console.log(this === window); //true 
         }
    
    test();
    

隐式绑定

按照我的理解是结合上下文的绑定,跟它所处的作用域是无关的,从第二段代码可以看出,谁调用他就指向谁

    let obj = {
            name: 'obj',
            foo: function () {
                    console.log(this); //this指向obj  
                    }  
                    }   
                    obj.foo()

            }}
            

改一下

   let obj = {
        name: 'obj',
        foo: function () {
            console.log(this); //obj
            function test() {
                console.log(this); //window  为什么?  因为test独立调用
            }
            test()
        }
    }
    obj.foo()

隐式丢失

       let obj = {
        name: 'obj',
        foo: function () {
            console.log(this); //window   为什么不是obj?  bar拿到obj.foo的引用,然后在全局下独立调用
        }
    }
    let bar = obj.foo
    bar()
    

函数作为参数

      function foo() {
        console.log(this); //window  obj.foo在bar函数内独立调用  
    }

    function bar(fn) {
        fn()
    }
    let obj = {
        name: 'obj',
        foo: foo
    }
    bar(obj.foo)

函数作为参数时,参数函数叫做子函数,外面的函数叫父函数,子函数也叫回调函数,像这样的函数有很多,

比如forEach 、setimeout,这些函数里的参数函数也叫内置参数

记住:父函数有能力决定子函数this的指向,例如forEach里第一个参数是一个函数,第二个参数就是this绑定的对象,不写默认绑定window

显式绑定

call、apply、bind bind返回一个新函数,新函数指向绑定的对象,旧函数不会

new绑定

this指向函数实例化之后的对象

箭头函数指向

  • 普通函数可以用于构造函数(new)。箭头函数不可用于构造函数,会报错。

  • 箭头函数没有自己的this,是当前上下文环境的this,不会被call()apply()bind()修改this指向。

  • 由于 箭头函数没有自己的this指针,通过 call()或apply()方法调用一个函数时,只能传递参数(不能绑定this),他们的第一个参数会被忽略。

  • 每一个普通函数调用后都具有一个arguments对象,用来存储实际传递的参数。但是箭头函数并没有此对象。

new一个实例对象发生了啥

function newOperator(ctor, ...args) {
// 创建一个新对象,并将该对象的[[prototype]]指向构造函数的prototype
const obj = Object.create(ctor.prototype);
// 绑定this到新对象中
const res = ctor.apply(obj, args);
// 判断构造函数返回的是否是一个对象,是则返回,否则返回新创建的对象
const resType = typeof res;

return (resType === 'object' && resType !== null) || resType === 'function'
    ? res
    : obj;
   }

    function Test() {
      this.a = 1;
this.b = 2;
     }

console.log(newOperator(Test)); // Test {a: 1, b: 2}

解构赋值

对应使用就可以了 详细可以看链接 链接:juejin.cn/post/684490…
但是这两天看到一道面试题,小恶心 `

    const { a , b:y } = { a:3 , b:4 }
    console.log(a)
    console.log(y)
    console.log(b) 

`
对应输出什么,答案是y是4,a是3,b会报错,涉及到别名的使用吧。