预编译的 JavaScript
JavaScript运行三部曲
- 语法分析:首先扫描整个代码,看看是否有语法错误
- 预编译(执行前一刻):变量和函数声明的提升
- 执行:逐行执行
预编译
- 在当前作用域内,在运行 JS 代码之前,浏览器首先默认声明或定义所有带有 var 和 function
- var变量声明只会声明,不会定义,且值未定义
- 函数声明声明、定义和分配函数体
全局预编译
- 创建全局对象
- 找到全局中的变量声明,将该变量声明为全局对象的属性名,并将值赋给undefined
- 在全局中找到函数声明,将函数名作为全局对象的属性名,并赋值给函数体
- 创建AO对象(执行上下文)
- 找到形参,并使用形参作为AO属性名称;如果存在实数参数,则分配实数参数;否则,分配值 undefined
- 找到变量声明,将变量作为AO属性名,并赋值undefined
- 找到函数声明,将函数名作为AO属性名,将函数体与 匹配
块范围内的预编译
- var 变量声明不变
- function 函数声明将提升到全局或函数作用域的头部,类似于var
- 同时函数的函数声明会提升到块级作用域的头部
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]
传递函数参数的预编译
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
函数中返回后的代码不会被执行,而是会被预编译
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
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。