JavaScript中的作用域.docx

上传人:b****4 文档编号:26821674 上传时间:2023-06-23 格式:DOCX 页数:15 大小:30.84KB
下载 相关 举报
JavaScript中的作用域.docx_第1页
第1页 / 共15页
JavaScript中的作用域.docx_第2页
第2页 / 共15页
JavaScript中的作用域.docx_第3页
第3页 / 共15页
JavaScript中的作用域.docx_第4页
第4页 / 共15页
JavaScript中的作用域.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

JavaScript中的作用域.docx

《JavaScript中的作用域.docx》由会员分享,可在线阅读,更多相关《JavaScript中的作用域.docx(15页珍藏版)》请在冰豆网上搜索。

JavaScript中的作用域.docx

JavaScript中的作用域

JavaScript中的作用域

2007-07-1801:

18JS/Dom

原文:

http:

//www.digital-

作用域(scope)是JavaScript语言的基石之一,在构建复杂程序时也可能是最令我头痛的东西。

记不清多少次在函数之间传递控制后忘记this关键字引用的究竟是哪个对象,甚至,我经常以各种不同的混乱方式来曲线救国,试图伪装成正常的代码,以我自己的理解方式来找到所需要访问的变量。

这篇文章将正面解决这个问题:

简述上下文(context)和作用域的定义,分析可以让我们掌控上下文的两种方法,最后深入一种高效的方案,它能有效解决我所碰到的90%的问题。

我在哪儿?

你又是谁

JavaScript程序的每一个字节都是在这个或那个运行上下文(executioncontext)中执行的。

你可以把这些上下文想象为代码的邻居,它们可以给每一行代码指明:

从何处来,朋友和邻居又是谁。

没错,这是很重要的信息,因为JavaScript社会有相当严格的规则,规定谁可以跟谁交往。

运行上下文则是有大门把守的社区而非其内开放的小门。

我们通常可以把这些社会边界称为作用域,并且有充足的重要性在每一位邻居的宪章里立法,而这个宪章就是我们要说的上下文的作用域链(scopechain)。

在特定的邻里关系内,代码只能访问它的作用域链内的变量。

与超出它邻里的变量比起来,代码更喜欢跟本地(local,即局部)的打交道。

具体地说,执行一个函数会创建一个不同的运行上下文,它会将局部作用域增加到它所定义的作用域链内。

JavaScript通过作用域链的局部向全局攀升方式,在特定的上下文中解析标识符。

这表示,本级变量会优先于作用域链内上一级拥有相同名字的变量。

显而易见,当我的好友们一起谈论”MikeWest”(本文原作者)时,他们说的就是我,而非bluegrasssinger或是Dukeprofessor,尽管(按理说)后两者著名多了。

让我们看些例子来探索这些含义:

varima_celebrity="Everyonecanseeme!

I'mfamous!

",

the_president="I'mthedecider!

";

functionpleasantville(){

varthe_mayor="IrulePleasantvillewithanironfist!

",

ima_celebrity="AllmyneighborsknowwhoIam!

";

functionlonely_house(){

varagoraphobic="Ifearthedaystar!

",

a_cat="Meow.";

}

}

我们的全明星,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极好地扮演了这个角色,为我们的对象提供了自我查找的能力,并指向它们本身的属性。

vardeep_thought={

the_answer:

42,

ask_question:

function(){

returnthis.the_answer;

}

};

varthe_meaning=deep_thought.ask_question();

这个例子建立了一个名为deep_thought的对象,设置其属性the_answer为42,并创建了一个名为ask_question的方法(method)。

当deep_thought.ask_question()执行时,JavaScript为函数的呼叫建立了一个运行上下文,通过”.“运算符把this指向被引用的对象,在此是deep_thought这个对象。

之后这个方法就可以通过this在镜子中找到它自身的属性,返回保存在this.the_answer中的值:

42。

∙构造函数

类似地,当定义一个作为构造器的使用new关键字的函数时,this可以用来引用刚创建的对象。

让我们重写一个能反映这个情形的例子:

functionBigComputer(answer){

this.the_answer=answer;

this.ask_question=function(){

returnthis.the_answer;

}

}

vardeep_thought=newBigComputer(42);

varthe_meaning=deep_thought.ask_question();

我们编写一个函数来创建BigComputer对象,而不是直白地创建deep_thought对象,并通过new关键字实例化deep_thought为一个实例变量。

当newBigComputer()被执行,后台透明地创建了一个崭新的对象。

呼叫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表示的又是什么呢?

functiontest_this(){

returnthis;

}

vari_wonder_what_this_is=test_this();

在这样的场合,我们并不通过new来提供上下文,也不会以某种对象形式在背后偷偷提供上下文。

在此,this默认下尽可能引用最全局的东西:

对于网页来说,这就是window对象。

∙事件处理函数

比普通函数的呼叫更复杂的状况,先假设我们使用函数去处理的是一个onclick事件。

当事件触发我们的函数运行,此处的this表示的是什么呢?

不凑巧,这个问题不会有简单的答案。

如果我们写的是行内(inline)事件处理函数,this引用的是全局window对象:

functionclick_handler(){

alert(this);//弹出window对象

}

...

Clickme!

但是,如果我们通过JavaScript来添加事件处理函数,this引用的是生成该事件的DOM元素。

(注意:

此处的事件处理非常简洁和易于阅读,但其他的就别有洞天了。

请使用真正的addEvent函数取而代之):

functionclick_handler(){

alert(this);//弹出按钮的DOM节点

}

functionaddhandler(){

document.getElementById('thebutton').onclick=click_handler;

}

