一文看懂函数中this的绑定规则

79 阅读4分钟

调用位置

this 是在函数被调用时才会绑定的,所以要确定函数中this绑定,我们首先需要找到函数的调用位置
寻找函数调用时位置,就是寻找到“函数是在哪个地方被调用”,但做起来实际没有那么容易,因为某些编程模式可能会隐藏掉真正的调用位置。
我们可以通过分析调用栈 来寻找调用位置,调用位置就在当前执行函数的前一个调用函数中。

  • 调用栈:就是为了达到当前执行位置所调用的所有函数
function a{
  b()
}
function b{
  // c的调用位置
  c()
}
function c{
  //通过a调用到c的调用栈是a => b => c
  
}

a();// a函数的调用位置,

a 的调用位置是在最后,通过 a => b => c 的顺序调用到c函数,所以c的调用栈是 a => b => c,b是c前调用的函数,所以可以得到 b 函数内部就是 c 函数调用位置

小结

你可以把栈想象成一个函数的调用链,就像我们前面代码那样,但这种方法纯看个人思路,容易出错。
开发中我们可以通过浏览器调试工具来打印调用栈,打上断点就会展示到此处之前,函数调用的列表,然后找到倒数第二个就是真正的调用位置。

绑定规则

找到调用位置后,我们还要分析,是以何种方式调用的函数,就可以确认函数中this的指向。

  • 绑定有四条原则

默认绑定,隐式绑定,显式绑定,new绑定。

  • 优先级 new绑定>显式>隐式>默认

默认绑定

函数被直接调用,不符合其他三条绑定规则则会默认绑定,非严格模式下this指向全局对象,严格模式下指向undefined

function foo( ){
  console.log( this.a )
}
var a =1 ;
foo( );//1
function foo( ){
"use strict"
  console.log( this.a )
}
var a =1 ;
foo( );// TypeError: this is undefined

隐式绑定

调用位置有上下文对象,或者说调用函数被某个对象拥有或者包裹。引用到了对象的属性上,然后通过对象调用。 this会指向这个对象

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

obj的foo属性,指向了foo函数,通过obj.foo()调用,this指向了obj。

  • 隐式丢失

但这里要注意,隐式丢失的情况,就是因为obj.foo其实也指向foo函数的引用,这个引用如果被赋值给另一个变量,再通过该变量调用,那就相当于跳过了obj,直接调用了,所以会造成隐式丢失。

function foo( ){
  console.log( this.a )
}
var obj={
   a:1,
  foo:foo
} ;
var b=obj.foo;
b( ); // TypeError: this is undefined

以上,相当于全局直接调用foo函数,所以是默认绑定。 注意,函数作为参数传参给别的函数,也是一种隐式赋值,一样会发生隐式丢失的情况。

显式绑定

通过,apply(...),call(...) ,bind( )硬绑定,修改this指向

function foo( ){
  console.log( this.a )
}
var obj={
   a:1
} ;
foo.call(obj); //1
foo.apply(obj); //1

var bar=foo.bind(obj);
bar(3);//1

bind其实就是基于包裹了一层函数,内部再通过apply实现的硬绑定,解决上面说的this丢失问题,这章暂不细纠。

以上代码都将,foo中this指向了obj。

new

通过new的方式来调用函数,会创建一个新的实例对象,构造函数中的this也会指向这个新创建的对象。 new的方式改变this指向的优先级式最高的。

function foo( a){
  this.a=a;
}
var b= new foo(2);
console.log(b.a);//2

判断this

现在我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则,可以按下面顺序进行判断:

  1. 是否new调用?是的话,this绑定的是新创建的对象(new绑定
  var a= new b();

2.函数是否通过call,apply,bind调用?有的话,this绑定的是传入的第一个值 (显式绑定

 var a= b.call(obj);

3.函数是否通过某个对象的属性值调用? (隐式绑定

 var a= obj.b();

4.如果不是以上就是默认绑定, 非严格模式下绑定全局对象,严格模式就绑定undefined (默认绑定

 var a= b();

总结

以上,我们知道了确认this绑定,需要知道调用位置,再按优先级从高到低,ew绑定>显式>隐式>默认,看匹配哪条规则,就能明确this指向了。

但还是有些绑定列外的情况,会再另起一篇文章分析