在 Javascript 里,函数被调用的时候,除了接受声明时定义的形式参数,每一个函数还接受两个附加的参数: this
和 arguments
。随着函数使用场合的不同,this
的值会发生变化。但是有一个总的原则,那就是 this
指的是,调用函数的那个对象。
下面分四种情况,具体讨论 this
的用法:
# 1. 对象方法调用模式
window.name = 'window';
var object = {
name: 'object',
run: function() {
console.log(this.name);
}
};
object.run(); // 'object'
分析:当一个函数被保存为对象的一个属性时,我们称它为一个方法。当方法被调用时(通过 .
表达式或 object[fun]
下标表达式),this
绑定到该对象。
# 2. 函数调用模式
当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的,以此模式调用函数时,this
被绑定到全局对象(ECMAScript6 的箭头函数(注意只是箭头函数)基本纠正了这个设计):
window.name = 'window';
var object = {
name: 'object',
run: function() {
innerFun();
function innerFun() {
console.log(this.name);
}
return function () {
console.log(this.name);
}
}
};
object.run()();
// =>
// 'window'
// 'window'
分析:从上面可以看出,不管外部环境的 this
是不是 window
,通过函数调用模式调用的函数,this
指向 window
。
来看看 ES6 的 this
:
window.name = 'window';
var object = {
name: 'object',
run: function() {
return () => {
console.log(this.name);
}
}
};
var o = { name: 'test_o' };
object.run()(); // 'object'
object.run().apply(o); // 'object'
如果这么写:
window.name = 'window';
var object = {
name: 'object',
run: () => {
console.log(this.name);
}
};
var o = { name: 'test_o' };
object.run(); // 'window'
object.run.apply(o); // 'window'
分析:箭头函数和普通函数之间有一个重要的差别 - 箭头函数没有自己的 this
值,其 this
值是继承外域的 this
值。所以箭头函数不仅仅是从外观上简化了函数的写法,更解决了普通函数中 this
的 hack 问题。
# 3. 构造函数调用模式
当一个函数被作为一个构造函数来使用(使用 new
关键字),它的 this
与即将被创建的新对象绑定。
function C() {
this.a = 37;
}
var o = new C();
console.log(o.a); // 37
function C2() {
this.a = 37;
return { a: 38 };
}
o = new C2();
console.log(o.a); // 38
注意:当构造器返回的默认值是一个 this
引用的对象时,可以手动设置返回其他的对象,如果返回值不是一个对象,返回 this
。
# 4. call, apply, bind 调用
call
, apply
方法的用途都是在特定的作用域中调用函数:
apply
方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments
对象call
方法与apply
方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call
方法而言,第一个参数是this
值没有变化,变化的是其余参数都直接传递给函数
var a = 1;
function test() {
console.log(this.a);
}
var o = {};
o.a = 2;
o.fun = test;
o.fun.apply(o); // 1
o.fun.call(o); // 1
ECMAScript5 引入了 Function.prototype.bind
。调用 f.bind(someObject)
会创建一个与 f
具有相同函数体和作用域的函数,但是在这个新函数中,this
将永久地被绑定到了 bind
的第一个参数,无论这个函数是如何被调用的。
function f() {
return this.a;
}
var g = f.bind({ a: 'azerty' });
console.log(g()); // azerty
var o = { a: 37, f: f, g: g };
console.log(o.f(), o.g()); // 37, azerty
# this 容易误用的情况
# 1. 内联式绑定 Dom 元素的事件处理函数
<script type="text/javascript">
function hello() {
console.log(this.tagName);
}
</script>
<input id="btnTest" type="button" value="点击我" onclick="hello()">
运行结果是 undefined
, 此时的 this
指针并不是指向 input
元素,使用内联式绑定 Dom 元素的事件处理函数时,实际 上相当于执行了以下代码:
<script type="text/javascript">
function hello() {
console.log(this.tagName);
}
document.getElementById("btnTest").onclick = function () {
hello();
};
</script>
<input id="btnTest" type="button" value="点击我">
这种情况下 hello
函数对象的所有权并没有发生转移,还是属于 window
对象所有。因为还是 window
对象在调用 hello
方法。
解决方案:把 this
作为参数传入函数,或者直接把方法作为对象的一个属性,这样方法中的 this
直接指定的是当前对象
<script type="text/javascript">
function hello(el) {
console.log(el.tagName);
}
</script>
<input id="btnTest" type="button" value="点击我" onclick="hello(this)">
或:
<script type="text/javascript">
function hello() {
console.log(this.tagName);
}
document.getElementById('btnTest').onclick = hello;
</script>
<input id="btnTest" type="button" value="点击我">
# 2. 临时变量导致的 this 指针丢失
window.name = 'window';
var object = {
name: 'object',
run: function() {
console.log(this.name);
}
};
function runTest() {
var tmpRun = object.run;
tmpRun();
}
runTest(); // 'window'
此时 object.run
被赋值给了临时变量 tmpRun
, 而临时变量是属于 window
对象的(只不过外界不能直接引用,只对 Javascript 引擎可见),于是在 run
函数内部的 this
指针指向的就是 window
对象了。
解决方案:
- 不引入临时变量,每次使用均使用
object.run()
进行调用 run
函数内部使用object.name
显式引用name
属性,而不通过this
指针隐式引用- 使用
apply
/call
函数指定this
指针
# 3. 函数传参导致 this 丢失
window.name = 'window';
var object = {
name: 'object',
run: function() {
console.log(this.name);
}
};
setTimeout(object.run, 100); // 'window'
其实这个问题和上一个示例中的问题是类似的,都是因为临时变量而导致的问题。当我们执行函数的时候,如果函数带有参数,那么这个时候 Javascript 引擎会创建一个临时变量,并将传入的参数复制(注意,Javascript 里面都是值传递的,没有引用传递的概念)给此临时变量。也就是说,整个过程就跟上面我们定义了一个 tmpRun
的临时变量,再将 object.run
赋值给这个临时变量一样。只不过在这个示例中,容易忽视临时变量导致的 bug。