聊一聊JavaScript作用域和作用域链


Posted in Javascript onMay 03, 2016

每种编程语言,其变量都有一定的有效范围,超过这个范围之后,变量就失效了,这就是变量的作用域。从数学的角度来看,就是自变量的域。

作用域是变量的可访问范围,即作用域控制着变量与函数的可见性和生命周期。在 JavaScript 中, 对象和函数同样也是变量,变量在声明他们的函数体以及这个函数体嵌套的任意函数体内部都是有定义的。

一、静态作用域和动态作用域

静态作用域

是指声明的作用域是根据程序正文在编译时就确定的,也称为词法作用域。大多数现代程序设计语言都是采用静态作用域规则,JavaScript就是采用的这种作用域。
采用静态作用域的语言中,基本都是最内嵌套作用域规则:由一个声明引进的标识符在这个声明所在的作用域里可见,而且在其内部嵌套的每个作用域里也可见,除非它被嵌套于内部的对同名标识符的另一个声明所掩盖。
为了找到某个给定的标识符所引用的对象,应该在当前最内层作用域里查找。如果找到了一个声明,也就可以找到该标识符所引用的对象。否则我们就到直接的外层作用域里去查找,并继续向外顺序地检查外层作用域,直到到达程序的最外嵌套层次,也就是全局对象声明所在的作用域。如果在所有层次上都没有找到有关声明,那么这个程序就有错误。如下:

function cha(){
 var name = "xiao;"
 function chb() {
 function chc() {
 console.log(name);
 }
 } 
}

首先函数从chb()搜索有没有name的定义,然后继续一层一层的向上搜索,最后在cha()中搜到了name的定义,如果没有搜到,则会报错。

2、动态作用域

动态作用域的语言中,程序中某个变量所引用的对象是在程序运行时刻根据程序的控制流信息来确定的。

二、JavaScript的作用域

JavaScript中有两种作用域,分别为全局作用域和局部作用域。

1、全局作用域(Global Scope)

在代码中任何位置都是有定义的。即使在html 页面中嵌套的一段js代码中定义了一个全局变量,在引用的js文件中仍能访问到该变量。这就很有可能会造成全局变量的污染。

以下三种情况的变量都会被视为全局变量
(1)最外层的函数和最外层的变量拥有全局作用域
(2)未经定义而直接赋值的变量自动被声明为拥有全局作用域
(3)所有window对象的属性拥有全局作用域

2、局部作用域(Local Scope)

局部作用域一般只能在固定的代码片段中才能访问,如函数内部的变量(函数作用域)

var name = "xuxiaoping";
function echoName() {
 var firstname = "xu";//局部作用域
 secondname = "xiao";//全局作用域
 function echoFirstName() {
 console.log(first name);//xu
 }
 console.log(secondname);
 return echoFirstName;
}
console.log(name);//全局作用域

var f = echoName();
f();
console.log(firstname);
console.log(secondname);

结果为:
xuxiaoping
xiao
xu//内层函数可以访问外层函数的变量
undenfined  //在函数外部无法访问函数的内部变量
xiao

JavaScript将全局变量附加到了window对象上,成为了window对象的属性。

3、函数作用域

块级作用域:任何一对花括号中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的。大多数类C语言都是有块级作用域的。
然而JavaScript的有个重要的特点就是没有块级作用域。

function echoi() {
 for(var i = 0;i<10;i++){
 ;//console.log(i);
 }
 if(true){
 var str = "hello";
 }
 console.log(i);
 console.log(str);
}
echoi();

输出结果为:

10
 hello

可见,在for语句外(也可以是if,while),块中定义的变量i仍然是可以访问的。也就是说,JavaScript并不支持块级作用域,它只支持函数作用域,而且在一个函数中的任何位置定义的变量在该函数中的任何地方都是可见的。作为一个一开始编程就学习学习C和java的人来说,这个有点难以适应。据我测试PHP也是这样的。

当然可以利用JavaScript的闭包的特性,模拟个块级作用域

function echoi() {
 (function() {
 for(var i = 0;i<10;i++){
 ;//console.log(i);
 }
 })();
 if(true){
 var str = "hello";
 }
 console.log(i);
 console.log(str);
}
echoi();

结果为:i undefined

这样就隔离了变量的定义。在js中,为了防止命名冲突,应该尽量避免使用全局变量和全局函数,因此这种闭包的用的特别的多。

4、JavaScript 变量生命周期

JavaScript 变量生命周期在它声明时初始化。
局部变量在函数执行完毕后销毁。
全局变量在页面关闭后销毁。

三、JavaScript的作用域链

一看是链,大概就可以跟数据结构中的链表相结合起来

在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。

当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。例如定义下面这样一个函数:

function add(num1,num2) {
 var sum = num1 + num2;
 return sum;
}

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

聊一聊JavaScript作用域和作用域链

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

var total = add(5,10);

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

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

聊一聊JavaScript作用域和作用域链

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

四、作用域链和代码优化

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

function changeColor(){
document.getElementById("btnChange").onclick=function()
{ 
document.getElementById("targetCanvas").style.backgroundColor="red";
 };
}

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

function changeColor(){
 var doc=document;
 doc.getElementById("btnChange").onclick=function(){
 doc.getElementById("targetCanvas").style.backgroundColor="red";
 };
}

这段代码比较简单,重写后不会显示出巨大的性能提升,但是如果程序中有大量的全局变量被从反复访问,那么重写后的代码性能会有显著改善。

五、with改变作用域链

