javascript核心 -- 解析过程

本文详细介绍了JavaScript的执行流程,包括代码段的读取、语法分析、预解析、执行及错误处理等步骤,并通过实例展示了语法分析树的构建过程及其关键特性。
[color=green][size=large]1.javascript解析过程[/size][/color]
如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),它们的运行顺序是:
步骤1. 读入第一个[color=red]代码段[/color](js执行引擎并非一行一行地执行程序,而是一段一段地分析执行的)
步骤2. 做[color=red]语法分析[/color],有错则报语法错误(比如括号不匹配等),并跳转到步骤5
步骤3. 对var变量和function定义做“[color=red]预解析[/color]”(永远不会报错的,因为只解析正确的声明)
步骤4. 执行代码段,有[color=red]错则报错[/color](比如变量未定义)
步骤5. 如果还有下一个代码段,则读入下一个代码段,重复步骤2
步骤6. 结束


[size=large][color=darkred]javascript解析模拟[/color][/size]
[size=x-large]1. 待解析的js[/size]

/*全局(window)域下的一段代码*/
var i = 1,j = 2,k = 3;
function a(o,p,x,q){
var x = 4;
alert(i);

// 在函数a中定义新的函数b,并且有变量i,y
function b(r,s) {
var i = 11,y = 5;
alert(i);

//在函数b中定义新的函数c,并且有变量z
function c(t){
var z = 6;
alert(i);
};

//在b中定义 函数表达式 d
var d = function(){
alert(y);
};
c(60);
d();
};
b(40,50);
}
a(10,20,30);




[size=x-large]2. 建立分析树[/size]
上面的代码很简单,就是先定义了一些全局变量和全局方法,接着在方法内再定义局部变量和局部方法,现在JS解释器读入这段代码开始解析,前面提到 JS 引擎会先通过语法分析和预解析得到语法分析树,至于语法分析树长什么样儿,都有些什么信息。

下面我们以一种简单的结构:一个 JS 对象(为了清晰表示个各种对象间的引用关系,这里的只是伪对象表示,可能无法运行)来描述语法分析树(这是我们比较熟悉的,实际结构我们不去深究,肯定复杂得多,这里是为了帮助理解解析过程而特意简化)。

/**
* 模拟建立一棵语法分析树,存储function内的变量和方法
*/
var SyntaxTree = {
// 全局对象在语法分析树中的表示
window: {
variables:{
i:{ value:1},
j:{ value:2},
k:{ value:3}
},
functions:{
a: this.a
}
},

a:{
variables:{
x:'undefined'
},
functions:{
b: this.b
},
scope: this.window
},

b:{
variables:{
y:'undefined'
},
functions:{
c: this.c,
d: this.d
},
scope: this.a
},

c:{
variables:{
z:'undefined'
},
functions:{},
scope: this.b
},

d:{
variables:{},
functions:{},
scope: {
myname:d,
scope: this.b
}
}
};


上面就是关于语法分析树的一个简单表示,正如我们前面分析的,语法分析树主要记录了每个 function 中的变量集(variables),方法集(functions)和作用域(scope)。

[size=large][color=darkred]语法分析树关键点[/color][/size]
变量集(variables)中,[color=red]只有变量定义,没有变量值[/color],这时候的变量值全部为“undefined”
作用域(scope),根据词法作用域的特点,这个时候[color=red]每个变量的作用域就已经明确了[/color],而不会随执行时的环境而改变。【什么意思呢?就是我们经常将一个方法 return 回去,然后在另外一个方法中去执行,执行时,方法中变量的作用域是按照方法定义时的作用域走。其实这里想表达的意思就是不管你在多么复杂,多么远的地方执行该方法,最终判断方法中变量能否被访问还是得回到方法定义时的地方查证】
作用域(scope)建立规则:
a.对于函数声明和匿名函数表达式来说,[scope]就是它创建时的作用域
b.对于有名字的函数表达式,[scope]顶端是一个新的JS对象(也就是继承了Object.prototype),这个对象有两个属性,第一个是自身的名称,第二个是定义的作用域,第一个函数名称是为了确保函数内部的代码可以无误地访问自己的函数名进行递归。

