学会四件套,this 没烦恼

2,055 阅读4分钟

任何足够先进的技术都和魔法无异。

前言

this 关键字是 JavaScript 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在 所有函数的作用域中。但是即使是非常有经验的 JavaScript 开发者也很难说清它到底指向什么。所以很多人觉得this会魔法

1.1 误解

我们之后会解释 this 到底是如何工作的,但是首先需要消除一些关于 this的错误认识。

太拘泥于“this”的字面意思就会产生一些误解。有两种常见的对于 this 的解释,但是它

们都是错误的。

1.1.1 指向自身

人们很容易把 this 理解成指向函数自身,这个推断从英语的语法角度来说是说得通的。

function foo(num) {
    console.log("foo:"+num);
    // 记录foo被调用的次数
    this.count++;
} 
foo.count = 0;
var i ;
for(i = 0; i < 10; i++) {
    if(i>5) {
        foo(i);
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo :9
// foo 被调用了多少次
console.log(foo.count);// 0 --?
   

console.log 语句产生了 4 条输出,证明 foo(..) 确实被调用了 4 次,但是 foo.count 仍然是 0。显然 从字面意思来理解 this 是错误的。

1.1.2 它的作用域

第二种常见的误解是,this 指向函数的作用域。

function foo() { 
    var a = 2; 
    this.bar(); 
   } 
   function bar() { 
    console.log( this.a ); 
   } 
   foo(); //TypeError: this.bar is not a function

编写这段代码的开发者还试图使用this连通 foo()bar() 的词法作用域,从而让bar() 可以访问foo()作用域里的变量 a,这是不可能实现的。每当你想要把 this 和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的。

那么 this 到底是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

2.1 this 的四种绑定规则

(1) 默认绑定 独立函数调用 this 指向全局

可以把这条规则看作是无法应用其他规则时的默认规则。

function foo() {

    console.log( this.a ); // this 指向全局
   } 
   var a = 2; 
   foo(); // 2

如果使用严格模式(strict mode),则不能将全局对象用于默认绑定,因此 this 会绑定到undefined

 function foo() {
    "use strict"
    console.log( this.a ); // this 指向全局
   } 
   var a = 2; 
   foo(); // TypeError: Cannot read properties of undefined (reading 'a')

(2) 隐式绑定 对象函数调用 this 指向上下文对象

  //隐式绑定
   function foo(){
       console.log(this.a);  // this.a 等同于 obj.a
   }
   var obj={
       a:2,
       foo:foo  // fool()的声明方式 被当作引用属性添加到对象,严格来说不属于obj对象

   };
   obj.foo() //2
   console.log(obj.a); //2

注意!!!对象属性引用链中只有上一层或者说最后一层在调用位置中起作用

// 隐式绑定
function foo(){
    console.log(this.a);
}
var obj3={
    a:34,
    foo:foo
}
var obj2={
    a:42,
    obj3:obj3
}
var obj1={
    a:2,
    obj2:obj2
}
// 对象属性引用链中只有上一层或者说最后一层在调用位置中起作用
obj1.obj2.obj3.foo();//34
var bar=obj3.foo;//函数别名
var a='oops';
bar();// oops 浏览器全局 node环境 undefined

(3) 显式绑定 直接指定 this 的绑定对象 apply , bind , call

e59e2f5d06be74ee3f43fffefa22834ebe6eb973f9f2cbaf.WEBP

//显式绑定
var a = 1 ;
function foo(){
    console.log(this.a)
}
var obj={
    a:2
}

foo.call(obj);  // 2

var bar=function(){
    foo.call(obj);/// 硬绑定
}
bar();
setTimeout(bar,1000);
bar.call(window);// 硬绑定,不能修改它的this了

(4) new 绑定 this 指向实例化的对象 使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。 1. 创建一个全新的对象。 2. 这个新对象会被执行 [[Prototype]] 连接。 3. 这个新对象会绑定到函数调用的 this。 4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

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

学习了 this 的四种绑定规则,让我们开做个小测试吧!

// 控制台输出什么?
var obj = {
    count : 0,
    cool : function coolfn() {
        if( this.count < 1 ){
            setTimeout(function timer(){
                this.count++;
                console.log( this.count );
            }.bind(this),1000);
        }
    }
}
console.log( obj.count );

你答对了吗?

var obj={
    count:0,
    cool:function coolfn(){
        if(this.count<1){ // 此时的 this 指向obj , this.count为 0
            setTimeout(function timer(){ 
                this.count++ // 1
                console.log(this.count,'----');  // 当前作用域count为1
                console.log('awesome');
            }.bind(this),1000);
        }
    }
}
var count = 4;// 迷惑你的
obj.cool();// 1

3. 总结

0e635082d2f7273b8e11d0978fa068d0de8bce27814c9cb7.WEBP

  1. 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。

             var bar = new foo()
             
    
  2. 函数是否通过 callapply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。

            var bar = foo.call(obj2)
         
    
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。

               obj.foo()
    
  4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。

              var bar =foo()
              
    

当然这四种规则并不是所有情况都适用,还有一些例外,这里就不一 一赘述了。 讨论到这里就结束了啊,欢迎大家多多指教啊!

参考文献

  • 你不知道的 javascript