javascript作用域链学习笔记
2016-03-14 16:05
399 查看
作用域链
”JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里.” ——权威指南在JavaScript中,一切皆对象,包括函数。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
在一个函数被定义的时候, 会将它定义时刻的scope chain链接到这个函数对象的[[scope]]属性.
在一个函数对象被调用的时候,会创建一个活动对象(也就是一个对象), 该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。
在每次调用一个函数的时候 ,就会进入一个函数内的作用域,当从函数返回以后,就返回调用前的作用域.
实例解析
var sayHello = function(l,s){ var word = "hello world"; } sayHello();
在执行sayHello定义语句的时候, 会创建一个这个函数对象的[[scope]]属性。
将这个[[scope]]属性, 链接到定义它的作用域链上。此时因为func定义在全局环境, 所以此时的[[scope]]只是指向全局活动对象window active object.
在调用sayHello的时候, 会创建一个活动对象(假设为fObj),并创建arguments属性。然后会给这个对象添加俩个命名属性fObj.l, fObj.s; 对于每一个在这个函数中申明的局部变量和函数定义, 都作为该活动对象的同名命名属性。对于局部变量,变量的值会在真正执行的时候才计算, 此时只是简单的赋为undefined.
将调用参数赋值给形参,对于缺少的调用参数,赋值为undefined。
将这个活动对象做为scope chain的最前端, 并将func的[[scope]]属性所指向的,定义sayHello时候的顶级活动对象, 加入到scope chain.
在发生标识符解析的时候, 就会逆向查询当前scope chain列表的每一个活动对象的属性,如果找到同名的就返回。找不到,那就是这个标识符没有被定义。
作用域链全过程解析:
function factory() { var name = 'laruence'; var intro = function(){ alert('I am ' + name); } return intro; } function app(para){ var name = para; var func = factory(); func(); } app('eve');
首先当调用app的时候, scope chain是由: {window活动对象(全局)}->{app的活动对象} 组成。此时的scope chain如下:(对于局部变量,变量的值会在真正执行的时候才计算, 此时只是简单的赋为undefined.
)
[[scope chain]] = [ { para : 'eve', name : undefined, func : undefined, arguments : [] }, { window call object } ]
当调用进入factory的函数体的时候, 此时的factory的scope chain为:
[[scope chain]] = [ { name : undefined, intor : undefined }, { window call object } ]
注意: 此时的作用域链中, 并不包含app的活动对象.(JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里.)
在定义intro函数的时候, intro函数的[[scope]]为:
[[scope chain]] = [ { name : 'laruence', intor : undefined }, { window call object } ]
从factory函数返回以后,在app体内调用intor的时候, 发生了标识符解析, 而此时的sope chain是:
[[scope chain]] = [ { intro call object }, { name : 'laruence', intor : undefined }, { window call object } ]
所以, name标识符解析的结果(在上面的作用域链中一层层往上匹配)应该是factory活动对象中的name属性, 也就是’laruence’。
标识符解析过程:
该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。
利用作用域链的代码优化
把全局变量存储到局部变量里再使用从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。例如下面的代码:
function changeColor(){ var doc=document; doc.getElementById("btnChange").onclick=function(){ doc.getElementById("targetCanvas").style.backgroundColor="red"; }; }
避免改变作用域链
函数每次执行时对应的运行期上下文都是独一无二的,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响。
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(); }; } }
with 语句的作用是将代码的作用域设置到一个特定的对象中
当代码运行到with语句时,运行期上下文的作用域链临时被改变了。一个新的可变对象被创建,它包含了参数指定的对象的所有属性。这个对象将被推入作用域链的头部,这意味着函数的所有局部变量现在处于第二个作用域链对象中,因此访问代价更高了。
参考文章:Javascript作用域原理、理解 JavaScript 作用域和作用域链
我的博客地址:http://bigdots.github.io、http://www.cnblogs.com/yzg1/
相关文章推荐
- Javascript学习笔记:闭包题解(3)
- JS 刷新随机统制DIV的的位置
- 【2016年Esri开发者大会亮点解析】JavaScript API 4.0即将发布,大力支持3D
- JS取出数组中重复的元素
- getElementsByClassName的用法 和 js获取class
- javascript小实例,PC网页里的拖拽(转)
- JavaScript substring() 方法
- XML和JSON优缺点
- Javascript学习笔记:闭包题解(2)
- iuni-core.js
- js第13天
- JavaScript学习笔记——基本包装类型(下)String
- jsp自定义标签--tagSupport和bodyTagSupport
- 探究JavaScript闭包
- jsp的自定义标签
- javascript中的几种继承方式
- js倒计时
- 【bzoj1443】【JSOI2009】【游戏game】【二分图博弈】
- JSP九大内置对象详解
- doT.js 模板引擎的使用