什么是作用域?
作用域是一种规则,在代码编译阶段就确定了,规定了变量与函数的可被访问的范围。全局变量拥有全局作用域,局部变量则拥有局部作用域。 js是一种没有块级作用域的语言(包括if、for等语句的花括号代码块或者单独的花括号代码块都不能形成一个局部作用域),所以js的局部作用域的形成有且只有函数的花括号内定义的代码块形成的,既函数作用域。
什么是作用域链?
作用域链是作用域规则的实现,通过作用域链的实现,变量在它的作用域内可被访问,函数在它的作用域内可被调用。
作用域链是一个只能单向访问的链表,这个链表上的每个节点就是执行上下文的变量对象(代码执行时就是活动对象),单向链表的头部(可被第一个访问的节点)始终都是当前正在被调用执行的函数的变量对象(活动对象),尾部始终是全局活动对象。
作用域链的形成?
我们从一段代码的执行来看作用域链的形成过程。
function fun01 () { console.log('i am fun01...'); fun02(); } function fun02 () { console.log('i am fun02...'); } fun01();
数据访问流程
如上图,当程序访问一个变量时,按照作用域链的单向访问特性,首先在头节点的AO中查找,没有则到下一节点的AO查找,最多查找到尾节点(global AO)。在这个过程中找到了就找到了,没找到就报错undefined。
延长作用域链
从上面作用域链的形成可以看出链上的每个节点是在函数被调用执行是向链头unshift进当前函数的AO,而节点的形成还有一种方式就是“延长作用域链”,既在作用域链的头部插入一个我们想要的对象作用域。延长作用域链有两种方式:
1.with语句
function fun01 () { with (document) { console.log('I am fun01 and I am in document scope...') } } fun01();
2.try-catch语句的catch块
function fun01 () { try { console.log('Some exceptions will happen...') } catch (e) { console.log(e) } } fun01();
ps:个人感觉with语句使用需求不多,try-catch的使用也是看需求的。个人对这两种使用不多,但是在进行这部分整理过程中萌发了一点点在作用域链层面的不成熟的性能优化小建议。
由作用域链引发的关于性能优化的一点不成熟的小建议
1.减少变量的作用域链的访问节点
这里我们自定义一个名次叫做“查找距离”,表示程序访问到一个非undefined变量在作用域链中经过的节点数。因为如果在当前节点没有找到变量,就跳到下一个节点查找,还要进行判断下一个节点中是否存在被查找变量。“查找距离”越长,要做的“跳”动作和“判断”动作也就越多,资源开销就越大,从而影响性能。这种性能带来的差距可能少数的几次变量查找操作不会带来太多性能问题,但如果是多次进行变量查找,性能对比则比较明显了。
(function(){ console.time() var find = 1 //这个find变量需要在4个作用域链节点进行查找 function fun () { function funn () { var funnv = 1; var funnvv = 2; function funnn () { var i = 0 while(i <= 100000000){ if(find){ i++ } } } funnn() } funn() } fun() console.timeEnd() })()
(function(){ console.time() function fun () { function funn () { var funnv = 1; var funnvv = 2; function funnn () { var i = 0 var find = 1 //这个find变量只在当前节点进行查找 while(i <= 100000000){ if(find){ i++ } } } funnn() } funn() } fun() console.timeEnd() })()
在mac pro的chrome浏览器下做实验,进行1亿次查找运算。
实验结果:前者运行5次平均耗时85.599ms,后者运行5次平均耗时63.127ms。
2.避免作用域链内节点AO上过多的变量定义
过多的变量定义造成性能问题的原因主要是查找变量过程中的“判断”操作开销较大。我们使用with来进行性能对比。
(function(){ console.time() function fun () { function funn () { var funnv = 1; var funnvv = 2; function funnn () { var i = 0 var find = 10 with (document) { while(i <= 1000000){ if(find){ i++ } } } } funnn() } funn() } fun() console.timeEnd() })()