1、预编译
1.1、JS的两次扫描
- 预编译:第一次扫描\前置扫描
- 解释执行:二次扫描
1.2、脚本步骤
- JS引擎创建全局对象Golobal Object(window)执行期上下文
- JS引擎加载脚本文件
- JS引擎脚本预编译
- 找出所有变量的声明,按照变量加入全局对象,如果已经存在,则忽略;不运行赋值
- 找出函数声明,按照函数名加入全局对象,如果已经存在同名变量或者函数,替换
- 非声明不予理睬
1.3、脚本预编译例子
1.3.1 例子1:没有var/let/const的变量认为是全局变量,不参与预编译
全局变量情况
<script>
// 没有var的变量,全部认为是window的全局变量,不参与预编译!
console.log(aa); // 报错`Uncaught ReferenceError: aa is not defined`
aa=5; // 即使在函数中,aa也是全局变量、是运行时,不是定义时。
console.log(aa); // 5
</script>
- 情况:第一个输出结果:
Uncaught ReferenceError: aa is not defined - 原因:预编译什么都不做;在执行中第一个
console.log打印aa的时候aa没有被定义,所以直接崩掉
var声明变量的情况
console.log(aa); //undefined
var aa=5;
console.log(aa); //5
- 原因:
- 预编译:生成了全局对象GO、然后开始扫描找到变量和函数声明并加入全局对象中,非声明不理睬
- 解释执行:因为在预编译中aa已经加入GO对象,但并没有初始化,所以第一个console为
undefined;再走一行aa被赋值成5,最后一行console结果是5。
小思考
这种机制有个好处,函数可以在后面的位置声明,同时可以再前面的位置使用
1.3.2 例子2:即使在函数中,aa=5也是全局变量
函数内存在全局变量,不调用函数的情况
// 即使在函数中,a也是全局变量、是运行时,不是定义时。
function test(){
a=5;
}
console.log(a);
- 情况:
Uncaught ReferenceError: a is not defined - 原因:1、预编译的时候扫到了test这个函数,但没有扫到a。
2、执行的时候test没有调用,
console.log的时候a没有定义所以出错
函数内存在全局变量,调用函数的情况
<script>
//即使在函数中,a也是全局变量、是运行时,不是定义时。
test();
function test(){
a=5;
}
console.log(a); //5
</script>
- 结果:5
- 原因:
-
- 预编译:扫到了test函数声明,没有扫到a。
-
- 执行test函数,函数进行预编译,函数预编译没有结果。执行时将a放到了window上,成全局变量并赋值为5,函数销毁。执行console.log时结果等于5。
-
1.3.3 例子3: 脚本中所有变量声明,在脚本的预编译阶段完成,所有变量的声明与实际的书写位置无关
console.log(aa); //undefined
var aa=5;
console.log(aa); //5
1.3.4 例子4:脚本中,所有函数声明,在脚本的预编译阶段完成,所有函数的声明与实际的书写位置无关。
<script>
console.log(haha);
function haha(){
console.log('h1');
}
</script>
- 结果:
haha(){ console.log('h1'); }
1.3.5 例子5:脚本中如果函数和变量同名那么函数会覆盖变量,与位置无关,变量无法覆盖函数!
<script>
console.log(haha);
var haha="123";
function haha(){ console.log('h1'); }
var haha="321";
</script>
- 结果:
haha(){ console.log('h1'); }
1.3.6 例子6:脚本中,后面的函数声明会覆盖前面的函数声明,并且忽略参数
<script>
console.log(haha);
function haha(a){ console.log('haha1'); }
function haha(a,b){ console.log('haha2'); }
</script>
- 结果:
haha2
1.4、函数步骤
- 创建活动对象AO(Active Object)(上下文)
- 预编译
- scope chain[作用域链]
- 初始化arguments
- 初始化形参,将arguments中的值赋值给形参
- 找出所有的变量声明,按照变量名加入AO,若存在则忽略
- 找出所有函数声明,按照函数名加入AO,若存在同名变量或函数就替换
- this初始化
1.5、函数预编译例子
1.5.1 函数中,所有变量声明,在函数的预编译阶段完成,所有变量的声明与实际的书写位置无关【同脚本结果】
function f(){
console.log(aa); // undefined
var aa=5;
console.log(aa); // 5
}
f();
1.5.2 函数中,所有函数声明,在函数的预编译阶段完成,所有函数的声明与 实际的书写位置无关【同脚本结果】
function f(){
console.log(haha);
function haha(){
console.log('h1');
}
}
f();
1.5.3 函数中,如果变量和函数同名,那么,函数将覆盖变量【同脚本结果】
<script>
function f(){
console.log(haha);
var haha=123;
function haha(){
console.log('h1');
}
}
f();
</script>
1.5.4 函数中,只有函数能够覆盖变量,变量无法覆盖函数。 【同脚本结果】
<script>
function f(){
console.log(haha);
function haha(){
console.log('h1');
}
var haha=123;
}
f();
</script>
1.5.5 函数中,后面的函数声明会覆盖前面的函数声明,并且,忽略参数。
function f(){
console.log(haha);
function haha(a){
console.log('haha1');
}
function haha(a,b){
console.log('haha2');
}
}
f();
1.5.6 当函数预编译后,遇到需要访问的变量或函数,优先考虑自己AO中定义的变量和函数;如果找不到,才会在其定义的上一层AO中寻找,直到到达GO
var scope ="global";
function f(){
console.log(scope); //undefined
var scope="local";
console.log(scope); //local
}
f();
console.log(scope); //global
- 分析:
- 1、脚本预编译GO:里面有scope=undefined和函数f = function。
- 2、执行时GO环境下scope赋值“global”。t:执行
- 3、生成t-AO:
- 预编译:扫描到了scope变量赋值undefined
- 执行时:函数内部第一个scope是undefined,然后是local,紧接着函数作用销毁。
2、练习
练习1
console.log(scope); // undefined
var scope = 'global';
function t(){
var scope = 't-local';
function t2(){
console.log(scope); // undefined
var scope = 't2-local';
console.log(scope); // t2-local
}
t2();
console.log(scope); // t-local
}
t();
console.log(scope); // global
- GO:
- scope:undefined -> global;
- t:function
- t-AO:
- scope:undefined -> t-local; [退出函数时 t-AO被摧毁]
- t2:function
- t2-AO:
- scope:undefined -> t2-local; [退出函数时 t2-AO被摧毁]
练习2
function test(x,x) {
console.log(x); // function
x = 5;
console.log(arguments); // [12,5]
function x() {}
}
test(12,13);
- Go: test:function
- test-AO:
- 初始化arguments:[12,13]
- 绑定x:arguments[1]:13 (x:第一次绑12、第二次绑13)->【x:arguments[1]:13】
- 预编译:
- x:function ->【x:arguments[1]:function】
- 执行:x:5【在执行x=5时】
-
x:arguments[1]:5
-
练习3
b = 'cba';
function a(a, a){
console.log(a); // function
console.log(b); // undefined
var b = 'abc';
a();
function a(){
console.log(a); // function
console.log(b); // abc
}
}
a(5,10);
- GO:(this:window)
| 预编译 | 执行 | |
|---|---|---|
| a | function | 进入 |
| b | cba |
-
a-AO: (this:window) | | 参数 | 参数绑定 | 预编译 | 执行 | --- | --- | --- | --- | --- | | | [5,10] | a-参数[1] | | | b | | | undefined | abc | | a | | | function(此时参数[1]由于绑定也变成function) | 进入 |
-
a.a-AO:(this:window) 到上一级查找
练习3
var str = 'aaa';
str += 1;
var test = typeof(str);
if(test.length == 6){
test.newproperty = 'string';
// var obj = new String(test);
// obj.newproperty = 'string';
// 摧毁 obj
}
console.log(test.newproperty); // undefined
练习4
var x = 1, y = z = 0; // x,y,z:undefined 1,0,0 1,4,4
function add(n){ // add:function
return n = n + 1;
}
y = add(x);
function add(n){ // add:function(n+3)
return n = n + 3;
}
z = add(x);
练习5: 哪个可以输出:[1,2,3,4,5]
function foo(x){ // 可以
console.log(arguments);
return x;
}
foo(1,2,3,4,5);
function foo(x){ // 不可以,不报错不输出;函数声明;数组表达式
console.log(arguments);
return x;
}[1,2,3,4,5]
function foo(x){ // 不可以
console.log(arguments);
return x;
}(1,2,3,4,5);
(function foo(x){ // 可以
console.log(arguments);
return x;
})(1,2,3,4,5);
练习6:
function test(x, y, a){
arguments[2] = 10;
alert(a); // 10
}
test(1,2,3);
扩展:
- 编译与链接:代码-编译->块结构-链接->exe
- 预编译:扫描函数声明到‘名字表’
- 脚本:没有函数,没遇到函数前的脚本
- 函数声明:function <函数名>([<形参>]){函数体}