javascript:this 关键字

前言

看过[阮一峰]()的关于 this 的教程,讲了不少比较好的例子,但没有对其本质的东西解释清楚,并且部分例证存在问题;因而,打算重写本章节,从this的本质入手;javascript

本文为做者的原创做品,转载需注明出处;html

this 是什么?

this能够理解为一个指针,指向调用对象;java

判断 this 是什么的四个法则

官网定义编程

先来看第一段官方的解释,windows

In JavaScript, as in most object-oriented programming languages, this is a special keyword that is used within methods to refer to the object on which a method is being invoked. The value of this is determined using a simple series of steps:数组

  1. If the function is invoked using Function.call or Function.apply, this will be set to the first argument passed to call/apply. If the first argument passed to call/apply is null or undefined, this will refer to the global object (which is the window object in Web browsers).
  2. If the function being invoked was created using Function.bind, this will be the first argument that was passed to bind at the time the function was created.
  3. If the function is being invoked as a method of an object, this will refer to that object.
  4. Otherwise, the function is being invoked as a standalone function not attached to any object, and this will refer to the global object.

大体翻译以下,
this是这么一个特殊的关键字,它是用来指向一个当前正在被调用( a being invoked )方法的调用对象的;( 等等,这句话其实隐藏了一个很是关键的信息,那就是this是在运行期 生效的,怎么生效的?在运行期this被赋值,将某个对象赋值给this,与声明期无关,也就是说,this是运行期相关的 );this的赋值场景,概括起来,分为以下四种状况,浏览器

  1. 若是方法是被Function.call或者Function.apply调用执行.... bla..bla..
    参考 function prototype call 小节
  2. 若是是被Function.bind... bla...bla
    参考 function prototype bind 小节
  3. 若是某个方法在运行期是被一个对象( an object )调用( 备注:这里为了便于理解,我针对这种状况,本身给起了个名称叫关联调用 ),在运行期,会将该 object 的引用赋值给该方法的this
    备注:被一个对象调用?何解?其实就是指语句obj.func(),这个时候,func()方法内部的this将会被赋值为obj对象的引用,也就是指向obj
  4. 若是该方法在运行期被当作一个没有依附在任何 object 上的一个独立方法被调用(is being invoked as a standalone function not attached to any object ),那么该方法内部的this将会被赋值为全局对象(在浏览器端就是 windows )
    独立方法 ( standalone function )?在运行期,若是func方法被obj关联调用的,既是经过obj.func()的方式,那么它就不是standalone的;若是是直接被调用,没有任何对象关联,既是经过func()调用,那么这就是standalone的。

this 运行期相关

官网定义 2闭包

再来看另一句很是精炼的描述,来加深理解app

The this keyword is relative to the execution context, not the declaration context.

this关键字与运行环境有关而与声明环境无关;(补充,而做用域链闭包是在函数的声明期建立的,参考建立时机)函数

补充,是如何与函数的运行期相关的,参考this 指针运行时赋值

个人补充

法则 #3 和 #4,大多数状况都很是容易理解,有几种状况须要特别注意,

  1. 函数嵌套
    须要注意的是object对象中的函数内部再次嵌套函数的状况,

    var name = "windows";  
          
    var obj = {  
    
        name:"object",  
    
        f1:function(){  
    
          console.log("this: "+this.name)
    
          function f2(){  
    
                console.log("this: " + this.name)
          }
    
          f2();  
        }  
    };

    执行

    > obj.f1();
     this: object
     this: windows

    能够看到,在运行期,被调用函数 f1() 中的this指向 obj_,而被调用函数 _f2() 中的this指向的是 windows ( global object );由于 f1 函数在当前的运行时中是经过 obj.f1() 进行的关联调用,因此,根据定义 #3,在当前的运行期间,_f1()_ 内部的 this 是指向 obj 对象的( 经过将 obj 的引用直接赋值给 this ),而, f2 函数在运行期是没有与其它 object 进行关联调用,因此,在当前的运行时期,_f2_ 是一个 standalone 的函数,因此,根据定义 #4,在当前的运行期间,_f2()_ 的内部this是指向 windows 的。(注意,这里我反复强调当前运行期间,是由于this是在运行时被赋值的,因此,要特别注意的是,即便某个函数的定义不变,但在不一样的执行环境(运行环境)中,this是会发生变化;)

  2. 回调函数
    参看函数回调场景-1函数回调场景-2
  3. 函数赋值
    参看将函数赋值-standalone以及相关变种章节

