之前作用域链在我眼里也只是在调用一个对象时一层一层向上找到自己所需的变量或是函数,若没有则返回undefined,其实大致上说却是这样的,但是我需要的是不断的深入。
在深入理解之前先记住两句话
1.js中一切皆对象,函数也是对象
2.函数运行在他们被定义的作用域内,而不是被执行的作用域内。
当定义函数时,会包含[[scope]]属性(因为js中一切皆对象,函数也是对象),此属性是函数内部属性,只允许js引擎访问,[[scope]]指向作用域链(scope lain),而此时仅包含
所有全局变量。
当调用函数时,会创建"运行期上下文"内部对象,此对象包含自己的作用域链,用于解析标识符,并初始化为调用函数的内部对象[[scope]](即全局变量),这些值按照出现的位置
依次复制到运行期上下文中,一起组成"活动对象",并添加至作用域链的首位。活动对象包括此次调用函数内部的所有局部变量,形参,命名参数以及this。当运行期函数销毁时,
活动对象也随之销毁。
说了那么多,肯定已经懵了,来个例子消化一下。
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