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 相关文章推荐
些很实用且必用的小脚本代码
Jun 26 Javascript
js用图作提交按钮或超连接
Mar 26 Javascript
MultiSelect左右选择控件的设计与实现介绍
Jun 08 Javascript
AngularJS使用ng-options指令实现下拉框
Aug 23 Javascript
通过JS获取Request.QueryString()参数的值实现方法
Sep 27 Javascript
微信小程序调用PHP后台接口 解析纯html文本
Jun 13 Javascript
详解vue服务端渲染(SSR)初探
Jun 19 Javascript
jQury Ajax使用Token验证身份实例代码
Sep 22 Javascript
p5.js实现斐波那契螺旋的示例代码
Mar 22 Javascript
JavaScript实现连连看连线算法
Jan 05 Javascript
vue 中Virtual Dom被创建的方法
Apr 15 Javascript
Vue 动态路由的实现及 Springsecurity 按钮级别的权限控制
Sep 05 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如何得到当前页和上一页的地址?
2006/11/27 PHP
php中将字符串转为HTML的实体引用的一个类
2013/02/03 PHP
详谈php ip2long 出现负数的原因及解决方法
2017/04/05 PHP
php处理多图上传压缩代码功能
2018/06/13 PHP
一个JS翻页效果
2007/07/23 Javascript
jquery插件制作 图片走廊 gallery
2012/08/17 Javascript
js文件Cookie存取值示例代码
2014/02/20 Javascript
js判断上传文件类型判断FileUpload文件类型代码
2014/05/20 Javascript
浅谈JavaScript中的字符编码转换问题
2015/07/07 Javascript
jQuery实现鼠标悬停背景翻转的黑色导航菜单代码
2015/09/14 Javascript
JS+CSS实现分类动态选择及移动功能效果代码
2015/10/19 Javascript
JavaScript html5 canvas画布中删除一个块区域的方法
2016/01/26 Javascript
jQuery unbind 删除绑定事件详解
2016/05/24 Javascript
基于jquery.page.js实现分页效果
2018/01/01 jQuery
Vue-Quill-Editor富文本编辑器的使用教程
2018/09/21 Javascript
vue vantUI tab切换时 list组件不触发load事件的问题及解决方法
2020/02/14 Javascript
在vue中使用jsonp进行跨域请求接口操作
2020/10/29 Javascript
[04:13]2014DOTA2国际邀请赛 专访DC目前形势不容乐观
2014/07/12 DOTA
[01:14:35]DOTA2上海特级锦标赛B组资格赛#1 Alliance VS Fnatic第一局
2016/02/26 DOTA
[01:01]青春无憾,一战成名——DOTA2全国高校联赛开启
2018/02/25 DOTA
python入门教程之识别验证码
2017/03/04 Python
Python使用struct处理二进制的实例详解
2017/09/11 Python
Python入门之三角函数sin()函数实例详解
2017/11/08 Python
python抓取网页内容并进行语音播报的方法
2018/12/24 Python
基于MSELoss()与CrossEntropyLoss()的区别详解
2020/01/02 Python
python如何实现递归转非递归
2021/02/25 Python
购买大码女装:Lane Bryant
2016/09/07 全球购物
英国美发和美容产品商城:HQhair
2019/02/08 全球购物
托管代码(Managed Code)和非托管代码(Unmanaged Code)有什么区别
2014/09/29 面试题
销售会计工作职责
2013/12/02 职场文书
小学生自我评价100字(15篇)
2014/09/18 职场文书
内乡县衙导游词
2015/02/05 职场文书
大学生实习推荐信
2015/03/27 职场文书
怎样写好演讲稿题目?
2019/08/21 职场文书
一个成功的互联网创业项目,必须满足这些要求
2019/08/23 职场文书
fastdfs+nginx集群搭建的实现
2021/03/31 Servers