javascript解析引擎
JavaScript解析引擎到底是干什么的?
JavaScript解析过程
第一阶段:语法检查
一:词法分析
例如:把字符流:
a = (b - c);
转换为记号流:
NAME "a"
EQUALS
OPEN_PARENTHESIS
NAME "b"
MINUS
NAME "c"
CLOSE_PARENTHESIS
SEMICOLON
二:语法分析
-
- var,if,else,break,continue等是JavaScript的关键词
- abstract,int,long等是JavaScript保留词
- 怎么样算是数字、怎么样算是字符串等等
- 定义了操作符(+,-,=)等操作符
- 定义了JavaScript的语法
- 定义了对表达式,语句等标准的处理算法,比如遇到==该如何处理
- ……
第二阶段:运行阶段
一:预解析
- 内部变量表varDecls:varDecls保存的用var进行显式声明的局部变量。
- 内嵌函数表funDecls:在“预解析”阶段,发现有函数定义的时候,除了记录函数的声明外,还会创建一个原型链对象(prototype)。
- …其他的信息。
- 变量对象(Variable Object):由var declaration、function declaration(变量声明、函数声明)、arguments(参数)构成。变量对象是以单例形式存在。
- 作用域链(Scope Chain):variable object + all parent scopes(变量对象以及所有父级作用域)构成。
- this值:(thisValue):content object。this值在进入上下文阶段就确定了。一旦进入执行代码阶段,this值就不会变了。
- 函数的形参:执行上下文的变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值;对于没有传递的参数,其值为undefined。
- 函数声明:执行上下文的变量对象的一个属性,属性名和值都是函数对象创建出来的;如果变量对象已经包含了相同名字的属性,则会替换它的值。
- 变量声明:执行上下文的变量对象的一个属性,其属性名即为变量名,其值为undefined;如果变量名和已经声明的函数名或者函数的参数名相同,则不会影响已经存在的函数声明的属性。
var a=1;
function b(a) {
alert(a);
}
var b;
alert(b); // function b(a) { alert(a); }
b(); //undefined
以上代码在进入执行上下文时,按照函数的形参->函数声明->变量声明顺序来填充,并且优先权永远都是函数的形参>函数声明>变量声明,所以只要alert(a)中的a是函数中的形参,就永远不会被函数和变量声明覆盖。就算没有赋值也是默认填充的undefined值。
第二部分:执行代码
alert(var);// SyntaxError: syntax error 语法分析阶段错误 :语法错误
var=1;; // SyntaxError: missing variable name 语法分析阶段错误 :var是保留字符,导致变量名丢失
a=b=v // ReferenceError: v is not defined 运行期错误: v 是未定义的
JavaScript错误信息)
有如此详细的错误提示,是不是就很快就知道代码中到底是哪里错了呢!
作用域链(Scope Chain)
作用域链以及执行上下文的关系
Scope = AO + [[Scope]] //预解析时的 Scope 属性
Scope = [AO].concat([[Scope]]); //执行阶段,将AO添加到作用域链的最前端
执行上下文定义的 Scope 属性变化过程
执行上下文中的[AO]是函数的活动对象,而[[Scope]]则是该函数属性作用域。当前函数的AO永远是在最前面的,保存在堆栈上,而每当函数激活的时候,这些AO都会压栈到该堆栈上,查询变量是先从栈顶开始查找,也就是说作用域链的栈顶永远是当前正在执行的代码所在环境的VO/AO(当函数调用结束后,则会从栈顶移除)。
通俗点讲就是:JavaScript解释器通过作用域链将不同执行位置上的变量对象串连成列表,并借助这个列表帮助JavaScript解释器检索变量的值。作用域链相当于一个索引表,并通过编号来存储它们的嵌套关系。当JavaScript解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象为止,如果没有找到值,则传递一个特殊的 undefined值。
是不是又想到了一条JavaScript高效准则:为什么说在该函数内定义的变量,能减少函数嵌套能提高JavaScript的效率?因为函数定义的变量,此变量永远在栈顶,这样子查询变量的时间变短了。
作用域的特性
作用域链感觉就是一个VO链表,当访问一个变量时,先在链表的第一个VO上查找,如果没有找到则继续在第二个VO上查找,直到搜索结束,也就是搜索到全局执行环境的VO中。这也就形成了作用域链的概念。
var color="blue"; function changecolor(){ var anothercolor="red"; function swapcolors(){ var tempcolor=anothercolor; anothercolor=color; color=tempcolor; // Todo something } swapcolors(); } changecolor();//这里不能访问tempcolor和anocolor;但是可以访问color; alert("Color is now "+color);
作用域链保护变量安
函数的作用域是在函数创建即“预解析”阶段就已经就已经定义了,而在代码执行阶段则是将函数的作用域添加到作用域链上。
原型链查询
function Foo() {
function bar() {
alert(x);
}
bar();
}
Object.prototype.x = 10;
Foo(); // 10
代码执行阶段对“预解析”的改变
执行改变作用域链
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
闭包
理论角度:所有函数都是闭包。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量。
应用角度:当在代码中引用了自由变量,即使创建它的上下文已经销毁,此变量还能访问。
ECMAScript标准中,同一个上下文创建的闭包(理论上的闭包)是共用一个作用域的,也就是说闭包中对其中变量修改会影响到其他闭包对其变量的读取。
所谓创建额外的闭包就是创建函数,不管是匿名函数、函数表达式、函数声明(除了构造函数),只要能创建作用域链就行,与函数类型无关,然而创建额外的函数不是唯一的方法。
遍历最外层代码:
for (i = 0; i < len; i++) {...//①}
遍历获取索引值最外层代码
使用函数创建闭包:
方法一:使用函数闭包获取索引值
(function(i){
lists[i].onmouseover = function () {
alert(i);
};
})(i);
直接将匿名函数赋值给事件,创建额外的函数来创建多个作用域。
方法二:使用函数闭包获取索引值
lists[i].onmouseover = (function (x) {
return function (){
alert(i);
};
})(i);
使用try { ... } catch (ex) { ... }改变作用域链:
try-catch改变作用域链的原理跟with一样,try 部分包含需要运行的代码,而 catch 部分包含错误发生时运行的代码。如下:
var array = null;
var x=10;
try {
document.write(array[0]);
} catch(x) {
x =20;
document.writeln("catch内的x值"+x); //20
}
document.writeln("catch外的x值"+x); //10