当前位置:文档之家› javascript运行原理

javascript运行原理

浅谈 JavaScript 的运行机理
——hechangmin@ 2010.10
这个话题看似简单,其实笔者是几次三番的下笔,又几次三番的放弃。

因为这个内容, 对于很多 JavaScript 的开发人员来讲都是一知半解的,当然笔者也在其中,今天之所以出来 献丑了,首先是有了更深的认识,其次微博上有人说“献丑是进步”,如果献丑那必定是有同 道之人能指出纰漏,那对于笔者本人来讲何尝不是进步呢?深表赞同! 今天会以几个小小的实例来解读这个课题。

希望能与大家共勉。

首先得先了解 JavaScript 执行起来的流程,笔者先简单画了一个 javascript 的执行流程图:
重点解释的有三步:词法分析、预解析、执行。

script 代码段:用 script 标签分隔的 js 代码或引入的 js 文件。




(1). 预解析 我们先从几个常见的 javascript 小题目入手,请大家先看看下面的范例输出什么? <script type="text/javascript"> alert(i); // ? var i = 1; </script> 对于 javascript 的从业者可以试着运行下。

看看你的答案和实际输出一致吗?别小看这样两 行脚本,这样的题目被当作 JavaScript 的笔试或者面试题目是常有的事情。

实际输出结果为:“undefined”, 这种现象被称成“预解析”:JavaScript 脚本引擎优先解析 var 变量和 function 定义。

在预解析 完成后,才会执行代码。

由于变量 i 是被 var 声明的,而被优先解析。

所以可以理解为在 alert(i) 执行时候,程序前 面已经有 var i; 所以上面代码等效解释为: <script type="text/javascript"> var i; alert(i); // 对于被声明, 但未赋值过的 i, 输出‘undefined’的结果, 是不应该有任何歧义了吧。

i = 1; </script> 注意:预解析不会报错,因为他只解析正确的声明。

(2). 解释(主要指词法分析,生成语法树的过程) 请注意,这里‘解释’的定义是笔者自己方便理解自己定义的,而这个‘解释’并不在预解 析之后。

我们知道 JavaScript 是脚本语言,脚本语言是相对于高级编译型语言而言他是解释性的。

解 释性语言没有编译成二进制代码,但是要进入到运行阶段,都应该是会经过词法分析、语法 分析生成语法树、语义检查过程,笔者把这个环节叫做“解释”,如果读者有更科学的名字记 得告诉我。

解释性语言在生成语法树后,就可以执行了。

(这个跟脚本引擎编译器有关)


在这个过程中,有语法检查(比如括号是否匹配),发现无法生成语法树,则报错,结束整 个代码块的解析。

(3) 执行 与 作用域 引入我们的第二个示例代码: <script type="text/javascript"> alert(i); // error: i is not defined. i = 1; </script> 听说 JavaScript 变量可以直接用,那为什么还报运行时脚本错误?—— i 未定义. 执行过程,需要理解 JavaScript 的作用域机制,JavaScript 变量的作用域是在定义时决定而 不是执行时决定,也就是说词法作用域取决于源码,编译器通过静态分析就能确定,因此词 法作用域也叫做静态作用域(static scope)。

但需要注意,with 和 eval 的语义无法仅通过 静态技术实现,实际上,只能说 JS 的作用域机制非常接近 lexical scope. JS 引擎在执行每个函数实例时,都会创建一个执行环境(execution context)。

execution c ontext 中包含一个调用对象(call object), 调用对象是一个 scriptObject 结构,用来保存内 部变量表 varDecls、内嵌函数表 funDecls、父级引用列表 upvalue 等语法分析结构(注意:v arDecls 和 funDecls 等信息是在预解析阶段就已经得到,并保存在语法树中。

函数实例执行 时,会将这些信息从语法树复制到 scriptObject 上)。

scriptObject 是与函数相关的一套静态 系统,与函数实例的生命周期保持一致。

lexical scope 是 JS 的作用域机制,还需要理解它的实现方法,这就是作用域链(scope chai n)。

scope chain 是一个 name lookup 机制,首先在当前执行环境的 scriptObject 中寻找,没 找到,则顺着 upvalue 到父级 scriptObject 中寻找,一直 lookup 到全局调用对象(global obj ect)。

当一个函数实例执行时,会创建或关联到一个闭包(closure)。

scriptObject 用来静态保存 与函数相关的变量表,closure 则在执行期动态保存这些变量表及其运行值。

closure 的生命 周期有可能比函数实例长。

函数实例在活动引用为空后会自动销毁,closure 则要等要数据 引用为空后,由 JS 引擎回收(有些情况下不会自动回收,就导致了内存泄漏)。




别被上面的一堆名词吓住,一旦理解了执行环境、调用对象、闭包、词法作用域、作用域链 这些概念,JS 语言的很多现象都能迎刃而解。

小结 “预解析”,其实是在的‘解释’阶段完成,并存储在语法树中。

当执行到函数实例时,会将 varDelcs 和 funcDecls 从语法树中复制到执行环境的 scriptObject 上。

对于示例解析: 未定义变量意味着在 scriptObject 的变量表中找不到,JS 引擎会沿着 scriptObject 的 upvalue 往上寻找,如果都没找到,对于写操作 i = 1; 最后就会等价为 window.i = 1; 给 window 对象新增了一个属性。

对于读操作,如果一直追溯到全局执行环境的 scriptObject 上都找不 到,就会产生运行期错误。

最后,留个问题给大家: <script type="text/javascript"> var arg = 1; function foo(arg) { alert(arg); var arg = 2; } foo(3); </script>













相关主题