数每次执行时对应的运行期上下文都是独一无二的,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响。

with语句是对象的快捷应用方式,用来避免书写重复代码。例如:

function initUI(){
 with(document){
 var bd=body,
 links=getElementsByTagName("a"),
 i=0,
 len=links.length;
 while(i < len){
 update(links[i++]);
 }
 getElementById("btnInit").onclick=function(){
 doSomething();
 };
 }
}

这里使用width语句来避免多次书写document,看上去更高效,实际上产生了性能问题。

当代码运行到with语句时,运行期上下文的作用域链临时被改变了。一个新的可变对象被创建,它包含了参数指定的对象的所有属性。这个对象将被推入作用域链的头部,这意味着函数的所有局部变量现在处于第二个作用域链对象中,因此访问代价更高了。如下图所示:

聊一聊JavaScript作用域和作用域链

因此在程序中应避免使用with语句,在这个例子中,只要简单的把document存储在一个局部变量中就可以提升性能。

总结

1、变量的作用域就是变量在哪些范围有效。
2、变量的作用域链就是被创建的作用域中对象的集合。

以上就是本文的全部内容,希望对大家学习javascript程序设计有所帮助。

Javascript 相关文章推荐
一个js拖拽的效果类和dom-drag.js浅析
Jul 17 Javascript
基于jquery DOM写的类似微博发布的效果
Oct 20 Javascript
js中点击空白区域时文本框与隐藏层的显示与影藏问题
Aug 26 Javascript
javascript使用正则表达式检测IP地址
Dec 03 Javascript
javascript字符串函数汇总
Dec 06 Javascript
Vue input控件通过value绑定动态属性及修饰符的方法
May 03 Javascript
10个最优秀的Node.js MVC框架
Aug 24 Javascript
js读取本地文件的实例
Dec 22 Javascript
VUE搭建手机商城心得和遇到的坑
Feb 21 Javascript
利用JavaScript将Excel转换为JSON示例代码
Jun 14 Javascript
微信小程序实现定位及到指定位置导航的示例代码
Aug 20 Javascript
vue动态渲染svg、添加点击事件的实现
Mar 13 Javascript
小白谈谈对JS原型链的理解
May 03 #Javascript
基于Bootstrap使用jQuery实现输入框组input-group的添加与删除
May 03 #Javascript
JQuery的Pager分页器实现代码
May 03 #Javascript
个人网站留言页面(前端jQuery编写、后台php读写MySQL)
May 03 #Javascript
JQuery核心函数是什么及使用方法介绍
May 03 #Javascript
jquery对象访问是什么及使用方法介绍
May 03 #Javascript
Bootstrap与KnockoutJs相结合实现分页效果实例详解
May 03 #Javascript
You might like
深入Nginx + PHP 缓存详解
2013/07/11 PHP
phpstorm编辑器乱码问题解决
2014/12/01 PHP
如何批量清理系统临时文件(语言:C#、 C/C++、 php 、python 、java )
2016/02/01 PHP
thinkPHP自定义类实现方法详解
2016/11/30 PHP
JavaScript事件列表解说
2006/12/22 Javascript
js 有框架页面跳转(target)三种情况下的应用
2013/04/09 Javascript
Node.js事件循环(Event Loop)和线程池详解
2015/01/28 Javascript
javascript实现在网页任意处点左键弹出隐藏菜单的方法
2015/05/13 Javascript
jquery+css实现的红色线条横向二级菜单效果
2015/08/22 Javascript
基于dropdown.js实现的两款美观大气的二级导航菜单
2015/09/02 Javascript
NodeJS远程代码执行
2016/08/28 NodeJs
提高JavaScript执行效率的23个实用技巧
2017/03/01 Javascript
Vue原理剖析 实现双向绑定MVVM
2017/05/03 Javascript
Bootstrap实现下拉菜单多级联动
2017/11/23 Javascript
Node.js中sequelize时区的配置方法
2017/12/10 Javascript
JavaScript 反射和属性赋值实例解析
2019/10/28 Javascript
vue实现移动端图片上传功能
2019/12/23 Javascript
详解element-ui 表单校验 Rules 配置 常用黑科技
2020/07/11 Javascript
Bootstrap告警框(alert)实现弹出效果和短暂显示后上浮消失的示例代码
2020/08/27 Javascript
Vue实现省市区三级联动
2020/12/27 Vue.js
[01:07:22]2014 DOTA2华西杯精英邀请赛 5 24 DK VS VG加赛
2014/05/26 DOTA
Python实现七彩蟒蛇绘制实例代码
2018/01/16 Python
Python request使用方法及问题总结
2020/04/26 Python
使用python编写一个语音朗读闹钟功能的示例代码
2020/07/14 Python
python 实现朴素贝叶斯算法的示例
2020/09/30 Python
Python random模块的使用示例
2020/10/10 Python
Python的轻量级ORM框架peewee使用教程
2021/02/05 Python
美国韩国化妆品和护肤品购物网站:Beautytap
2018/07/29 全球购物
世界排名第一的万圣节服装店:Spirit Halloween
2018/10/16 全球购物
优秀教师的感人事迹
2014/02/04 职场文书
2014年党员创先争优承诺书
2014/05/29 职场文书
个人债务授权委托书范本
2014/10/05 职场文书
共青团员自我评价
2015/03/10 职场文书
什么是创业计划书?什么是商业计划书?这里一一解答
2019/07/12 职场文书
Python合并pdf文件的工具
2021/07/01 Python
使用Python解决图表与画布的间距问题
2022/04/11 Python