老生常谈的this

1. 前言

JavaScript中的this可能是当年设计的时候存在着设计缺陷,在ES6中能使用()=>这个高端的箭头函数就尽量使用箭头函数,箭头函数在其他语言中还有个高大上的名字Lambda表达式。

PS:nodejs环境中没有window对象

2. this指向

对于this的指向,我的理解就是记住一句话,**如果没有使用apply和call还有箭头函数的情况下,this指向最后一次调用它的对象,如果最后一次调用它的是函数,则在严格模式指向undefined非严格模式下指向全局变量window**。

下面的例子全部为非严格模式

var a = 1;
var obj = {
  a: 2,
  b: function() {
    return this.a;
  }
};
console.log(obj.b()); //2

上面的例子应该很清晰,因为最后一次调用b方法的是obj这个对象,而对象obj里面a的值为2,所以这个例子会输出2。

那么我们加大一下难度看下面的例子

var a = 1;
var obj = {
  a: 2,
  b: function() {
    return this.a;
  }
};
var t = obj.b;
console.log(t());
console.log(obj.b());
console.log(t() === obj.b()); 
console.log(t === obj.b);

这个时候我们新加入一个变量t,将b方法赋值给t,大家思考一下这个时候函数t会输出什么。

神奇的事情发生了

console.log(t()); //1
console.log(obj.b()); //2
console.log(t() === obj.b()); //false
console.log(t === obj.b); //true

函数t输出的结果是1。

因为这次调用方法的是函数t而他的对象为window,因为对象window上的a值为1,故t的最后输出结果为1。

下面这个例子也同样

var a = 1;
var obj = {
  a: 2,
  b: function() {
    function fun() {
      return this.a;
    }
    console.log(fun());
  }
};
obj.b(); //1

因为调用fun方法的是方法b,这时最后一次调用fun的不为对象,则this指向全局变量window

3. 箭头函数

  • 箭头函数会捕获其所在上下文的 this 值,作为自己的 this
  • 箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined

3.1 解决的痛点

ES5语法中使用this有时候会很难以预测this的指向,从而产生bug,这个问题到了现在框架时代大量通过类进行封装时变得尤为明显。如果学习过react,就会发现将父组件的函数传递给子组件使用时,this指向总是个头疼的问题。

虽然使用call,apply,bind可以解决这部分痛点,但是需要额外的代码,不仅增大了工作量,而且增加了维护成本。

3.2 简单题目

var a = 1;
var obj = {
  a: 2,
  b: () =>{
    return this.a;
  }
};
console.log(obj.b()); //1

因为b是通过箭头函数进行声明,则它的this就指向了obj的this,而obj的this就是全局变量window

3.3 中等题目

var a = 1;
var obj = {
  a: 2,
  b: function () {
    var fun = () => {
      return this.a;
    };
    console.log(fun());
  },
};
obj.b(); //2

因为箭头函数会找上下文的this,所以这里找到的上文就是方法b的this,即为对象obj

var a = 1;
var obj = {
  a: 2,
  b: () => {
    var fun = () => {
      return this.a;
    };
    console.log(fun());
  },
};
obj.b(); //1

箭头函数是没有this的,这个时候方法fun就逐层寻找this,最终找到的是对象obj的this

4. call,apply,bind

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

  1. apply:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.apply(A, arguments);即A对象应用B对象的方法。

  2. call:调用一个对象的一个方法,用另一个对象替换当前对象。例如:B.call(A, args1,args2);即A对象调用B对象的方法。

  3. bind除了返回是函数以外,它的参数和call一样。

4.1 箭头函数

this是不能改变的,所以对箭头函数使用call,apply,bind是没有效果的

var a = 1;
var obj = {
  a: 2,
  b: function () {
    var fun = () => {
      return this.a;
    };
    console.log(fun.call(a));
  },
};
obj.b(); //2

4.2 特殊情况

var a = 1;
var obj = {
  a: 2,
  b: function () {
    var fun = () => {
      return this.a;
    };
    console.log(fun());
  },
};
var obj1 = {
  a: 3,
};
obj.b.call(obj1); //3

箭头函数的this虽然不能被改变,但是可以改变它上下文的this。

5. 一大堆题

既然上面都讲了那么多,那么我们来看一下下面的题:

不知道是哪个大神想的题目,反正就是不让人好过。┓( ´∀` )┏

var age = 10;
var person = {
  age: 20,
  getAge() {
    var age = 30;
    return this.age; //20
  }
};
console.log(age, age * 2); //10 20
console.log(person.getAge()); //20
var b = person.getAge;
console.log(b()); //10
console.log(person.getAge()); //20
console.log((1, person.getAge)()); //10
console.log((1, person.getAge.bind(person))()); //20
console.log((person.getAge, person.getAge)()); //10
console.log((person.getAge = person.getAge)()); //10
console.log(person.getAge.call()); //10
console.log(person.getAge.call(person)); //20
function getAge2() {
  this.age = 40;
  console.log(person.getAge());
}
getAge2(); //20
console.log(age); //node环境和浏览器环境不一样,因为浏览器环境中有window对象
function getAge3() {
  this.age = 50;
  this.getAge4 = () => {
    console.log(person.getAge.call(this)); //50
  };
}
console.log(new getAge3().getAge4());
console.log(age); //10
function getAge4() {
  this.age = 60;
  this.getAge5 = () => {
    console.log(person.getAge.call(this)); //60
  };
}
console.log(new getAge4().getAge5()); //undefined
console.log(age); //10
var age2 = 10;
var person2 = {
  age2: 20,
  getAge2: () => {
    var age2 = 30;
    return this.age2;
  }
};
console.log(person2.getAge2.call()); //10
console.log(person2.getAge2.call(person2)); //10

6. 写在最后

看了很多篇文章,对那些文章进行了总结,我也不能说我百分百懂了this,如果有错误的地方,欢迎在评论指出~