普通函数与箭头函数的this指向问题,以及什么时候不该使用箭头函数

152 阅读3分钟

this指向问题

箭头函数本身没有this,而是继承了父级作用域中的this。而且箭头函数一旦定下来,this指向不会根据任何情况变化。

普通函数包括全局函数,其this的指向情况可概括为4种:

  1. 函数调用,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象。
    // 宏任务中之所以要用箭头函数,是因为宏任务方法是定义在全局函数环境,
    // 如果不用箭头函数this会指向全局对象,用箭头函数的this代表当前函数作用域的父级作用域的this。
    setInterval(() => {
      this.arg = xxx;
    }, interval);
  1. 方法调用,如果一个函数作为一个对象的方法来调用时,this指向这个对象。如

    item.func(arg) //func中的this此时指向item

  2. 构造函数调用,this指向这个用new新创建的对象。

    const Func = function(arg){
        this.arg = arg;
    }
    let instance = new Func(arg);
  1. 第四种是apply 、call 和 bind调用模式,这三个方法都可以显式地指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,bind方法通过传入一个对象,返回一个this 绑定了传入对象的新函数。这个函数的 this指向除了使用new时会被改变,其他情况下都不会改变。如:
    const Func = function(){
        this.arg1 = xx;
        this.arg2 = xxx;
    }
    Func.apply(obj,[arg1,arg2]) //此时this 指向obj

不该使用箭头函数的情况

1. 通过构造函数调用

    const createItem = (itemName, numOfItem) => {
        this.itemName = itemName;
        this.num = numOfItem;
        this.getItemName = ()=>{return this.itemName};
        this.getNumOfItem = ()=>{return this.num};
    };
    // TypeError: Foo is not a constructor

2. 需要使用原型prototype继承,仅限ES5继承写法 由于JavaScript中的继承底层本质上都是基于原型prototype的,因此涉及到继承或者原型方法的调用,都不该使用箭头函数。

    const Func = ()=>{};
    console.log(Func.prototype);
    // undefined

3. 需要使用supper继承 箭头函数没有supper,如果用ES6 class创建类,其中的函数方法采用箭头函数,如果出现基类方法为箭头函数,子类方法为普通函数,当我们想访问子类的方法时,却访问到了父类的方法。造成混乱

    class TypeA{
      constructor(typeName="typeA"){
        this.typeName = typeName;
      }
      getTypeName=()=>{
        console.log("father");
        return this.typeName;
      }
    }
    class TypeB extends TypeA{
      constructor(typeName="typeB"){
        super(typeName);
      }
      getTypeName(){
        console.log("child");
        return this.typeName;
      }
    }
    let typeB = new TypeB();
    console.log(typeB.getTypeName());
    // ouput: father '\n' typeB

原因在于JavaScript访问属性变量和函数的方式,是先查找当前作用域中是否存在该变量或者函数,如果找不到,就从prototype上找,以此在原型链上找,这里由于基类TypeA的getTypeName是箭头函数,实际上class中涉及=都是Field Declaration(域声明),子类TypeB的方法是在原型prototype上的,由于TypeB继承了TypeA的方法,但是当我们访问函数方法时,却优先访问TypeA的方法了,因此随便混用箭头函数容易造成混乱,建议还是将函数方法存放在prototype上。

4. 需要使用生成器 MDN上描述只有"使用 function*来定义一个生成器"的定义,也没有使用箭头函数定义的方式。