JavaScript预编译原理

在JS中,遇到 script 标签的话就会进行预解析,将变量 var 和 function 声明提升,但不会执行 function,然后就进入上下文执行,上下文执行还是执行预解析同样操作,直到没有 var 和 function,就开始执行上下文。

变量提升
指JS标签里的变量声明和函数都会被提到函数的最顶部,在JS中,变量可以先使用后声明。

x = 100;
document.write(x);
var x;

等同与

var x;
x  = 100;
document.write(x);

这两个代码块是一模一样的。

但是,已经初始化的变量不会被提升。

document.write(x);
var x = 100;

这个代码块的运行结果为undefined,因为变量x已经被初始化成100,不会被提升到函数的顶部。

JavaScript运行三步曲:

1,语法分析:先通篇扫描一遍,检查是否有低级的语法错误,如漏写大括号,写了中文等等。

2,预编译:函数 声明整体提升,变量 声明提升。而且,预编译发生在函数运行的前一刻。

a,函数声明整体提升:
无论写在哪里,在哪里调用,系统总会把函数提到逻辑的最前面

<script>
	test();
	
	function test(){
		console.log("abc");
	}
</script>

b,变量声明提升:

变量 声明提升,比如 var a = 123;这个被拆分为 (var a; a = 123;) 系统会把变量声明提到最前面,也就是说把var a;提到了最前面。

document.write(x);
var x = 100;

上面这个代码块在进行提升后等同于:

var x;
document.write(x);
x = 100;

运行结果为undefined,这个例子可以用来解释上面提到的为什么已经初始化的变量不能进行提升。

在进行预编译,也会分为四个步骤:
1,创建AO对象(activation object,也叫做执行期上下文)

AO{
	formal parameter: undefined,
	...
	...
}	

这里我们借用一个例子来更好的解释。

1    function jsFun6(){  //函数声明和函数表达式的区别
2        
3        test1();//函数声明提升,在执行代码之前会先读取函数声明,不会报错
4        function test1(){//函数声明方式创建函数
5            alert("测试1");
6        }
7        
8        //test2();报错,函数还不存在
9        console.log(test2)//不会报错,变量提升只是提升变量的声明,并不会把赋值也提升上来,输出undefined
10        var test2=function(){
11            alert("测试2");
12        };//使用函数表达式创建一个匿名函数(实际是以变量test3命名的函数)
13        test2();//不会报错,以创建函数
14                    
15        var test3=function(){
16            alert("测试3");
17        }();//加了括号立即执行
18        
19        var test4 = 12;// !注意看,一旦变量被赋值后,将会输出变量
20        //函数提升优先级高于变量提升,所以函数先提升,然后变量提升覆盖之前的函数声明,表                
21        //现为变量
22        function test4() {
23            alert("测试4");               
24        }
25        console.log(test4); //12
26    
27        var test5="test5_1";
28        (function(){
29            //js中的变量搜索顺序:找变量时,先找局部变量,如果没有局部变量;再找全局变量。
30        alert(test5);//此时的test5为局部变量的提升,undefined
31        var test5="test5_2";
32        })();
33                    
34    }
35    jsFun6();

找函数形参和变量声明,然后把形参和变量声明的名作为AO里面的属性名,值为undefined,
所以在进行创建AO对象时,第10,15,19,27的参数名会作为AO对象的属性名,属性值为undefined。所以AO对象就会变成下面这样:

AO{
	test2: undefined,
	test3: undefined,
	test4: undefined,
	test5: undefined,
}

然后会将实参的值传到AO对象里,替代相对于的undefined值。
然后再找到函数声明作为AO属性名,第4,22行的function test1(){}; function test4(){};,放到相对于的位置。
所以AO对象则变成下面这样:

AO{
 test2: undefined,
 test3: undefined,
 test4: function test4(){},
 test5: undefined,
 test1: function test1(){},
}

这里需要注意因为之前 AO 对象里已经有 test4 属性了所以 AO 对象里原来由变量声明时得到的 test4 属性会被现在函数声明的 test4 覆盖

