深入理解JavaScript之this的四种绑定

  之前对this的四种绑定不太理解,好在浏览了https://www.cnblogs.com/xiaohuochai/p/5735901.html这篇博文,才得以清晰思路,接下来我再次总结this的四种绑定机制。

1  this的四种绑定机制

  在JavaScript中this共有四种绑定机制:分别是

  • new绑定
  • 显式绑定(包括硬绑定)
  • 隐式绑定
  • 默认绑定

  绑定优先级从上到下

2  默认绑定

  •  在非严格模式下默认绑定指的是,函数独立调用,不添加修饰的函数调用,它绑定的是全局对象
  • 严格模式下,this绑定"undefined"

接下来我们将默认绑定分为几种情况,在这几种情况下发生的一般都是默认绑定

2.1全局作用域中使用this

  我们不在任何函数中使用this,直接在全局作用域中使用this,那么这个this指向的是全局对象

 var a=2;
      console.log(this.a);    //绑定全局对象属性a=2

2.2  函数独立调用时

  我们在全局作用域中单独调用某一个函数,这个函数的this绑定的是全局对象

   function foo() {
         var a=3;
         console.log(this.a);
     }
     var a=2;
     foo();   //值为2,而不是3,代表绑定的是全局对象

2.3  被嵌套的独立函数调用时

  被嵌套的函数独立调用时,this默认绑定的是window对象

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

  虽然bar(...)是嵌套在obj的foo(...)函数中,由于bar(...)是独立调用,而不是方法调用,因此this被绑定到全局对象window

2.4 立即执行函数表达式

  在立即执行函数表达式中的this也会默认绑定全局对象。

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

  foo(...)是obj的属性函数,应该是属于上下文调用,this的绑定是隐式绑定,但是在foo(...)函数中我们执行的程序是IIFE,那么this就会默认绑定全局对象。

2.5  闭包

  我们先回忆一下闭包,闭包是外部函数内部定义了内部函数,在全局作用域(或者是其他的作用域,只要不是外部函数本身的作用域)中调用内部函数,使得可以访问外部函数作用域的功能。

 var a=2;
   function foo() {     //外部函数
      function bar() {      //内部函数
          console.log(this.a)
      }
      return bar();         //返回内部函数
   }
   var obj={
       a:3,
       foo:foo()
   };
   obj.foo;        //2  

  那么在闭包中,我们需要访问到外部函数的作用域,这时我们该怎么做呢?答案是使用  var self=this,在内部函数中使用self代替this。

 var a=2;
        function foo() {     //外部函数
            var self=this;        //self代替this
            function bar() {      //内部函数
                console.log(self.a)       //self代替this
            }
            return bar;         //返回内部函数
        }
        var obj={
            a:3,
            foo:foo
        };
        obj.foo()();        //3

2.6  隐式丢失

  在this的隐式绑定过程中,由于失去了绑定对象,从而应用默认绑定规则。

2.6.1 函数别名的隐式丢失

  在this的隐式绑定过程中,给带有上下文的this函数值起一个别名(创建一个新的对象)

 var a=2;
   function foo() {
    console.log(this.a);
   }
   var obj={
       a:3,
       foo:foo
   };
obj.foo;    //3
   var bar=obj.foo;     //函数别名
   bar();        //2  隐式绑定的过程中发生了丢失

  原本在obj中的foo属性:foo(...)中的this绑定上下文obj对象,但是我们创建一个bar()作为obj.foo的引用,实际上bar()引用的是foo(...)函数的本身,bar()是一个不带任何装饰的函数调用,因此默认函数调用

2.6.2  传入回调函数时,参数的隐式赋值

  在传入回调函数时,参数之间的隐式赋值也会导致this对象丢失从而应用默认绑定

 var a=2;
   function foo() {
    console.log(this.a);
   }
   var obj={
       a:3,
       foo:foo
   };
     function doo(fn) {   //传入obj.foo,传入的实际上只有foo(...),这算是一个独立的函数调用
         fn();
     }
     doo(obj.foo);        //2    obj.foo传值给foo,就跟之前的var fn=obj.foo;