可见,要判断this运行期到底指的是什么,并无那么容易,可是,只要紧紧的把握好两点,就能够迎刃而解,

  • this运行期相关的
    更确切的说,this是在运行期被赋值的,因此,它的值是在运行期动态肯定的。
  • this是否与其它对象关联调用
    这里的关联调用指的是 javascript 的一种语法,既是调用语句显式的写为obj.func(),另外须要注意的是,_javascript_ 方法的调用不会隐式的隐含 this。只要没有显式的关联调用,那么就是standalone的调用,就符合法则 #4,因此,this指向 _Global Object_。

this 的 Object

注意,this定义中所指的Object指的是 javascriptObject 类型,既是经过

var o1 = {};
var o2  = new Object();
var o3 = Object.create(Object.prototype);

这样的方式构建出来的对象;

备注,最开始,本身有个思惟的误区,认为既然 javascript 一切皆为对象,那么this指针是指向对象的,那么是否是也能够指向FunctionNumber等对象?答案是否认的。

起初,我是按照上面的逻辑来理解的,直到当我总结到bind 是如何实现的小节后,发现Function对象在调用方法属性bind的时候,bind方法内部的this指向的是Function,这才恍然大悟,thisObject其实是能够指向任何 javascript Object的,包括 Object_、_Function 等。

this 是变化的

咱们来看这样一个例子,

var C = "王麻子";

