javascript

位置:IT落伍者 >> javascript >> 浏览文章

JavaScript作用域链使用介绍


发布日期:2019年06月21日
 
JavaScript作用域链使用介绍

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

作用域

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

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

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

复制代码 代码如下:

var a=; //全局变量

function fn(b){ //局部变量

c=; //全局变量

var d=; //局部变量

function subFn(){

var e=d; //父函数的局部变量对子函数可见

for(var i=;i<;i++){

consolewrite(i);

}

alert(i);// 在for循环内声明循环外function内仍然可见没有块作用域

}

}

alert(c); //在function内声明但不带var修饰仍然是全局变量

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

复制代码 代码如下:

consolelog(a); //undefined

var a=;

consolelog(a); //

consolelog(b); //Uncaught ReferenceError: b is not defined

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

复制代码 代码如下:

var a;

consolelog(a); //undefined

a=;

consolelog(a); //

然而

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

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

作用域链

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

复制代码 代码如下:

function a(xy){

var b=x+y;

return b;

}

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

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

复制代码 代码如下:

function a(xy){

var b=x+y;

return b;

}

var tatal=a();

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

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

再来看看闭包

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

复制代码 代码如下:

for(var i=;i<elementslength;i++){

elements[i]onclick=function(){

alert(i);

}

}

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

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

复制代码 代码如下:

for(var i=;i<elementslength;i++){

(function(n){

elements[n]onclick=function(){

alert(n);

}

})(i);

}

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

最后

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

               

上一篇:javascript模拟实现C# String.format函数功能代码

下一篇:jValidate 基于jQuery的表单验证插件