[size=x-large]3、执行环境[/size]
语法分析完成,开始执行代码。我们调用每一个方法的时候,JS 引擎都会自动为其建立一个执行环境和一个活动对象,它们和方法实例的生命周期保持一致,为方法执行提供必要的执行支持,针对上面的几个方法,我们这里统一为其建立了活动对象(按道理是在执行方法的时候才会生成活动对象,为了便于演示,这里一下子定义了所有方法的活动对象),具体如下:

/**
* 执行环境:函数执行时创建的执行环境
*/
var ExecutionContext = {
window: {
type: 'global',
name: 'global',
body: ActiveObject.window
},

a:{
type: 'function',
name: 'a',
body: ActiveObject.a,
scopeChain: this.window.body
},

b:{
type: 'function',
name: 'b',
body: ActiveObject.b,
scopeChain: this.a.body
},

c:{
type: 'function',
name: 'c',
body: ActiveObject.c,
scopeChain: this.b.body
},

d:{
type: 'function',
name: 'd',
body: ActiveObject.d,
scopeChain: this.b.body
}
}

上面每一个方法的执行环境都存储了相应方法的类型(function)、方法名称(funcName)、活动对象(ActiveObject)、作用域链(scopeChain)等信息,其关键点如下:

[color=red]body属性[/color],直接指向当前方法的活动对象
[color=red]scopeChain属性[/color],作用域链,它是一个链表结构,根据语法分析树中当前方法对应的[color=red]scope属性[/color],它指向scope对应的方法的活动对象(ActivceObject),变量查找就是跟着这条链条查找的

[size=x-large]4. 活动对象[/size]

/**
* 活动对象:函数执行时创建的活动对象列表
*/
var ActiveObject = {
window: {
variables:{
i: { value:1},
j: { value:2},
k: { value:3}
},
functions:{
a: this.a
}
},

a:{
variables:{
x: {value:4}
},
functions:{
b: SyntaxTree.b
},
parameters:{
o: {value: 10},
p: {value: 20},
x: this.variables.x,
q: 'undefined'
},
arguments:[this.parameters.o,this.parameters.p,this.parameters.x]
},

b:{
variables:{
y:{ value:5}
},
functions:{
c: SyntaxTree.c,
d: SyntaxTree.d
},
parameters:{
r:{value:40},
s:{value:50}
},
arguments:[this.parameters.r,this.parameters.s]
},

c:{
variables:{
z:{ value:6}
},
functions:{},
parameters:{
u:{value:70}
},
arguments:[this.parameters.u]
},

d:{
variables:{},
functions:{},
parameters:{},
arguments:[]
}
}

上面每一个活动对象都存储了相应方法的内部变量集(variables)、内嵌函数集(functions)、形参(parameters)、实参(arguments)等执行所需信息,活动对象关键点

创建活动对象,从语法分析树复制方法的内部变量集(variables)和内嵌函数集(functions)
[color=red]方法开始执行,活动对象里的内部变量集全部被重置为 undefined[/color]
创建形参(parameters)和实参(arguments)对象,[color=red]同名的实参,形参和变量之间是【引用】关系[/color]
执行方法内的赋值语句,这才会对变量集中的变量进行赋值处理
[color=red]变量查找规则[/color]是首先在当前执行环境的 ActiveObject 中寻找,没找到,则顺着执行环境中属性 [color=red]ScopeChain 指向的 ActiveObject[/color] 中寻找,一直到 Global Object(window)
方法执行完成后,内部变量值不会被重置,至于变量什么时候被销毁,请参考下面一条
方法内变量的生存周期取决于方法实例是否存在活动引用,如没有就销毁活动对象
6和7 是使闭包能访问到外部变量的根本原因


[color=red]以上内容多数转载至网络,非个人原创.[/color]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值