var A = {
  name: '张三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

var B = {
  name: '李四'
};

// 执行,
> A.describe();
  '张三'

> B.describe = A.describe;
> B.describe()
  '李四'

> var describe = A.describe;
> describe();
  '王麻子'

能够看到,虽然 A.describe 方法的定义不变,可是其运行时环境发生了变化,_this_ 的指向也就发生了变化。

> B.describe = A.describe;
> B.describe()
  '李四'

在运行时,至关于运行的是 B 的 describe 方法

> var describe = A.describe;
> describe();
  '王麻子'

在运行时,至关于运行的是 windows 的 describe 方法

方法调用没有隐含 this

常常写 Java 代码的缘由,常常会习惯性的认为只要在对象方法里面调用某个方法或者属性,隐含了 this,好比

public class Person{

  String name;

  public String getName(){
    return name;
  }

  public String getName2(){
    return this.name;
  }

}

而 Javascript 实际上并无这种隐含的表达方式;详细验证过程参考将函数赋值-standalone

关联调用 - 容易混淆的场景

this 是什么章节中,为了方便对 #3 进行描述,我起了个名字叫作 关联调用 ;那么有些状况看似是 _关联调用_,实则否则;

咱们有一个标准的对象,定义以下,

var name = "windows";
var obj = {
  name: "obj",
  foo: function () {
    console.log("this: "+ this.name);
  }
};

经过标准的 关联调用 的方式,咱们进行以下的调用,

> obj.foo() 
  'this: obj'

根据法则 #3 既 关联调用 的定义,获得 this -> obj_;若是事事都如此的简单,如此的标准,那可就行了,总会有些让人费解的状况,如今来看看以下的一些特殊的例子,加深对 _关联调用 的理解。

将函数赋值 - standalone

> var fooo = obj.foo
> fooo();
  'this: windows'

输出的 windows_,既是 _this -> global object_,而不是咱们指望的 _obj_;为何?缘由是,_obj.foo 实际上是 foo 函数的函数地址,经过 var fooo = obj.foo 将该函数的地址赋给了变量 _fooo_,那么当执行

> fooo();

的时候,fooo() 执行的是是一个standalone的方法,根据法则 #4,因此该方法内部的this指向的是 Global Object_;注意,_obj.foo 表示函数 foo 的入口地址,因此,变量 fooo 等价与 foo 函数。

备注:因为受到写 Java 代码习惯的缘由,很容易将这里解释为默认执行的是this.fooo(),_fooo()_ 的调用隐含了this,所以就会想到,因为this指向的 Global Object_,因此这里固然返回的就是this: windows;可是,这样解释,是不对的,由于 _Javascript 压根没有这种隐含this的概念,参看用例,

var name = "windows";

var o = {

  name : "o",

  f2 : function(){
      console.log( "o -> f2");
      console.log( "this: "this.name );
  },

  f : function(){

      console.log("f.this -> " + this.name);

      var f2 = function(){
          console.log( "f -> f2");
          console.log( this.name );
      }

      f2(); // f -> f2

      this.f2(); // o -> f2

  }

}

能够看到,在 o.f() 函数中,若是 f2() 的调用隐含了this,那么 f2()this.f2() 二者调用应该是等价的;可是,在实际执行过程当中,_f2()_ 和 this.f2() 执行的是两个大相径庭的方法,所以 f2()this.f2()_,因此 _f2() 并无隐示的表示为 _this.f2()_;

将函数赋值变种 - 匿名 standalone 函数当即执行

> (obj.foo = obj.foo)() 
  'this: windows'

首先,当即执行 foo 函数,而后将 foo 函数赋值给对象 obj 对象的 foo 属性;等价于执行以下的代码,

var name = "windows";    
var obj = { name : "obj" };
(obj.foo = function () {
  console.log("this: " + this.name);
})();

输出,

'this: windows'

能够看到,_this_ -> _global object_,这里为何指向的是 _global object_?其实这里的当即执行过程,就是执行的以下代码,

(function () {
  console.log("this: " + this.name);
}());

由此能够看出,实际上进行一个匿名函数的当即执行;也就是说执行过程当中并无使用 关联调用_,而是一次 _standalone 函数的自身调用,因此根据法则 #4,_this_ -> _global object_。执行完之后,将该匿名函数赋值给 _obj.foo_。

再次执行,

> obj.foo();
 'this: obj'

此次执行的过程是一次标准的 关联调用 过程,因此根据法则 #3,_this_ -> _obj_。

做为判断条件 - 匿名函数当即执行

> (false || obj.foo)() 
  'windows'

等价于执行,

(false || function () {
  console.log("this: " + this.name);
})()

原理和函数赋值变种-匿名 standalone 函数当即执行 一致,等价于当即执行以下的匿名函数

(function () {
  console.log("this: " + this.name);
})()

其实,把这个例子再作一个细微的更改,其中逻辑就看得更清楚了,为 foo 函数添加一个返回值 return true

var name = "windows";
var obj ={
  name: "obj",
  foo: function () {
    console.log("this: "+ this.name);
    return true;
  }
};

再次执行,

> (false || obj.foo)() 
  'windows'
  true

可见,_obj.foo_ 函数执行之后,返回 _true_。上述代码其实等价于执行以下的代码,

(false || function () {
  console.log("this: " + this.name);
  return true;
})()

函数回调场景 0 - 基本原理

var counter = {
  count: 0,
  inc: function () {
    'use strict';
    this.count++;
  }
};

function callIt(callback) {
  callback();
}

> callIt(counter.inc)
  TypeError: Cannot read property 'count' of undefined

能够看到,把一个定义有this关键字的函数做为其它函数的回调函数,是危险的,由于this运行期会被从新赋值,上述例子很直观的描述了这一点,之因此报错,是由于this指向了 _Global Object_。要解决这样的问题,可使用bind,调用的时候改成

> callIt(counter.inc.bind(counter))
  1

函数回调场景 1 - setTimeout

var name = "Bob";  
var nameObj ={  
    name : "Tom",  
    showName : function(){  
        console.log(this.name);  
    },  
    waitShowName : function(){  
        setTimeout(this.showName, 1000);  
    }  
};  

// 执行,

> nameObj.waitShowName();
  'Tom'
  undefined

setTimeout(this.showName, 1000);nameObj.showName 函数做为回调函数参数传递给 setTimeout_;那么为何当 _setTimeout 执行回调的时候,_nameObj.showName_ 方法返回的是 undefined 呢?为何不是返回全局对象对应的 name Bob_?缘由只有一个,那就是 _setTimeout 有本身的 this 对象,而它没有 name 属性,而在回调 showName 函数的时候,_showName_ 函数中的 this 正是 setTimeout 上下文中的 this_,而该 _this 并无定义 name 属性,因此这里返回 _undefined_。

函数回调场景 2 - DOM 对象

var o = new Object();

o.f = function () {
  console.log(this === o);
}

o.f() // true,获得指望的结果 this -> o

可是,若是将f方法指定给某个click事件,this的指向发生了改变,

$('#button').on('click', o.f);

点击按钮之后,返回的是false,是由于在执行过程当中,this再也不指向对象o了而改成指向了按钮的DOM对象了;Sounds Good,但问题是,怎么被改动的?看了一下 jQuery 的源码,_event.js_,摘录重要的片断以下,

function on( elem, types, selector, data, fn, one ) {
  .......
  if ( one === 1 ) {
    origFn = fn;
    fn = function( event ) {

      // Can use an empty set, since event contains the info
      jQuery().off( event );
      return origFn.apply( this, arguments );
    };

    // Use same guid so caller can remove using origFn
    fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
  }
  .......
}

o.f 函数的地址赋值给 fn 参数,_fn_ -> origFn_,最后是经过origFn.apply( this, arguments );来调用 _o.f 函数的,而这里的 this 就是当前的 DOM 对象,既是这个按钮 button_;经过这样的方式,在执行过程当中,经过回调函数 _$("button").on(...) 成功的将新的 this 对象 button 注入了 o.f 函数。那么如何解决呢?参看function.prototype.apply())
的小节#3,动态绑定回调函数。

