前言
看过阮一峰的关于 this 的教程,讲了很多比较好的例子,但没有对其本质的东西解释清楚,而且部分例证存在问题;于是,打算重写本章节,从this
的本质入手;
本文为作者的原创作品,转载需注明出处;
References
Javascript中this关键字详解
jQuery Fundamentals Chapter - The this keyword
this 是什么?
this
可以理解为一个指针,指向调用对象;
判断 this 是什么的四个法则
官网定义
先来看第一段官方的解释,
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 prototype call 小节如果是被
Function.bind
… bla…bla
参考 function prototype bind 小节如果某个方法在运行期是被一个对象( an object )调用( 备注:这里为了便于理解,我针对这种情况,自己给起了个名称叫
关联调用
),在运行期
,会将该 object 的引用赋值给该方法的this
。
备注:被一个对象调用?何解?其实就是指语句obj.func()
,这个时候,func()
方法内部的this
将会被赋值为obj
对象的引用,也就是指向obj
;如果该方法在运行期被当做一个没有依附在任何 object 上的一个
独立方法
被调用(is being invoked as astandalone function
not attached to any object ),那么该方法内部的this
将会被赋值为全局对象(在浏览器端就是 windows )独立方法 ( standalone function )
?在运行期,如果func
方法被obj
关联调用的,既是通过obj.func()
的方式,那么它就不是standalone
的;如果是直接被调用,没有任何对象关联,既是通过func()
调用,那么这就是standalone
的。
this 运行期相关
官网定义 2
再来看另外一句非常精炼的描述,来加深理解
The this keyword is relative to the execution context, not the declaration context.
this
关键字与运行环境
有关而与声明环境
无关;(补充,而作用域链
和闭包
是在函数的声明期
创建的,参考创建时机)
补充,是如何与函数的运行期
相关的,参考this 指针运行时赋值
我的补充
法则 #3 和 #4,大多数情况都非常容易理解,有几种情况需要特别注意,
函数嵌套
需要注意的是object
对象中的函数内部再次嵌套函数的情况,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var name = "windows";
var obj = {
name:"object",
f1:function(){
console.log("this: "+this.name)
function f2(){
console.log("this: " + this.name)
}
f2();
}
};执行
1
2
3> 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
是会发生变化;)- 函数赋值
参看将函数赋值-standalone以及相关变种章节
可见,要判断this
在运行期
到底指的是什么,并没有那么容易,但是,只要牢牢的把握好两点,就可以迎刃而解,
this
是运行期
相关的
更确切的说,this
是在运行期
被赋值的,所以,它的值是在运行期动态确定的。this
是否与其它对象关联调用
这里的关联调用
指的是 javascript 的一种语法,既是调用语句显式的写为obj.func()
,另外需要注意的是,javascript 方法的调用不会隐式的隐含 this。只要没有显式的关联调用
,那么就是standalone
的调用,就符合法则 #4,所以,this
指向 Global Object。
this 的 Object
注意,this
定义中所指的Object
指的是 javascript 的 Object
类型,既是通过
1 | var o1 = {}; |
这样的方式构建出来的对象;
备注,最开始,自己有个思维的误区,认为既然 javascript 一切皆为对象,那么this
指针是指向对象
的,那么是不是也可以指向Function
,Number
等对象?答案是否定的。
起初,我是按照上面的逻辑来理解的,直到当我总结到bind 是如何实现的小节后,发现Function
对象在调用方法属性bind
的时候,bind
方法内部的this
指向的是Function
,这才恍然大悟,this
的Object
实际上是可以指向任何 javascript Object
的,包括 Object、Function 等。
this 是变化的
我们来看这样一个例子,
1 | var C = "王麻子"; |
可以看到,虽然 A.describe 方法的定义不变,但是其运行时环境发生了变化,this 的指向也就发生了变化。
1 | > B.describe = A.describe; |
在运行时,相当于运行的是 B 的 describe 方法
1 | > var describe = A.describe; |
在运行时,相当于运行的是 windows 的 describe 方法
方法调用没有隐含 this
经常写 Java 代码的原因,经常会习惯性的认为只要在对象方法里面调用某个方法或者属性,隐含了 this
,比如1
2
3
4
5
6
7
8
9
10
11
12
13public class Person{
String name;
public String getName(){
return name;
}
public String getName2(){
return this.name;
}
}
而 Javascript 实际上并没有这种隐含的表达方式;详细验证过程参考将函数赋值-standalone
关联调用 - 容易混淆的场景
从this 是什么章节中,为了方便对 _#3_ 进行描述,我起了个名字叫做 关联调用 ;那么有些情况看似是 关联调用,实则不然;
我们有一个标准的对象,定义如下,
1 | var name = "windows"; |
通过标准的 关联调用 的方式,我们进行如下的调用,
1 | > obj.foo() |
根据法则 #3 既 关联调用 的定义,得到 this -> obj;如果事事都如此的简单,如此的标准,那可就好了,总会有些让人费解的情况,现在来看看如下的一些特殊的例子,加深对 关联调用 的理解。
将函数赋值 - standalone
1 | > var fooo = obj.foo |
输出的 windows,既是 this -> global object,而不是我们期望的 obj;为什么?原因是,obj.foo 其实是 foo 函数的函数地址,通过 var fooo = obj.foo 将该函数的地址赋给了变量 fooo,那么当执行
1 | > fooo(); |
的时候,fooo()
执行的是是一个standalone
的方法,根据法则 #4,所以该方法内部的this
指向的是 Global Object;注意,obj.foo 表示函数 foo 的入口地址,所以,变量 fooo 等价与 foo 函数。
备注:由于受到写 Java 代码习惯的原因,很容易将这里解释为默认执行的是this.fooo()
,fooo() 的调用隐含了this
,因此就会想到,由于this
指向的 Global Object,所以这里当然返回的就是this: windows
;但是,这样解释,是不对的
,因为 Javascript 压根没有这种隐含this
的概念,参看用例,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27var 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 函数立即执行
1 | > (obj.foo = obj.foo)() |
首先,立即执行 foo 函数,然后将 foo 函数赋值给对象 obj 对象的 foo 属性;等价于执行如下的代码,
1 | var name = "windows"; |
输出,
1 | 'this: windows' |
可以看到,this -> global object,这里为什么指向的是 global object?其实这里的立即执行过程,就是执行的如下代码,
1 | (function () { |
由此可以看出,实际上进行一个匿名函数
的立即执行;也就是说执行过程中并没有使用 关联调用,而是一次 standalone 函数的自身调用,所以根据法则 #4,this -> global object。执行完以后,将该匿名函数赋值给 obj.foo。
再次执行,
1 | > obj.foo(); |
这次执行的过程是一次标准的 关联调用 过程,所以根据法则 #3,this -> obj。
作为判断条件 - 匿名函数立即执行
1 | > (false || obj.foo)() |
等价于执行,
1 | (false || function () { |
原理和函数赋值变种-匿名 standalone 函数立即执行 一致,等价于立即执行如下的匿名函数
1 | (function () { |
其实,把这个例子再做一个细微的更改,其中逻辑就看得更清楚了,为 foo 函数添加一个返回值 return true1
2
3
4
5
6
7
8var name = "windows";
var obj ={
name: "obj",
foo: function () {
console.log("this: "+ this.name);
return true;
}
};
再次执行,
1 | > (false || obj.foo)() |
可见,obj.foo 函数执行以后,返回 true。上述代码其实等价于执行如下的代码,
1 | (false || function () { |
函数回调场景 0 - 基本原理
1 | var counter = { |
可以看到,把一个定义有this
关键字的函数作为其它函数的回调函数,是危险的,因为this
在运行期
会被重新赋值,上述例子很直观的描述了这一点,之所以报错,是因为this
指向了 Global Object。要解决这样的问题,可以使用bind
,调用的时候改为
1 | > callIt(counter.inc.bind(counter)) |
函数回调场景 1 - setTimeout
1 | var name = "Bob"; |
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 对象
1 | var o = new Object(); |
但是,如果将f
方法指定给某个click
事件,this
的指向发生了改变,1
$('#button').on('click', o.f);
点击按钮以后,返回的是false
,是因为在执行过程中,this
不再指向对象o
了而改为指向了按钮的DOM
对象了;Sounds Good,但问题是,怎么被改动的?看了一下 jQuery 的源码,event.js,摘录重要的片段如下,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function 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 - 数组对象方法的回调
1 | var obj = { |
这里我们期望的是,依次根据数组 times 的长度,输出 obj.name 三次,但是实际运行结果是,数组虽然循环了三次,但是每次输出都是 undefined,那是因为匿名函数1
2
3function(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
1 | obj.print = function () { |
将 obj 对象作为 this
绑定到该匿名函数上,然后再作为回调函数参数传递给 forEach 函数,这样,在 forEach 函数中,用 standalone 的方式调用 _fn_ 的时候,_fn_ 中的 this
指向的就是数组对象 obj 对象,这样,我们就能顺利的输出 obj.name 了。
绑定 this
有上述描述可知,this
的值在运行时
根据不同上下文环境有不同的值,因此我们说this
的值是变化的,这就给我们的编程带来了麻烦,有时候,我们期望,得到一个固定的this
。Javascript 提供了call
、apply
以及bind
这三个方法,来固定this
的指向;这三个方法存储在 function.prototype 域中,
function.prototype.call()
总结起来,就是解决函数在调用的时候,如何解决this
动态变化的问题。
调用格式,1
func.call(thisValue, arg1, arg2, ...)
第一个参数是在运行时用来赋值给 func 函数内部的 this 的。
通过f.call(obj)
的方式调用函数,在运行时,将 obj 赋值给 this;
1 | var obj = {}; |
call
方法的参数是一个对象,如果参数为 _空_、null 或者 undefined,则使用默认的全局对象;
1 | var n = 123; |
如果call
方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后赋值给 this
1 | var f = function () { |
call
方法可以接受多个参数,第一个参数就是赋值给 this 的对象,
1 | var obj = { |
call
方法可以调用对象的原生方法;
1 | var obj = {}; |
方法 hasOwnProperty 是对象 obj 从 Object.prototype 中继承的方法,如果一旦被覆盖,就不会得到正确的结果,那么,我们可以使用call
的方式调用原生方法,将 obj 作为 this 在运行时调用,这样,变通的,我们就可以调用 obj 对象所继承的原生方法了。
function.prototype.apply()
总结起来,和call
一样,就是解决函数在调用的时候,如何解决this
动态变化的问题。
apply
方法的作用与call
方法类似,也是改变this
指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下。
1 | func.apply(thisValue, [arg1, arg2, ...]) |
apply
方法的第一个参数也是this
所要指向的那个对象,如果设为null或undefined,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在
call
方法中必须一个个添加,但是在apply
方法中,必须以数组形式添加
1 | function f(x,y){ |
找出数组最大的元素
1
2
3var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a)
// 15将数组的空元素变为 undefined
1
2Array.apply(null, ["a",,"b"])
// [ 'a', undefined, 'b' ]空元素
与undefined
的差别在于,数组的forEach
方法会跳过空元素,但是不会跳过undefined
。因此,遍历内部元素的时候,会得到不同的结果。1
2
3
4
5
6
7
8
9
10
11
12
13
14var 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
,如下,1
2
3
4
5
6
7
8
9
10
11
12var 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
,使其永远指向 objecto
,这样,就达到了this
不被动态修改的目的。
function.prototype.bind()
总结起来,其实就是在把函数作为参数传递的时候,如何解决this
动态变化的问题。
解决的问题
在认识关联调用 - 容易混淆的场景中,我们浓墨重彩的描述了将函数赋值
以后,导致this
在运行期发生变化的种种场景,而且在编程过程当中,也是非常容易导致问题的场景;那么有没有这么一种机制,即便是在函数赋值
后,在运行期依然能够保护并固定住我的this
?答案是有的,那就是bind
。下面,我们来看一个例子,
1 | var d = new Date(); |
我们使用语句 d.getTime() 通过对象 _d_ 关联调用函数 getTime(),根据法则 #3,函数 getTime() 内部的 this
指向的是对象 _d_,然后从 _d_ 对象中成功获取到了时间。但是,我们稍加改动,将对象 _d_ 中的函数 getTime 赋值给另外一个变量,在执行呢?
1 | var print = d.getTime; |
Wow~, 画风突变,得不到时间了,而且还抛出了一个程序异常,好玩,你的程序因此崩溃.. 这就是this
在执行期动态变化所导致的,当我们将函数 d.getTime 赋值给 print,然后语句 print() 表示将函数 getTime 作为 standalone 的函数在运行期
调用,所以,内部的this
发生变化,指向了 Global Object,也因此,我们得不到时间了,但我们得到一个意想不到的异常..
Ok, 别怕,孩子,bind
登场了,
1 | var print = d.getTime.bind(d); |
在 赋值过程中,将函数通过bind
语法绑定this
对象 _d_ 以后,再赋值给一个新的变量;这样,即便 print() 再次作为 standalone 的函数在运行期
调用,this
的指向也不再发生变化,而是固定的指向了对象 _d_。
bind 是如何实现的
1 | if(!('bind' in Function.prototype)){ |
给Function
对象的prototype
原型中新增一个属性bind
,该bind
是一个 function 函数;这里要特别特别注意,每次bind
调用以后,返回的是一个新的function
,
1 | var fnbound = function(){ |
通过 fnbound 函数套一层原函数 _fn_ 作为闭包,然后返回这个新的 function fnbound;大部分教程就是这样介绍即止了;其实,我想问的是,为什么bind
要这么设计,直接返回fn.apply(context, args);
不是挺好吗?为什么还要在外面套一层新函数 fnbound?Ok,这里我就来试图解释下原因吧;
采用反证法,如果,我们不套这么一层新函数 fubound,看看,会怎样?于是,我们得到如下的实现,
1 | if(!('bind' in Function.prototype)){ |
直接返回fn.apply(context, args)
,oh,顿时,我明白了,fn.apply(...)
这是一条执行命令啊,它会立即执行 _fn_,将 _fn_ 执行的结果返回.. 而我们这里的bind
的初衷只是扩充 _fn_ 函数的行为(既绑定this
对象),然后返回一个函数的引用
,而正式因为我们无法在绑定以后,直接返回原有函数的引用,所以,这里,我们才需要创建一个新的函数并返回这个新的函数的引用,已达到bind
的设计目的。Ok,这下总算是清楚了。
特性
绑定匿名函数
1 | obj.print = function () { |
可见,我们可以直接改匿名函数执行bind
,然后在将其赋值给某个对象;更详细的用例参考函数回调场景 3 - 数组对象方法的回调
作为函数直接调用
1 | var altwrite = document.write; |
在浏览器运行这个例子,得到错误Uncaught ReferenceError: alwrite is not defined
,这个错误并没有真正保留底层的原因,真正的原因是,document 对象的 write 函数再执行的时候,内部this
指向了 Global Object
为了解决上述问题,我们可以bind
document 对象,
1 | altwrite.bind(document)("hello") |
注意这里的写法,altwrite.bind(document)
返回的是一个Function
,所以可以直接跟参数调用。
绑定函数参数
除了绑定this
对象意外,还可以绑定函数中的参数,看如下的例子,
1 | var add = function (x, y) { |
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。1
2
3
4
5
6
7
8
9
10
11
12var 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 | > [1, 2, 3].push(4) |
等价于
1 | Array.prototype.push.call([1, 2, 3], 4) |
第一个参数 [1, 2, 3] 绑定 push 函数的this
关键字,第二个参数 _4_,是需要被添加的值。
补充一下
为什么说这里是等价的?我们来解读一下
1 | > [1, 2, 3].push(4) |
的执行过程,[1, 2, 3] 作为数组对象,调用其原型中的 Array.prototype.push 方法,很明显,采用的是关联调用
,因此 push 函数内部的 this 指向的是数组对象 [1, 2, 3];而这里,我们通过
1 | Array.prototype.push.call([1, 2, 3], 4) |
这样的调用方式,只是换汤不换药,同样是执行的数组中的原型方法 push,只是this
的传递方式不同而已,这里是通过bind
直接将this
赋值为数组对象 [1, 2, 3],而不是通过之前的关联调用
;所以,两种调用方式是等价的。
补充完毕
再次,
call 方法调用的是 Function 对象的原型方法既 Function.prototype.call(…),那么我们再来将它 bind 一下,看看会有什么结果
1 | > var push = Function.prototype.call.bind(Array.prototype.push); |
我们得到了一个具备数组 push 操作的一个新的函数 push(…) ( 注: bind 每次回返回一个新的函数 );
那是为什么呢?
可以看到,背后的核心是,
1 | push([1, 2, 3], 4); |
等价于执行
1 | 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
方法每运行一次,就返回一个新函数
,这会产生一些问题。比如,监听事件的时候,不能写成下面这样。
1 | element.addEventListener('click', o.m.bind(o)); |
上面代码中,click 事件绑定bind
方法新生成的一个匿名函数。这样会导致无法取消绑定,所以,下面的代码是无效的。
1 | element.removeEventListener('click', o.m.bind(o)); |
正确的方法是写成下面这样,使得 add 和 remove 使用的是同一个函数的引用。
1 | var listener = o.m.bind(o); |
use strict
使用严格模式
,该部分可以参考阮一峰的教程严格模式,说得非常详细;不过应用到面向对象编程里面,主要就是为了避免this
在运行期
动态指向 Global Object,如果发生这类的情况,报错;例如1
2
3
4
5
6function f() {
;
this.a = 1;
};
f();// 报错,this未定义
当执行过程中,发现函数 _f_ 中的this
指向了 Global Object,则报错。
构造函数中的 this
this -> Object.prototype instance
构造函数比较特别,javascript 解析过程不同于其它普通函数;
假如我们有如下的构造函数,1
2
3
4var Person = function(name, age){
this.name = name;
this.age = age;
}
当 javascript 语法解析器解析到如下语句以后,
1 | var p = new Person('张三', 35); |
实际上执行的是,
1 | function new( /* 构造函数 */ constructor, /* 构造函数参数 */ param1 ) { |
备注: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
,所以1
2
3
4var 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
1 | var Obj = function (p) { |
执行,
1 | > var o = new Obj('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 进行调用。