以下内容/例子均为抄袭  (js教程:javascript作用域(Scope) 文章写得太好了,全抄完了- -!)

 

//呼叫对象方法
var deep_thought = { 
the_answer: 42, 
ask_question: function () { 
return this.the_answer;      
} 
}; 
var the_meaning = deep_thought.ask_question(); 
-----------------------------------------------------
 //刚创建的函数/构造函数
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();//创建后的对象
-----------------------------------------------------
 //呼叫函数
function test_this() { 
return this;                                  
} 
var i_wonder_what_this_is = test_this(); 

以上是this比较容易理解的几个作用

//事件处理函数
function click_handler() { 
alert(this); // 弹出 window 对象 ,但是调用对象不同,返回this不同..
}  -------
<button id='thebutton' onclick='click_handler()'>Click me!</button>

此时,直接调用的this与添加事件中的this是2个不同的this,这种添加方法的方式是不利于理解与阅读的,解决如下

function click_handler() { 
alert(this); // 弹出按钮的DOM节点 
} 

function addhandler() { 
document.getElementById('thebutton').onclick = click_handler; 
} 
window.onload = addhandler; 
------- 
<button id='thebutton' >Click me!</button>

我有说过要严格讲js与前台代码分离的事情吗?这就是其中一个原因

复杂的情况来了,我们需要询问deep_thought一个问题,如果不是直接运行click_handler而是通过点击按钮的话,那会发生什么事情?解决此问题的代码貌似十分直接,我们可能会这样做:

<script type="text/javascript"> 
function BigComputer(answer) { 
this.the_answer = answer; 
this.ask_question = function () { 
alert(this.the_answer); //代表产生时间的DOM元素
} 
} 
function addhandler() { 
var deep_thought = new BigComputer(42), 
the_button = document.getElementById('thebutton'); 
the_button.onclick = deep_thought.ask_question; 
} 
window.onload = addhandler; 
</script> 

其实问题显而易见:我们给ask_question传递一个引用,它作为一个事件处理函数来执行,与作为对象方法来运行的上下文并不一样。简而言之,ask_question中的 this关键字指向了产生事件的DOM元素,而不是在BigComputer的对象中。DOM元素并不存在一个the_answer属性,所以我们得到的是 undefined而不是”42″. setTimeout也有类似的行为,它在延迟函数执行的同时跑到了一个全局的上下文中去了,这个问题会在程序的所有角落时不时突然冒出,如果不细致地追踪程序的每一个角落的话,还是一个非常难以排错的问题,尤其在你的对象有跟DOM元素或者window对象同名属性的时候。

使用.apply()和.call()掌控上下文 在点击按钮的时候,我们真正需要的是能够咨询deep_thought一个问题,更进一步说,我们真正需要的是,在应答事件和setTimeout的呼叫时,能够在自身的本原上下文中呼叫对象的方法。有两个鲜为人知的javascript方法,apply和call,在我们执行函数呼叫时,可以曲线救国帮我们达到目的,允许我们手工覆盖this的默认值。我们先来看call:

<script type="text/javascript"> 
var first_object = { 
num: 42 
}; 
var second_object = { 
num: 24 
}; 

function multiply(mult) {              //multiply()相当于 multiply.call(this);此时this所代表的对象相当明确
return this.num * mult;                                     
} 
 
multiply.call(first_object, 5); // 返回 42 * 5                                     
multiply.call(second_object, 5); // 返回 24 * 5 
</script>

 

在这个例子中,我们首先定义了两个对象,first_object和second_object,它们分别有自己的num属性。然后定义了一个multiply函数,它只接受一个参数,并返回该参数与this所指对象的num属性的乘积。如果我们呼叫函数自身,返回的答案极大可能是undefined,因为全局window对象并没有一个num属性除非有明确的指定。我们需要一些途径来告诉multiply里面的this关键字应该引用什么。而multiply的call方法正是我们所需要的。
call的第一个参数定义了在业已执行的函数内this的所指对象。其余的参数则传入业已执行的函数内,如同函数的自身呼叫一般。所以,当执行multiply.call(first_object, 5)时,multiply被呼叫,5传入作为第一个参数,而this关键字被设置为first_object的引用。同样,当执行multiply.call(second_object, 5)时,5传入作为第一个参数,而this关键字被设置为second_object的引用。
apply以call一样的方式工作,但可以让你把参数包裹进一个数组再传递给呼叫函数,在程序性生成函数呼叫时尤为有用。使用apply重现上一段代码,其实区别并不大:

<script type="text/javascript"> 
... 

multiply.apply(first_object, [5]); // 返回 42 * 5 
multiply.apply(second_object, [5]); // 返回 24 * 5 
</script> 

apply和call本身都非常有用,并值得贮藏于你的工具箱内,但对于事件处理函数所改变的上下文问题,也只是送佛到西天的中途而已,剩下的还是得我们来解决。在搭建处理函数时,我们自然而然地认为,只需简单地通过使用call来改变this的含义即可:

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

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

代码之所以有问题的理由很简单:call立即执行了函数(译注:其实可以用一个匿名函数封装,例如the_button.onclick = function(){deep_thought.ask_question.call(deep_thought);},但比起即将讨论的bind来,依然不够优雅)。我们给onclcik处理函数一个函数执行后的结果而非函数的引用。所以我们需要利用另一个javascript特色,以解决这个问题。 .bind()之美 我并不是 Prototype javascript framework的忠实粉丝,但我对它的总体代码质量印象深刻。具体而言,它为Function对象增加一个简洁的补充,对我管理函数呼叫执行后的上下文产生了极大的正面影响:bind跟call一样执行相同的常见任务,改变函数执行的上下文。不同之处在于bind返回的是函数引用可以备用,而不是call的立即执行而产生的最终结果。
如果需要简化一下bind函数以抓住概念的重点,我们可以先把它插进前面讨论的乘积例子中去,看它究竟是如何工作的。这是一个相当优雅的解决方案:

<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); // 返回 42 * 5 

var second_multiply = multiply.bind(second_object); 
second_multiply(5); // 返回 24 * 5 
</script> 

 

首先,我们定义了first_object, second_object和multiply函数,一如既往。细心处理这些后,我们继续为Function对象的prototype定义一个bind方法,这样的话,我们程序里的函数都有一个bind方法可用。当执行multiply.bind(first_object)时,javascript为bind方法创建一个运行上下文,把this置为multiply函数的引用,并把第一个参数obj置为first_object的引用。目前为止,一切皆顺。
这个解决方案的真正天才之处在于method的创建,置为this的引用所指(即multiply函数自身)。当下一行的匿名函数被创建,method通过它的作用域链访问,obj亦然(不要在此使用this, 因为新创建的函数执行后,this会被新的、局部的上下文覆盖)。这个this的别名让apply执行multiply函数成为可能,而传递obj则确保上下文的正确。用计算机科学的话说,temp是一个闭包(closure),它可以保证,需要在first_object的上下文中执行multiply,bind呼叫的最终返回可以用在任何的上下文中。
这才是前面说到的事件处理函数和setTimeout情形所真正需要的。以下代码完全解决了这些问题,绑定deep_thought.ask_question方法到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); 
} 
posted on 2013-09-08 00:55  Glimis  阅读(188)  评论(0编辑  收藏  举报