2.6.3  内置函数

  当我们使用Javascript时内置函数时,也会发生this对象的改变,从而应用默认绑定

 var a=2;
    function foo() {
        console.log(this.a);
    }
    var obj={
        a:3,
        foo:foo
    };
    setTimeout(obj.foo,100);

  我们应用了"setTimeout(...)"内置函数,原本foo(...)中的this应该绑定到obj上,但是我们引用了javascript内置函数,“obj.foo”作为参数,导致this应用了默认绑定。

3  隐式绑定

  当调用有上下文对象的函数时,便是隐式绑定。通俗点来说,就是被直接对象所包含的函数调用时,也称为该直接对象的方法调用。

 function foo() {
          console.log(this.a);
      }
      var obj={
          a:2,
          foo:foo     //foo成为obj中的方法
      }
      obj.foo();    //obj对象的foo方法----方法调用,

  函数foo(...)被保存在obj对象中,成为obj对象的一个属性,这时,我们称这个函数(foo)为这个对象(obj)的方法,在最后一行,我们对这个函数进行调用(对象.属性或者说是对象.方法),这就是方法的调用。而这个对象我们称之为上下文对象。

 

3.1  多个嵌套的上下文对象调用

  什么是多个嵌套的上下文对象调用呢,就是在一个对象中引用另一个对象的方法,此时的this会绑定第一的对象还是最后的对象呢?

function foo() {
          console.log(this.a);
      }
      var obj2={
          a:2,
          foo:foo
      };
      var obj1={
          a:3,
          obj2:obj2,
          foo:foo
      };
       obj1.foo();            //3
       obj2.foo();         //2
       obj1.obj2.foo();     //2    嵌套的上下文调用

  从结果可以看出,当发生嵌套的上下文调用时,首先this传入的值是obj2的值,那么就是说发生嵌套的上下文调用时,this的绑定对象是离this最近的那一个。

  注意!!!在这里,var  obj2的定义一定要比  var  obj1的定义位置靠前,因为之前讲了函数的提升,变量(对象)的提升优先级是一样的,当先声明obj1后声明obj2时,会发生错误。

3.2  间接引用

  在一般的程序设计中,函数的间接引用是最容易导致隐式丢失的

ar a=2;
  function foo() {
      console.log(this.a);
  }
  var obj1={
      a:3,
      foo:foo
  };
  var obj2={
      a:4,
  };
  obj1.foo();   //3
  obj2.foo=obj1.foo;   //foo不存在obj2中,this绑定的对象应该是obj1
  obj2.foo();          //4
   var a=2;
  function foo() {
      console.log(this.a);
  }
  var obj1={
      a:3,
      foo:foo
  };
  var obj2={
      a:4,
  };
  obj1.foo();             //3
    (obj2.foo=obj1.foo)();     //2 IIFE中this默认绑定

3.3  其他情况

  在JavaScript内部,obj对象单独有一个内存地址M1,obj的属性foo函数单独有一个内存地址M2,当"obj.foo"时,引擎首先找到M1地址,再从M1地址处调用M2地址,这样foo的this就会绑定到obj上。

  如果单独引用M2地址,就会发生隐式丢失

4  显示绑定

  显示绑定是利用JavaScript中的方法".call(...)、.apply(...)"强制this绑定到指定对象上。对于被调用的函数来说这是间接引用

4.1  call()方法

  call方法是JavaScript中,强制使对象绑定到指定对象上。

var a=3;
 function foo(b) {
     console.log(this.a);
 }
 var obj={
     a:2
 };
 foo(1);    //4
 foo.call(obj,1);         //3  强制foo绑定到obj上

4.2  apply()方法

    apply()方法与call()方法作用相同,不同的是两者除了第一个参数是对象其余的参数不同。具体可看这一篇文章

https://www.cnblogs.com/lengyuehuahun/p/5643625.html

 var a=3;
 function foo(b,c) {
     console.log(this.a+b+c);
 }
 var obj={
     a:2
 };
 foo(1,2);    //3
