JavaScript预编译
JavaScript运行三部曲
- 语法分析:先将代码全部扫一遍,看有没有语法错误
- 预编译(执行前一刻):变量和函数声明提升
- 执行:逐行执行
预编译
- 在当前的作用域中,JS代码执行之前,浏览器首先会默认地把所有带var、function的进行提前的声明或者定义
- var变量声明只会声明,不会定义,赋值undefined
- function函数声明会声明和定义,赋值函数体
全局中的预编译
- 创建全局对象
- 找到全局里的变量声明,将变量声明为全局对象的属性名,赋值为undefined
- 找到全局里的函数声明,将函数名作为全局对象的属性名,赋值为函数体
函数中的预编译
- 创建AO对象(执行上下文)
- 找到形参,将形参作为AO属性名;如果有实参,赋值实参;否则,赋值undefined
- 找到变量声明,将变量作为AO的属性名,赋值undefined
- 找到函数声明,将函数名作为AO的属性名,赋值函数体
块级作用域中的预编译
- var变量声明没有变化
- function函数声明会提升到全局作用域或函数作用域的头部,类似于var
- 同时,function函数声明会提升到块级作用域的头部
if(false) {
function f() {}
}
//等价于
var f;//var f= undefined;全局作用域
if(false) {
function f() {}
}
预编译案例
变量和函数同名
当变量和函数同名时,只留下函数的值,不管谁前谁后,所以函数声明的优先级更高
console.log(b);
function b() {
console.log('bbb');
}
var b = 2;
//解析过程
//预编译
function b() {console.log('bbb');}
var b;
//逐行执行
console.log(b);//[Function: b]
b = 2;
console.log(a);
var a = 1;
function a() {
console.log(2);
}
console.log(a);
var a = 3;
console.log(a);
function a() {
console.log(4);
}
//解析过程
//预编译
var a;
function a() {console.log(2);}
function a() {console.log(4);}
//逐行执行
console.log(a);//function a() {console.log(4);}
a = 1;
console.log(a);//1
a = 3;
console.log(a);//3
函数中的预编译
函数中的预编译要等到函数执行前的一刻才进行预编译
var a = 10;
function f1() {
var b = 2 * a;
var a = 20;
var c = a + 1;
console.log(b);
console.log(c);
};
f1();
//解析过程
//预编译
var a;
function f1() {...}
//逐行执行
a = 10;
f1();
//函数中的预编译
var b;
var a;
var c;
//函数中的逐行执行
b = 2 * a;//2 * undefined
a = 20;
c = a + 1;
console.log(b);//NaN
console.log(c);//21
函数表达式的预编译
console.log(fn);
var fn = function() {
console.log('ok');
};
console.log(fn);
//解析过程
//预编译
var fn;
//逐行执行
console.log(fn);//undefined
fn = function() {console.log('ok');};
console.log(fn);//[Function: fn]
函数传参的预编译
函数中的形参是函数中声明的变量
function fun(n) {
console.log(n);
var n = 456;
console.log(n);
}
var n = 123;
fun(n);
//解析过程
//预编译
function fun(n) {}
var n;
//逐行执行
n = 123;
fun(n);
//函数中的预编译
var n = 123;//实参给形参赋值,函数中已经声明变量n了,后面就不需要重新声明了
//函数中的逐行执行
console.log(n);//123
n = 456;
console.log(n);//456
立即执行函数的预编译
立即执行函数不会进行预编译,其它和普通函数一样,当代码逐行执行到这个位置的时候,定义和执行一起完成
(function(num) {
console.log(num);
console.log(n);
})(100);
var n = 10;
//解析过程
//预编译
var n;
//逐行执行
(function(num) {...})(100);//函数执行
//立即执行函数中的预编译
var num = 100;//实参赋值给形参
//立即执行函数中的逐行执行
console.log(num);//100
console.log(n);//undefined
n = 10;
函数内部return的预编译
函数中return后面的代码虽然不会执行,但是会进行预编译
function fn() {
console.log(num);
return num;
var num = 100;
}
fn();
//解析过程
//预编译
function fn() {...}
//逐行执行
f();
//函数中的预编译
var num;//return后面的代码不会执行,但会预编译
//函数中的逐行执行
console.log(num);//undefined
return num;
if代码块中的预编译
- 不管if判断是否成立,if中都会进行预编译;如果判断为真,就会执行if中的代码块;否则,if中只会预编译,不会执行代码。
- 根据ES6标准入门(只对ES6的浏览器实现有效):
- 允许在块级作用域内声明函数(应该避免函数声明,可以写成函数表达式的形式)
- 函数声明类似于var,即会提升到全局作用域或函数作用域的头部
- 同时,函数声明还会提升到所在的块级作用域的头部
//ES6环境的浏览器
(function() {
if(false) {
function f() {
console.log('ok');
}
}
f();
}());
//会报错,实际运行的是以下代码
(function() {
var f;//var f = undefined
if(false) {
//块级作用域中函数的声明会提升到函数作用域的头部
function f() {
console.log('ok');
}
}
f();
}());
//if判断为假
console.log(num);
console.log(f);
if(false) {
var num = 100;
function f() {
console.log(123);
}
}
console.log(f);
console.log(num);
//解析过程
//预编译
var num;
//块级作用域中函数的声明会提升到全局作用域的头部
var f;//块级作用域中函数的预编译类似于var,先声明后赋值
//逐行执行
console.log(num);//undefined
console.log(f);//undefined
//由于if判断为假所以if代码块中的代码不执行
console.log(f);//undefined
console.log(num);//undefined
//if判断为真
console.log(num);
console.log(f);
if(true) {
var num = 100;
function f() {
console.log(123);
}
}
console.log(f);
console.log(num);
//解析过程
//预编译
var num;
//块级作用域中函数的声明会提升到全局作用域的头部
var f;//块级作用域中函数的预编译类似于var,先声明后赋值
//逐行执行
console.log(num);//undefined
console.log(f);//undefined
//if判断条件为真,执行if代码块中的代码
num = 100;
f = function() {console.log(123);}
console.log(f);//[Function: f]
console.log(num);//100
if代码块中函数声明前后的作用域不同
console.log(a);
var a = 0;
if(true) {
console.log(a);
a = 1;
console.log(a);//1
function a() {}
a = 21;
console.log(a);
}
console.log(a);
//解析过程
//预编译
var a;
//逐行执行
console.log(a);//undefined
a = 0;
//if判断为真
function a() {}//预编译,函数声明会提升到块级作用域的头部
console.log(a);//[Function: a]
a = 1;//改变全局变量的值
console.log(a);//1
//函数名和全局变量名同名
function a() {}//函数后面是另一个作用域,函数上面是全局作用域,后边是局部作用域
a = 21;//局部作用域中的值
console.log(a);//21
console.log(a);//1