这篇文章主要介绍了 javascript 作用域链 (Scope Chain) 用法, 结合实例形式较为详细的分析了 javascript 作用域链 (Scope Chain) 的概念、功能与相关使用技巧, 具有一定参考借鉴价值, 需要的朋友可以参考下
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
本文实例分析了 javascript 作用域链 (Scope Chain) 用法。分享给大家供大家参考,具体如下:
关于 js 的作用域链,早有耳闻,也曾看过几篇介绍性的博文,但一直都理解的模棱两可。近日又精心翻看了一下《悟透 Javascript》这本书,觉得写得太深刻,在 "代码的时空" 一节里有一段介绍作用域链的地方寥寥数语,回味无穷(其实还是理解的模棱两可 ^_^)。现在整理下自己的读书笔记,顺便借鉴网上资源,写下来。
一、从一个简单的问题说起
下面的 js 代码在页面中运行显示什么结果:
- var arg = 1;
- function fucTest(arg) {
- alert(arg);
- var arg = 2;
- //alert(arg);
- }
- fucTest(10);
您的答案是什么?没错,就是弹出 10。我的理解是这样的,funTest 函数有一个形参 arg,funTest 函数传入实参 10,alert 方法把 10 弹出就是了,囧。
好,问题又来了:
- var arg = 1;
- function funcTest() {
- alert(arg);
- var arg = 2;
- }
- arg = 10;
- funcTest();
答案是什么?如果是 5 年前的我,肯定不会再往下想了,还是 10!这么简单的问题还用想什么呀?我的理解是这样的:funTest 函数是一个无参数的函数,函数内部通过 alert 方法,调用外部(全局)的变量 arg, 在函数执行前,arg 赋值为 10,弹出 arg 值后改变 arg 值为 2,所以弹出值为 10。
真的是 10 吗?是还是不是?
测试的结果:弹出 "undefined", 瀑布汗.
二、理解作用域链,从 javascript 运行机制说起
1、js 的运行顺序
如果一个文档流中包含多个 script 代码段(用 script 标签分隔的 js 代码或引入的 js 文件),它们的运行顺序是:
步骤 1. 读入第一个代码段(js 执行引擎并非一行一行地执行程序,而是一段一段地分析执行的)
步骤 2. 做语法分析,有错则报语法错误(比如括号不匹配等),并跳转到步骤 5
步骤 3. 对 var 变量和 function 定义做 "预解析"(永远不会报错的,因为只解析正确的声明)
步骤 4. 执行代码段,有错则报错(比如变量未定义)
步骤 5. 如果还有下一个代码段,则读入下一个代码段,重复步骤 2
步骤 6. 结束
上面的分析已经足够清楚,步骤二、三和步骤四里的红色字体可能是我们新手理解上的一个盲点,尤其是步骤三的 "预解析",如果不清楚什么叫预解析,总觉得不踏实。而步骤四的 "有错则报错" 也是经常碰到的。举例来说:
- function funcTest() {
- alert(arg);
- var arg = 2;
- }
- funcTest();
上面这段代码执行时,弹出 "undefined",也就是说 arg 没有定义,js 的变量不是不用定义也可以吗?
2、语法分析和 "预解析"
(1)、从解释型语言的编译过程说起
众所周知,javascript 是解释型语言,它不同于 c# 和 java 等编译型语言。对于传统编译型语言来说,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成;但对于解释型语言来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。
a、词法分析
简单地说,词法分析是将字符流 (char stream) 转换为记号流(token stream)。
但是这个转换过程并不是可以用一句话就可以概括的那么简单,我们可以试着用伪代码理解一段简单的程序:
代码 var result=x-y; 的转换大致可以表示如下:
NAME "result"
EQUALS
NAME "x"
MINUS
NAME "y"
SEMICOLON
b、语法分析
简单地说,语法分析就是为了构造合法的语法分析树,而语法分析树可以直观地表示出推导的过程。
那么什么是语法分析树?简单地说,就是程序推导过程的描述。但是到底什么是语法树,请参考专业文章,本篇略过。
c、其他
通过语法分析,构造出语法分析树后,接下来还可能需要进一步的语义检查。对于传统强类型语言来说,语义检查的主要部分是类型检查,比如函数的实参和形参类型是否匹配等等。
结论:通过上面的分析可以看出,对于 javascript 引擎来说,肯定有词法分析和语法分析,之后可能还有语义检查、代码优化等步骤,等这些编译步骤完成之后(任何语言都有编译过程,只是解释型语言没有编译成二进制代码),才会开始执行代码。
(2)、执行过程
a、javascript 的作用域机制
通过编译,javascript 代码已经翻译成了语法树,然后会立刻按照语法树执行。
进一步的执行过程,需要理解 javascript 的作用域机制:词法作用域(lexcical scope)。通俗地讲,就是 javascript 变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,编译器通过静态分析就能确定,因此词法作用域也叫做静态作用域(static scope)。但需要注意,with 和 eval 的语义无法仅通过静态技术实现,所以只能说 javascript 的作用域机制非常接近词法作用域(lexical scope).
javascript 引擎在执行每个函数实例时,都会创建一个执行环境(execution context)。执行环境中包含一个调用对象(call object), 调用对象是一个 scriptObject 结构(scriptObject 是与函数相关的一套静态系统,与函数实例的生命周期保持一致),用来保存内部变量表 varDecls、内嵌函数表 funDecls、父级引用列表 upvalue 等语法分析结构(注意 varDecls 和 funDecls 等信息是在语法分析阶段就已经得到,并保存在语法树中。函数实例执行时,会将这些信息从语法树复制到 scriptObject 上)。
b、javascript 作用域机制的实现方法
词法作用域(lexical scope)是 javascript 的作用域机制,还需要理解它的实现方法,就是作用域链(scope chain)。作用域链是一个 name lookup 机制,首先在当前执行环境的 scriptObject 中寻找,没找到,则顺着 upvalue 到父 scriptObject 中寻找,一直 lookup 到全局调用对象(global object)。
现在回过头来分析第二个问题:
- var arg = 1;
- function funcTest() {
- alert(arg);
- var arg = 2;
- }
- arg = 10;
- funcTest();
在执行 funcTest 函数时,也即进入了 funcTest 对应的作用域,js 引擎在执行时,当遇到对变量名或者函数名的使用时,会首先在当前作用域(也即 funcTest 对应的作用域)查找变量或者函数(显然,arg 变量在 funcTest 对应的作用域里被定义为 var arg=2 所以 alert 方法的参数采用的是当前作用域的 arg,但是因为 arg 被定义在 alert 方法后,所以 arg 变量默认值为 undefined)。当然,如果没有找到就到上层作用域查找,依此类推(作用域范围可以持续到 javascript 运行环境的根:window 对象)。
最后,让你看的更清楚,上面的代码其实可以等价于:
- var arg = 1;
- function funcTest() {
- var arg; //默认值undefined
- alert(arg);
- arg = 2;
- }
- arg = 10;
- funcTest();
c、闭包(closure)
当一个函数实例执行时,会创建或关联到一个闭包。 (关于闭包,打算另写一篇学习笔记)
scriptObject 用来静态保存与函数相关的变量表,闭包则在执行期动态保存这些变量表及其运行值;
闭包的生命周期有可能比函数实例长。函数实例在活动引用为空后会自动销毁;
闭包则要等要数据引用为空后,由 javascript 引擎回收(有些情况下不会自动回收,就导致了内存泄漏)。
ps:关于 "执行过程" 这一段比较拗口,名词很多,不过别被它们吓住,一旦理解了执行环境(execution context)、调用对象(call object)、词法作用域(lexical scope)、作用域链(scope chain)、闭包(closure)等这些概念,javascript 的很多现象都能迎刃而解。
三、结语
通过第二段的分析,对照第一段笔者曾经做出的判断 (你是不是也觉得笔者曾经的分析和结论很幼稚(哪怕有时结果碰巧也对!)?!不是一般的肤浅啊,^_^),你会发现原来 javascript 还有这么多 "玄机",而要真正理解精通又谈何容易?先 "悟透" 再说吧。
希望本文所述对大家 JavaScript 程序设计有所帮助。
来源: http://www.phperz.com/article/17/0215/268408.html