【javascript】函数中的this的四种绑定形式 — 你们准备好瓜子,我要讲故事啦~~

 
  javascript中的this和函数息息相关,因此今天,我就给你们详细地讲述一番:javascript函数中的this
 
一谈到this,不少让人晕晕乎乎的抽象概念就跑出来了,这里我就只说最核心的一点——函数中的this总指向调用它的对象,接下来的故事都将围绕这一点展开
 
(提醒前排的筒子们准备好茶水和西瓜,我要开始讲故事啦!!)
 
 
【故事】有一个年轻人叫"迪斯"(this),有一天,迪斯不当心穿越到一个叫 “伽瓦斯克利”(javascript)的 异世界,此时此刻迪斯身无分文, 他首先要作的事情就是——找到他的住宿的地方——调用函数的对象
 

 

this的默认绑定

 

【故事——线路1】若是迪斯(this)直到天黑前都没有找到能收留本身的住所,他眼看就要过上非洲难民的生活, 这时候,一位乐善好施的魔法师村长——window救世主通常地出现了:先住在我家吧!
 

 

【正文】
当一个函数没有明确的调用对象的时候,也就是单纯做为独立函数调用的时候,将对函数的this使用默认绑定:绑定到全局的window对象
function fire () {
     console.log(this === window)
}
fire(); // 输出true

 

上面的例子我相信对大多数人都很简单,但有的时候咱们把例子变一下就会具备迷惑性:
function fire () {
  // 我是被定义在函数内部的函数哦!
     function innerFire() {
  console.log(this === window)
      }
     innerFire(); // 独立函数调用
}
fire(); // 输出true

 

 
函数 innerFire在一个外部函数fire里面声明且调用,那么它的this是指向谁呢? 仍然是window
 
许多人可能会顾虑于fire函数的做用域对innerFire的影响,但咱们只要抓住咱们的理论武器——没有明确的调用对象的时候,将对函数的this使用默认绑定:绑定到全局的window对象,即可得正确的答案了
 
下面这个增强版的例子也是一样的输出true
var obj = {
   fire: function () {
       function innerFire() {
          console.log(this === window)
        }
        innerFire();   // 独立函数调用
     }
}
obj.fire(); //输出 true

 

【注意】在这个例子中, obj.fire()的调用实际上使用到了this的隐式绑定,这就是下面我要讲的内容,这个例子我接下来还会继续讲解
 
【总结】 凡事函数做为独立函数调用,不管它的位置在哪里,它的行为表现,都和直接在全局环境中调用无异
 

this的隐式绑定

【故事——线路2】 迪斯(this)穿越来异世界“伽瓦斯克利”(javascript)的时候,恰好身上带了一些钱,因而他找到一个旅馆住宿了下来
 
 
 

 

当函数被一个对象“包含”的时候,咱们称函数的this被隐式绑定到这个对象里面了,这时候,经过this能够直接访问所绑定的对象里面的其余属性,好比下面的a属性
 
var obj = {
     a: 1,
      fire: function () {
           console.log(this.a)
        }
}
obj.fire(); // 输出1

 

 
如今咱们须要对日常司空见惯的的代码操做作一些更深的思考,首先,下面的这两段代码达到的效果是相同的:
// 我是第一段代码
function fire () {
      console.log(this.a)
}
  
var obj = {
      a: 1,
      fire: fire
  }
obj.fire(); // 输出1
 
// 我是第二段代码
var obj = {
        a: 1,
        fire: function () {
             console.log(this.a)
         }
}
obj.fire(); // 输出1

 

 
fire函数并不会由于它被定义在obj对象的内部和外部而有任何区别,也就是说在上述隐式绑定的两种形式下,fire经过this仍是能够访问到obj内的a属性,这告诉咱们:
 
1.  this是动态绑定的,或者说是在代码运行期绑定而不是在书写期
2.  函数于对象的独立性, this的传递丢失问题
 
(下面的描述可能带有我的的情感倾向而显得不太严谨,但这是由于我但愿阅读者尽量地理解我想表达的意思)

隐式绑定下,做为对象属性的函数,对于对象来讲是独立的

 
基于this动态绑定的特色,写在对象内部,做为对象属性的函数,对于这个对象来讲是独立的。(函数并不被这个外部对象所彻底拥有”)
 
我想表达的意思是:在上文中,函数虽然被定义在对象的内部中,但它和“在对象外部声明函数,而后在对象内部经过属性名称的方式取得函数的引用”,这两种方式在性质上是等价的而不只仅是效果上
 
定义在对象内部的函数只是“刚好能够被这个对象调用”而已,而不是“生来就是为这个对象所调用的”
 
借用下面的隐式绑定中的this传递丢失问题来讲明:
var obj = {
      a: 1,    // a是定义在对象obj中的属性   1
      fire: function () {
   console.log(this.a)
        }
      }
 