foo.apply(obj,[1,2]);   //apply()传入的参数必须要放到一个数组中

   尽管显式绑定很厉害,但是还是解决不了隐式丢失的问题。

4.3  硬绑定

  尽管显式绑定无法解决隐式丢失的问题,但是显式绑定的变种-----硬绑定,可以。

  硬绑定是,在一个函数中通过call(...)或者是apply(...)方法将带有this的函数绑定到指定的对象中。

  简而言之就是,将call()或者是apply()方法强制使this指向一个对象这个操作封装起来,调用这个封装函数就行了。

 var a=3;
 function foo() {
     console.log(this.a);
 }
 var obj={
     a:2
 };
   function bind() {     //在bind中this与obj对象绑定到了一起,所以只要调用bing函数,this肯定指向obj
       foo.call(obj);
   };
   bind();      //2
  setTimeout(bind,100);    //2
   bind.call(wind);      //2

  硬绑定是如何解决隐式丢失的呢?答案是通过封装call()、apply()方法将this绑定到指定的对象上,等到调用时,用这个封装函数就行了。如此一来,无论何时,在封装函数中,this永远指向对象。

  在JavaScript中由于对硬绑定的需求是非常大,所以JavaScript中定义了一个内置函数"bind(...)"来代表硬绑定

 function foo(something) {
        console.log(this.a,something);
        return this.a+something;
    }
    var obj={
        a:2
    };
    var bar=foo.bind(obj);
    var b=bar(3);
    console.log(b);               //5

4.4  API调用

  JavaScript中不止有"bind(...)"这一个函数,还有许多内置函数具有显式绑定功能,如数组迭代的五个方法:map(...)、forEach(...)、filter(...)、some(...)、every(...)

 var id='window';
   function foo(el) {
       console.log(el,this.id);
   }
   var obj={
       id:'fn'
   };
   [1,2,3].forEach(foo);  //1|2|3  window
   [1,2,3].forEach(foo,obj);  //1|2|3  fn

  

5  new绑定

  new绑定有太多细节要讲,具体的可以看我之前的文章,现在是简单介绍下new绑定的概念。

  new绑定:函数或者方法调用之前带有关键字"new",它就是构造函数调用,这也就是this绑定

5.1  不带return的new绑定

  当不带return的new构造函数调用时,它会初始化新对象,当构造函数的函数体执行完毕以后,它会显式返回。在这种情况下 ,构造函数的计算值便是"new"创建的新对象的值

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

5.2  带有return 但是却不返回值的new绑定

  当带有return的函数,发生,构造函数调用(new绑定)时,它同上面类型的new绑定是一致的,初始化新对象,执行构造函数体,显示返回,构造函数的计算值是"new"创建的新对象的值。

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

5.3  带有return返回一个对象

  带有return的函数,发生,构造函数调用时,并且显式返回一个对象时,新创建的对象的值等于这个对象。

 var obj1={
        a:3
    }
   function foo() {
       this.a=2;
       return obj1;
   };
   var obj=new foo();
   console.log(obj);     //a:3

5.5  new绑定发生的那些事

  new绑定到底发生了什么导致this对象的改变呢?

  1. 创建一个全新的对象
  2. 这个对象被执行原型链接
  3. 这个对象会被绑定到指定的this
  4. 如果函数中没有返回对象,那么new表达式中的函数会自动返回这个对象

5.6  new绑定的用法

  使用new绑定非常之简单,只需要创建指定的对象,new一个this函数。

  var a=3;
    function foo(a) {
       this.a=a;
    }
         foo(a);
    var bar=new foo(2);    //创建的bar绑定到foo中的this上
    console.log(bar.a);    //2

  注意!!!尽管构造函数看起来很像一个方法调用,它依然会使用这个新对象作为this。也就是说,在表达式new o.m()  中,this不是o而是m()

 

6  优先级

  在优先级上:new>显式绑定>隐式绑定>默认绑定

7  总结

  new------构造函数调用

  显式绑定-----间接调用

  隐式绑定-----方法调用

  默认绑定-----独立调用

猜你喜欢

转载自blog.csdn.net/qq_41889956/article/details/83507528