JavaScript作用域链使用介绍


Posted in Javascript onAugust 29, 2013

之前写过一篇JavaScript 闭包究竟是什么的文章理解闭包,觉得写得很清晰,可以简单理解闭包产生原因,但看评论都在说了解了作用域链和活动对象才能真正理解闭包,起初不以为然,后来在跟公司同事交流的时候发现作用域和执行环境确实很重要,又很基础,对理解JavaScript闭包很有帮助,所以在写一篇对作用域和执行环境的理解。

作用域

作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域。

单纯的JavaScript作用域还是很好理解的,在一些类C编程语言中花括号内的每一段代码都有各自的作用域,而且变量在声明它们的代码段外是不可见的,称之为块级的作用域,JavaScript容易让初学者误会的地方也在于此,JavaScript并没有块及的作用域,只有函数级作用域:变量在声明它们的函数体及其子函数内是可见的。

变量没有在函数内声明或者声明的时候没有带var就是全局变量,拥有全局作用域,window对象的所有属性拥有全局作用域;在代码任何地方都可以访问,函数内部声明并且以var修饰的变量就是局部变量,只能在函数体内使用,函数的参数虽然没有使用var但仍然是局部变量。

var a=3; //全局变量
            function fn(b){ //局部变量
                c=2; //全局变量
                var d=5; //局部变量
                function subFn(){
                    var e=d; //父函数的局部变量对子函数可见
                    for(var i=0;i<3;i++){
                        console.write(i);
                    }
                    alert(i);//3, 在for循环内声明,循环外function内仍然可见,没有块作用域
                }
            }
            alert(c); //在function内声明但不带var修饰,仍然是全局变量

只要是理解了JavaScript没有块作用域,简单的JavaScript作用域很好理解,还有一点儿容易让初学者迷惑的地方是JavaScript变量可函数的与解析或者声明提前,好多种叫法但说的是一件事情,JavaScript虽然是解释执行,但也不是按部就班逐句解释执行的,在真正解释执行之前,JavaScript解释器会预解析代码,将变量、函数声明部分提前解释,这就意味着我们可以在function声明语句之前调用function,这多数人习以为常,但是对于变量的与解析乍一看会很奇怪

console.log(a); //undefined
            var a=3;
            console.log(a); //3
            console.log(b); //Uncaught ReferenceError: b is not defined

     上面代码在执行前var a=3; 的声明部分就已经得到预解析(但是不会执行赋值语句),所以第一次的时候会是undefined而不会报错,执行过赋值语句后会得到3,上段代码去掉最后一句和下面代码是一样的效果。
var a;
            console.log(a); //undefined
            a=3;
            console.log(a); //3

然而
如果只是这样那么JavaScript作用域问题就很简单了,然而由于函数子函数导致的问题使作用域不止这样简单。大人物登场——执行环境或者说运行期上下文(好土鳖):执行环境(execution context)定义了变量或函数有权访问的其它数据,决定了它们的各自行为。每个执行环境都有一个与之关联的变量对象(variable object, VO),执行环境中定义的所有变量和函数都会保存在这个对象中,解析器在处理数据的时候就会访问这个内部对象。

全局执行环境是最外层的一个执行环境,在web浏览器中全局执行环境是window对象,因此所有全局变量和函数都是作为window对象的属性和放大创建的。每个函数都有自己的执行环境,当执行流进入一个函数的时候,函数的环境会被推入一个函数栈中,而在函数执行完毕后执行环境出栈并被销毁,保存在其中的所有变量和函数定义随之销毁,控制权返回到之前的执行环境中,全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。

作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain,不简称sc)来保证对执行环境有权访问的变量和函数的有序访问。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)

function a(x,y){
            var b=x+y;
            return b;
        }
 

在函数a创建的时候它的作用域链填入全局对象,全局对象中有所有全局变量

JavaScript作用域链使用介绍

如果执行环境是函数,那么将其活动对象(activation object, AO)作为作用域链第一个对象,第二个对象是包含环境,下一个是包含环境的包含环境。。。。。

function a(x,y){
            var b=x+y;
            return b;
        }
        var tatal=a(5,10);

 这时候 var total=a(5,10);语句的作用域链如下

JavaScript作用域链使用介绍

在函数运行过程中标识符的解析是沿着作用域链一级一级搜索的过程,从第一个对象开始,逐级向后回溯,直到找到同名标识符为止,找到后不再继续遍历,找不到就报错。

再来看看闭包

之前博客曾经总结道:只要存在调用内部函数的可能,JavaScript就需要保留被引用的函数。而且JavaScript运行时需要跟踪引用这个内部函数的所有变量,直到最后一个变量废弃,JavaScript的垃圾收集器才能释放相应的内存空间。回头再看看好理解了很多,父函数定义的变量在子函数的作用域链中,子函数没有被销毁,其作用域链中所有变量和函数就会被维护,不会被销毁。

for(var i=0;i<elements.length;i++){
                elements[i].onclick=function(){
                    alert(i);
                }
            }

  这是上篇博客提到过的经典错误,每次element点击alert都是length,这段代码中为element绑定的click事件处理程序的作用域链是这样的

JavaScript作用域链使用介绍

