注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

星期五

明天不上班

 
 
 

日志

 
 
关于我

一个特立独行的Java程序员,比较宅,上上网,写博客,听音乐,看电影。

网易考拉推荐

javascript核心 -- 解析过程 (转载 + 重新整理)  

2014-05-21 18:42:46|  分类: Javascript |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

了解 JS 引擎的工作方式,所以下面我们就把 JS 引擎对一个方法的解析过程进行一个稍微深入一些的介绍 
解析过程

1、执行顺序

编译型语言,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成。 解释型语言,通过词法分析和语法分析得到语法分析树后,就可以开始解释执行了。这里是一个简单原始的关于解析过程的原理,仅作为参考,详细的解析过程(各种JS引擎还有不同)还需要更深一步的研究

JavaScript执行过程,如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),它们的运行顺序是:

步骤1. 读入第一个代码段(js执行引擎并非一行一行地执行程序,而是一段一段地分析执行的)
步骤2. 做词法分析和语法分析,有错则报语法错误(比如括号不匹配等),并跳转到步骤5 
步骤3. 对【var】变量和【function】定义做“预解析“(永远不会报错的,因为只解析正确的声明) 
步骤4. 执行代码段,有错则报错(比如变量未定义) 
步骤5. 如果还有下一个代码段,则读入下一个代码段,重复步骤2 
步骤6. 结束

2、特殊说明
全局域(window)域下所有JS代码可以被看成是一个“匿名方法“,它会被自动执行,而此“匿名方法“内的其它方法则是在被显示调用的时候才被执行


3、关键步骤
上面的过程,我们主要是分成两个阶段

解析:就是通过语法分析和预解析构造合法的语法分析树。 
执行:执行具体的某个function,JS引擎在执行每个函数实例时,都会创建一个执行环境(ExecutionContext)和活动对象(activeObject)(它们属于宿主对象,与函数实例的生命周期保持一致)


4、关键概念
到这里,我们再更强调以下一些概念,这些概念都会在下面用一个一个的实体来表示,便于大家理解

语法分析树(SyntaxTree)可以直观地表示出这段代码的相关信息,具体的实现就是JS引擎创建了一些表,用来记录每个方法内的变量集(variables),方法集(functions)和作用域(scope)等。执行环境(ExecutionContext)可理解为一个记录当前执行的方法【外部描述信息】的对象, 记录所执行方法的类型,名称,参数和活动对象(activeObject) 活动对象(activeObject)可理解为一个记录当前执行的方法【内部执行信息】的对象,记录内部变量集(variables)、内嵌函数集(functions)、实参(arguments)、作用域链(scopeChain)等执行所需信息,其中内部变量集(variables)、内嵌函数集(functions)是直接从第一步建立的语法分析树复制过来的 词法作用域:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。 with和eval除外,所以只能说JS的作用域机制非常接近词法作用域(Lexical scope) 作用域链:词法作用域的实现机制就是作用域链(scopeChain)。作用域链是一套按名称查找(Name Lookup)的机制,首先在当前执行环境的 ActiveObject 中寻找,没找到,则顺着作用域链到父 ActiveObject 中寻找,一直找到全局调用对象(Global Object)


5、实体表示

javascript核心 -- 解析过程 (转载 + 重新整理) - 星期五 - 星期五

JavaScript解析模拟 
1. 待解析的js

/*全局(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);

2. 建立分析树 
上面的代码很简单,就是先定义了一些全局变量和全局方法,接着在方法内再定义局部变量和局部方法,现在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)。 

语法分析树关键点 
变量集(variables)中,只有变量定义,没有变量值,这时候的变量值全部为“undefined” 
作用域(scope),根据词法作用域的特点,这个时候每个变量的作用域就已经明确了,而不会随执行时的环境而改变。【什么意思呢?就是我们经常将一个方法 return 回去,然后在另外一个方法中去执行,执行时,方法中变量的作用域是按照方法定义时的作用域走。其实这里想表达的意思就是不管你在多么复杂,多么远的地方执行该方法,最终判断方法中变量能否被访问还是得回到方法定义时的地方查证】 
作用域(scope)建立规则: 
    a.对于函数声明和匿名函数表达式来说,[scope]就是它创建时的作用域 
    b.对于有名字的函数表达式,[scope]顶端是一个新的JS对象(也就是继承了Object.prototype),这个对象有两个属性,第一个是自身的名称,第二个是定义的作用域,第一个函数名称是为了确保函数内部的代码可以无误地访问自己的函数名进行递归。 
3、执行环境 
语法分析完成,开始执行代码。我们调用每一个方法的时候,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)等信息,其关键点如下: 
body属性,直接指向当前方法的活动对象。 
scopeChain属性,作用域链,它是一个链表结构,根据语法分析树中当前方法对应的scope属性
,它指向scope对应的方法的活动对象(ActivceObject),变量查找就是跟着这条链条查找的。

4. 活动对象 

/**
* 活动对象:函数执行时创建的活动对象列表
*/
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)等执行所需信息,活动对象关键点 

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

6和7 是使闭包能访问到外部变量的根本原因 

原文地址:
http://hi.baidu.com/jsdoudou/item/d96e6a35af471e9cb80c0386
http://ganludong.blog.51cto.com/801200/425343
  评论这张
 
阅读(457)| 评论(0)
推荐

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017