本文是前端学习笔记中的第三篇,对应的是渡一教育的web前端开发JavaScript精英课js中的第九课时,这一课时讲的相当好,由浅入深,把预编译的过程都讲的极度透彻,如果对预编译不太熟悉的最好还是去看一看
目录
JS运行三部曲
JS运行时一共分三步
(1)语法分析
JS运行前会先把JS代码通篇扫描一遍,看有没有低级的语法错误,比如少了个分号,或者多了些不该有的字符什么的
(2)预编译
本文的重点,后面会详细介绍,这里先放两句总结性结论
函数声明 --- 整体提升
也就是说,函数声明后,系统会把整个函数放到当前script标签的开头处(这种说法并不严谨,仅仅是帮助理解)
变量 --- 声明提升
注意,是变量 --- 声明提升,也就是说var a = 123; 系统会将var a;放到当前script标签的开头处(这种说法并不严谨,仅仅是帮助理解),但是a此时是undefined的,因为仅仅是声明提升,赋值操作并没有执行
(3)解释执行
解释型语言的特点,不通篇编译,而是解释一行执行一行
预编译
预编译发生在函数执行前一刻
准确来说,预编译一共有两种,一种是相对于调用函数而言的预编译,一种是在全局中的预编译
先说函数中的预编译过程
- 创建AO对象 (Activation Object)活动对象,又称上下文对象
- 找形参和变量声明,将变量名和形参名作为AO属性名,值为undefined,
- 将形参值和实参值统一,即把实参传递到形参处 (这一步是相对于调用函数而言,若直接在全局中可忽略)
- 在函数体里找到函数声明,并将名字作为AO对象属性名,并把函数作为值,若之前已存在同名声明,则替换为函数
为方便理解,先看一个demo
<script>
function fn(a) {
console.log(a);
var a = 123;
console.log(a);
function a( ) { }
console.log(a);
var b = function ( ) { }
console.log(b);
function d( ) { }
}
fn(1);
</script>
根据上面提到的预编译步骤,函数执行前
- 创建AO对象
- 找形参和变量声明,将变量名和形参名作为AO属性名,值为undefined
AO {
a : undefined
b : undefined
}
- 将形参值和实参值统一,即把实参传递到形参处
AO {
a : 1
b : undefined
}
- 在函数体里找到函数声明,并将名字作为AO对象属性名,并把函数作为值,若之前已存在同名声明,则替换为函数
AO {
a : function a ( ){ }
b : undefined
d : function d ( ) { }
}
到此,预编译过程便结束了,接着开始正式执行函数体内容,
console.log(a); // function a ( ) { }
a = 123; // 注意之前预编译的步骤仅仅是声明了,并没有赋值
此时AO对象的属性被修改
- AO {
a : 123
b : undefined
d : function d ( ) { }
}
console.log(a); // 123
// function a ( ) { } // 本行被跳过,因为在预编译环节已执行
console.log(a); // 123
b = function ( ) { } // 之前预编译步骤已声明,这里仅仅是赋值
此时AO对象的属性被修改
- AO {
a : 123
b : function ( ) { }
d : function d ( ) { }
}
console.log(b); // function ( ) { }
// function d( ) { } // 不会执行,预编译过程已经执行过
此时,整个函数从预编译到执行环节便完整结束了
接下来,我们再来说说全局中的预编译过程,其实和函数的预编译过程极其相似,只是少了一步
- 创建GO对象 (Global Object)全局对象,又称window对象 (window object === global object)
- 找变量声明,将变量名作为GO属性名,值为undefined,
- 在函数体里找到函数声明,并将名字作为GO对象属性名,并把函数作为值,若之前已存在同名声明,则替换为函数
这里就不再赘述了,原理都是一样的
值得一提的,未经声明的变量是归全局变量所有的
<script>
function test() {
var a = b = 10;
}
test();
console.log(b); // 10
</script>
var a = b =10;的过程:先把10赋给了未经声明的b,再把b的值赋给了a,那么此时b便提升为全局变量,如今在函数test外可以通过b或window.b直接访问这个全局变量
还有最后一点,预编译过程无视if() {} 之类的判断语句,因为此时根本就还没执行函数,若if语句中有变量声明/函数声明是完全没问题的,是起作用的