阅读 1828

JS 的 this 指来指去到底指向哪?(call, apply, bind 改变 this 指向)

前言:
this指向问题一直是js中最容易犯的错误之一。
今天就写下这篇博文,谈一下我对this的理解。
如果大家觉得有帮助,请点个赞关注我吧。
如果有不对的地方,欢迎大家指正!

先来看个思维导图吧:

一、ES5中funciton的this指向

  1. 函数通过new 构造函数创建出来的实例对象this指向的是该实例对象。
function Person() {
    console.log(this, '1') //Person
    this.say = function (){
      console.log( this, '2'); //Person
    }
  }
  // 在构造函数中,this指向的是new出来的实例,所以两处this都指向Person
  var per = new Person()
  per.say()
复制代码


2. 因为构造函数本身是无法访问其自身的值,实例化对象可以。 这里为了比较,做了以下的写法:

   function Person() {
      console.log(this, '1') //Window
      this.say = function (){
        console.log( this, '2'); //Window
        this.eat = function () {
          console.log(this, '3'); //Window
        }
        eat()
      }
      say()
    }
    Person()
复制代码

解析:用函数名()调用时,无论嵌套多少层,this默认指向的也是window。


3. 在对象中创建的函数this指向是:this 永远指向最后调用它的那个对象!this 永远指向最后调用它的那个对象!this 永远指向最后调用它的那个对象!(重要的话说3次)牢记这条就不会出错。

//eg1:  
var a = 2
var obj = {
  a: 1,
  b: function() {
    console.log(this.a) //1  这里是obj调用的,所以this指向obj
  }
}
obj.b()
复制代码

假如我们假如了setTimeout呢,情况会是什么样的呢?

//eg2:  
var a = 2
var obj = {
  a: 1,
  b: function() {
    window.setTimeout(function() {
      console.log(this.a) //2  
    })
    console.log(this.a) //1 
  }
}
obj.b()
复制代码

解析:setTimeout的执行环境是Window,所以会把this指向改变到Window上。


4. 那假如在对象中嵌套多层呢?情况会是怎么样的呢?

    const obj = {
      a: function() { console.log(this) },
      b: {
        c: function() {console.log(this)}
      }
    }
    obj.a()          // obj, obj调用的a()方法
    obj.b.c()        //obj.b, obj.b调用的a()方法
复制代码

解析:还是那句话:this 永远指向最后调用它的那个对象!,这下记住了吧。


5. 还有一个情况就是闭包中的this指向,这里也讲解一下。 (面试的时候可能会出这种变形题。)

//eg1:
var name = '张' 
function Person() {
  this.name = '柳';
  let say = function (){
    console.log( this.name + ' do Something');//张 do Something
  }
  say()
}
var per = new Person()
复制代码

我们可以把这道题换种写法:

//eg2: 
var name = '张' 
function Person() {
  this.name = '柳';
  return function (){  //本质都是匿名函数的自执行
    console.log( this.name + ' do Something');//张 do Something
  }
}
var per = new Person()
per()
复制代码

解析:看到这就明白了,其实这是一道闭包题。闭包函数具有匿名函数自执行的特性,默认this指向是挂在Window下的。



二、 ES6箭头函数中的this

《深入浅出ES6》(65页)中关于箭头函数this的解释:
    箭头函数中没有this绑定,必须通过查找作用域链来决定其值。
    如果箭头函数被非箭头函数包围,那么this绑定的是最近一层非箭头函数的this;
    否则,this的值会被设置为undefined。
复制代码

我的理解:

如果箭头函数被非箭头函数包围,那么this绑定的是最近一层非箭头函数的this,我理解的这句话是说,如果箭头函数的父级是function,那么箭头函数的this指向会与funciton中的this指向保持一致。
如果上面的听不太明白,那么可以参考大家的理解方式:为箭头函数的this是在定义的时候绑定的。那么问题来了:

1.何为定义时绑定?

//eg1:
var x=11;
var obj={
  x:22,
  say:function(){
    console.log(this.x)
  }
}
obj.say();
//console.log输出的是2
复制代码

解析:一般的定义函数跟我们的理解是一样的,运行的时候决定this的指向,我们可以知道当运行obj.say()时候,this指向的是obj这个对象。

//eg2:
var x=11;
var obj={
 x:22,
 say:()=>{
   console.log(this.x);
 }
}
obj.say();
//输出的值为11

复制代码

