- 浏览: 248668 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
moxia:
softor 写道这是我格式化的代码:
<div id= ...
addEventListener 和attachEvent用法 -
softor:
这是我格式化的代码:
<div id="div ...
addEventListener 和attachEvent用法 -
softor:
你第一段实例里有连个事件监听,你说的“如果userCaptur ...
addEventListener 和attachEvent用法 -
tvmovie:
([\s\S]*) 非常需要,谢谢博主。
关于正则表达式匹配任意字符(包括换行符)的写法 -
zhongxinhu:
昨晚搞了一晚上,很郁闷,也是这个问题
关于正则表达式匹配任意字符(包括换行符)的写法
作用域(scope
)是JavaScript语言的基石之一,在构建复杂程序时也可能是最令我头痛的东西。记不清多少次在函数之间传递控制后忘记 this
关键字引用的究竟是哪个对象,甚至,我经常以各种不同的混乱方式来曲线救国,试图伪装成正常的代码,以我自己的理解方式来找到所需要访问的变量。
这篇文章将正面解决这个问题:简述上下文(context)和作用域的定义,分析可以让我们掌控上下文的两种方法,最后深入一种高效的 方案,它能有效解决我所碰到的90%的问题。
我在哪儿?你又是谁
JavaScript 程序的每一个字节都是在这个或那个运行上下文(execution context)中执行的。你可以把这些上下文想象为代码的邻居,它们可以给每一行代码指明:从何处来,朋友和邻居又是谁。没错,这是很重要的信息,因为 JavaScript社会有相当严格的规则,规定谁可以跟谁交往。运行上下文则是有大门把守的社区而非其内开放的小门。
我们通常可以把这些社会边界称为作用域 ,并且有充足的重要性在每一位邻居的宪章里立法,而这个宪章就是我们要说的上下文的作用域链 (scope chain)。在特定的邻里关系内,代码只能访问它的作用域链内的变量。与超出它邻里的变量比起来,代码更喜欢跟本地(local,即局部)的打交道。
具体地说,执行一个函数会创建一个不同的运行上下文,它会将局部作用域增加到它所定义的作用域链内。JavaScript通过作用域链的局部向全局 攀升方式,在特定的上下文中解析标识符。这表示,本级变量会优先于作用域链内上一级拥有相同名字的变量。显而易见,当我的好友们一起谈论”Mike West”(本文原作者)时,他们说的就是我,而非bluegrass singer 或是Duke professor , 尽管(按理说)后两者著名多了。
让我们看些例子来探索这些含义:
<script type="text/javascript">
var ima_celebrity = "Everyone can see me! I'm famous!",
the_president = "I'm the decider!";
function pleasantville() {
var the_mayor = "I rule Pleasantville with an iron fist!",
ima_celebrity = "All my neighbors know who I am!";
function lonely_house() {
var agoraphobic = "I fear the day star!",
a_cat = "Meow.";
}
}
</script>
我们的全明星,ima_celebrity
, 家喻户晓(所有人都认识她)。她在政治上积极活跃,敢于在一个相当频繁的基层上叫嚣总统(即the_president
)。她会为碰到的每一个人签名和回答问题。就是说,她不会跟她的粉丝有私下的联系。她相当清楚粉丝们的存在
并有他们自己某种程度上的个人生活,但也可以肯定的是,她并不知道粉丝们在干嘛,甚至连粉丝的名字都不知道。
而在欢乐市(pleasantville
)内,市长(the_mayor
)是众所周知的。她经常在她的城镇内散步,跟她的选民聊天、握手并亲吻小孩。因为欢乐市(pleasantville
)还算比较大且重要的邻居,市长在她办公室内放置一台红色电话,它是一条可以直通总统的7×24热线。她还可以看到市郊外山上的孤屋(lonely_house
),但从不在意里面住着的是谁。
而孤屋(lonely_house)是一个自我的世界。旷恐患者时常在里面囔囔自语,玩纸牌和喂养一个小猫(a_cat
)。他偶尔会给市长(the_mayor
)打电话咨询一些本地的噪音管制,甚至在本地新闻看到ima_celebrity
后会写些粉丝言语给她(当然,这是pleasantville
内的ima_celebrity
)。
this
? 那是虾米?
每一个运行上下文除了建立一个作用域链外,还提供一个名为this
的关键字。它的普遍用法是,this
作为一个独特的功能,为邻里们提供一个可访问到它的途径。但总是依赖于这个行为并不可靠:取决于我们如何进入一个特定邻居的具体情况,this
表示的完全可能是其他东西。事实上,我们如何进去邻居家
本身,通常恰恰就是this
所指。有四种情形值得特别注意:
-
呼叫对象的方法
在经典的面向对象编程中,我们需要识别和引用当前对象。
this
极好地扮演了这个角色,为我们的对象提供了自我查找的能力,并指向它们本身的属性。<script type="text/javascript"> var deep_thought = { the_answer: 42, ask_question: function () { return this.the_answer; } }; var the_meaning = deep_thought.ask_question(); </script>
这个例子建立了一个名为
deep_thought
的对象,设置其属性the_answer
为42,并创建了一个名为ask_question
的方法(method)。当deep_thought.ask_question()
执行时, JavaScript为函数的呼叫建立了一个运行上下文,通过”.
“运算符把this
指向被引用的对象,在此是deep_thought
这个对象。之后这个方法就可以通过this
在镜子中找到它自身的属性,返回保存在this.the_answer
中的值:42。 -
构造函数
类似地,当定义一个作为构造器的使用
new
关键字的函数时,this
可以用来引用刚创建的对象。让我们重写一个能反映这个情形的例子:<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(); </script>
我们编写一个函数来创建
BigComputer
对象,而不是直白地创建deep_thought
对象,并通过new
关键字实例化deep_thought
为一个实例变量。当new BigComputer()
被执行,后台透明地创建了一个崭新的对象。呼叫BigComputer
后,它的this
关键字被设置为指向新对象的引用。这个函数可以在this
上设置属性和方法,最终它会在BigComputer
执行后透明地返回。尽管如此,需要注意的是,那个
deep_thought.the_question()
依然可以像从前一样执行。那这里发生了什么事?为何this
在the_question
内与BigComputer内会有所不同?简单地说,我们是通过new
进入BigComputer
的,所以this
表示“新(new)的对象”。在另一方面,我们通过deep_thought
进入the_question
,所以当我们执行该方法时,this
表示 “deep_thought
所引用的对象”。this
并不像其他的变量一样从作用域链中读取,而是在上下文的基础上,在上下文中重置 。 -
函数呼叫
假如没有任何相关对象的奇幻东西,我们只是呼叫一个普通的、常见的函数,在这种情形下
this
表示的又是什么呢?<script type="text/javascript"> function test_this() { return this; } var i_wonder_what_this_is = test_this(); </script>
在这样的场合,我们并不通过
new
来提供上下文,也不会以某种对象形式在背后偷偷提供上下文。在此,this
默认下尽可能引用最全局的东西:对于网页来说,这就是window
对象。 -
事件处理函数
比普通函数的呼叫更复杂的状况,先假设我们使用函数去处理的是一个
onclick
事件。当事件触发我们的函数运行,此处的this
表示的是什么呢?不凑巧,这个问题不会有简单的答案。如果我们写的是行内(inline)事件处理函数,
this
引用的是全局window
对象:<script type="text/javascript"> function click_handler() { alert(this); // 弹出 window 对象 } </script> ... <button id='thebutton' onclick='click_handler()'>Click me!</button>
但是,如果我们通过JavaScript来添加事件处理函数,
this
引用的是生成该事件的DOM元素。(注意:此处的事件处理非常简洁和易于阅读,但其他的就别有洞天了。请使用真正的addEvent函数 取而代之):<script type="text/javascript"> function click_handler() { alert(this); // 弹出按钮的DOM节点 } function addhandler() { document.getElementById('thebutton').onclick = click_handler; } window.onload = addhandler; </script> ... <button id='thebutton'>Click me!</button>
复杂情况
让我们来短暂地运行一下这个最后的例子。我们需要询问deep_thought
一个问题,如果不是直接运行click_handler
而是通过点击按钮的话,那会发生什么事情?解决此问题的代码貌似十分直接,我们可能会这样做:
<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;
</script>
很完美吧?想象一下,我们点击按钮,deep_thought.ask_question
被执行,我们也得到了“42”。但是为什么浏览器却给我们一个undefined
? 我们错在何处?
其实问题显而易见:我们给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) {
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);
}
漂亮。
发表评论
-
div+css布局
2010-08-31 15:12 1833今天发现了一个不错的css+div布局网站。学习css+div ... -
JS的IE和FF兼容_div+css教程
2009-08-13 07:57 1482以下以 IE 代替 Internet Explorer,以 M ... -
DIV+CSS与TABLE布局的区别_div+css教程
2009-08-13 07:55 1775站布局从以前的TABLE方 ... -
img标签下多余空白的解决方法
2009-08-13 07:53 1021在进行页面的css.shtml' ta ... -
HTML&&JS小常识
2009-07-24 10:47 905别人的笔记,转载下 1.几个document属性 docu ... -
javascript在IE和Firefox中的兼容考虑
2009-07-23 11:14 7241.document.formName.item(" ... -
几种刷新的区别
2009-07-23 10:53 1262window.location.reload():重新从服 ... -
addEventListener 和attachEvent用法
2009-07-23 10:29 20079对于IE的attachEvent方法 ... -
JavaScript中的arguments,callee,caller,call,appy [备忘]
2009-07-23 10:19 14001.演示arguments的用法 Js代码 ... -
typeof和constructor总结
2009-07-23 10:14 1104下面都对变量test的值进行说明。有一点可以先说明的是,t ... -
从零开始学习jQuery框架 收藏
2009-07-21 17:20 1227从零开始学习jQuery框架 收藏 摘要: 本文由简到繁地 ...
相关推荐
### 理解JavaScript中的`caller`...综上所述,理解`caller`、`callee`、`call`、`apply`以及`arguments`对象在JavaScript编程中至关重要,它们不仅增强了函数的灵活性和复用性,还提供了深入分析和调试代码的强大工具。
call和apply方法是JavaScript中的两个重要方法,它们可以改变函数体内的this对象的值,以扩充函数赖以运行的作用域。它们的作用是一样的,但是参数的传递方式不同。call方法的参数是逐个传递的,而apply方法的参数...
在JavaScript中,`apply`和`call`是两种非常重要的函数调用方式,它们都用于改变函数内部`this`的指向以及动态传递参数。这两者的主要区别在于参数的传递方式。 首先,`this`在JavaScript中是一个关键字,它在不同...
本文将详细解释JavaScript中call(), apply(), 和 bind() 方法的作用、语法以及使用场景,并且会探讨回调函数的使用,帮助理解这些概念在实际编程中的应用。 首先,我们来探讨call() 和 apply() 方法。这两个方法都...
JavaScript中的this、new、apply和call是理解JavaScript面向对象编程的关键知识点。首先,我们要知道,this关键字在JavaScript中表示当前上下文的对象,但它并不像Java中的this那样始终指向同一个对象。JavaScript的...
JavaScript中的`callee`、`call`和`apply`都是与函数有关的重要概念,它们提供了不同的方式来处理函数调用和上下文。以下是这三个特性的详细解释: **arguments.callee** `arguments.callee`是一个在函数内部可以...
为了进一步控制函数执行的上下文,JavaScript提供了特殊的内置方法,即call和apply方法,这两个方法都是Function对象的方法,可以用来在特定的作用域中调用函数,即改变函数体内this的指向。 call和apply方法的主要...
在JavaScript中,函数的作用域分为全局作用域和局部作用域。如果一个变量在当前函数作用域内没有定义,那么JavaScript引擎会去上一级作用域中查找,这个查找过程会一直持续到全局作用域为止,这种从内向外查找变量的...
而call和apply则是Function对象上的两个方法,用于在特定的作用域中调用函数,并可改变函数内的this值。 1. this关键字的指向 在JavaScript中,this关键字用于获取当前执行环境中的对象。它的指向根据函数的调用...
JavaScript中的作用域和`this`关键字是编程中非常重要的概念,尤其在JavaScript中,它们具有独特的特点和行为。本文将深入探讨这两个概念及其在实际编程中的应用。 首先,我们来看一下**作用域(Scope)**: 作用...
JQuery源码分析与JavaScript中的apply与call方法是JavaScript编程中非常重要的概念,这两者都是函数对象的方法,用于在特定的作用域中执行函数,并可以指定函数体内this的指向。从JQuery源码中分析apply和call方法,...
在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,一般来说,this总是指向调用某个方法的对象,但是使用call()和apply()方法时,就会改变this的指向 语法: apply() 接收两...
在实际开发中,`call`和`apply`常常用于继承、对象的模拟以及在不支持箭头函数(箭头函数不会创建自己的`this`,而是捕获其所在(定义时)的作用域的`this`值)的环境下实现事件处理等场景。例如,当你需要在不同的...
在JavaScript中,`this`关键字的作用域是一个经常让人困惑的主题,尤其对于那些习惯于其他面向对象语言...在实际开发中,深入理解`this`的作用域和它如何根据上下文变化,将有助于写出更安全、更可靠的JavaScript代码。
JavaScript中的`call`和`apply`都是函数对象的方法,它们的主要用途在于改变函数内部`this`的指向,并且能够灵活地传递参数。这两者的主要区别在于处理参数的方式。 `apply`方法接受两个参数:第一个参数是希望`...
以下内容会分为如下小节: ...6.call,apply,bind方法的联系和区别 1.call/apply/bind方法的来源 首先,在使用call,apply,bind方法时,我们有必要知道这三个方法究竟是来自哪里?为什么可以使用的到这三个方法?
JavaScript中的面向对象编程是一个非常重要的概念,理解好Object...对于JavaScript开发者来说,深入理解和掌握作用域、引用类型、函数作用域的扩展以及call和apply的使用是基本功,也是进阶到高级编程技巧的重要一步。
`call` 和 `apply` 是函数对象自带的方法,它们的作用都是能够在特定的作用域中调用函数,改变函数体内 `this` 的指向。通过这种方式,可以将父对象的属性和方法复制给子对象。 ```javascript function Person(name...
JavaScript中的函数作用域和`this`指向是编程中至关重要的概念,它们决定了代码中变量的可见性和`this`关键字的绑定。以下是对这两个主题的详细解释: ### 函数作用域 函数作用域决定了变量在何处可以被访问。...