函数回调场景 3 - 数组对象方法的回调

var obj = {
  name: '张三',
  times: [1, 2, 3],
  print: function () {
    this.times.forEach(function (n) {
      console.log(this.name);
    });
  }
};

> obj.print();
  'undefined'
  'undefined'
  'undefined'

这里咱们指望的是,依次根据数组 times 的长度,输出 obj.name 三次,可是实际运行结果是,数组虽然循环了三次,可是每次输出都是 _undefined_,那是由于匿名函数

function(n){
  console.log(this.name);
}

做为数组 times 的方法 forEach 的回调函数执行,在 forEach 方法内部该匿名函数必然是做为 standalone 方法执行的,因此,this指向了 _Global Object_;

进一步,为何“在 forEach 方法内部该匿名函数必然是做为 standalone 方法执行的”?为何必然是做为 standalone 方法执行?是由于不能在 forEach 函数中使用 this.fn() 的方式来调用该匿名回调函数( fn 做为参数引用该匿名回调函数 ),由于若是这样作,在运行时期会报错,由于在 forEach 函数的 this 对象中找不到 fn 这样的属性,而该 this 对象指向的是 obj.times 数组对象。所以,获得结论“在 forEach 方法内部该匿名函数必然是做为 standalone 方法执行的”

解决办法,使用 bind

obj.print = function () {
  this.times.forEach(function (n) {
    console.log(this.name);
  }.bind(this));
};

> obj.print()
  '张三'
  '张三'
  '张三'

obj 对象做为 this 绑定到该匿名函数上,而后再做为回调函数参数传递给 forEach 函数,这样,在 forEach 函数中,用 standalone 的方式调用 fn 的时候,_fn_ 中的 this 指向的就是数组对象 obj 对象,这样,咱们就能顺利的输出 obj.name 了。

绑定 this

有上述描述可知,this的值在运行时根据不一样上下文环境有不一样的值,所以咱们说this的值是变化的,这就给咱们的编程带来了麻烦,有时候,咱们指望,获得一个固定的this。Javascript 提供了callapply以及bind这三个方法,来固定this的指向;这三个方法存储在 function.prototype 域中,

function.prototype.call()

总结起来,就是解决函数在调用的时候,如何解决this动态变化的问题。

调用格式,

func.call(thisValue, arg1, arg2, ...)

第一个参数是在运行时用来赋值给 func 函数内部的 this 的。

经过f.call(obj)的方式调用函数,在运行时,将 obj 赋值给 _this_;

var obj = {};

var f = function () {
  return this;
};

