您的位置:首页 > Web前端 > JavaScript

JavaScript的运行机制(2)

2013-06-20 16:30 405 查看
一、理解编译过程

对于传统编译型语言(例如:Java)来说,编译步骤分为:词法分析->语法分析->语义检查->代码优化和字节生成。

对于解释型语言(例如JavaScript)来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。

词法分析是将字符流(char stream)转换为记号流(token stream)

词法分析的例子

//代码

var result = testNum1 - testNum2;

//词法分析后

NAME "result"

EQUALS

NAME "testNum1"

MINUS

NAME "testNum2"

SEMICOLON

//代码 var result = testNum1 - testNum2; //词法分析后 NAME "result" EQUALS NAME "testNum1" MINUS NAME "testNum2" SEMICOLON

可以拿自然语言来类比,词法分析是一对一的硬性翻译,比如一段英文,逐词翻译成中文,得到的是一堆记号流,还很难理解。进一步的翻译,就需要语法分析了。
生成语法树例子

//条件语句

if(typeof a == "undefined" ){

a = 0;

}

else

{

a = a;

}

alert(a);

//条件语句 if(typeof a == "undefined" ){ a = 0; } else { a = a; } alert(a);



当JavaScript解释器在构造语法树的时候,如果发现无法构造,就会报语法错误,并结束整个代码块的解析。

二、理解JavaScript执行过程

经过编译阶段的准备,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

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 testFun(); // 调用函数,返回值1 function testFun(){ alert(1); } //代码2 testFun(); // 调用函数,返回语法错误 var testFun = function(){ alert(1); }

分析:

代码1,在函数声明前调用函数也是合法的,并能够被正确解析。

代码2,定义的函数仅作为值赋值给变量testFun,所以在预编译期,JavaScript解释器只能够为声明变量f进行处理,而对于变量testFun的值,只能等到执行期时按顺序进行赋值,自然就会出现语法错误,提示找不到对象testFun。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: