javascript笔记:深入了函数的作用域链及标识符解析的过程

16小时前来源:红黑联盟

函数在javascript中扮演着一个重要的角色,作用域可以确定哪些变量可以被函数访问,确定this的值,而且也关系到代码的性能,所以理解函数的创建和执行过程及作用域至关重要。

首先得了解几个名词(其实有些名词本人也不是很明白):

1.作用域(scope):在javascript没有块级作用域,是由函数来划分的。变量和函数的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域(with和eval除外)。当定义了一个函数,当前的作用域链就保存起来,并且成为函数的内部状态的一部份。在最顶级作用域链仅由全局对象组成,而不和词法作用域相关,然而,当定义一个嵌套的函数时,作用域链就包括外面的包含函数。这意味着嵌套函数可以访问包含函数的所有参数和局部变量。尽管当一个函数定义时作用域链就固定了,但作用域链中定义的属性还没有固定。作用域链是活的,并且函数被调用时,可以访问任何当前的绑定。
2.作用域链(scope chain):存储变量对象的集合(环境栈?),保证对执行环境有权访问的所有变量和函数的有序访问,也就是用于标识符解析(变量访问)。
3.执行环境(execution context):定义了变量和函数有权访问的其他数据,决定了它们的各自行为。每个执行环境都有一个与之关联的变量对象。
在web浏览器中,window对象就是全局执行环境。每个函数都有自己的执行环境,当函数执行完毕,该环境被销毁,保存在其中的变量和函数也随之销毁。
4.变量对象(variable object):保存了环境中定义的所有变量和函数。该对象无法访问,仅供解析器在后台使用。
5.活动对象(activation object):如果执行环境是函数,则将其活动对象作为变量对象(调用对象?)。活动对象最开始的两个属性是arguments和this

总的来说,以上所说的名词都是js程序员不可直接操作的,了解它们可帮助我们理解js引擎的在处理代码的是如何工作的。

一、作用域链

a) 一个函数创建时,javascript后台(引擎)会默认创建一个仅供后台使用的内部属性[[Scope]],此属性存储函数的作用域链,如果是全局函数,此时则包含一个变量对象(全局变量),如果是嵌套函数(闭包),作用域链还加上了父函数的变量对象。例如下面的这个全局函数: function add(num1,num2){ var sum = num1 + num2; return sum; }
javascript笔记:深入了函数的作用域链及标识符解析的过程

(此图是函数定义时的作用域链)

b)函数被调用时--add(5,10),javascript后台会创建一个内部对象(execution context)--“执行环境”或“运行期上下文”,执行环境有它自己的作用域链,执行环境创建时就以定义函数时的作用域链初始化它自己的作用域链,并且随后创建了一个活动对象,活动对象作为函数执行期的一个变量对象,包含所有局部变量(在函数内定义的)、命名参数、arguments、this,它会被推入到执行环境作用域链的前端(如下图)。每执行一次函数都会创建一个新的执行环境,当函数执行完毕执行环境就会被销毁。


javascript笔记:深入了函数的作用域链及标识符解析的过程

(此图是函数运行时的作用域链)

另外关于延长作用域链问题:以下的两种情况会使作用域链延长

1) try-catch语句的catch块;

2) with语句;

这个两个语句都会再原本的作用域链的前端添加一个变量对象。对于with语句来说,新添加的变量对象包含着with括号中指定对象的所有属性和方法所作的变量声明。对于catch来说,当try块发生错误时,代码执行流程自动转入到catch块,并将异常对象推入到作用域链的前端。catch块执行完毕后,作用域链就会返回原来的状态。

请看下面的例子:

function initUI{ with(document){ var bd = body, links = getElementsByTagName("a"), i = 0, len = links.length; while(i

当代码流执行到一个with表达式时,执行环境的作用域链会被临时改变,此时with的变量对象会被创建添加到作用域链的前端,这就意味着此时函数的所有局部变量都被推入到第二个作用域链中的变量对象,如下图:


javascript笔记:深入了函数的作用域链及标识符解析的过程

由上图可清晰的看到,在执行with语句时,访问局部变量的代价更高了。所以尽可能避免使用with语句,可以使用局部变量代替

var doc = document; // 代替with(document){...}

二、标识符解析/变量访问

当在某个环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定该标识符实际代表什么,搜索过程始终从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部环境中找到了该标识符,搜索过程就会停止,变量就绪。否则继续向上级搜索直到找到标识符为止(如果在全局环境都找不到标识符,则意味着该变量未声明,通常会导致错误发生),通过下面的例子来理解一下标识符查询过程:

var color = "blue"; function getColor{ var color = "red"; return color; } alert(getColor); //red

当执行函数getColor会引用变量color,为了确定变量color的值,将开始变量color的搜索过程,通过前面所述,我们知道这个函数执行环境的作用域链包含两个变量对象,一个是执行函数时本身的活动对象,另一个就是全局对象。在这个搜索过程中,首先就搜索getColor的变量对象,如果存在一个局部的变量定义,则搜索会自动停止,不在进入下一个变量对象。所以函数搜素变量color,返回"red"。

理解上面标识符的解析过程,很明显知道,变量查询是要付出代价的,访问局部变量要比访问全局变量更快,因为不用向上搜索作用域链。所以,当函数需要重复引用一个变量时,最好在局部变量定义它,尽管它已经在全局环境中已经定义好了。

如果你完全理解了下面的几个例子,那你就掌握了本文所述的知识:

例子1:

1 var name = "window"; 2 var obj ={ 3 name:"object", 4 getName:function{ 5 return function{ 6 return this.name; 7 } 8 } 9 }; 10 alert(obj.getName); // 输出什么?

例子2:

1 f = function{return true;}; 2 g = function{return false;}; 3 (function { 4 //alert(g); 5 if (g && == !) { 6 f = function f {return false;}; 7 function g {return true;} 8 } 9 10 }); 11 alert(f); // true or false ?

例子3:

1 function createFunc{ 2 var funcs = new Array; 3 for(var i=0;i<10;i++){ 4 funcs[i] = function(num){ 5 return function{return num;} 6 }(i); 7 } 8 return funcs; 9 } 10 var aFuncs = createFunc; 11 alert(aFuncs[1]);

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

分页:12
转载请注明
本文标题:javascript笔记:深入了函数的作用域链及标识符解析的过程
本站链接:http://www.codesec.net/view/531071.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(30)