f() === this // true
f.call(obj) === obj // true

call方法的参数是一个对象,若是参数为 空_、_null 或者 _undefined_,则使用默认的全局对象;

var n = 123;
var obj = { n: 456 };

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

> a.call() 
  123
> a.call(null) 
  123
> a.call(undefined) 
  123
> a.call(window) 
  123
> a.call(obj) 
  456

若是call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,而后赋值给 this

var f = function () {
  return this;
};

> f.call(5)
  [Number: 5]

call方法能够接受多个参数,第一个参数就是赋值给 this 的对象,

var obj = {
    name : 'obj'
}

function add(a, b) {
  console.log(this.name);
  return a + b;
}

> add.call(obj, 1, 2) 
  obj
  3

call方法能够调用对象的原生方法;

var obj = {};
obj.hasOwnProperty('toString') // false

// “覆盖”掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
  return true;
};
obj.hasOwnProperty('toString') // true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false

方法 hasOwnProperty 是对象 objObject.prototype 中继承的方法,若是一旦被覆盖,就不会获得正确的结果,那么,咱们可使用call的方式调用原生方法,将 obj 做为 this 在运行时调用,这样,变通的,咱们就能够调用 obj 对象所继承的原生方法了。

function.prototype.apply()

总结起来,和call同样,就是解决函数在调用的时候,如何解决this动态变化的问题。

apply方法的做用与 call方法相似,也是改变 this指向,而后再调用该函数。惟一的区别就是,它接收一个数组做为函数执行时的参数,使用格式以下。
func.apply(thisValue, [arg1, arg2, ...])
apply方法的第一个参数也是 this所要指向的那个对象,若是设为null或undefined,则等同于指定全局对象。第二个参数则是一个数组,该数组的全部成员依次做为参数,传入原函数。

原函数的参数,在call方法中必须一个个添加,可是在apply方法中,必须以数组形式添加

function f(x,y){
  console.log(x+y);
}

f.call(null,1,1) // 2
f.apply(null,[1,1]) // 2
  1. 找出数组最大的元素

    var a = [10, 2, 4, 15, 9];
    Math.max.apply(null, a)
    // 15
  2. 将数组的空元素变为 undefined

    Array.apply(null, ["a",,"b"])
    // [ 'a', undefined, 'b' ]

    空元素undefined的差异在于,数组的forEach方法会跳过空元素,可是不会跳过undefined。所以,遍历内部元素的时候,会获得不一样的结果。

    var a = ['a', , 'b'];
    
     function print(i) {
       console.log(i);
     }
    
     a.forEach(print)
     // a
     // b
    
     Array.apply(null, a).forEach(print)
     // a
     // undefined
     // b
  3. 绑定回调函数的对象
    函数回调场景-2咱们看到this被动态的更改成了 DOM 对象 _button_,这每每不是咱们所指望的,因此,咱们能够再次绑定回调函数来固定this,以下,

    var o = new Object();
    
     o.f = function () {
       console.log(this === o);
     }
    
     var f = function (){
       o.f.apply(o);
       // 或者 o.f.call(o);
     };
    
     $('#button').on('click', f);

    这样,咱们用 f 函数封装原来的回调函数 o.f_,并使用apply方法固定住this,使其永远指向 _object o,这样,就达到了this不被动态修改的目的。

function.prototype.bind()

总结起来,其实就是在把函数做为参数传递的时候,如何解决this动态变化的问题。

解决的问题

在认识关联调用 - 容易混淆的场景中,咱们浓墨重彩的描述了将函数赋值之后,致使this在运行期发生变化的种种场景,并且在编程过程中,也是很是容易致使问题的场景;那么有没有这么一种机制,即使是在函数赋值后,在运行期依然可以保护并固定住个人this?答案是有的,那就是bind。下面,咱们来看一个例子,

var d = new Date();
d.getTime() // 1481869925657

咱们使用语句 d.getTime() 经过对象 d 关联调用函数 getTime()_,根据法则 #3,函数 _getTime() 内部的 this 指向的是对象 d_,而后从 _d 对象中成功获取到了时间。可是,咱们稍加改动,将对象 d 中的函数 getTime 赋值给另一个变量,在执行呢?

