预编译
通俗来说js特点:单线程、解释性语言(翻译一句,执行一句)
js运行三部曲
- 语法分析(扫描基础语法是否错误)
- 预编译
- 解释执行
函数声明、变量 声明提升
- 函数声明 整体提升
- 变量 声明提升
//案例一
//正常写法:
function test1() {
console.log("a")
}
test1();
//异常写法:
test2();
function test2() {
console.log("a")
}
//把函数写在执行下面,也不报错!
/*
真相:函数声明 整体提升:
其实表面看起来test2()在函数前面就执行,
但是编译器会把function test2(){console.log("a")}提前到执行前面运行。
所以,不会报错~
*/
//案例二
//正常写法:
var a = 123;
console.log(a) //结果:123
//异常写法:
console.log(c) //结果:报错了~!打印一个为声明的变量,当然报错!
//异常写法:
console.log(b) //结果:undefined
var b = 123;
//也不报错,但结果是undefined。奇怪~
/*
真相:变量 声明提升:
1. 看似console.log(b)在var b = 123前面执行,但实际编译的时候,会把var b = 123提前执行。
这样console.log(b)就不会报错~
2. var b = 123; 其实是2步:
(1) var b; 变量声明的这一步,会提升。(所以,你会看到打印b的结果是:undefined)
(2) b = 123; 赋值的这一步并不会提升
*/
// 上面的现象都是预编译在作怪:
// 1. 函数声明 整体提升
// 2. 变量 声明提升
imply global 暗示全局变量
- 任何变量,如果未经声明就赋值,此变量就为全局对象window所有
abc = 123; //未声明abc变量,就直接赋值。
document.write(abc); //打印也不会报错
document.write(window.abc); //abc未声明就赋值,abc就会属于window对象
//连等的情况下,有意思的事情发生了!
function test() {
var a = b = 5;
console.log("我是a,我在函数俩面,我的值是:"+ a)
console.log("我是b,我在函数俩面,我的值是:"+ b)
}
test();
console.log("函数外面,可以访问test函数内部定义的a变量吗?" + window.a) //结果:undefined
console.log("函数外面,可以访问test函数内部定义的b变量吗?" + window.b) //结果:5
//为啥b的值可以访问到?
/*
因为:var a = b = 5 这一条拆分开,其实是2步骤:
1. b = 5 这一步, b未声明接直接赋值,就属于全局变量window的,所以函数外面也可以访问到!
2. var a = 5 这一步可以需要拆成2步骤:
2.1 var a; 这一步a的值是undefined(注意这是函数内部的局部变量a,外面访问不到!)
2.2 a = 5; 这一步a的值是5
*/
- 一切声明的全局变量,都是window属性
//例子1
var b = 234;
document.write(window.b); //声明的变量b,也归window所有。
//例子2
function f() {
var abc = 123;
}
f();
console.log(window.abc) //结果:undefined 因为abc是函数f内的局部变量
函数的预编译过程
- 预编译发生在函数执行的前一刻
- 预编译四部曲
- 创建AO对象(Activation Object 活跃对象,叫执行期上下文)
- 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
- 将实参值和形参值统一
- 在函数体里面找到函数声明,值授予函数体。
//函数编译过程中,形参、函数名、变量名 冲突的时候,谁的优先级高?
function fn(a) {
console.log("第一次输出:" + a ); //结果:function a() {}
var a = 123;
console.log("第二次输出:" + a);
function a() {}
console.log("第三次输出:" + a);
var b = function () {};
console.log("第四次输出:" + b);
function d() {}
}
fn(666);
/* 结果:
第一次输出:function a() {}
第二次输出:123
第三次输出:123
第四次输出:function () {}
*/
/*
探究里面的函数编译过程:
1.创建AO对象
AO {
空的
}
2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined。
AO {
a : undefined,
b : undefined
}
3. 将实参值的形参值统一(也就是把实参的具体值,传给函数的形参,并替换值)
AO {
a : 666,
b : undefined
}
4. 在函数体里,找到函数声明。值授予函数体。
4.1 找到函数中的函数声明,其实就找到了2个。
1. function a() {}
2. function d() {}
4.2 把4.1找到的函数声明的"名"(也就上面的:a 和 d),作为AO对象的属性名,值授予函数体。
4.2.1 其中a的属性名,已经有在第3步,创建叫a的属性名了,这一步就不用创建了。
4.2.2 那么a的值呢?值授予函数体,函数体是:function a() {}
4.2.3 其中d的属性名,没创建过,所以这一步要创建上。
4.2.3 那么d的值呢?值也是授予函数体,函数体是:function d() {}
5. 所以最终AO对象变成了这样:
AO {
a : function a() {},
b : undefined,
d : function d() {}
}
6. 上面编译过程结束,开始顺序执行函数。
console.log("第一次输出:" + a ); //结果:function a() {}
因为此时的a在AO对象里面,所以确实应该输出:function a() {}
var a = 123 分成2步骤
1. var a; 这一步在编译的时候就已经完成了(变量名提升)
2. a = 123 这一步需要执行,最终把AO对象中的a给改变为了123,如下:
AO {
a : 123,
b : undefined,
d : function d() {}
}
console.log("第二次输出:" + a); //结果:123
function a () {} 这一步已经在编译的时候就提升上去了,所以这一步不用执行了。
console.log("第三次输出:" + a); //结果:123
var b = function () {} 分2步骤
1. var b ; 这一步在编译的时候就已经完成了(变量名提升)
2. b = function () {} 这一步需要执行,最终把AO对象中的b的值改变了:
AO {
a : 123,
b : function () {},
d : function d() {}
}
console.log("第四次输出:" + b);//结果: function () {}
*/
//练习一
function test(a,b) {
document.write(a);
c = 0;
var c ;
a = 3;
b = 2;
document.write(b);
function b() {}
function d() {}
document.write(b);
}
test(1)
/*
1. 经过4不函数编译过程后AO对象:
AO {
a : 1,
b : function b() {},
c : undefined,
d : function d() {}
}
2. 依次按顺序执行函数内容:
第一行:document.write(a); 应该a的值是1
第二行:c = 0 ,会把AO对象改为:
AO {
a : 1,
b : function b() {},
c : 0,
d : function d() {}
}
第三行:var c ; 不会执行,因为这一步已经提前执行过了(变量 声明提升)
第四行:a = 3; 会把AO对象改为:
AO {
a : 3,
b : function b() {},
c : 0,
d : function d() {}
}
第五行:b = 2; 会把AO对象改为:
AO {
a : 3,
b : 2,
c : 0,
d : function d() {}
}
第六行:document.write(b); b的结果应该是2
第七行、第八行 不会执行,因为提前执行过了(函数声明 整体提升)
第九行:document.write(b); b的结果应该还是2
*/
全局的预编译过程
- 预编译3部曲
- 创建GO对象(Global Object 全局上下文)
- 找变量声明,将变量作为GO属性名,值为undefined。
- 在函数体里面找到函数声明,值授予函数体。
//全局也会发生预编译,3步曲。
console.log("3步曲后编译后,全局变量a的值是:" + a);
var a = 123;
function a() {}
console.log("顺序执行,最终a的值是:" + a);
/* 结果:
3步曲后编译后,全局变量a的值是:function a() {}
顺序执行,最终a的值是:123
*/
/*
细节:
1. 3步曲后,GO对象:
GO {
a : function a() {}
}
2. 顺序执行:
第一步: console.log("3步曲后编译后,全局变量a的值是:" + a);
所以结果是:function a() {}
第二步: var a = 123; 分2步
1. var a; 这一步提前就做了,不需要做了(变量 声明提前)
2. a = 123; 这一步需要做,GO对象此时变成:
GO {
a : 123
}
第三步: function a() {} 不会执行,因为提前就做了(函数声明 整体提升)
第四步:console.log("顺序执行,最终a的值是:" + a);
所以结果是:123
*/
- 全局上下文GO对象 === window 对象(GO就是window,所以说:任何全局变量都是window属性)
//编译过程,结合imply global 实例
function test() {
var a = b = 123;
console.log(window.b);
console.log(window.a);
console.log(a);
console.log(b);
var b = 456;
console.log(b);
}
test()
/*
1. 函数编译4步曲,最后AO对象:
AO {
a : undefined
}
2.按顺序执行
var a = b = 123; 拆分2个部分:
第一部分:b = 123 此时b没有声明就赋值,变成了imply global,此时GO对象产生了:
GO {
b : 123
}
第二部分 var a = 123 ,分成2个部分:
1. var a; 这一步不会执行,因为提升了(变量 命名提升)
2. a = 123 这一步会执行, AO变成了这样:
AO {
a : 123
}
打印:
console.log(window.b); 结果123,因为GO==window,所以GO对象中b的值确实是123。
console.log(window.a); 结果undefined,因为GO对象中,根本就没有a属性。所以找不到~
console.log(a); 结果 123,因为在函数内部打印,就是在打印AO中的a属性,AO中的a属性就是123.
console.log(b); 结果 123,因为此时实际打印的是GO中的b[ console.log(b)写法相当于console.log(window.b)]
var b = 456; 这一步相当于,在AO对象中,创建了新的b属性,此时AO对象:
AO {
a : 123
b :456
}
再次打印:console.log(b); 这时候,实际打印的是AO中的b属性(而不是GO中的b属性),因为如果能在AO中找到的属性名,肯定优先打印(局部变量的优先级高于全局变量)。
结果:456
*/
//练习二
console.log(test)
function test(test) {
console.log(test);
var test = 234;
console.log(test);
function test() {}
}
test(1);
var test = 123;
/*结果:
ƒ test(test) {
console.log(test);
var test = 234;
console.log(test);
function test() {}
}
test() {}
234
*/
/*
细节:
1. 解释器先扫描所有代码,发现有var test = 123 以及 function test(){xx},发现有变量和函数声明,所以就先创建GO对象
GO {
test : function test(test) {
console.log(test);
var test = 234;
console.log(test);
function test() {}
}
}
2. 顺序执行代码
第一行:console.log(test) 执行 结果是:function test(text){....} 显而易见,从GO对象中查找。
第二行: function test(test) {...} 不会执行,因为函数声明 整体提高原则,早在编译的时候就执行了。
第三行:test(1) 这一步会执行
1. 创建AO对象,经过4步曲,创建AO对象内容为:
AO {
test : function test() {}
}
2. 顺序执行函数:
第一行:console.log(test); 结果肯定是:function test() {}
第二行:var test = 234; 拆分2个步骤
1. var test; 不会执行,因为 变量 声明提高提高原则,早在编译的创建AO对象的时候就执行过了,所以这里不再执行。
2. test = 234 这一行会执行,AO对象变为:
AO {
test : 234
}
第三行:console.log(test); 结果肯定为234
*/
//练习三
//百度面试题一
function bar() {
return foo;
foo = 10;
function foo() {}
var foo = 11;
}
document.write(bar());
//百度面试题二
document.write(bar());
function bar() {
foo = 100;
function foo() {}
var foo = 11;
return foo;
}
//练习
a = 100;
function demo(e) {
function e() {}
arguments[0] = 2;
document.write(e);
if(a){
var b = 123;
function c() {} //理论来讲if中不能声明函数的~但就当练习!
}
var c;
a = 10;
var a;
document.write(b);
f = 123;
document.write(c);
document.write(a);
}
var a;
demo(1);
document.write(a);
document.write(f);
/*结果:
2
undefined
undefined
10
100
123*/
/* 具体细节如下:
1. 生成全局GO对象
GO {
demo : function demo(e) {..}
a : undefined
}
2. 顺序执行
第一行:a = 100,此时GO对象变为:
GO {
demo : function demo(e) {..}
a : 100
}
第二行:function demo(e) {..} 不会执行,因为函数声明提升,在初始GO对象就执行过了。
第三行:var a; 不会执行,因为变量声明提升。
第四行:demo(1);
(1) 初始AO对象
AO {
e : function e() {..},
b : undefined,
c : undefined (练习的时候,可以把C想成:function c() {..}, 仅仅为了练习,其实if中不能包含函数声明)
a : undefined,
}
(2) 顺序执行函数:
第一行:function e() {} 不会执行,因为函数声明提升。
第二行:arguments[0] = 2; arguments[0] 这个就是形参e,把形参e给变成了2。此时AO对象变成了:
AO {
e : 2,
b : undefined,
c : undefined (function c() {..},)
a : undefined,
}
第三行:document.write(e); 结果:2
第四行:if判断a的值,但是此时a的值是undefined,所以if内的语句不会执行。
第五行:var c; 不会执行,变量声明提升
第六行:a = 10; 会执行,此时a的值改变,AO对象变成了:
AO {
e : 2,
b : undefined,
c : undefined (function c() {..},)
a : 10,
}
第七行:var a; 不会执行,变量声明提升
第八行:document.write(b); 结果:undefined
第九行:f = 123; 未声明就赋值(imply global),修改了GO对象,此时:
GO {
demo : function demo(e) {..}
a : 100
f : 123
}
第十行:document.write(c); 结果:undefined (在AO对象中查找)
第十一行:document.write(a); 结果:10 (在AO对象中查找)
第五行:document.write(a); 结果:100 (在GO对象中查找)
第六行:document.write(f); 结果:123 (在GO对象中查找)
*/