以前做用域链在我眼里也只是在调用一个对象时一层一层向上找到本身所需的变量或是函数,若没有则返回undefined,其实大体上说倒是这样的,可是我须要的是不断的深刻。javascript
在深刻理解以前先记住两句话html
1.js中一切皆对象,函数也是对象java
2.函数运行在他们被定义的做用域内,而不是被执行的做用域内。缓存
当定义函数时,会包含[[scope]]属性(由于js中一切皆对象,函数也是对象),此属性是函数内部属性,只容许js引擎访问,[[scope]]指向做用域链(scope lain),而此时仅包含函数
全部全局变量。性能
当调用函数时,会建立"运行期上下文"内部对象,此对象包含本身的做用域链,用于解析标识符,并初始化为调用函数的内部对象[[scope]](即全局变量),这些值按照出现的位置优化
依次复制到运行期上下文中,一块儿组成"活动对象",并添加至做用域链的首位。活动对象包括这次调用函数内部的全部局部变量,形参,命名参数以及this。当运行期函数销毁时,this
活动对象也随之销毁。spa
说了那么多,确定已经懵了,来个例子消化一下。3d
function add(num1,num2) { var sum = num1 + num2; return sum; }
当定义add函数时,建立[[scope]]属性,指向做用域链(scope chain),仅包含全局对象(global object)以下图:
在调用add函数时,建立“执行上下文”(execution context)并建立活动对象(activation object),更新做用域链,以下图:
在调用函数每遇到一个变量时,都会经历标识符解析从而知道从哪里获取数据并存储数据,查找是否有同名标识符,若没有则查找下一个对象,直到全局变量,若一直没有找到,则证实此标识符并无被定义。
那么,来个小问题,为何编写js代码时尽可能避免使用全局变量?
答案:由于使用全局变量时,在做用域链中需查找到最后一层,影响性能。
根据上面的所讲的以及文章开始时的第二个注意问题,再举个例子
function output() { var name = "nana"; var intro = function() { alert(name); }; return intro; } function a(para) { var name = para; var func = output(); func(); } a('lili');
在调用函数a时做用域链为:
[[scope chain]] = {
pare: 'lili',
name: undefined,
func: undefined,
arguments: []
},{
window call object
}
在调用函数output时做用域链为:(注意:并不包含a的活动对象)
[[scope chain]] = [{
name: undefined,
intro: undefined
},{
window call object
}]
在定义intro函数时做用域链为
[[scope chain]] = [{
name: 'nana',
intro: undefined
},{
window call object
}]
当从output返回,在a函数中调用intro时,发生标志符解析,此时做用域链为:
[[scope chain]] = [{
intro call object
},{
name: 'nana',
intro: 'undefined'
},{
window call object
}]根据文章开始的第二句话,函数运行在被定义的做用域内,而不是被执行的做用域内。如上intro函数运行在被定义的做用域内。所以,最后答案是nana。
改变做用域链
1>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(); }; } }
with能够改变做用域,看似很方便,实际上却影响性能。当代码读到with时,做用域链又被改为下图所示,因此全部函数内部的局部变量都须要查找第二次才可被访问,影响了性能,避免使用。
2>try-catch
当try代码块发生异常时,则执行catch语句,此时做用域也会像上面那样临时被改动,那么,局部变量也是只能在第二次查找时才可匹配到,影响性能,但在调试时try-catch有很大帮助,因此没必要彻底避免。注意:在执行完catch语句时,做用域链又恢复回来。
优化:
1>尽可能避免使用全局变量
2>缓存变量,将使用一次以上的全局变量缓存给一个局部变量。
预编译
每次说到做用域链的时候不得不提一下预编译
在每次预编译时,先找var关键字,再找function定义式,
看个例子吧!
console.log(a); var a = 10; console.log(a); console.log(typeof func); console.log(typeof d); function func() {} var d = function(){}; console.log(typeof d);
猜猜会输出什么呢?
答案是:undefined 10 function undefined function
你猜对了么?
首先来讲变量,在预编译时找到var关键字并简单赋值为undefined,只有在执行时,才被赋值。var a = 10,至关于var a; a = 10; 所以,第一个输出undefined,第二个a已经执行了,因此输出10。
再来讲函数,在预编译时找到函数定义式,而函数表达式只会在执行过程当中才被执行,所以,第三个打印出function,第四个打印undefined,第五个打印function。
参考资料:
http://naotu.baidu.com/viewshare.html?shareId=av8cg0e3dckw&qq-pf-to=pcqq.group
http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html