解析:
这样就非常奇怪了如果按照之前function定义应该输出的是22,而此时输出了11,那么如何解释ES6中箭头函数中的this是定义时的绑定呢?

所谓的定义时候绑定,this绑定的是最近一层非箭头函数的this;因为箭头函数是在obj中运行的,obj的执行环境就是window,因此这里的this.x实际上表示的是window.x,因此输出的是11。


2. 理解了对象中的this定义后,还有几点要提一下:
对象中箭头函数的this指向的是window。且无法改变其指向。
原因:函数的this可以用call方法来手动指定,是为了减少this的复杂性,箭头函数无法用call等方法来指定this。

//eg3:
  var a = 2
  const obj = {
    a: 1,
    b: () => {
      console.log(this.a)
    }
  }
  obj.b()//2 默认绑定外层this
  obj.b.call(obj.a)//2 不能用call方法修改里面的this
复制代码
  1. 假如在window.setTimeout中使用箭头函数呢?
//eg4:
const obj = {
    a: function() {
        console.log(this)
        window.setTimeout(() => { 
            console.log(this) 
        }, 100)
    }
  }
  obj.a()  //第一个this是obj对象,第二个this还是obj对象
复制代码

解析:函数obj.a没有使用箭头函数,因为它的this还是obj;
而setTimeout里的函数使用了箭头函数,所以它会和外层的this保持一致,也是obj;
如果setTimeout里的函数没有使用箭头函数,那么它打印出来的应该是window对象。

  1. 多层对象嵌套里函数的this
 const obj = {
    a: function() { console.log(this) },
    b: {
    	c: () => {console.log(this)}
    }
  }
  obj.a()   //没有使用箭头函数打出的是obj
  obj.b.c()  //打出的是window对象!!
复制代码

解析:obj.a调用后打出来的是obj对象,而obj.b.c调用后打出的是window对象而非obj, 这表示多层对象嵌套里箭头函数里this是和最最外层保持一致的。



三、改变this指向的办法

  1. call()、apply()、bind()改变this指向
//eg1:
const obj = {
    log: function() {
      console.log(this)
    }
  }
  
//1.不用变量接收:this默认指向为obj
 obj.log() //obj
  obj.log.call(window)  //window  call和apply都会返回一个新函数
  obj.log.apply(window)  //window
  obj.log.bind(window)() //window bind则是返回改变了上下文后的一个函数,要想执行的话,还需要加个括号调用。
  
//2.用变量接收:用变量接收的话,this的默认指向就变成全局了
  var objName = obj.log
  objName()  //王
  // call、apply、bind的用法还和上面的一样,这里就不重复了。
复制代码
  1. 除了call、apply、bind这三种改变this指向的办法, 还有常规的用一个变量保存当前this指向,然后在去调用。
eg2:
  var _this = this 
  const obj = {
    log: function() {
      console.log(_this) 
    }
  }
  obj.log()//此时this的当前指向不再是obj,而是window了。
复制代码

小结: 3种改变this指向方法的区别

 1、call和apply改变了函数的this上下文后便执行该函数,而bind则是返回改变了上下文后的一个函数。
 2、call、apply的区别: 他们俩之间的差别在于参数的区别,
    call和aplly的第一个参数都是要改变上下文的对象,而call从第二个参数开始以参数列表的形式展现,
    apply则是把除了改变上下文对象的参数放在一个数组里面作为它的第二个参数
复制代码
  1. call()、apply()的一些其他用处:求数组中的最大、最小值
  let arr1 = [1, 2, 19, 6];
  //例子:求数组中的最值
  console.log(Math.max.call(null, 1,2,19,6)); // 19
  console.log(Math.max.call(null, arr1)); // NaN
  console.log(Math.max.apply(null, arr1)); //  19 直接可以用arr1传递进去

复制代码

合并数组:

  var arr1=[1,2,3];
  var arr2=[4,5,6];
  arr1.push.apply(arr1,arr2);
  alert(arr1)//1,2,3,4,5,6 
  alert(arr2)//4,5,6 
复制代码

解析:同理,apply将数组装换为参数列表的集合。

结尾: 以上就是对js中的this指向,和call()、apply()方法一些需要知道的小技巧。
如果觉得对你有帮助,请给作者一点小小的鼓励,点个赞或者收藏吧。
有需要沟通的请联系我:微信( wx9456d ) 邮箱( allan_liu986@163.com )

关注下面的标签,发现更多相似文章
评论