="t18">从
![](/icons/50338yi.gif)
个简单
![](/icons/50338de.gif)
问题谈起:
<script type="text/javascript">
alert(i); // ?
var i = 1;
</script>
输出结果是un
![](/icons/50338define.gif)
d, 这种现象被称成“预解析”:JavaScript引擎会优先解析var变量和function定义
![](/icons/50338dou2.gif)
在预解析完成后
![](/icons/50338dou.gif)
才会执行代码
![](/icons/50338dou2.gif)
如果
![](/icons/50338yi.gif)
个文档流中包含多个script代码段(用script标签分隔
![](/icons/50338de.gif)
js代码或引入
![](/icons/50338de.gif)
js文件)
![](/icons/50338dou.gif)
运行顺序是:
step1. 读入第
![](/icons/50338yi.gif)
个代码段
step2. 做语法分析
![](/icons/50338dou.gif)
有错则报语法
![](/icons/50338cuowu.gif)
(比如括号不匹配等)
![](/icons/50338dou.gif)
并跳转到step5
step3. 对var变量和function定义做“预解析”(永远不会报错
![](/icons/50338de.gif)
![](/icons/50338dou.gif)
![](/icons/50338yinwei.gif)
只解析正确
![](/icons/50338de.gif)
声明)
step4. 执行代码段
![](/icons/50338dou.gif)
有错则报错(比如变量未定义)
step5. 如果还有下
![](/icons/50338yi.gif)
个代码段
![](/icons/50338dou.gif)
则读入下
![](/icons/50338yi.gif)
个代码段
![](/icons/50338dou.gif)
重复step2
step6. 结束
上面
![](/icons/50338de.gif)
分析
![](/icons/50338dou.gif)
已经能解释很多问题了
![](/icons/50338dou.gif)
但老觉得欠缺点什么
![](/icons/50338dou2.gif)
比如step3里
![](/icons/50338dou.gif)
“预解析”究竟是如何回事?还有step4里
![](/icons/50338dou.gif)
看下面
![](/icons/50338de.gif)
例子:
<script type="text/javascript">
alert(i); // error: i is not
![](/icons/50338define.gif)
d.
i = 1;
</script>
为什么第
![](/icons/50338yi.gif)
句会导致
![](/icons/50338cuowu.gif)
?JavaScript中
![](/icons/50338dou.gif)
变量不是可以不定义吗?
编译过程
时间如白马过隙
![](/icons/50338dou.gif)
书柜旁翻开恍如隔世般
![](/icons/50338de.gif)
![](/icons/50338smhl.gif)
编译原理
![](/icons/50338smhr.gif)
![](/icons/50338dou.gif)
熟悉而又陌生
![](/icons/50338de.gif)
空白处有着这样
![](/icons/50338de.gif)
笔记:
对于传统编译型语言来说
![](/icons/50338dou.gif)
编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成
![](/icons/50338dou2.gif)
但对于解释型语言来说
![](/icons/50338dou.gif)
通过词法分析和语法分析得到语法树后
![](/icons/50338dou.gif)
就可以开始解释执行了
简单地说
![](/icons/50338dou.gif)
词法分析是将
![](/icons/50338zifu.gif)
流(char stream)转换为记号流(token stream), 比如将c = a - b;转换为:
NAME "c"
EQUALS
NAME "a"
MINUS
NAME "b"
SEMICOLON
上面只是举例
![](/icons/50338dou.gif)
更进
![](/icons/50338yi.gif)
步
![](/icons/50338de.gif)
了解请查看 Lexical Analysis.
![](/icons/50338smhl.gif)
JavaScript权威指南
![](/icons/50338smhr.gif)
![](/icons/50338de.gif)
第2章
![](/icons/50338dou.gif)
讲
![](/icons/50338de.gif)
就是词法结构(Lexical Structure)
![](/icons/50338dou.gif)
ECMA-262 中也有描述
![](/icons/50338dou2.gif)
词法结构是
![](/icons/50338yi.gif)
门语言
![](/icons/50338de.gif)
基础
![](/icons/50338dou.gif)
很容易掌握
![](/icons/50338dou2.gif)
至于词法分析
![](/icons/50338de.gif)
实现那是另
![](/icons/50338yi.gif)
个研究领域
![](/icons/50338dou.gif)
在此不探究
![](/icons/50338dou2.gif)
可以拿自然语言来类比
![](/icons/50338dou.gif)
词法分析是
![](/icons/50338yi.gif)
对
![](/icons/50338yi.gif)
![](/icons/50338de.gif)
硬性翻译
![](/icons/50338dou.gif)
比如
![](/icons/50338yi.gif)
段英文
![](/icons/50338dou.gif)
逐词翻译成中文
![](/icons/50338dou.gif)
得到
![](/icons/50338de.gif)
是
![](/icons/50338yi.gif)
堆记号流
![](/icons/50338dou.gif)
还很难理解
![](/icons/50338dou2.gif)
进
![](/icons/50338yi.gif)
步
![](/icons/50338de.gif)
翻译
![](/icons/50338dou.gif)
就需要语法分析了
![](/icons/50338dou.gif)
下图是
![](/icons/50338yi.gif)
个条件语句
![](/icons/50338de.gif)
语法树:
构造语法树
![](/icons/50338de.gif)
时候
![](/icons/50338dou.gif)
如果发现无法构造
![](/icons/50338dou.gif)
比如
![](/icons/50338if.gif)
(a { i = 2; }, 就会报语法
![](/icons/50338cuowu.gif)
![](/icons/50338dou.gif)
并结束整个代码块
![](/icons/50338de.gif)
解析
![](/icons/50338dou.gif)
这就是本文开头部分
![](/icons/50338de.gif)
step2.
通过语法分析
![](/icons/50338dou.gif)
构造出语法树后
![](/icons/50338dou.gif)
翻译出来
![](/icons/50338de.gif)
句子可能还会有模糊不清
![](/icons/50338de.gif)
地方
![](/icons/50338dou.gif)
接下来还需要进
![](/icons/50338yi.gif)
步
![](/icons/50338de.gif)
语义检查
![](/icons/50338dou2.gif)
对于传统强类型语言来说
![](/icons/50338dou.gif)
语义检查
![](/icons/50338de.gif)
主要部分是类型检查
![](/icons/50338dou.gif)
比如
![](/icons/50338hanshu.gif)
![](/icons/50338de.gif)
实参和形参类型是否匹配
![](/icons/50338dou2.gif)
对于弱类型语言来说
![](/icons/50338dou.gif)
这
![](/icons/50338yi.gif)
步可能没有(精力有限
![](/icons/50338dou.gif)
没时间去看JS
![](/icons/50338de.gif)
引擎实现
![](/icons/50338dou.gif)
不敢确定JS引擎中是否有语义检查这
![](/icons/50338yi.gif)
步)
![](/icons/50338dou2.gif)
通过上面
![](/icons/50338de.gif)
分析可以看出
![](/icons/50338dou.gif)
对于JavaScript引擎来说
![](/icons/50338dou.gif)
肯定有词法分析和语法分析
![](/icons/50338dou.gif)
的后可能还有语义检查、代码优化等步骤
![](/icons/50338dou.gif)
等这些编译步骤完成的后(任何语言都有编译过程
![](/icons/50338dou.gif)
只是解释型语言没有编译成 2进制代码)
![](/icons/50338dou.gif)
才会开始执行代码
![](/icons/50338dou2.gif)
上面
![](/icons/50338de.gif)
编译过程
![](/icons/50338dou.gif)
还是无法更深入
![](/icons/50338de.gif)
解释文章开头部分
![](/icons/50338de.gif)
“预解析”
![](/icons/50338dou.gif)
我们还得仔细探究下JavaScript代码
![](/icons/50338de.gif)
执行过程
![](/icons/50338dou2.gif)
执行过程
周爱民在
![](/icons/50338smhl.gif)
JavaScript语言精髓和编程实战
![](/icons/50338smhr.gif)
![](/icons/50338de.gif)
第 2部分
![](/icons/50338dou.gif)
对此有非常仔细
![](/icons/50338de.gif)
分析
![](/icons/50338dou2.gif)
下面是我
![](/icons/50338de.gif)
![](/icons/50338yi.gif)
些领悟:
通过编译
![](/icons/50338dou.gif)
JavaScript代码已经翻译成了语法树
![](/icons/50338dou.gif)
然后会立刻按照语法树执行
![](/icons/50338dou2.gif)
进
![](/icons/50338yi.gif)
步
![](/icons/50338de.gif)
执行过程
![](/icons/50338dou.gif)
需要理解JavaScript
![](/icons/50338de.gif)
作用域机制
![](/icons/50338dou.gif)
JavaScript采用
![](/icons/50338de.gif)
是词法作用域(lexcical scope)
![](/icons/50338dou2.gif)
通俗地讲
![](/icons/50338dou.gif)
就是JavaScript变量
![](/icons/50338de.gif)
作用域是在定义时决定而不是执行时决定
![](/icons/50338dou.gif)
也就是说词法作用域取决于源码
![](/icons/50338dou.gif)
编译器通过静态分析就能确定
![](/icons/50338dou.gif)
因此词法作用域也叫做静态作用域(
![](/icons/50338static.gif)
scope)
![](/icons/50338dou2.gif)
但需要注意
![](/icons/50338dou.gif)
with和eval
![](/icons/50338de.gif)
语义无法仅通过静态技术实现
![](/icons/50338dou.gif)
实际上
![](/icons/50338dou.gif)
只能说JS
![](/icons/50338de.gif)
作用域机制非常接近lexical scope.
JS引擎在执行每个
![](/icons/50338hanshu.gif)
例子时
![](/icons/50338dou.gif)
都会创建
![](/icons/50338yi.gif)
个执行环境(execution context)
![](/icons/50338dou2.gif)
execution context中包含
![](/icons/50338yi.gif)
个
![](/icons/50338diaoyong.gif)
对象(call object),
![](/icons/50338diaoyong.gif)
对象是
![](/icons/50338yi.gif)
个scriptObject结构
![](/icons/50338dou.gif)
用来保存内部变量表varDecls、内嵌
![](/icons/50338hanshu.gif)
表funDecls、父级引用列表upvalue等语法分析结构(注意:varDecls和funDecls等信息是在语法分析阶段就已经得到
![](/icons/50338dou.gif)
并保存在语法树中
![](/icons/50338dou2.gif)
![](/icons/50338hanshu.gif)
例子执行时
![](/icons/50338dou.gif)
会将这些信息从语法树复制到scriptObject上)
![](/icons/50338dou2.gif)
scriptObject是和
![](/icons/50338hanshu.gif)
相关
![](/icons/50338de.gif)
![](/icons/50338yi.gif)
套静态系统
![](/icons/50338dou.gif)
和
![](/icons/50338hanshu.gif)
例子
![](/icons/50338de.gif)
生命周期保持
![](/icons/50338yi.gif)
致
![](/icons/50338dou2.gif)
lexical scope是JS
![](/icons/50338de.gif)
作用域机制
![](/icons/50338dou.gif)
还需要理解它
![](/icons/50338de.gif)
实现思路方法
![](/icons/50338dou.gif)
这就是作用域链(scope chain)
![](/icons/50338dou2.gif)
scope chain是
![](/icons/50338yi.gif)
个name lookup机制
![](/icons/50338dou.gif)
首先在当前执行环境
![](/icons/50338de.gif)
scriptObject中寻找
![](/icons/50338dou.gif)
没找到
![](/icons/50338dou.gif)
则顺着upvalue到父级scriptObject中寻找
![](/icons/50338dou.gif)
![](/icons/50338yi.gif)
直lookup到全局
![](/icons/50338diaoyong.gif)
对象(global object)
![](/icons/50338dou2.gif)
当
![](/icons/50338yi.gif)
个
![](/icons/50338hanshu.gif)
例子执行时
![](/icons/50338dou.gif)
会创建或关联到
![](/icons/50338yi.gif)
个闭包(closure)
![](/icons/50338dou2.gif)
scriptObject用来静态保存和
![](/icons/50338hanshu.gif)
相关
![](/icons/50338de.gif)
变量表
![](/icons/50338dou.gif)
closure则在执行期动态保存这些变量表及其运行值
![](/icons/50338dou2.gif)
closure
![](/icons/50338de.gif)
生命周期有可能比
![](/icons/50338hanshu.gif)
例子长
![](/icons/50338dou2.gif)
![](/icons/50338hanshu.gif)
例子在活动引用为空后会自动销毁
![](/icons/50338dou.gif)
closure则要等要数据引用为空后
![](/icons/50338dou.gif)
由JS引擎回收(有些情况下不会自动回收
![](/icons/50338dou.gif)
就导致了内存泄漏)
![](/icons/50338dou2.gif)
别被上面
![](/icons/50338de.gif)
![](/icons/50338yi.gif)
堆名词吓住
![](/icons/50338dou.gif)
![](/icons/50338yi.gif)
旦理解了执行环境、
![](/icons/50338diaoyong.gif)
对象、闭包、词法作用域、作用域链这些概念
![](/icons/50338dou.gif)
JS语言
![](/icons/50338de.gif)
很多现象都能迎刃而解
![](/icons/50338dou2.gif)
小结
至此
![](/icons/50338dou.gif)
对于文章开头部分
![](/icons/50338de.gif)
疑问
![](/icons/50338dou.gif)
可以解释得很清楚了:
step3中所谓
![](/icons/50338de.gif)
“预解析”
![](/icons/50338dou.gif)
其实是在step2
![](/icons/50338de.gif)
语法分析阶段完成
![](/icons/50338dou.gif)
并存储在语法树中
![](/icons/50338dou2.gif)
当执行到
![](/icons/50338hanshu.gif)
例子时
![](/icons/50338dou.gif)
会将varDelcs和funcDecls从语法树中复制到执行环境
![](/icons/50338de.gif)
scriptObject上
![](/icons/50338dou2.gif)
step4中
![](/icons/50338dou.gif)
未定义变量意味着在scriptObject
![](/icons/50338de.gif)
变量表中找不到
![](/icons/50338dou.gif)
JS引擎会沿着scriptObject
![](/icons/50338de.gif)
upvalue往上寻找
![](/icons/50338dou.gif)
如果都没找到
![](/icons/50338dou.gif)
对于写操作i = 1; 最后就会等价为 window.i = 1; 给window对象新增了
![](/icons/50338yi.gif)
个属性
![](/icons/50338dou2.gif)
对于读操作
![](/icons/50338dou.gif)
如果
![](/icons/50338yi.gif)
直追溯到全局执行环境
![](/icons/50338de.gif)
scriptObject上都找不到
![](/icons/50338dou.gif)
就会产生运行期
![](/icons/50338cuowu.gif)
![](/icons/50338dou2.gif)
理解后
![](/icons/50338dou.gif)
雾散花开
![](/icons/50338dou.gif)
天空
![](/icons/50338yi.gif)
片晴朗
![](/icons/50338dou2.gif)
最后
![](/icons/50338dou.gif)
留个问题给大家:
<script type="text/javascript">
var arg = 1;
function foo(arg) {
alert(arg);
var arg = 2;
}
foo(3);
</script>
请问alert
![](/icons/50338de.gif)
输出是什么?