看过[阮一峰]()的关于 this 的教程,讲了不少比较好的例子,但没有对其本质的东西解释清楚,并且部分例证存在问题;因而,打算重写本章节,从this
的本质入手;javascript
本文为做者的原创做品,转载需注明出处;html
this
能够理解为一个指针,指向调用对象;java
官网定义编程
先来看第一段官方的解释,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 methodis being invoked
. The value of this is determined using a simple series of steps:数组
- If the function is invoked using
Function.call
orFunction.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).- 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.- If the function is
being invoked
as a method ofan object
, this will refer to that object.- 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
的赋值场景,概括起来,分为以下四种状况,浏览器
Function.call
或者Function.apply
调用执行.... bla..bla..Function.bind
... bla...bla关联调用
),在运行期
,会将该 object 的引用赋值给该方法的this
。obj.func()
,这个时候,func()
方法内部的this
将会被赋值为obj
对象的引用,也就是指向obj
;独立方法
被调用(is being invoked as a standalone function
not attached to any object ),那么该方法内部的this
将会被赋值为全局对象(在浏览器端就是 windows )独立方法 ( standalone function )
?在运行期,若是func
方法被obj
关联调用的,既是经过obj.func()
的方式,那么它就不是standalone
的;若是是直接被调用,没有任何对象关联,既是经过func()
调用,那么这就是standalone
的。官网定义 2闭包
再来看另一句很是精炼的描述,来加深理解app
The this keyword is relative to the execution context, not the declaration context.
this
关键字与运行环境
有关而与声明环境
无关;(补充,而做用域链
和闭包
是在函数的声明期
建立的,参考建立时机)函数
补充,是如何与函数的运行期
相关的,参考this 指针运行时赋值
法则 #3 和 #4,大多数状况都很是容易理解,有几种状况须要特别注意,
函数嵌套
须要注意的是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
是会发生变化;)
可见,要判断this
在运行期
到底指的是什么,并无那么容易,可是,只要紧紧的把握好两点,就能够迎刃而解,
this
是运行期
相关的this
是在运行期
被赋值的,因此,它的值是在运行期动态肯定的。this
是否与其它对象关联调用
关联调用
指的是 javascript 的一种语法,既是调用语句显式的写为obj.func()
,另外须要注意的是,_javascript_ 方法的调用不会隐式的隐含 this。只要没有显式的关联调用
,那么就是standalone
的调用,就符合法则 #4,因此,this
指向 _Global Object_。注意,this
定义中所指的Object
指的是 javascript 的 Object
类型,既是经过
var o1 = {}; var o2 = new Object(); var o3 = Object.create(Object.prototype);
这样的方式构建出来的对象;
备注,最开始,本身有个思惟的误区,认为既然 javascript 一切皆为对象,那么this
指针是指向对象
的,那么是否是也能够指向Function
,Number
等对象?答案是否认的。
起初,我是按照上面的逻辑来理解的,直到当我总结到bind 是如何实现的小节后,发现Function
对象在调用方法属性bind
的时候,bind
方法内部的this
指向的是Function
,这才恍然大悟,this
的Object
其实是能够指向任何 javascript Object
的,包括 Object_、_Function 等。
咱们来看这样一个例子,
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 方法
常常写 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_;若是事事都如此的简单,如此的标准,那可就行了,总会有些让人费解的状况,如今来看看以下的一些特殊的例子,加深对 _关联调用 的理解。
> 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()_;
> (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; })()
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
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_。
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,动态绑定回调函数。
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
。Javascript 提供了call
、apply
以及bind
这三个方法,来固定this
的指向;这三个方法存储在 function.prototype 域中,
总结起来,就是解决函数在调用的时候,如何解决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 是对象 obj 从 Object.prototype 中继承的方法,若是一旦被覆盖,就不会获得正确的结果,那么,咱们可使用call
的方式调用原生方法,将 obj 做为 this 在运行时调用,这样,变通的,咱们就能够调用 obj 对象所继承的原生方法了。
总结起来,和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
找出数组最大的元素
var a = [10, 2, 4, 15, 9]; Math.max.apply(null, a) // 15
将数组的空元素变为 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
绑定回调函数的对象
函数回调场景-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
不被动态修改的目的。
总结起来,其实就是在把函数做为参数传递的时候,如何解决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_。
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 参数。
若是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
方法每运行一次,就返回一个新函数
,这会产生一些问题。好比,监听事件的时候,不能写成下面这样。
element.addEventListener('click', o.m.bind(o));
上面代码中,click 事件绑定bind
方法新生成的一个匿名函数。这样会致使没法取消绑定,因此,下面的代码是无效的。
element.removeEventListener('click', o.m.bind(o));
正确的方法是写成下面这样,使得 add 和 remove 使用的是同一个函数的引用。
var listener = o.m.bind(o); element.addEventListener('click', listener); // ... element.removeEventListener('click', listener);
使用严格模式
,该部分能够参考阮一峰的教程严格模式,说得很是详细;不过应用到面向对象编程里面,主要就是为了不this
在运行期
动态指向 _Global Object_,若是发生这类的状况,报错;例如
function f() { 'use strict'; this.a = 1; }; f();// 报错,this未定义
当执行过程当中,发现函数 f 中的this
指向了 _Global Object_,则报错。
构造函数比较特别,_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
指针;这也是大多数构造函数应用的场景。
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 进行调用。
本文转载自笔者的私人博客,伤神的博客,http://www.shangyang.me/2017/...
[Javascript中this关键字详解](
http://www.cnblogs.com/justan...
jQuery Fundamentals Chapter - The this keyword