window.onload=addhandler;

...

Clickme!

复杂情况

让我们来短暂地运行一下这个最后的例子。

我们需要询问deep_thought一个问题,如果不是直接运行click_handler而是通过点击按钮的话,那会发生什么事情?

解决此问题的代码貌似十分直接,我们可能会这样做:

functionBigComputer(answer){

this.the_answer=answer;

this.ask_question=function(){

alert(this.the_answer);

}

}

functionaddhandler(){

vardeep_thought=newBigComputer(42),

the_button=document.getElementById('thebutton');

the_button.onclick=deep_thought.ask_question;

}

window.onload=addhandler;

很完美吧?

想象一下,我们点击按钮,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:

varfirst_object={

num:

42

};

varsecond_object={

num:

24

};

functionmultiply(mult){

returnthis.num*mult;

}

multiply.call(first_object,5);//返回42*5

multiply.call(second_object,5);//返回24*5

在这个例子中,我们首先定义了两个对象,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重现上一段代码,其实区别并不大:

...

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

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

apply和call本身都非常有用,并值得贮藏于你的工具箱内,但对于事件处理函数所改变的上下文问题,也只是送佛到西天的中途而已,剩下的还是得我们来解决。

在搭建处理函数时,我们自然而然地认为,只需简单地通过使用call来改变this的含义即可:

functionaddhandler(){

vardeep_thought=newBigComputer(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()之美

我并不是PrototypeJavaScriptframework的忠实粉丝,但我对它的总体代码质量印象深刻。

具体而言,它为Function对象增加一个简洁的补充,对我管理函数呼叫执行后的上下文产生了极大的正面影响:

bind跟call一样执行相同的常见任务,改变函数执行的上下文。

不同之处在于bind返回的是函数引用可以备用,而不是call的立即执行而产生的最终结果。

如果需要简化一下bind函数以抓住概念的重点,我们可以先把它插进前面讨论的乘积例子中去,看它究竟是如何工作的。

这是一个相当优雅的解决方案:

varfirst_object={

num:

42

};

varsecond_object={

num:

24

};

functionmultiply(mult){

returnthis.num*mult;

}

Function.prototype.bind=function(obj){

varmethod=this,

temp=function(){

returnmethod.apply(obj,arguments);

};

returntemp;

}

varfirst_multiply=multiply.bind(first_object);

first_multiply(5);//返回42*5

varsecond_multiply=multiply.bind(second_object);

second_multiply(5);//返回24*5

首先,我们定义了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的上下文中,因此能在任何事件触发时都能正确运行:

functionaddhandler(){

vardeep_thought=newBigComputer(42),

the_button=document.getElementById('thebutton');

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

}

漂亮。

YoucanfollowanyresponsestothisentrythroughtheRSS2.0feed.Youcanleavearesponse,ortrackbackfromyourownsite.

«小巧三条

HTML5新增的元素»

19条留言

1.1

怿飞@2007-07-1808:

53说:

谢谢安安的翻译,看了些许醍醐灌顶=。

=

2.2

old9@2007-07-1811:

39说:

赞,收藏~

3.3

FlashSoft@2007-07-1814:

16说:

受教了

4.4

Arrix@2007-07-1817:

06说:

nicewrite-up.翻译是一种再创作

5.5

刀河@2007-07-2121:

33说:

受教了。

能把您blog的皮肤发一个到我邮箱马?

很欣赏您的皮肤。

好看,简单。

6.6

awflasher@2007-07-2310:

38说:

好文章!

7.7

Robin@2007-07-2810:

47说:

大哥有个细节上的文字错误..第一段源代码里并没有(img_)而是(ima_).被手快的人已经转到BI上去了.大哥的文章真是不错呀.

8.8

fdcn@2007-07-2817:

36说:

bind定义中的

varmethod=this

让我较痛苦,我以为自己对this很了解了,这篇文章阐述的东西也自认都知道,但这个method=this等于调用函数让我一下子弄混了,为什么是调用函数(或方法)本身?

而不是全局对象或其它的呼叫方法(函数)所在的对象?

9.9

fdcn@2007-07-2817:

41说:

是我想混了,bind方法所在的对象当然是函数(调用bind方法的)这个对象。

不好意思……

10.10

Sam@2007-07-3117:

45说:

你的空间挺好的,是那家什么型号的呢?

谢谢

11.11

哉崽@2007-08-0210:

52说:

文章内容不错,不过感觉老外的举例似乎不够简洁,有把简单问题说复杂之嫌。

对于JS新手,我想是不容易理解的,即便对this比较了解的虾级人物,光看代码还比较容易理解,但看了文字可能就迷糊了。

个人观点,呵呵。

对于this的绑定,我认为是灵活的,其实bind也是给包了一匿名函数,这个在批量应用中非常好,但是在只有一两个地方需要切换this的引用时,直接包一个匿名函数更加简洁。

甚至很多实际应用中,往往直接搞一个var_this=this,直接把this传传进去更明了。

另外,我想这里推荐一关于this的另外一篇好文,《Thethiskeyword》http:

//www.quirksmode.org/js/this.html

对于JS新手还是有很大帮助的。

偶e文水平实在不好,怕翻译过来祸国殃民,如果楼主有心可以翻译一下。

为了解决select盖住其他对象的棘手问题,前两天写了个JS的仿select,在处理事件时碰到了障碍,是看了楼主的《事件冒泡》一文才得以解决的,在此表示感谢:

12.12

t@2007-08-2015:

31说:

谢谢你,看完之后我更糊涂了。

13.

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1