编译 (解析)
对于传统编译型语言(例如:Java)来说,编译步骤分为:词法分析->语法分析->语义检查->代码优化和字节生成。
对于解释型语言(例如:JavaScript)来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。
词法分析是将字符流(char stream)转换为记号流(token stream),例如:
正常代码
var result = testNum1 - testNum2;
词法分析后:
NAME "result"
EQUALS
NAME "testNum1"
MINUS
NAME "testNum2"
SEMICOLON
可以拿自然语言来类比,词法分析是一对一的硬性翻译,比如一段英文,逐词翻译成中文,得到的是一堆记号流,还很难理解。进一步的翻译,就需要语法分析了。生成语法树例子:
正常代码
if(typeof a == "undefined" ){
a = 0;
}else{
a = a;
}
alert(a);
语法树
执行
经过编译阶段的准备,JavaScript代码在内存中已经被构建为语法树,然后JavaScript引擎就会根据这个语法树结构边解释边执行了。
在解释过程中,JavaScript引擎是严格按着作用域机制(scope)来执行的。JavaScript语法采用的是词法作用域 (lexcical scope),也就是说JavaScript的变量和函数作用域是在定义时决定的,而不是执行时决定的,由于词法作用域取决于源代码结构,所以JavaScript解释器只需要通过静态分析就能确定每个变量、函数的作用域,这种作用域也称为静态作用域(static scope)。但需要注意,with和eval的语义无法仅通过静态技术实现,实际上,只能说JS的作用域机制非常接近lexical scope。
JavaScript引擎在调用执行每个函数时,都会创建一个执行环境(execution context)。执行环境中包含一个调用对象(call object), 调用对象是一个scriptObject结构,用来保存内部变量表varDecls、内嵌函数表funDecls、父级引用列表upvalue等语法分析结构(注意:varDecls和funDecls等信息是在语法分析阶段就已经得到,并保存在语法树中。函数调用执行时,会将这些信息从语法树复制到 scriptObject上)。scriptObject是与函数相关的一套静态系统,与函数实例的生命周期保持一致。
JavaScript引擎通过作用域链(scope chain)把多个嵌套的作用域串连在一起,并借助这个链条帮助JavaScript解释器检索变量的值。这个作用域链相当于一个索引表,并通过编号来存储它们的嵌套关系。当JavaScript解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象(global object)为止,如果没有找到值,则传递一个特殊的undefined值。
如果函数引用了外部变量的值,则JavaScript引擎会为该函数创建一个闭包体(closure),闭包体是一个完全封闭和独立的作用域,它不会在函数调用完毕后就被JavaScript引擎当做垃圾进行回收。闭包体可以长期存在,因此开发人员常把闭包体当做内存中的蓄水池,专门用来长期保存变 量的值。
只有当闭包体的外部引用被全部设置为null值时,该闭包才会被回收。当然,也容易引发垃圾泛滥,甚至出现内存外溢的现象。
编译和执行的关系
例子:
alert(a); // 返回值undefined
var a =1;
alert(a); // 返回值1
为什么这段代码执行不会报错?
因为:JavaScript引擎解析脚本时,它会在预编译期对所有声明的变量和函数进行处理。
分析:由于变量声明是在预编译期被处理的,所以在执行期间对于所有代码来说,都是可见的。但是,你也会看到,执行上面代码,提示的值是 undefined,而不是1。这是因为,变量初始化过程发生在执行期,而不是预编译期。在执行期,JavaScript解释器是按着代码先后顺序来解释执行的,如果在前面代码行中没有为变量赋值,则JavaScript解释器会使用默认值undefined。由于在第二行中为变量a赋值了,所以在第三行代 码中会提示变量a的值为1,而不是undefined。
//代码1
testFun(); // 调用函数,返回值1
function testFun(){
alert(1);
}
//代码2
testFun(); // 调用函数,返回语法错误
var testFun = function(){
alert(1);
}
分析:
代码1,在函数声明前调用函数也是合法的,并能够被正确解析。
代码2,定义的函数仅作为值赋值给变量testFun,所以在预编译期,JavaScript解释器只能够为声明变量f进行处理,而对于变量testFun的值,只能等到执行期时按顺序进行赋值,自然就会出现语法错误,提示找不到对象testFun。
原文:JavaScript的运行机制