var a = 2// a是定义在全局环境中的变量    2
var fireInGrobal = obj.fire;  
fireInGrobal(); //  输出 2

 

 
上面这段简单代码的有趣之处在于: 这个于obj中的fire函数的引用( fireInGrobal)在调用的时候,行为表现(输出)彻底看不出来它就是在obj内部定义的其缘由在于:咱们隐式绑定的this丢失了!! 从而 fireInGrobal调用的时候取得的this不是obj,而是window
 
上面的例子稍微变个形式就会变成一个可能困扰咱们的bug:
 
var a = 2;
var obj = {
    a: 1,    // a是定义在对象obj中的属性
    fire: function () {
          console.log(this.a)
     }
}  
function otherFire (fn) {
     fn();
}  
otherFire(obj.fire); // 输出2

 

 
在上面,咱们的关键角色是otherFire函数,它接受一个函数引用做为参数,而后在内部直接调用,但它作的假设是参数fn仍然可以经过this去取得obj内部的a属性,但实际上, this对obj的绑定早已经丢失了,因此输出的是全局的a的值(2),而不是obj内部的a的值(1)
 

在一串对象属性链中,this绑定的是最内层的对象

在隐式绑定中,若是函数调用位置是在一串对象属性链中,this绑定的是最内层的对象。以下所示:
var obj = {
      a: 1,
      obj2: {
           a: 2,
           obj3: {
                a:3,
                getA: function () {
                    console.log(this.a)   
                 }
           }
       }
}
 
obj.obj2.obj3.getA();  // 输出3

 

 

this的显式绑定:(call和bind方法)

【故事——线路3】 迪斯(this)穿越来异世界“伽瓦斯克利”(javascript),通过努力的打拼,积累了必定的财富,因而他买下了本身的房子
 

 

上面咱们提到了this的隐式绑定所存在的this绑定丢失的问题,也就是对于 “ fireInGrobal = obj.fire”
fireInGrobal调用和obj.fire调用的结果是不一样的由于这个函数赋值的过程没法把fire所绑定的this也传递过去。这个时候,call函数就派上用场了

 

call的基本使用方式: fn.call(object)
fn是你调用的函数,object参数是你但愿函数的this所绑定的对象。
fn.call(object)的做用:
1.即刻调用这个函数(fn)
2.调用这个函数的时候函数的this指向object对象
 
例子:
var obj = {
      a: 1,    // a是定义在对象obj中的属性
      fire: function () {
         console.log(this.a)
      }
}
 
var a = 2// a是定义在全局环境中的变量  
var fireInGrobal = obj.fire;
fireInGrobal();   // 输出2
fireInGrobal.call(obj); // 输出1

 

 
本来丢失了与obj绑定的this参数的fireInGrobal再次从新把this绑回到了obj
 
可是,咱们其实不太喜欢这种每次调用都要依赖call的方式,咱们更但愿:可以一次性 返回一个this被永久绑定到obj的fireInGrobal函数,这样咱们就没必要每次调用fireInGrobal都要在尾巴上加上call那么麻烦了。
 
怎么办呢? 聪明的你必定能想到,在fireInGrobal.call(obj)外面包装一个函数不就能够了嘛!
var obj = {
      a: 1,    // a是定义在对象obj中的属性
      fire: function () {
        console.log(this.a)
      }
}
 
var a = 2// a是定义在全局环境中的变量  
var fn = obj.fire;
var fireInGrobal = function () {
    fn.call(obj)   //硬绑定
}
       
fireInGrobal(); // 输出1

 

若是使用bind的话会更加简单
var fireInGrobal = function () {
    fn.call(obj)   //硬绑定
}

 

能够简化为:
var fireInGrobal = fn.bind(obj);

 

call和bind的区别是:在绑定this到对象参数的同时:
 
1.call将当即执行该函数
2.bind不执行函数,只返回一个可供执行的函数
 
【其余】:至于apply,由于除了使用方法,它和call并无太大差异,这里不加赘述
 
在这里,我把显式绑定和隐式绑定下,函数和“包含”函数的对象间的关系比做买房和租房的区别
 

 

由于this的缘故
 
在隐式绑定下:函数和只是暂时住在“包含对象“的旅馆里面,可能过几天就又到另外一家旅馆住了
在显式绑定下:函数将取得在“包含对象“里的永久居住权,一直都会”住在这里“
 

new绑定

【故事】 迪斯(this)组建了本身的家庭,并生下多个孩子(经过构造函数new了许多个对象)
 
 

 

执行new操做的时候,将建立一个新的对象,而且将构造函数的this指向所建立的新对象
 
function foo (a) {
     this.a = a;
}
 
var a1  = new foo (1);
var a2  = new foo (2);
var a3  = new foo (3);
var a4  = new foo (4);
 
console.log(a1.a); // 输出1
console.log(a2.a); // 输出2
console.log(a3.a); // 输出3
console.log(a4.a); // 输出4

 

 
谢谢你们哦!!
 
【完】
 
相关文章
相关标签/搜索