var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

Wow~, 画风突变,得不到时间了,并且还抛出了一个程序异常,好玩,你的程序所以崩溃.. 这就是this在执行期动态变化所致使的,当咱们将函数 d.getTime 赋值给 print_,而后语句 _print() 表示将函数 getTime 做为 standalone 的函数在运行期调用,因此,内部的this发生变化,指向了 _Global Object_,也所以,咱们得不到时间了,但咱们获得一个意想不到的异常..

Ok, 别怕,孩子,bind登场了,

var print = d.getTime.bind(d);
print() // 148186992565

赋值过程当中_,将函数经过bind语法绑定this对象 _d 之后,再赋值给一个新的变量;这样,即使 print() 再次做为 standalone 的函数在运行期调用,this的指向也再也不发生变化,而是固定的指向了对象 _d_。

bind 是如何实现的

if(!('bind' in Function.prototype)){
  Function.prototype.bind = function(){
    var fn = this; // 当前调用 bind 的当前对象 fn ( fn.bind(..) )
    var context = arguments[0]; // 用来绑定 this 对象的参数
    var args = Array.prototype.slice.call(arguments, 1);
    var fnbound = function(){
      return fn.apply(context, args);
    }
    return fnbound;
  }
}

Function对象的prototype原型中新增一个属性bind,该bind是一个 function 函数;这里要特别特别注意,每次bind调用之后,返回的是一个新的function,

var fnbound = function(){
      return fn.apply(context, args);
    }
    return fnbound;

经过 fnbound 函数套一层原函数 fn 做为闭包,而后返回这个新的 function _fnbound_;大部分教程就是这样介绍即止了;其实,我想问的是,为何bind要这么设计,直接返回fn.apply(context, args);不是挺好吗?为何还要在外面套一层新函数 _fnbound_?Ok,这里我就来试图解释下缘由吧;

采用反证法,若是,咱们不套这么一层新函数 _fubound_,看看,会怎样?因而,咱们获得以下的实现,

if(!('bind' in Function.prototype)){
  Function.prototype.bind = function(){
    var fn = this; // 当前调用 bind 的当前对象 fn ( fn.bind(..) )
    var context = arguments[0]; // 用来绑定 this 对象的参数
    var args = Array.prototype.slice.call(arguments, 1);
    return fn.apply(context, args);
  }
}

直接返回fn.apply(context, args),oh,顿时,我明白了,fn.apply(...)这是一条执行命令啊,它会当即执行 fn_,将 _fn 执行的结果返回.. 而咱们这里的bind的初衷只是扩充 fn 函数的行为(既绑定this对象),而后返回一个函数的引用,而正式由于咱们没法在绑定之后,直接返回原有函数的引用,因此,这里,咱们才须要建立一个新的函数并返回这个新的函数的引用,已达到bind的设计目的。Ok,这下总算是清楚了。

特性

绑定匿名函数
obj.print = function () {
  this.times.forEach(function (n) {
    console.log(this.name);
  }.bind(this));
};

可见,咱们能够直接改匿名函数执行bind,而后在将其赋值给某个对象;更详细的用例参考函数回调场景 3 - 数组对象方法的回调

做为函数直接调用
var altwrite = document.write;
altwrite("hello");

在浏览器运行这个例子,获得错误Uncaught ReferenceError: alwrite is not defined,这个错误并无真正保留底层的缘由,真正的缘由是,_document_ 对象的 write 函数再执行的时候,内部this指向了 Global Object

为了解决上述问题,咱们能够bind document 对象,

altwrite.bind(document)("hello")

注意这里的写法,altwrite.bind(document)返回的是一个Function,因此能够直接跟参数调用。

绑定函数参数

除了绑定this对象意外,还能够绑定函数中的参数,看以下的例子,

var add = function (x, y) {
  return x * this.m + y * this.n;
}

var obj = {
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);

newAdd(5);
// 20

add.bind(obj, 5);除了绑定 add 函数的this对象为 obj 之外,将其固定obj 之外,还绑定了 add 函数的第一个参数 x_,并将其固定为 _5_;这样,获得的 _newAdd 函数只能接收一个参数,那就是 y 了,由于 x 已经被bind绑定且固定了,因此能够看到,随后执行的语句newAdd(5)传递的其实是 y 参数。

