Javascript中的作用域(scope)
2009-06-22 16:54
441 查看
事前准备
学习本教程的最佳方法是随手准备好如果机子上还没有FireFox和FireBug,就应该尽快安装一套来用。
定义
作用域scope1.(名词)某样事物执行、操作、拥有控制权的那么一个区域
2.(名词)编写程序时,程序之中变量的可见度;例如,一个函数能否使用另外一个函数所创建的变量。
可是这能够说明什么问题呢?每当有人在说“这是作用域的问题”或“作用域搞错了”的时候,那就是说某个函数运行起来的时候,找不到正确变量的位置。这样我们便知道应该从哪一方面入手,查找出问题所在。
正式开始
实际上每一个你定义的函数都是某个对象的方法。甚至是这样的写法:functionfn(){
alert(11);
}
老兄你不是故弄玄虚吧~。做一个这样的演示可真得是简单得要命。没错!本例不需要任何Javascript文件,服务器或html。你只要打开firefox,弹出firebug,点击consoletab。在Firefox状态栏上面看到有>>>提示的地方就可以输入了。
输入:
functionfn(){alert(11);};
然后回车。一切安然...你刚才做的实际上是定义了一个函数fn。接着试试:
fn();
然后回车。得到11的警告窗口?还不错吧?接着试试:
window.fn();
this.fn();
得到一样的结果吧?这是因为函数fn是window对象的一个方法,在第二行的"this"的作用域实际指向了windows对象。不过多数情况中你不需要像这样window.myFunction(...)地调用函数,这样太麻烦了,程序员工作起来会很不方便。
window对象
window对象总是存在的,你可理解其为一个浏览器窗口对象。它包含了其它所有的对象如document和所有的全局变量。你可以打开Firebug,切换到Script页面并在Firebug右侧的Newwatchexpression...里面输入window。观察window对象究竟有什么在里面。
接着,尝试找出我们之前定义过的fn函数。
另外,每个frame或iframe拥有其自身的window对象,其自身的全局空间。
理解作用域
接下的内容开始有点复杂了。切换到FirebugConsole标签页然后输入:varo1={testvar:22,fun:function(){alert('o1:'+this.testvar);}};
varo2={testvar:33,fun:function(){alert('o2:'+this.testvar);}};
结果是什么?你声明了o1和o2两个对象,分别都有一些属性和方法,但值不同。
接着试试:
fun();
window.fun();
this.fun();
出错了,是吧?因为window对象(等价于this)并没有fun的方法。试一试下面的:
o1.fun();
o2.fun();
22和33出来了?非常好!
接下来这部分的内容最复杂啦。基于这个原始的函数,如果对象的数量多的话,你必须为每个对象加上这个函数-明显是重复劳动了。这样说吧,o1.fun写得非常清晰的而且为了搞掂它已经占用了我一个星期的开发时间。想象一下代码到处散布着this变量,怎么能不头疼?如果要将调用(执行)的o1.fun方法但this会执行o2,应该怎么实现呢?试一试下面的:
o1.fun.call(o2);
明白了吗?当执行o1的fun方法时你强行将变量this指向到o2这个对象,换句话说,更加严谨地说:o1.fun的方法在对象o2的作用域下运行。
当运行一个函数,一个对象的方法时,你可将作用域当作this值的变量。
变量的可见度
变量的可见度和作用域的关系非常密切。我们已经了解到,可在任何对象的外部,声明变量,或在全局的函数(函数也是变量的一种)也可以,更严格说,它们是全局对象window的属性。全局变量在任何地方都可见;无论函数的内部还是外部。如果你在某一个函数内修改了一个全局变量,其它函数也会得知这个值是修改过的。对象可以有它自己的属性(像上面的testvar),这些属性允许从内部或是外部均是可见的。试:
alert(o1.testvar);//从外部访问o1的属性testvar
从内部访问的演示可在两个测试对象的fun方法找到。
用关键字var在内部声明,相当于声明局部变量(局部声明也是在一条链上,即ScopeChain作用域链上,Frank注):
i=44;
functionfn2(){
vari=55;
alert(i);
}
fn2();
将得到什么?对了,55。声明在函数fn2的变量i是一个本地变量(局部变量),和等于44的全局变量i44没什么关系。But:
alert(i);
这会访问全局变量i,显示44。
希望本文能帮助读者彻底理解作用域变量可见性的含义。
EXT程序规划入门
事前准备
本教程假设你已经安装好ExtJS库。安装的目录是extjs并位于你程序的上一级目录。如果安装在其它地方你必须更改路径,更改示例文件中script标签的src的属性。需要些什么?
除ExtJS库本身外,我们还需要两个文件:·applayout.html
·applayout.js
先看看一份html文档,比较精简。并附有详细说明:
applayout.html
<!DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.01Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=utf-8">
<linkrel="stylesheet"type="text/css"href="../extjs/resources/css/ext-all.css">
<scripttype="text/javascript"src="../extjs/adapter/ext/ext-base.js"></script>
<scripttype="text/javascript"src="../extjs/ext-all-debug.js"></script>
<scripttype="text/javascript"src="applayout.js"></script>
<!--本地化的脚本引用在这里-->
<scripttype="text/javascript">
Ext.onReady(myNameSpace.app.init,myNameSpace.app);
</script>
<title>ApplicationLayoutTutorial</title>
</head>
<body>
</body>
</html>
开头的两行声明了文档的类型。程序可以不用doctype,但是这样的话浏览器可能默认其为
我们采用HTML4.01Transitional的文档类型,该类型在主流浏览器上支持得不错。当然,你也可以根据你的需求采用
接着指定HTML头部的Content-Type。做这些事情其实很琐碎,但总是有益处。
然后引入EXT的样式,适配器和EXTJS本身。有两种版本的ExtJS:
·ext-all.js-不能直接阅读,加载时更快,用于产品发布
·ext-all-debug.js-便于阅读,开发阶段使用,
开发阶段的时候便需要引入debug的版本。
applayout.js这个文件就是我们的程序,紧跟着的是本地化的脚本,这里可以换成Extjs翻译好的版本
跟着我们开始分配事件句柄(eventhandler),使得在文档全部加载完毕后,程序就可以初始化(运行)。
下面的这一行:
Ext.onReady(myNameSpace.app.init,myNameSpace.app);
可这样说:当文档全部加载完毕,就运行myNameSpace.app的init方法,规定的
然后是标题,头部的结尾,body(当前空)和结束标签。
文档的解说就说到这儿了。
applayout.js
/**
*ApplicationLayout
*byJozefSakalos,akaSaki
* 'target='_blank'>http://extjs.com/learn/Tutorial:Application_Layout_for_Beginners_(Chinese)*///填充图片的本地引用Ext.BLANK_IMAGE_URL='../extjs/resources/images/default/s.gif';//创建命名空间Ext.namespace('myNameSpace');//创建应用程序myNameSpace.app=function(){//元素还没创建,未能访问//私有变量//私有函数//公共空间return{//公共的属性,如,要转换的字符串//公共方法init:function(){alert('应用程序初始化成功。');}};}();//程序底部//文件底部
文件最开始的几行是注释,说明该文件的主要内容,作者,作者相关的资讯。没有任何注释的程序也可以正常的运行,-但请记住:每次写的程序要容易给人看得懂的Alwayswriteyourapplicationasifyouwouldwriteitforanother.当你回头看你几个月前写的代码,发现你根本不记得自己写过什么的时候,就会明白这个道理,并养成编码的好习惯。接着是要指向你服务器的填充图片,如不指定则默认extjs.com。每次运行程序的时候都访问extjs.com,不推荐这样,应该先修改这个常量值指向到本地。
现在自定义命名空间。将所有变量和方法都划分到一个全局对象下管理,这样的好处是避免了变量名冲突和由不同函数干扰了全局变量的值。名字(namespace)可按自己的方案选择。
整段代码的重点是,我们创建了myNameSpace对象的属性app,其值是一个函数立刻运行之后的返回值。
如果运行我们的代码:varo=function(){return{p1:11,p2:22};}();
实际上我们创建了一个匿名函数(没有名字的函数),经过解释(预编译?)之后让它立刻运行(注意函数后面的())。最后将函数返回的对象(注意此时是一个object变量)分配到变量o。我们的程序便是这种思路去写的。
你可以把私有变量和私有函数直接定义在function和return这两个声明之间,但是请切记:此时不能访问任何html页面中的元素,那会导致错误,因为这段代码在加载时页面的head中就运行了,而这时候html页面中的其它元素还没有被加载进来。
另外一方面,函数init,是由匿名函数返回的对象的一个方法而已。它会在文档全部加载后才运行。换言之整个DOM树已经是可用的了。
一切都还好吧~如果能正确运行http://yourserver.com/applayout/applayout.html,不出现什么错误的话将出现一个警告。
接下来是利用这个空白的模板,讨论本文的重点。公开Public、私有Private、特权的Privileged?
让我们加入一些有趣内容到程序中去吧。在页面applayout.html的body标签中加入:<divid="btn1-ct"></div>
空白的div会当作按钮的容器来使用。然后在applayout.js加入下来代码:/***ApplicationLayout*byJozefSakalos,akaSaki* 'target='_blank'>http://extjs.com/learn/Tutorial:Application_Layout_for_Beginners_(Chinese)*///填充图片的本地引用Ext.BLANK_IMAGE_URL='../extjs/resources/images/default/s.gif';//创建命名空间Ext.namespace('myNameSpace');//创建应用程序myNameSpace.app=function(){//元素还没创建,未能访问//私有变量varbtn1;varprivVar1=11;//私有函数varbtn1Handler=function(button,event){alert('privVar1='+privVar1);alert('this.btn1Text='+this.btn1Text);};//公共空间return{//公共的属性,如,要转译的字符串btn1Text:'Button1'//公共方法,init:function(){btn1=newExt.Button('btn1-ct',{text:this.btn1Text,handler:btn1Handler});}};}();//程序底部//文件底部
变量btn1和privVar1是私有的。那意味着在程序外部他们是不能够被访问的,而且也不可见。私有函数btn1Handler也是同样道理。
另外一个方面,btn1Text是公共变量所以可以被程序外部访问或者是修改(我们稍后将会演示)。
函数init是特权的,即是私有变量和公共变量两者都可以被它访问到。在本例中,公共变量this.btn1Text和私有函数btn1Handler都能够被特权函数init所访问。同时,相对外界来说,它也属于公共成员的一种。
Ok,运行看看。能看到左上角的按钮吗?很好,点击它。得到一个privVar1=11的警告。这说明私有函数能访问私有变量。
但是在第二个警告中遇到了this.btn1Text=undefined的问题,这好像不应该这样。个中原因是因为位于事件句柄(eventhandler)中的变量this没有正确指向到我们的程序。你需要用scope:this指明这个作用域(这里的this关键字所指示的scope应该是btn1变量):btn1=newExt.Button('btn1-ct',{text:this.btn1Text,handler:btn1Handler,scope:this});
刷新一下,可以了吧?重写公共变量
在applayout.js底部加入下列代码:Ext.apply(myNameSpace.app,{btn1Text:'Taste1'});//文件底部
这代码是用来干什么的呢?首先它创建了我们的程序对象然后改变(重写)公共变量btn1Text的值。运行后将看到按钮上文字的变化。
把文本都放到公共变量,以便于以后的国际化翻译工作,而不需要修改程序的内部代码。
当然你也可以将其它的值例如尺寸、样式、等等的总之是可自定义的选项都放到公共变量中。免于在数千行代码之中为查找某个颜色而费劲。重写(Overriding)公共函数
接着更改一下代码,让它读起来是这样的:Ext.apply(myNameSpace.app,{btn1Text:'Taste1',init:function(){try{btn1=newExt.Button('btn1-ct',{text:this.btn1Text,handler:btn1Handler,scope:this});}catch(e){alert('错误:"'+e.message+'"发生在行:'+e.lineNumber);}}});//endoffile
我们这里将init重写了一篇,主要是在原来代码的基础上加入异常控制,以便能够捕获异常。试运行一下看还有没有其它的变化?
嗯是的,出现了btn1Handler未定义的错误。这是因为新的函数虽然尝试访问私有变量但它实际是不允许的。正如所见,原init是特权函数可访问私有空间,但新的init却不能。之所以不能访问私有空间,是因为:禁止外部代码Nocodefromoutside,哪怕是尝试重写特权方法。
相关文章推荐
- javascript作用域scope例子
- JavaScript 中的执行环境、作用域(scope)以及变量提升(hoisting)
- JavaScript scope作用域与this关键字
- Javascript中的作用域(scope)是什么?
- js教程:javascript作用域(Scope)
- Javascript中的作用域(scope)
- JavaScript 作用域 (Scope)
- Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收
- [译] 你该知道的javascript作用域 (javascript scope)(转)
- Javascript中的作用域(scope)
- JavaScript中的作用域(Scope in JavaScript)
- 你不知道的JavaScript--Item20 作用域与作用域链(scope chain)
- href=”javascript:void(0);的作用, href="#"和 href="###"
- spring中scope(作用越)理解
- 深入探究AngularJs之$scope对象(作用域)
- JavaScript要理解闭包先了解词法作用域
- Maven依赖关系中Scope的作用
- 干货!你想知道的关于 javascript scope 的一切
- maven依赖范围,scope标签作用
- 你不知道的JavaScript-1.作用域是什么