函数作用域链
Posted 学如逆水行舟,不进则退。
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数作用域链相关的知识,希望对你有一定的参考价值。
基本任何变成语言都有作用域的概念,即各种变量的可见性和生命周期,通俗来说,就是变量在什么地方可以被调用,什么地方不可以被调用。此处是js的函数作用域链的概念理解。
1、全局作用域, 局部作用域
全局作用域:处于全局作用域的变量为全局变量,在代码中的任何地方都可被可视,即在任何地方都可被调用。
常见情况有以下几种:
(1)最外层函数和最外层定义的变量拥有全局作用域。
(2)所有未声明而直接赋值的变量拥有全局作用域。
(3)所有window对象拥有全局作用域。
2.局部作用域:
仅在一定的代码段,才可见(才可被调用),常见的是函数内部声明的变量。
var a = 1; function fun1(){ var b = 2; mn = 123; function fun2(){ var c = 3; } }
/*
a: 全局,最外层函数之外定义的变量。
fun1:全局,最外层函数
b:局部,函数内定义
mn:全局,未声明而直接赋值,为window对象属性,属于全局作用域
fun2:局部,在函数内声明的函数
c:局部,函数内声明
*/
3.作用域链:
在javascript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义,该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。例如定义下面这样一个函数:
function add(num1,num2) { var sum = num1 + num2; return sum; }
在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只例举了全部变量中的一部分):
函数add的作用域将会在执行时用到。例如执行如下代码:
var total = add(5,10);
执行此函数时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。每个运行期上下文都有自己的作用域链,用于标识符解析,当运行期上下文被创建时,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。
这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。新的作用域链如下图所示:
在函数执行过程中,没遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。
简单总结: 以函数为例:函数定义时(或被声明时),此时会生成一个全局执行期上下文,函数每次执行都会产生一个自己唯一的执行期上下文,然后和之前函数定义产生的执行期上下文形成栈式作用域链,全局位于栈底,而刚生成上下文位于栈顶,访问时,优先访问栈顶执行期上下文,若没有目标,再往下寻找,依次规则,一般全局变量都是被最后访问的,所以尽量少定义全局变量。
4.作用域的代码优化:
访问变量在作用域链的位置越深,访问之越耗时,所以尽量少的定义全局变量,解决方法:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用
function changeColor(){ var doc=document; /*用一个局部变量储存全局的document,从而提高访问效率,当该种情况重复次数多时效果会很明显*/ doc.getElementById("btnChange").onclick=function(){ doc.getElementById("targetCanvas").style.backgroundColor="red"; }; }
5.改变作用域链的方法:
1.with(){ 此处的作用域为with参数指定的}
当调用with时会产生一个新的执行期上下文,然后插入到当前作用域链的栈顶处,导致结果是仅仅with代码中某些变量访问快了,但所有局部作用域被挤到第二个位置,局部作用域的变量访问变慢,最后的结果是访问代价更高,所以with尽量能不用就别用。
2.catch{},
try-catch语句中,当try中代码报错时会跳到catch块中,此时会将异常对象放到作用域链的栈顶,所有局部作用域被挤到第二个位置,导致性能降低,解决方法:将异常对象作为形参传入处理函数即可。
6.预编译过程创建的AO对象,就是执行期上下文,联系着理解感觉挺好。
预编译过程:
*JS执行三部曲:
1.语法分析:
通篇扫描,校验低级语法错误,即一眼就可看出的错误。(eg:括号未补全,引号未补全,变量未声明就打印等等)
2.预编译:
(1)预编译前奏:
a)imply\\globle 暗示全局变量 ,任何变量未经声明就赋值,此变量为全局对象window所有;
var a = b = 0;
==>
var a = 0;
b = 0;(从右向左)
连续赋值,优先级是从右向左
b)一切声明的全局变量,此变量为全局对象window所有。
(2)正式预编译:
a)说明:
Activation object = {
a: undefined,
b: function b(){}
}
即执行上下文。
b)预编译四部曲:
1.创建AO对象
2.找形参 和 变量的声明,将形参 和 声明的变量 作为AO的属性,并赋值为undefined;(注:函数声明不属于变量的声明, 赋值操作也不属于变量的声明)
3.将传递的形参和实参值统一
4.在函数体里找函数声明,值赋予函数体
3.解释执行(即一行一行执行)。
执行过程中,若在函数外赋值操作,则进行这样的操作:
//例如:a = 100; -> window.a = 100;与AO中同名的变量不冲突,一个局部,一个全局
参考资料:http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html
以上是关于函数作用域链的主要内容,如果未能解决你的问题,请参考以下文章