若绑定 null 或者 undefined

若是bind方法的第一个参数是 null 或 _undefined_,等于将this绑定到全局对象,函数运行时this指向 _Global Object_。

var name = 'windows';

function add(x, y) {
  console.log(this.name);
  return x + y;
}

var plus = add.bind(null, 5); // 绑定了 x 参数

> plus(10) // 赋值的是 y 参数,因而执行的是 5 + 10
  'windows'
  15
改写原生方法的使用方式

首先,

> [1, 2, 3].push(4)
  4 // 输出新增后数组的长度

等价于

Array.prototype.push.call([1, 2, 3], 4)

第一个参数 [1, 2, 3] 绑定 push 函数的this关键字,第二个参数 _4_,是须要被添加的值。

补充一下

为何说这里是等价的?咱们来解读一下

> [1, 2, 3].push(4)
  4 // 输出新增后数组的长度

的执行过程,_[1, 2, 3]_ 做为数组对象,调用其原型中的 Array.prototype.push 方法,很明显,采用的是关联调用,所以 push 函数内部的 this 指向的是数组对象 _[1, 2, 3]_;而这里,咱们经过

Array.prototype.push.call([1, 2, 3], 4)

这样的调用方式,只是换汤不换药,一样是执行的数组中的原型方法 _push_,只是this的传递方式不一样而已,这里是经过bind直接将this赋值为数组对象 _[1, 2, 3]_,而不是经过以前的关联调用;因此,两种调用方式是等价的。

补充完毕

再次,

call 方法调用的是 Function 对象的原型方法既 Function.prototype.call(...)_,那么咱们再来将它 _bind 一下,看看会有什么结果

> var push = Function.prototype.call.bind(Array.prototype.push);

> push([1, 2, 3], 4);
  4 // 返回数组长度

// 或者写为

> var a = [1, 2, 3];
> push(a, 4);
  4
> a
  [1, 2, 3, 4]

咱们获得了一个具有数组 push 操做的一个新的函数 push(...) ( 注: bind 每次回返回一个新的函数 );

那是为何呢?

能够看到,背后的核心是,

push([1, 2, 3], 4);

等价于执行

Array.prototype.push.call([1, 2, 3], 4)

因此,咱们得证实Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4)Array.prototype.push.call([1, 2, 3], 4)两个函数的执行过程是等价的( 注意,为何比较的是执行过程等价,由于call函数是当即执行的,而bind返回的是一个函数引用,因此必须比较二者的执行过程 );其实,要证实这个问题,最直接方法就是去查看函数Function.prototype.call的源码,惋惜,我在官网 MDN Function.prototype.call() 上面也没有看到源码;那么这里,其实能够作一些推理,

Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4)

经过bind,这里返回一个新的 call 函数,该函数绑定了 Array.prototype.push Function 对象作为其this对象;那么Function.prototype.call函数内部会怎么执行呢?我猜测应该就是执行this.apply(context, params)之类的,this表示的是 Array.prototype.push_,context表示的既是这里的数组对象 _[1, 2, 3]_, params表示的既是这里的参数 _4

Array.prototype.push.call([1, 2, 3], 4)
同理,由上述Function.prototype.call函数内部的执行过程是执行this.apply(context, params)的推断来看,this依然是指向的 Array.prototype.push_,context表示的既是这里的数组对象 _[1, 2, 3]_, params表示的既是这里的参数 _4_;因此,这里的调用方式与 _Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4) 的方式等价;因此,咱们得出以下结论,
Array.prototype.push.call([1, 2, 3], 4) <=> Function.prototype.call.bind(Array.prototype.push)([1, 2, 3], 4) <=> push([1, 2, 3], 4)

使用 bind 的一些注意事项

每次返回一个新函数

bind方法每运行一次,就返回一个新函数,这会产生一些问题。好比,监听事件的时候,不能写成下面这样。

element.addEventListener('click', o.m.bind(o));

上面代码中,click 事件绑定bind方法新生成的一个匿名函数。这样会致使没法取消绑定,因此,下面的代码是无效的。