到这里预编译就完成了开始执行。

执行时会先去查找 AO 对象,如果没找到就会去全局对象GO(Global Object)对象里查找(AO 对象相当于局部变量,GO 对象相当于全局变量)。

当执行第 3 行时在 AO 里找到 test1 执行,弹出提示框显示提示信息“测试1”。

第 4 行已经提升了所以可以略过,下面其他的类似行与此相同。

执行第 8 行时, 在 AO 里找到 test2,并作为函数运行,由于 AO 里的 test2 的值是 undefined 所以执行会报错。

第 9 行输出 test2 所以输出的是 undefined。

AO对象也变成了:

AO{
    test2:function(){alert("测试2");},
    test3:undefined,
    test4:function test4(){alert("测试4");},
    test5:undefined,
    test1:function test1(){alert("测试1");}
}

所以第 13 行执行时在 AO 里能找到 test2 并作为函数运行,弹出提示框显示提示信息“测试2”。

第 15 行由于用于赋值的函数体后面加了 () 变成立即执行函数所以函数会立即运行(这里会弹出提示框显示提示信息“测试3”)然后将返回值赋值给 test3,由于函数没有返回值所以是 undefined,也就导致了 AO 里的 test3 的属性值还是 undefined(大家可以在第18行输出一下 test3,会发现还是输出 undefined)。

第 19 行将 12 赋值给 test4,将原来 test4 的属性值覆盖了,AO 对象变为:

AO{
    test2:function(){alert("测试2");},
    test3:undefined,
    test4:12,
    test5:undefined,
    test1:function test1(){alert("测试1");},
}

所以在第 25 行会输出 12(如果在这里再把 test4 作为函数去运行就会报错)。

第 27 行将 test5_1 赋值给 test5。

第 28 行是立即执行函数,会立即执行。

在执行之前会进行预编译所以这个函数又会创建一个 属于他自己的 AO 对象,为了区分我们将他标识为 AO2。

AO2{

}

然后将形参和变量声明作为AO对象的属性名,属性值为undefined。

AO2{
    test5:undefined
}

然后将实参形参相统一,以及函数声明提升。
执行到第30行时在AO2中找到了test5,属性值为undefined,
执行到31行时将test5_2赋值给test5,所以AO2变成了:

