Javascript:作用域 学习总结

时间:2023-03-08 16:56:14
Javascript:作用域 学习总结
作用域(scope):
变量与函数的可访问范围,控制着变量与函数的可见性和生命周期
作用域分类:
javascript中,变量的作用域分为:全局作用域,局部作用域

局部变量的优先级大于全局变量,或者说内围作用域的变量的优先级比外围的高

----------------------------------
 
全局作用域(Global Scope)
全局作用域:在代码任何地方都能访问到
一般全局作用域出现在以下几种情形:
1#最外层函数和在最外层函数外面定义的变量拥有全局作用域 
Javascript:作用域 学习总结
 Javascript:作用域 学习总结
Javascript:作用域 学习总结
Javascript:作用域 学习总结
 
 
2#所有末定义直接赋值的变量自动声明为拥有全局作用域
 
Javascript:作用域 学习总结
Javascript:作用域 学习总结
此处的a为全局变量
Javascript:作用域 学习总结
Javascript:作用域 学习总结
函数只定义,没调用,没鸟用~

3#所有window对象的属性拥有全局作用域

 

一般情况下,window对象的内置属性都拥有全局作用域,例如window.name、window.location、window.top等等

------------------------------------------------
局部作用域( Local Scope)
 
一般只在固定的代码片段内可以访问到
 Javascript:作用域 学习总结
Javascript:作用域 学习总结
此时,变量b和函数bbb都只拥有局部作用域
一道测试题:
Javascript:作用域 学习总结
Javascript:作用域 学习总结
分析:

为什么第一个alert为undefined,而第二个为true。这问题也可以延伸为——alert(b)时怎么就会找外部的b,而alert(a)时就不会往外面找?!

我们都明白局部变量的优先级大于全局变量,或者说内围作用域的变量的优先级比外围的高。
 
当JS引擎在当前作用域找不到此变量时,它就往外围的作用域找。
不过,在这之前,有一个严肃的问题是,究竟当前作用域存不存在这个变量。像javascript这样的解释型语言,基本分为两个阶段,编译期(下面为符合大多数语言的称呼习惯,改叫预编译)与运行期在预编译阶段,它是用函数来划分作用域,然后逐层为其以 var 声明的变量(下略称为var变量)与函数定义开辟内存空间,再然后对var变量进行特殊处理,统统赋初始值为undefined,如下图:
Javascript:作用域 学习总结 
Javascript:作用域 学习总结

由上图,我们便可以推知,当前网页拥有两个a,一个b,一个test函数。如果在运行期用到除此以外的东东,如c函数或d变量啦,就会报未定义错误(用eval等非正常手段生成变量与函数的情况除外),此外,它们最多出现未赋值警告。

javascript的运行期是在为var变量与函数定义分配空间后立即执行,并且是逐行往下执行的。

    - 第1行它为外围作用域的a赋值为100
    - 第2行它为外围作用域的b赋值为true
    - 第3行进行test的作用域,我们简称为内围作用域。
    - 第4行就立即调用内围作用域的a,这时它还没有来得及赋值呢!不过它已经声明过了,因此默认为其赋值为undefined(在预编译阶段,见图),于是alert为undefined
    - 第5行就调用b时,JS引擎就拿起我画的图看了(笑),发现test的作用域内没有b,眼睛往外望,发现b了,而b在第二行就赋值为true,于是alert为true。
    - 第6行为一个赋值操作,把外围的b变量改赋为false。于是到第7行时,alert为false。以下说法不说了。
相关之一:
Javascript:作用域 学习总结
Javascript:作用域 学习总结
相关之二
Javascript:作用域 学习总结
 Javascript:作用域 学习总结
------------------------------------------------------
作用域链( Scope Chain )
javascript里一切都是对象

函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属;

其中一个内部属性是[[scope]],该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
示例:
Javascript:作用域 学习总结

在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只例举了全部变量中的一部分):

Javascript:作用域 学习总结
Javascript:作用域 学习总结
 

函数add的作用域将会在执行时用到。例如执行如下代码:

var total = add(5,10);

执行此函数时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。每个运行期上下文都有自己的作用域链,用于标识符解析,当运行期上下文被创建时,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。

  这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。新的作用域链如下图所示:

Javascript:作用域 学习总结

 在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程

 
作用域链和代码优化:
 

  从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。如上图所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。例如下面的代码:

Javascript:作用域 学习总结

Javascript:作用域 学习总结

  这个函数引用了两次全局变量document,查找该变量必须遍历整个作用域链,直到最后在全局对象中才能找到。这段代码可以重写如下:

Javascript:作用域 学习总结

Javascript:作用域 学习总结
这段代码比较简单,重写后不会显示出巨大的性能提升,但是如果程序中有大量的全局变量被从反复访问,那么重写后的代码性能会有显著改善。
Javascript:作用域 学习总结
 Javascript:作用域 学习总结
上述代码运行期各作用域变量的公布情况

Javascript:作用域 学习总结

Javascript:作用域 学习总结

通过这图也教育我们,一定要用局部变量啊,要不,一层层往上爬,效率是多么低啊。另外,这图也告诉我们,window是一个多么高级的存在啊,Object都比它低一等,更别提什么继承问题啦!(在FF与IE中)

该篇总结的形成得益于众多优质博客作者的知识分享,现将该篇文章涉及到的资料地址罗列如下:

参考资料:

http://www.ibm.com/developerworks/cn/web/1103_qinjian_javascriptscope/

http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html