爲了建立一個scope chain, 每個JavaScript的代碼執行上下文都提供了this關鍵字。In its most common usage, this
serves as an identity function, providing our neighborhoods a way of referring to themselves. We can’t always rely on that behavior, however: Depending on how we get into a particular neighborhood, this
might mean something else entirely. In fact, how we get into the neighborhood is itself exactly what this
generally refers to. 需要注意特殊的四種情況:
Calling an Object’s Method
在典型的面向對象編程時,我們需要一種方式去指向和引用我們調用的對象.
this
serves the purpose admirably, providing our objects the ability to examine themselves, and point at their own properties.[javascript] view plaincopy
This example builds an object named
deep_thought
, sets itsthe_answer
property to 42, and creates anask_question
method. Whendeep_thought.ask_question()
is executed, JavaScript establishes an execution context for the function call, settingthis
to the object referenced by whatever came before the last ”.”, in this case:deep_thought
. The method can then look in the mirror viathis
to examine its own properties, returning the value stored inthis.the_answer
: 42.
var deep_thought = {
the_answer: 42,
ask_question: function () {
return this.the_answer;
}
};
var the_meaning = deep_thought.ask_question();
<mce:script type="text/javascript"><!--
function BigComputer(answer) {
this.the_answer = answer;
this.ask_question = function () {
return this.the_answer;
}
}
var deep_thought = new BigComputer(42);
var the_meaning = deep_thought.ask_question();
// --></mce:script>
<mce:script type="text/javascript"><!--
function test_this() {
return this;
}
var i_wonder_what_this_is = test_this();
// --></mce:script>
<button id='thebutton'>Click me!</button>
<mce:script type="text/javascript"><!--
function click_handler() {
alert(this); // alerts the button DOM node
}
function addhandler() {
document.getElementById('thebutton').onclick = click_handler;
}
window.onload = addhandler;
// --></mce:script>
<button id='thebutton' onclick='click_handler()'>Click me!</button>
<mce:script type="text/javascript"><!--
function click_handler() {
alert(this); // alerts the window object
}
// --></mce:script>
Complications
Let’s run with that last example for a moment longer. What if instead of running click_handler
, we wanted to askdeep_thought
a question every time we clicked the button? The code for that seems pretty straightforward; we might try this:
[javascript] view plaincopy
<mce:script type="text/javascript"><!--
function BigComputer(answer) {
this.the_answer = answer;
this.ask_question = function () {
alert(this.the_answer);
}
}
function addhandler() {
var deep_thought = new BigComputer(42),
the_button = document.getElementById('thebutton');
the_button.onclick = deep_thought.ask_question;
}
window.onload = addhandler;
// --></mce:script>
對上面的代碼,我們期望點擊按鈕, deep_thought.ask_question被執行,我們得到返回結果
“42.” 但爲什麼得到的結果反而是undefined?哪裏錯了
?
The problem is simply this: We’ve passed off a reference to the ask_question
method, which, when executed as an event handler, runs in a different context than when it’s executed as an object method. 簡而言之,ask_question 中的this關鍵字是指向產生事件的DOM元素節點,而不是BigComputer對象
. DOM元素節點並沒有the_answer屬性,所以返回結果是
undefined而不是
“42.” setTimeout
exhibits similar behavior, delaying the execution of a function while at the same time moving it out into a global context.
This issue crops up all over the place in our programs, and it’s a terribly difficult problem to debug without keeping careful track of what’s going on in all the corners of your program, especially if your object has properties that do exist on DOM elements or the window
object.
Manipulating Context With .apply()
and .call()
We really do want to be able to ask deep_thought
a question when we click the button, and more generally, wedo want to be able to call object methods in their native context when responding to things like events andsetTimeout
calls. Two little-known JavaScript methods, apply
and call
, indirectly enable this functionality by allowing us to manually override the default value of this
when we execute a function call. Let’s look at call
first:
[javascript] view plaincopy
<mce:script type="text/javascript"><!--
var first_object = {
num: 42
};
var second_object = {
num: 24
};
function multiply(mult) {
return this.num * mult;
}
multiply.call(first_object, 5); // returns 42 * 5
multiply.call(second_object, 5); // returns 24 * 5
// --></mce:script>
In this example, we first define two objects, first_object
and second_object
, each with a num
property. Then we define a multiply
function that accepts a single argument, and returns the product of that argument, and thenum
property of its this
object. If we called that function by itself, the answer returned would almost certainly beundefined
, since the global window
object doesn’t have a num
property unless we explicitly set one. We need some way of telling multiply
what its this
keyword ought refer to; the call
method of the multiply
function is exactly what we’re looking for.
call方法的第一個參數定義了this關鍵字在被調用方法的執行上下文中指向和對象,call方法的剩餘參數則是被調用方法的參數。因此當
multiply.call(first_object, 5)被執行,
multiply函數被調用
, 5
爲傳入方法的第一個參數, this
執行 first_object對象。
Likewise, when multiply.call(second_object, 5)
is executed, the multiply
function is called, 5
is passed in as the first argument, and the this
keyword is set to refer to objectsecond_object
.
apply方法和
call方法基本一致
,但是允許你以數組的形式向被調用的函數傳遞參數, which can be quite useful when programatically generating function calls. Replicating the functionality we just talked about using apply
is trivial:
[javascript] view plaincopy
<mce:script type="text/javascript"><!--
...
multiply.apply(first_object, [5]); // returns 42 * 5
multiply.apply(second_object, [5]); // returns 24 * 5
// --></mce:script>
apply
and call
are very useful on their own, and well worth keeping around in your toolkit, but they only get us halfway to solving the problem of context shifts for event handlers. It’s easy to think that we could solve the problem by simply using call
to shift the meaning of this
when we set up the handler:
[javascript] view plaincopy
function addhandler() {
var deep_thought = new BigComputer(42),
the_button = document.getElementById('thebutton');
the_button.onclick = deep_thought.ask_question.call(deep_thought);
}
上面的代碼仍然存在問題: call是立即執行函數的,因此我們提供的
onclick
handler是函數的執行結果而不是函數本身.我們需要JavaScript的另一個特性來解決這個問題:bind方法。
The Beauty of .bind()
I’m not a huge fan of the Prototype JavaScript framework, but I am very much impressed with the quality of its code as a whole. In particular, one simple addition it makes to the Function
object has had a hugely positive impact on my ability to manage the context in which function calls execute: bind
performs the same general task as call
, altering the context in which a function executes. The difference is that bind
returns a function reference that can be used later, rather than the result of an immediate execution that we get with call
.
If we simplify the bind
function a bit to get at the key concepts, we can insert it into the multiplication example we discussed earlier to really dig into how it works; it’s quite an elegant solution:
[javascript] view plaincopy
<mce:script type="text/javascript"><!--
var first_object = {
num: 42
};
var second_object = {
num: 24
};
function multiply(mult) {
return this.num * mult;
}
Function.prototype.bind = function(obj) {
var method = this,
temp = function() {
return method.apply(obj, arguments);
};
return temp;
}
var first_multiply = multiply.bind(first_object);
first_multiply(5); // returns 42 * 5
var second_multiply = multiply.bind(second_object);
second_multiply(5); // returns 24 * 5
// --></mce:script>
First, we define first_object
, second_object
, and the multiply
function, just as before. With those taken care of, we move on to creating a bind
method on the Function
object’s prototype
, which has the effect of makingbind
available for all functions in our program. When multiply.bind(first_object)
is called, JavaScript creates an execution context for the bind
method, setting this
to the multiply
function, and setting the first argument,obj
, to reference first_object
. So far, so good.
The real genius of this solution is the creation of method
, set equal to this
(the multiply
function itself). When the anonymous function is created on the next line, method
is accessible via its scope chain, as is obj
(this
couldn’t be used here, because when the newly created function is executed, this
will be overwritten by a new, local context). This alias to this
makes it possible to use apply
to execute the multiply
function, passing in obj
to ensure that the context is set correctly. In computer-science-speak, temp
is a closure that, when returned at the end of the bind
call, can be used in any context whatsoever to execute multiply
in the context offirst_object
.
This is exactly what we need for the event handler and setTimeout
scenarios discussed above. The following code solves that problem completely, binding the deep_thought.ask_question
method to the deep_thought
context, so that it executes correctly whenever the event is triggered:
[javascript] view plaincopy
function addhandler() {
var deep_thought = new BigComputer(42),
the_button = document.getElementById('thebutton');
the_button.onclick = deep_thought.ask_question.bind(deep_thought);
}
Beautiful.