彻底搞懂this指向

62 阅读5分钟

this的绑定规则

js中函数的this指向是在调用时绑定的,所以this指向就是取决于函数调用的位置。

主要以下几条绑定规则

默认绑定

最常见就是独立函数调用,就是不带任何修饰的函数引用进行调用,结果就是会将this绑定到window上面。这个规则就是其他规则无法应用时使用的规则。

var a=1;

function foo() {  
  console.log(this); // window  
}  
foo();

function bar(){
    console.log(this.a);
}
bar();//调用后会打印1

但是如果是严格模式下,this就会绑定到undefined,这里注意是函数体处于严格模式,而不是调用位置处于严格模式。

function foo() {  
    "use strict";
    
    console.log(this); // undefined
}  
foo();

函数体处于严格模式,此时注意this是window,不是undefined

function foo(){
    console.log(this.a);
};

var a=1;

function bar(){
    "use strict";
    
    foo();
};
bar();//1

在看下面的例子

function bar(){
    console.log(this);//window
}
function foo(){
    bar();
}
foo();

这里bar还是独立函数调用,没有被绑定到任何对象上。

隐式绑定

隐式绑定最常见的就是某个对象发起函数调用,那么函数的this就会被隐式绑定到这个对象上。

var a=2;
function foo(){
    console.log(this.a);//输出1
}

var obj={
    a:1,
    foo:foo
}
obj.foo();

绑定的对象是离函数调用最近的那一个对象,例如

function foo() {  
  console.log(this.a); // obj对象  
}  
  
var obj1 = {  
  a1,  
  foo: foo  
}  
  
var obj2 = {  
  a2,  
  obj1: obj1  
}  
  
obj2.obj1.foo();//输出1

隐式丢失

隐式丢失就是被隐式绑定的函数会丢失绑定的对象,会使用默认绑定。例如

function foo() {  
  console.log(this.a);  
}  
  
var obj = {  
  a1,  
  foo: foo  
}  
var a=2;
  
var bar = obj.foo;  
bar();//输出2

虽然bar引用了obj.foo函数,但是其实只是引用了foo函数,所以此时bar函数的调用就是不带任何修饰的函数调用,相当于foo()调用,因此就是使用了默认绑定,绑定到window对象。

还有另一种形式,如下:

function foo(){
    console.log(this.a);
};

function bar(fn){
    //这里跟上面一样,fn=obj.foo
    fn();
}
var obj={
    a:1,
    foo:foo
};
var a=2;
bar(obj.foo);//打印2

参数传递的时候是一种赋值过程,就如fn=obj.foo,所以这种情况也是跟上面的一样,使用默认绑定

还有当把函数传入内置的函数或第三方库的一些函数时,例如setTimeout,此时结果跟上面一样。

function foo(){
    console.log(this.a);
};

var obj={
    a:1,
    foo:foo
};
var a=2;
setTimeout(obj.foo,200);//2

因为内部都是跟上面的bar函数一样,做独立函数调用。

显式绑定

显示绑定就是通过call,apply,bind将this绑定到传入的对象上

function foo(){
    console.log(this.a);
}

var obj={
    a:1
 };
 var a=3;
 foo.call(obj);//1
 foo.call(window)//3;

在js中还有一些内置函数,提供一个可选的参数,用于指定你传入的回调函数的this。例如forEach

function foo(){
    console.log(this.a);
};
var obj={
    a:1
}
var a=2;
[1,2,3].forEach(foo,obj);//1 1 1

函数内部就是通过call或apply进行显示绑定。

new绑定

new绑定就是使用new关键字来调用函数,new的过程中会发生如下操作。

  1. 创建一个新的对象。
  2. 新对象的__proto__属性会指向构造函数的prototype。
  3. 新对象绑定为函数的this。
  4. 如果函数没有返回其他对象,那么就会返回该新对象。

new的模拟过程,可以看这篇,new操作符原理

function foo(a){
    this.a=a;
};

var bar=new foo(1);
console.log(bar.a);//1

规则优先级

当调用的位置应用了多条规则,它会怎么绑定?此时这些规则就需要有优先级。当然,毫无疑问就是默认绑定的优先级是最低的。

1.隐式绑定和显示绑定的优先级

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

很明显看到显示绑定的优先级更高。

2.new绑定和隐式绑定的优先级比较

function foo(a){
    this.a=a;
};

var obj={
    a:1,
    foo:foo
};

var bar=new obj.foo(2);
console.log(bar.a)//2

可以看到new绑定比隐式绑定优先级更高。

3.new绑定和显示绑定的优先级 new和call,apply无法一起使用,如,无法通过new foo.call(obj),但可以先通过bind后再new调用。

function foo(a){
    this.a=a;
};

var obj={
    a:1
 };
 
 const bar=foo.bind(obj);
 
 const c=new bar(4);
 console.log(c.a);//4

因此new绑定比显示绑定优先级更高。

所以优先级从高到低为:new绑定,显示绑定,隐式绑定,默认绑定。

规则之外

被忽略的this

当你把null或undefined作为显示绑定的this值时,这个值会被忽略,而使用默认绑定规则

function foo(){
    console.log(this.a);
};
var a=1;

foo.call(null);//1

间接引用

这种情况就是有时候会创建严格函数的间接引用,此时调用应用的是默认绑定规则。主要发生再赋值的时候。

function foo(){
    console.log(this.a);
};
var a=2;
var obj1={
    a:1,
    foo:foo
};
var obj2={
    a:3
};
(obj2.foo=obj1.foo)();//2

这个obj2.foo=obj1.foo赋值表达式会返回foo函数的引用,实际就是(foo)();所以这里会使用默认绑定。

箭头函数

箭头函数不使用四种规则,而是根据外层作用域来决定this。

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

注意是寻找外层(函数或者全局)作用域,这里的obj没有作用域,所以只能是最外层的window,如果obj包含在一个函数中,那么箭头函数就会找到这个函数的this。

箭头函数并不会绑定this,而是会往外层作用域寻找this。而且箭头函数的调用call,apply来修改this是不行的。

function foo(){

    return ()=>{//this会是foo这个函数的this
        console.log(this.a);
    };
};
var obj={
    a:1
};
var obj2={
    a:2
};

var bar=foo.call(obj);//foo函数的this指向改成obj对象
bar.call(obj2);//输出的是1,而不是2

总结

总之,要判断this指向,需要找到这个函数的调用位置,然后按四条规则进行匹配,除了一些不满足规则的需要另外查看。