由于内部函数(click事件处理程序时刻有调用可能),所以其作用域链不能被销毁(更别说本例中i在全局作用域中,只能页面卸载是销毁),i的值一直保持for循环执行完后的length值,所以每次触发onclick的时候才会alert length。

for(var i=0;i<elements.length;i++){
                (function(n){
                    elements[n].onclick=function(){
                        alert(n);
                    }
                })(i);
            }

为什么这样就行了呢,这时候onclick引用的变量变成了n,而由于立即执行函数的原因,每个onclick函数在作用域链中分别保持着对应的n(0~length-1),这时候就可以了。

最后

其实理解了执行环境和作用域链后,闭包翻了变成显而易见的东西,但是也不能滥用闭包,从上面例子可以看出,闭包会使子函数保持其作用域链的所有变量及函数与内存中,内存消耗很大,在使用的时候尽量销毁父函数不再使用的变量。

Javascript 相关文章推荐
JavaScript 克隆数组最简单的方法
Feb 12 Javascript
JS 巧妙获取剪贴板数据 Excel数据的粘贴
Jul 09 Javascript
ExtJS下 Ext.Direct加载和提交过程排错小结
Apr 02 Javascript
jquery 日期控件datepicker属性详细解析
Nov 08 Javascript
js+HTML5实现canvas多种颜色渐变效果的方法
Jun 05 Javascript
超漂亮的jQuery图片轮播特效
Nov 24 Javascript
简单理解JavaScript中的封装与继承特性
Mar 19 Javascript
js前端解决跨域问题的8种方案(最新最全)
Nov 18 Javascript
Vue + Webpack + Vue-loader学习教程之相关配置篇
Mar 14 Javascript
详解各版本React路由的跳转的方法
May 10 Javascript
解决在Vue中使用axios用form表单出现的问题
Oct 30 Javascript
原生JavaScript实现拖动校验功能
Sep 29 Javascript
JavaScript 命名空间 使用介绍
Aug 29 #Javascript
JavaScript prototype 使用介绍
Aug 29 #Javascript
JavaScript创建对象的写法
Aug 29 #Javascript
jQuery实现用户注册的表单验证示例
Aug 28 #Javascript
Jquery实现显示和隐藏的4种简单方式
Aug 28 #Javascript
jQuery动画效果-slideUp slideDown上下滑动示例代码
Aug 28 #Javascript
jQuery动画效果-fadeIn fadeOut淡入浅出示例代码
Aug 28 #Javascript
You might like
php 广告调用类代码(支持Flash调用)
2011/08/11 PHP
ThinkPHP的I方法使用详解
2014/06/18 PHP
新浪SAE搭建PHP项目教程
2015/01/28 PHP
php curl请求信息和返回信息设置代码实例
2015/04/27 PHP
解决JS浮点数运算出现Bug的方法
2013/03/12 Javascript
在页面加载完成后通过jquery给多个span赋值
2014/05/21 Javascript
Javascript实现可旋转的圆圈实例代码
2015/08/04 Javascript
基于React.js实现原生js拖拽效果引发的思考
2016/03/30 Javascript
JSON 对象未定义错误的解决方法
2016/09/29 Javascript
JavaScript实现左右下拉框动态增删示例
2017/03/09 Javascript
关于使用axios的一些心得技巧分享
2017/07/02 Javascript
浅谈JS 数字和字符串之间相互转化的纠纷
2017/10/20 Javascript
实例详解Vue项目使用eslint + prettier规范代码风格
2018/08/20 Javascript
AI小程序之语音听写来了,十分钟掌握百度大脑语音听写全攻略
2020/03/13 Javascript
vue 递归组件的简单使用示例
2021/01/14 Vue.js
VUE实现吸底按钮
2021/03/04 Vue.js
Python实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器
2014/06/04 Python
python代码 if not x: 和 if x is not None: 和 if not x is None:使用介绍
2016/09/21 Python
使用Python中的tkinter模块作图的方法
2017/02/07 Python
使用Python更换外网IP的方法
2018/07/09 Python
Python中垃圾回收和del语句详解
2018/11/15 Python
Python线性拟合实现函数与用法示例
2018/12/13 Python
python用opencv批量截取图像指定区域的方法
2019/01/24 Python
Python之虚拟环境virtualenv,pipreqs生成项目依赖第三方包的方法
2019/07/23 Python
Python 二叉树的层序建立与三种遍历实现详解
2019/07/29 Python
PyTorch中的padding(边缘填充)操作方式
2020/01/03 Python
Python如何读取文件中图片格式
2020/01/13 Python
来自圣地亚哥的实惠太阳镜:Knockaround
2018/08/27 全球购物
龟牌英国商店:Turtle Wax Brand Store UK
2019/07/02 全球购物
绩效工资分配方案
2014/01/18 职场文书
小学家长会邀请函
2014/01/23 职场文书
动物科学专业求职信
2014/07/27 职场文书
2014镇副书记群众路线专题民主生活会思想汇报
2014/09/23 职场文书
2016年区委书记抓基层党建工作公开承诺书
2016/03/25 职场文书
JavaScript控制台的更多功能
2021/04/28 Javascript
anaconda python3.8安装后降级
2021/06/11 Python