在JavaScript中存在一种预编译的机制,这也是Java等一些语言中没有的特性,也就正是因为这个预编译的机制,导致了js中变量提升的一些问题
- 函数声明整体提升
- 变量声明提升
众所周知,在js中undefined通常用于指示变量尚未赋值。对未定义值的引用返回特殊值 undefined。
而我们也知道js属于解释型语言即解释一句执行一句。
<script>
console.log(a)
var a = 10;
console.log(b);
</script>
按照其定义来说我们是先控制台输出a,再进行声明变量和赋值
我们不难得出,如果我们不对一个变量进行声明与赋值直接输出,控制台会报错,而一开始的a却没有报错,这就是上面所说的声明变量提升。即将变量var提升至script代码块最上方。所以,代码应该是这样的:
<script>
var a;
console.log(a)
a = 10;
console.log(b);
</script>
这就是为什么两个都没在输出前进行申名与赋值,但是输出结果却不一样。
从这里我们就不难理解函数的声明整体提升。即声明与代码块一起提升至代码块最上方。
JS执行过程
- 语法检测:通篇扫描语法错误
- 预编译
- 代码执行:逐行执行
预编译前奏
- imply global暗示全局变量,任何变量,如果变量未经声明就赋值,这些变量就为全局对象所有。
- 一切声明的全局变量和未经声明的变量,全归window所有。
变量分为局部变量和全局变量;而他俩的区别就是作用域不同:
- 全局变量的作用域为整个程序
- 而局部变量的作用域为当前函数或循环等。
<script>
function fn (){
a=0
console.log(a);
}
fn();
console.log(a);
</script>
这里就可以知道在函数fn里的变量为局部,而在外面就是全局。
如果你这么认为就错了,上面说了任何变量,如果变量未经声明就赋值,这些变量就为全局对象所有。
这个a看样子是写在函数里面的,应该算局部变量,但是他并没有声明,所以,他属于全局变量。所以,这两个在函数里和函数外输出的对象是同一个。
全局预编译
- 创建一个函数的GO对象(Global Object),执行期上下文对象
- 将var关键字声明的变量,成为GO对象的属性,值为undefined,遇到重名,不做任何变化
- 将function声明的函数(函数声明)成为GO对象的属性,值为函数体,重名直接覆盖
函数预编译
从函数执行的前一刻开始:
- 创建一个函数的AO对象(Activation Object),执行期上下文对象
- 函数的形参,成为AO对象的属性,值为实参的值,若未传值,值为undefined
- 将var关键字声明的变量,成为AO对象的属性,值为undefined,遇到重名,不做任何变化
- 将function声明的函数(函数声明)成为AO对象的属性,值为函数体,重名直接覆盖
<script>
function fn (a){
console.log(a);
console.log(b);
var b = 1;
console.log(b);
console.log(c);
var c = function(){};
console.log(c);
}
fn(1);
</script>
综上所述,在函数声明开始是,创建一个上下文对象AO = {};
- 形参a,实参1;AO = {a:1};
- var b,声明b;AO = {a:1,b:undefined};
函数c;AO = {a:1,b:undefined,c:function(){}};
注:将function声明的函数(函数声明)成为AO对象的属性,值为函数体,重名直接覆盖
虽然这是函数表达式,但是,但是,但是:function声明即必须是function c (){};才算;像某些爱偷懒的编程猿+function c (){}也不行…
所以AO最后应该是{a:1,b:undefined,c:undefined},所以,输出应该是1,undefined,1,undefined, ƒ (){}
全局预编译与函数预编译差不多,唯一的区别就是全局没有函数的实参形参。
案例详解
<script>
console.log(b);
var b = "boy";
console.log(b);
function fighting() {
console.log(a);
console.log(c);
if (a === "apple") {
a = "Alice"
} else {
a = "Ada"
}
console.log(a)
var a = "Andy";
middle();
function middle() {
console.log(c++);
var c = 100;
console.log(++c);
small();
function small() {
console.log(a)
}
}
var c = a = 88;
function bottom() {
console.log(this.b);
b = "baby";
console.log(b)
}
bottom();
}
fighting();
console.log(b)
</script>
- 首先,建立GO对象,看见声明变量b,所以GOb属性,值为undefined;
- 然后fighting属性,值为函数。遇见函数执行,建立fighting函数的AO对象
- var a,AOa属性,值为undefined;middle属性,值为function middle(){};
- 遇见middle执行,建立middleAO对象;
- var c,small function 同理
- 之后回到fighting函数继续,发现var c,值为undefined;之后bottom,值为function
- 遇见bottom执行,创建其AO对象,这里b未声明,则为GO对象属性
- 编译结束之后开始逐行执行代码
- 在代码块中输出b,即输出全局中的b,即一开始声明是给的值undefined
- 接下来赋值“boy”,那么输出就是“boy”;
- 函数fighting里输出a,c前面没有赋值,那么从fightingAO对象找对应的属性值,为undefined;
- 后面判断,a为undefined,不等于apple,则赋值为“Andy”并输出
- middle里c为undefined,不能自增,所以c++为NaN
- 后面赋值一百在自增输出,就为101
- 函数small输出a,small没有,就向middle要,middle没有就找fighting,fighting还没有就只能找window,
- 注:只能儿子找父级,父级找儿子。。。嘿嘿
- 所以输出之前的a“Andy”
- 88赋值给a,a的值赋值给c
- bottom函数输出this.b,虽然强调要this的b,但是没办法,没有就是没有,方法总比困难多,你找我我没,只能找父亲,那么b为先前的“boy”
- 赋值“baby”并输出,那么b为“baby”
- 最后结束fighting之后输出b,b为“baby”
这里为啥是baby而不是boy呢?
变量里面赋值,变量外面输出是输出不了的,因为父亲不好意思找子级要,但是,这里b未声明,所以b是属于window的,所以本来就是父亲的,虽然在子级里面赋值变化了,但是b是父亲的,虽然子级里面改变了但输出还是输出“baby”
输出结果: