如何理解JS中的this指向

823 阅读5分钟

谈一谈函数中的this

  • this指向在函数定义的时候是确定不了指向的,在函数执行的时候才能确定this到底是指向谁,实际上this的最终指向是调用它的对象
  • 《javascript语言精髓》中大概概括了4种调用方式:
  • 方法调用模式
  • 函数调用模式
  • 构造器调用模式
  graph LR
  A-->B
  • call/apply调用模式

箭头函数和普通函数的区别

  • 箭头函数是没有this的,而普通函数是有this
  • 箭头函数的this是在定义函数时绑定的 而普通函数的this是在执行函数绑定的
  • 箭头函数没有this,所以需要通过查找作用域链来确定this的值,这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this
  • 普通函数的this就是指向最终调用它的对象

例子说明

  • 例1
function a(){
    var user = "小明";
    console.log(this.user); //undefined
    console.log(this); //Window
}
a();

在这里我们可以看到函数awindow调用了之后 this就指向window 所以就查找不到this.user

  • 例2
var aa = {
    user:"小明",
    fn:function(){
        console.log(this.user);  //小明
    }
}
aa.fn();

而在这个例子中fn()这个函数是aa调用起来的,所以这个时候 this 就指向了aa这个对象

总结1:看了这两个例子之后我们可以总结出来 this指向了最终调用者的对象,但是其实这样的说法不太准确的,下面我们来看其他例子,就可以推翻上面的两个例子

  • 例3
var aa = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //12
        }
    }
}
aa.b.fn();

这里同样是aa点击调用出来的 但是this是指向b的 那1 2的例子中的结论是不是错误的呢,其实不是的,其实都是正确的,只是描述的太绝对了 不太准确,this指向的问题需要分情况来看的

总结

从上面的例子来看 我们可以总结出this指向的几种不同的情况来彻底弄懂其中的指向问题

  • 如果一个函数中有this,但是他没有被上一级的对象所调用,这时候就是指向最终调用者
  • 如果一个函数中有this,这个函数有被上一级对象所调用,这时候就是指向上一层的对象
  • 如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象

扩展

  • 扩展1
  function aa() {
    let circle = {
      outerDiameter: () => {
        let innerDiameter = () => {
          console.log(this);
        };
        innerDiameter();
      }
    };
    circle.outerDiameter();
  }
  let a = {
    ccc: aa
  }
  a.ccc()

  • 扩展2
function aa() {
    let circle = {
      outerDiameter() {
        let innerDiameter = () => {
          console.log(this);
        };
        innerDiameter();
      }
    };
    circle.outerDiameter();
  }
  let a = {
    ccc: aa
  }
  a.ccc()

this的绑定方式

  • 隐式绑定
var a = {
    str: 'hello',
    sayHi() {
        console.log(this.str)
    }
}
a.sayHi()

因为a调用了sayHi这个方法,所以this指向了a,这种绑定方式是最常用的规则,因为上面举了一些例子,我们这里就不多说了

  • new绑定
function Person(name) {
    this.name = name;
    console.log(this);		// Object { name: "wyh" }
}

var p = new Person('wyh');	//使用new

p是构造函数new(实例化出来的对象),所以this就指向了构造函数调用的时候,实例化出来的对象

  • window绑定
function a(){
    var user = "小明";
    console.log(this.user); //undefined
    console.log(this); //Window
}
a();

这个a是window调用起来的 所以this就指向了window

  • 箭头函数绑定
function User() {
    this.name = 'wyh';

    setTimeout(() => {
        console.log(this.name);		 // wyh
        console.log(this); 			// User { name: "wyh" }
    }, 1000);
}

var user = new User();


箭头函数在自己的作用域内不绑定 this,即没有自己的 this 如果要使用 this ,就会指向定义时所在的作用域的 this 值,在上面这个例子中,箭头函数内的 this 指向定义这个箭头函数时作用域内的 this,也就是 User中的 this,而 User 函数通过 new 绑定,所以 this 实际指向 user 对象。

  • 显示绑定 通过callbind绑定this (这个在下面改变this指向一起展开说明)

如何改变this指向

  • call() apply() bind()可以改变this指向,那么他们之间有什么区别呢
  • 例1
var name = '小张'
age = '18'
var obj = {
   name:'小明',
   Age:this.age,
   myFun:function(){
  console.log(this.name + '年龄'+ this.age)
	}
}
var db = {
    name:'弯弯',
    age:'35'
}

  • obj.myFun() 小明年龄undefined, 这时候this指向obj
  • obj.myFun.call() 小张年龄18 , 这时候call()改变了this指向 指向window,因为没有传入参数
  • obj.myFun.call(db) 弯弯年龄35 , 这时候call()改变了this指向db
  • obj.myFun.apply(db) 弯弯年龄35 ,这时候apply()改变了this指向 db
  • obj.myFun.bind(db)() 弯弯年龄35 ,这时候bind()改变了this指向 db

通过这几个简单的例子我们可以看出call()、apply()、bind()第一个参数都是传入this的指向对象 有一些差别 ,相对于call()、apply()来说 bind返回的是一个新函数 ,需要重新调用才会执行

  • 例2
var name = '小张'
age = '18'
var obj = {
   name:'小明',
   Age:this.age,
   myFun:function(x,y){
  console.log(this.name + '年龄'+ this.age+'性别'+ x + '身高' + y)
	}
}
var db = {
    name:'弯弯',
    age:'35'
}

  • obj.myFun.call(db,'男生','180') 弯弯年龄35性别男生身高180 , 结论: call()可以传多个参数,用逗号隔开
  • obj.myFun.apply(db,['女生','160']) 弯弯年龄35性别女生身高160 , 结论: apply()只能传入两个参数,第二个参数需要将多个参数放入一个数组中然后传入
  • obj.myFun.bind(db,'女生','150')() 弯弯年龄35性别女生身高150 结论: bind()传入的方式和call一样 ,可以直接传入多个参数用逗号隔开,与之不同的是bind返回的是个新函数 需要重新调用