当前位置:首页 » JavaScript技术

JavaScript的this,call(),apply(),bind()

2014-07-17 13:09 本站整理 浏览(3)
为了建立一个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 plaincopyprint?

var deep_thought = {  

   the_answer: 42,  

   ask_question: function () {  

    return this.the_answer;  

   }  

  };  

   

  var the_meaning = deep_thought.ask_question();   

[javascript] view plaincopyprint?

[code]<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 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> [/code]

 
Instead of explicitly creating the
deep_thought
object, we’ll write a function to create
BigComputer
objects, and instantiate
deep_thought
as an instance variable via the
new
keyword. When
new BigComputer()
is executed, a completely new object is created transparently in the background.
BigComputer
is called, and its
this
keyword is set to reference that new object. The function can set properties and methods on
this
, which is transparently returned at the end of
BigComputer
’s execution.
Notice, though, that
deep_thought.the_question()
still works just as it did before. What’s going on there? Why does
this
mean something different inside
the_question
than it does inside
BigComputer
? Put simply, we entered
BigComputer
via
new
, so
this
meant “the new object.” On the other hand, we entered
the_question
via
deep_thought
, so while we’re executing that method,
this
means “whatever
deep_thought
refers to”.
this
is not read from the scope chain as other variables are, but instead is reset on a context by context basis.
Function Call
What if we just call a normal, everyday function without any of this fancy object stuff? What does
this
mean in that scenario?
[javascript] view plaincopyprint?
<mce:script type="text/javascript"><!--  
  function test_this() {  
   return this;  
  }  
  var i_wonder_what_this_is = test_this();  
   
// --></mce:script>   
[javascript] view plaincopyprint?

[code] <mce:script type="text/javascript"><!--  

  function click_handler() {  

   alert(this); // alerts the window object  

  }  

   

// --></mce:script>   

<mce:script type="text/javascript"><!--
function click_handler() {
alert(this); // alerts the window object
}
// --></mce:script> [/code]

 ... 

[xhtml] view plaincopyprint?
<button id='thebutton' onclick='click_handler()'>Click me!</button>   

[javascript] view plaincopyprint?

[code]<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>   

<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> [/code]

 ... 

[xhtml] view plaincopyprint?
<button id='thebutton'>Click me!</button>   

[javascript] view plaincopyprint?

[code]<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>   

<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> [/code]
 
对上面的代码,我们期望点击按钮,
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, we do want to be able to call object methods in their native context when responding to things like events and
setTimeout
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 plaincopyprint?

<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>   

[javascript] view plaincopyprint?

[code]<mce:script type="text/javascript"><!--  

 ...  

   

 multiply.apply(first_object, [5]); // returns 42 * 5  

 multiply.apply(second_object, [5]); // returns 24 * 5  

// --></mce:script>   

<mce:script type="text/javascript"><!--
...
multiply.apply(first_object, [5]); // returns 42 * 5
multiply.apply(second_object, [5]); // returns 24 * 5
// --></mce:script> [/code]
 
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 plaincopyprint?

function addhandler() {  

 var deep_thought = new BigComputer(42),  

  the_button = document.getElementById('thebutton');  

   

 the_button.onclick = deep_thought.ask_question.call(deep_thought);  

}   

[javascript] view plaincopyprint?

[code]<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>   

<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> [/code]
 
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 making
bind
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 of
first_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 plaincopyprint?

function addhandler() {  

 var deep_thought = new BigComputer(42),  

  the_button = document.getElementById('thebutton');  

   

 the_button.onclick = deep_thought.ask_question.bind(deep_thought);  

}   

function addhandler() {
var deep_thought = new BigComputer(42),
the_button = document.getElementById('thebutton');
the_button.onclick = deep_thought.ask_question.bind(deep_thought);

 
Beautiful.