AO{
    test5 :test5_2
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书系统介绍了经典的编译理论和技术,同时也包含了面向对象语言等当前较新语言的编译技术。本书更可贵之处在于提供了较完整的适用于教学实践的样例语言,是一本理论和实践内容相结合的、不可多得的好书。 本书可用作大专院校教材、教师参考书以及编译器研究人员的参考资料。 目 录 译者序 前言 第1章 概论 1 1.1 为什么要用编译器 2 1.2 与编译器相关的程序 3 1.3 翻译步骤 5 1.4 编译器中的主要数据结构 8 1.5 编译器结构中的其他问题 10 1.6 自举与移植 12 1.7 TINY样本语言与编译器 14 1.7.1 TINY语言 15 1.7.2 TINY编译器 15 1.7.3 TM机 17 1.8 C-Minus:编译器项目的一种语言 18 练习 19 注意与参考 20 第2章 词法分析 21 2.1 扫描处理 21 2.2 正则表达式 23 2.2.1 正则表达式的定义 23 2.2.2 正则表达式的扩展 27 2.2.3 程序设计语言记号的正则表达式 29 2.3 有穷自动机 32 2.3.1 确定性有穷自动机的定义 32 2.3.2 先行、回溯和非确定性自动机 36 2.3.3 用代码实现有穷自动机 41 2.4 从正则表达式到DFA 45 2.4.1 从正则表达式到NFA 45 2.4.2 从NFA到DFA 48 2.4.3 利用子集构造模拟NFA 50 2.4.4 将DFA中的状态数最小化 51 2.5 TINY扫描程序的实现 52 2.5.1 为样本语言TINY实现一个扫描 程序 53 2.5.2 保留字与标识符 56 2.5.3 为标识符分配空间 57 2.6 利用Lex 自动生成扫描程序 57 2.6.1 正则表达式的Lex 约定 58 2.6.2 Lex输入文件的格式 59 2.6.3 使用Lex的TINY扫描程序 64 练习 65 编程练习 67 注意与参考 67 第3章 上下文无关文法及分析 69 3.1 分析过程 69 3.2 上下文无关文法 70 3.2.1 与正则表达式比较 70 3.2.2 上下文无关文法规则的说明 71 3.2.3 推导及由文法定义的语言 72 3.3 分析树与抽象语法树 77 3.3.1 分析树 77 3.3.2 抽象语法树 79 3.4 二义性 83 3.4.1 二义性文法 83 3.4.2 优先权和结合性 85 3.4.3 悬挂else问题 87 3.4.4 无关紧要的二义性 89 3.5 扩展的表示法:EBNF和语法图 89 3.5.1 EBNF表示法 89 3.5.2 语法图 91 3.6 上下文无关语言的形式特性 93 3.6.1 上下文无关语言的形式定义 93 3.6.2 文法规则和等式 94 3.6.3 乔姆斯基层次和作为上下文无关 规则的语法局限 95 3.7 TINY语言的语法 97 3.7.1 TINY的上下文无关文法 97 3.7.2 TINY编译器的语法树结构 98 练习 101 注意与参考 104 第4章 自顶向下的分析 105 4.1 使用递归下降分析算法进行自顶向下 的分析 105 4.1.1 递归下降分析的基本方法 105 4.1.2 重复和选择:使用EBNF 107 4.1.3 其他决定问题 112 4.2 LL(1)分析 113 4.2.1 LL(1)分析的基本方法 113 4.2.2 LL(1)分析与算法 114 4.2.3 消除左递归和提取左因子 117 4.2.4 在LL(1)分析中构造语法树 124 4.3 First集合和Follow集合 125 4.3.1 First 集合 125 4.3.2 Follow 集合 130 4.3.3 构造LL(1)分析表 134 4.3.4 再向前:LL(k)分析程序 135 4.4 TINY语言的递归下降分析程序 136 4.5 自顶向下分析程序中的错误校正 137 4.5.1 在递归下降分析程序中的错误 校正 138 4.5.2 在LL(1)分析程序中的错误校正 140 4.5.3 在TINY分析程序中的错误校正 141 练习 143 编程练习 146 注意与参考 148 第5章 自底向上的分析 150 5.1 自底向上分析概览 151 5.2 LR(0)项的有穷自动机与LR(0)分析 153 5.2.1 LR(0)项 153 5.2.2 项目的有穷自动机 154 5.2.3 LR(0)分析算法 157 5.3 SLR(1)分析 160 5.3.1 SLR(1)分析算法 160 5.3.2 用于分析冲突的消除二义性 规则 163 5.3.3 SLR(1)分析能力的局限性 164 5.3.4 SLR(k)文法 165 5.4 一般的LR(1)和LALR(1)分析 166 5.4.1 LR(1)项的有穷自动机 166 5.4.2 LR(1)分析算法 169 5.4.3 LALR(1)分析 171 5.5 Yacc:一个LALR(1)分析程序的 生成器 173 5.5.1 Yacc基础 173 5.5.2 Yacc选项 176 5.5.3 分析冲突与消除二义性的规则 180 5.5.4 描述Yacc分析程序的执行 183 5.5.5 Yacc中的任意值类型 184 5.5.6 Yacc中嵌入的动作 185 5.6 使用Yacc生成TINY分析程序 186 5.7 自底向上分析程序中的错误校正 188 5.7.1 自底向上分析中的错误检测 188 5.7.2 应急方式错误校正 188 5.7.3 Yacc中的错误校正 189 5.7.4 TINY中的错误校正 192 练习 192 编程练习 195 注意与参考 197 第6章 语义分析 198 6.1 属性和属性文法 199 6.1.1 属性文法 200 6.1.2 属性文法的简化和扩充 206 6.2 属性计算算法 207 6.2.1 相关图和赋值顺序 208 6.2.2 合成和继承属性 212 6.2.3 作为参数和返回值的属性 219 6.2.4 使用扩展数据结构存储属性值 221 6.2.5 语法分析时属性的计算 223 6.2.6 语法中属性计算的相关性 226 6.3 符号表 227 6.3.1 符号表的结构 228 6.3.2 说明 230 6.3.3 作用域规则和块结构 232 6.3.4 同层说明的相互作用 236 6.3.5 使用符号表的属性文法的一个 扩充例子 237 6.4 数据类型和类型检查 241 6.4.1 类型表达式和类型构造器 242 6.4.2 类型名、类型说明和递归类型 246 6.4.3 类型等价 248 6.4.4 类型推论和类型检查 253 6.4.5 类型检查的其他主题 255 6.5 TINY语言的语义分析 257 6.5.1 TINY的符号表 258 6.5.2 TINY语义分析程序 259 练习 260 编程练习 264 注意与参考 264 第7章 运行时环境 266 7.1 程序执行时的存储器组织 266 7.2 完全静态运行时环境 269 7.3 基于栈的运行时环境 271 7.3.1 没有局部过程的基于栈的环境 271 7.3.2 带有局部过程的基于栈的环境 281 7.3.3 带有过程参数的基于栈的环境 284 7.4 动态存储器 286 7.4.1 完全动态运行时环境 286 7.4.2 面向对象的语言中的动态存储器 287 7.4.3 堆管理 289 7.4.4 堆的自动管理 292 7.5 参数传递机制 292 7.5.1 值传递 293 7.5.2 引用传递 294 7.5.3 值结果传递 295 7.5.4 名字传递 295 7.6 TINY语言的运行时环境 296 练习 297 编程练习 303 注意与参考 304 第8章 代码生成 305 8.1 中间代码和用于代码生成的数据 结构 305 8.1.1 三地址码 306 8.1.2 用于实现三地址码的数据结构 308 8.1.3 P-代码 310 8.2 基本的代码生成技术 312 8.2.1 作为合成属性的中间代码或目标 代码 312 8.2.2 实际的代码生成 314 8.2.3 从中间代码生成目标代码 317 8.3 数据结构引用的代码生成 319 8.3.1 地址计算 319 8.3.2 数组引用 320 8.3.3 栈记录结构和指针引用 325 8.4 控制语句和逻辑表达式的代码生成 328 8.4.1 if 和while 语句的代码生成 328 8.4.2 标号的生成和回填 330 8.4.3 逻辑表达式的代码生成 330 8.4.4 if 和while 语句的代码生成过程 样例 331 8.5 过程和函数调用的代码生成 334 8.5.1 过程和函数的中间代码 334 8.5.2 函数定义和调用的代码生成过程 336 8.6 商用编译器中的代码生成:两个案 例研究 339 8.6.1 对于80×86的Borland 3.0版C编 译器 339 8.6.2 Sun SparcStation的Sun 2.0 C编 译器 343 8.7 TM:简单的目标机器 346 8.7.1 Tiny Machine的基本结构 347 8.7.2 TM模拟器 349 8.8 TINY语言的代码生成器 351 8.8.1 TINY代码生成器的TM接口 351 8.8.2 TINY代码生成器 352 8.8.3 用TINY编译器产生和使用TM 代码文件 354 8.8.4 TINY编译器生成的TM代码文 件示例 355 8.9 代码优化技术考察 357 8.9.1 代码优化的主要来源 358 8.9.2 优化分类 360 8.9.3 优化的数据结构和实现技术 362 8.10 TINY代码生成器的简单优化 366 8.10.1 将临时变量放入寄存器 366 8.10.2 在寄存器中保存变量 367 8.10.3 优化测试表达式 367 练习 368 编程练习 371 注意与参考 372 附录A 编译器设计方案 373 附录B 小型编译器列表 381 附录C Tiny Machine模拟器列表 417
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值