element.removeEventListener('click', o.m.bind(o));

正确的方法是写成下面这样,使得 addremove 使用的是同一个函数的引用。

var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);

use strict

使用严格模式,该部分能够参考阮一峰的教程严格模式,说得很是详细;不过应用到面向对象编程里面,主要就是为了不this运行期动态指向 _Global Object_,若是发生这类的状况,报错;例如

function f() {
  'use strict';
  this.a = 1;
};

f();// 报错,this未定义

当执行过程当中,发现函数 f 中的this指向了 _Global Object_,则报错。

构造函数中的 this

this -> Object.prototype instance

构造函数比较特别,_javascript_ 解析过程不一样于其它普通函数;

假如咱们有以下的构造函数,

var Person = function(name, age){
   this.name = name;
   this.age = age;
}

javascript 语法解析器解析到以下语句之后,

var p = new Person('张三', 35);

实际上执行的是,

function new( /* 构造函数 */ constructor, /* 构造函数参数 */ param1 ) {
  // 将 arguments 对象转为数组
  var args = [].slice.call(arguments);
  // 取出构造函数
  var constructor = args.shift();
  // 建立一个空对象,继承构造函数的 prototype 属性
  var context = Object.create(constructor.prototype);
  // 执行构造函数
  var result = constructor.apply(context, args);
  // 若是返回结果是对象,就直接返回,则返回 context 对象
  return (typeof result === 'object' && result != null) ? result : context;
}

备注:_arguments_ 可表示一个函数中全部的参数,也就是一个函数全部参数的结合。

下面,咱们一步一步的来分析该构造函数的实现,弄清楚this指的是什么,

constructor

就是 Person 构造函数,

context

var context = Object.create(constructor.prototype);经过 constructor.prototype 建立了一个新的对象,也就是 Person.prototype 的一个实例 _Person.prototype isntance_;

constructor.apply(context, args);

注意,这步很是关键,_context_ 做为 constructor 构造函数的this,因此

var Person = function(name, age){
   this.name = name;
   this.age = age;
}

中的this在执行过程当中指向的实际上就是该 context 对象。

result

constructor.apply(context, args);方法调用的返回值,咱们当前用例中,_Person_ 构造函数并无返回任何东西,因此,这里是 _null_。

return (typeof result === 'object' && result != null) ? result : context;

new方法的最后返回值,若是 result 不为 null_,则返回 _result 不然返回的是 context_;咱们这个用例,当初始化构造函数完成之后,返回的是 _context 既 _Person.prototype instance_,也就是构造函数中的this指针;这也是大多数构造函数应用的场景。

Object.prototype instance -> Object.prototype

var Obj = function (p) {
  this.p = p;
};

Obj.prototype.m = function() {
  return this.p;
};

执行,

> var o = new Obj('Hello World!');

> o.p 
  'Hello World!'

> o.m() 
  'Hello World!'

说实话,当我第一次看到这个例子的时候,_o.p_ 还好理解,_o_ 就是表示构造函数 Obj 内部的this对象,是一个经过 Object.create(Obj.prototype) 获得的一份 Obj.prototype 的实例对象;可是,当我看到 o.m 的时候,仍是有点懵逼,_Obj.prototype_ 并非表明的this呀,_Object.create(Obj.prototype)_ 才是( 既 Obj.prototype instance ),因此在 Obj.prototype 上定义的 m 方法,怎么能够经过 o.m() 既经过 Obj.prototype instance 来调用呢?( 注意,关系 o -> Object.create(Obj.prototype) -> Obj.prototype instance -> this != Obj.prototype ) 当理解到 prototype 的涵义有,才知道,_Obj.prototype instance_ 会继承 Obj.prototype 中的公共属性的,因此,这里经过 Obj.prototype 对象定义的 m 函数能够经过 Object.prototype instance 进行调用。

References

本文转载自笔者的私人博客,伤神的博客,http://www.shangyang.me/2017/...
[Javascript中this关键字详解](
http://www.cnblogs.com/justan...
jQuery Fundamentals Chapter - The this keyword

相关文章
相关标签/搜索