JavaScript 预编译
复习
作用域定义:变量(变量作用于又称上下文)和函数生效(能被访问)的区域
全局、局部变量
作用域的访问顺序
复习上一节学习的内容:
函数就像屋子一样,一个函数从定义的那一刻就形成了自己的一个域,姑且管它叫做"作用域"虽然说不太标准,这个"域"能干什么呢?
两个独立的屋子之间的变量是不能相互访问的,比如在test()里面不能访问b,在demo()里面也不能访问a,这是两个独立的屋子,它们之间任何关系都没有。
function test(){ var a = 123; document.write(b); // test里面不能访问b } function demo(){ var b = 234; document.write(a); // demo里面不能访问a }
如果两个函数样嵌套呢?
function test(){ var a = 123; function demo(){ var b = 234; document.write(a); } demo(); document.write(b); } test(); // Uncaught ReferenceError: b is not defined
互相嵌套的函数,外层函数不能访问里层的函数的东西,但里面的函数可以访问外层函数的东西,越往里面的权限却大。就像我国国情的祖孙关系,孙子可以向任何长辈要零花钱,反过来长辈不能向小辈要钱。
定义在全局里面的变量叫"全局变量",定义在全局里面的函数叫"全局函数",定义在局部函数里面的变量叫"局部变量"。
一、js运行三部曲
1. 语法分析
2. 预编译
3. 解释执行
学预编译之前先了解一下js执行的过程
js有两个特点:
1. 第一是单线程
2. 第二是解释性语言
什么叫解释性语言?
解析性语句是翻译一句执行一句,翻译一句执行一句,不是通篇编译成一个文件然后再执行。
解释性语句的特点很容易理解,就是读一句执行一句,其实还没有这么的直观,读一句执行一句那是最后一步,js执行的时候分三步
第一步:叫“语法分析”或者叫语言分析,是通篇执行的一个过程,在js代码执行之前系统会扫描一遍,
看看有没有低级语法错误,少些大括号啊,写中文字符什么的,
系统通篇扫描一遍但不执行,这个通篇扫描的过程就叫做"语法分析"的过程。
第二步:通篇扫描完之后,系统会进行第二个过程叫“预编译”
第三步:然后才会解释一行执行一行,解释一行执行一行……
也就是说在解释执行之前还有两步,第一步叫"语法分析",第二步叫"预编译",预编译是干什么的呢?学预编译之前先铺垫点,看下面两个效果
第一个效果
函数test()执行,结果显而易见必须能输出a
function test(){ console.log('a'); } test(); // a
换一个位置执行,test()执行写在函数声明的上面还能执行吗?还是能执行输出a
test(); // a function test(){ console.log('a'); }
JS是解释执行解释一行…执行一行,下面的函数声明还没读出来呢,可结果还能执行,为什么能执行呢?就是因为有“预编译”的过程。
第二个效果
定义一个变量a等于123,在下面访问a必须能输出123
var a = 123; console.log(a); // 123
输出语句放到变量声明的上面能输出吗?能输出,但输出的结果是undefined
console.log(a); // undefined var a = 123;
如果注释掉变量a声明的语句,变量a没有声明,输出a的结果会报错a is not defined,变量没声明就会出现报错
console.log(a); // Uncaught ReferenceError: a is not defined // var a = 123;
把变量声明写到输出语句下面,在视觉上也是没声明就调用了,为什么不报错而且还能输出undefined呢?这是一个特殊的效果,这个效果也来自于“预编译”的过程。
console.log(a); // undefined var a = 123;
把上面的两个效果提纯成两句话
第一句:函数声明整体提升
第二句:变量 声明提升
有些人不学预编译只记住这两句话,也能解决一些问题,但解决不了深入的问题,学完预编译明白原理后,就永远不需要记这两句话了。
解释第一句:函数声明整体提升
如果写一个"函数声明"不管写到哪里,系统总会是把函数提到逻辑的最前面,所以不管在哪调用test()函数执行,是在函数声明下面调用,还是在函数声明上面调用,其实本质上都是在函数声明下面调用,系统会把函数声明永远提升到逻辑的最前面,这是函数声明整体提升的意思。
function test(){} // 函数声明整体提升 // -------------[systm]----------------- test(); // function test(){ // test函数声明,已经被系统提升 // } test();
第二句:变量声明提升
变量声明也提升,但提升的没有函数声明整体提升的这么邪乎,
变量 声明提升,中间要有空格,一定要分开读叫"变量"、"声明提升"。
var a = 123; 叫变量声明 再加 变量赋值,是两步叫"定义变量"
var a = 123; // 变量声明 + 变量赋值
相当于把 var a = 123; 拆成两份,第一份是var a,第二份是a = 123,并且并把 var a; 放到程序的最前面
var a; // 提升到程序最前面 // -------------[systm]----------------- document.write(a); // 所以即使在这访问变量a,也能打印出的值是undefined a = 123;
所以这样 var a = 123; 变量声明 + 赋值写在一起也是一样的,就相当于把 var a; 隐式的飞(提升)上去了
document.write(a); // undefined var a = 123;
但是函数声明整体提升 和 变量声明提升,这两句话太过肤浅了,解决不了所有的问题。
比如下面函数名字是a,变量名是a,然后后访问a,访问的是什么?
console.log(a); // 打印结果是什么? function a(){ } var a = 123;
还可以再复杂,函数的参数也是a,函数里面再定义一个变量a,函数里面还定义一个函数a,在函数里执行这个被嵌套函数a(),全部的名字都是a
console.log(a); function a(a){ var a = 234; var a = function(){} a(); } var a = 123;
这是那两句话不能解决的,那两句话只把“预编译过程”的两个现象给抽象出来当做方法来用,那不是知识点,学完"预编译"这些问题就都能轻松解决。
学习预编译前先铺垫点知识点,第一个知识点"暗示全局变量",这个知识点和预编译没什么太大关系但必须得懂。
二、预编译前奏
1. imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有。
eg: a = 123;
eg: var a = b = 123;
2. 一切声明的全局变量,全是window的属性。
eg: var a = 123; ===> window.a = 123;
imply是暗示的意思,男生不太好意思表白,向喜欢的女生暗示一下,
imply global是暗示全局变量的意思。
暗示全局变量的语法是:任何一个变量如果未经声明,就直接赋值了系统不报错,这就生成了一个暗示全局变量。
比如直接访问一个 没有声明的变量a 肯定会报错
console.log(a); // Uncaught ReferenceError: a is not defined
但是变量a未经声明就直接赋值系统不会报错,访问变量a可以输出它的值10,就好像它声明了一样,但和真正的声明不一样,这个叫"暗示全局变量"。
a = 10; console.log(a); // 10
1、任何变量如果未经声明就赋值,此变量就归全局对象(window)所有
"全局对象"是什么呢?全局对象是window
window是一个对象,对象上面可以加一些属性,全局对象上面加的属性很有意思,比如在window对象上加了一个属性a等于10,访问 window.a 就输出了10,然后单纯访问一个a也输出10
window.a = 10; console.log(window.a); // 10 console.log(a); // 10
2、一切在全局上声明的任何变量,即使声明了它也归window所有
在全局上声明变量了b等于234,访问b输出234,访问window.b也输出234,一切声明在全局的变量全是window的属性
var b = 234; console.log(b); // 234 console.log(window.b); // 234
第二条很重要,声明在全局的变量归window所有,解释这句话的意思是window就是全局的域,全局的域是什么意思呢?
下面声明一个变量a等于123,这个变量a放在哪里?
1). window是一个对象,他就像一个仓库一样
2). 如果把变量a定义在全局上,就相当于再window上面挂了一个属性a值是123 ,访问变量a的时候会到window上面找变量a
var a = 123; // 全局上定义一个变量a window{ a : 123, // window对象上挂里一个属性a等于123 } console.log(window.a); // 123 console.log(a); // 全局范围上访问变量a其实访问的就是window.a
一切定义在全局范围的变量都归window所有,全局范围上访问变量a其实访问的就是window.a var a = 123; ===> window.a = 123;
3、看一个问题 var a = b = 123;
下面是一个"连等"的过程,js里面存在这种的连等,连等意思是先把123赋给b,再把b的值赋给a
var a = b = 123;
在一个函数test里面写连等,然后执行函数test()
function test(){ var a = b = 123; } test();
这是一个连续赋值的过程:
赋值一定是"自右向左"的 左 <-- 右 先把123赋给b,再把b赋值给a,
但是这个赋值存在一个现象,就是把123赋值给b的时候,这个b是没有经过声明的,它会先把123赋给b,然后再去声明a,然后在把b的值赋给a,
能理解这个过程吗?因为var a= 123; 的时候,不会先把123赋给a,绝对会先声明a
先把123赋给b,然后在声明变量a,再把b赋值给a,这个过程中连续赋值中导致一个结果,b未经过声明就赋值了,未经声明就赋值这个值归window所有。
归window所有的话,在全局的范围内访问 window.a 输出undefined,访问 window.b 输出123,因为b归window所有
function test(){ var a = b = 123; } test(); console.log(window.a); // undefined console.log(window.b); // 123
一切声明在全局的变量都是window属性(window就是全局),记住定律window就是全局,在全局范围内定义变量归window所有
看下面的代码,在局部函数里面定义一个局部变量b,在全局window上肯定没有对应的b,访问 window.b 返回undefine
function test(){ var b = 123; // 局部变量 } test(); console.log(window.b); // undefined
window就是全局,全局的变量 加 全局的函数都在window身上,在全局去访问这个变量、去访问这个函数,就相当于在window身上去拿这个东西。
var a = 123; var b = 234; var c = 345; // 但凡在全局里定义的变量,在window上有对应的属性 // window { // a : 123, // b : 234, // c : 345 // } console.log(a); // 123
如果访问这个a,其实就相当于在访问window.a,因为window就是全局 console.log(a) --> console.log(window.a)
接下来是预编译过程
三、预编译过程(四部曲)
第一步:创建AO对象
第二步:找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
第三步:将实参值和形参统一
第四步:在函数体里面找函数声明,值赋予函数体
1、下面代码打印的结果是什么?
要想知道结果必须要弄明白一个问题,这里面又有函数、又有变量它们都提升。是谁先提升到谁前面、谁覆盖谁,肯定有一个覆盖的问题。
怎么覆盖符合什么样的原则?参数、变量、函数都叫一个名a,这就是预编译发生的奇妙过程。
function fn(a){ console.log(a); var a = 123; console.log(a); function a(){} console.log(a); var b = function(){} console.log(b); function d(){} } fn(1);
预编译发生在函数执行的前一刻。
也就是函数刚刚要执行之前,预编译就完事了在那等着。然后函数执行的时候,预编译已经发生完了,预编译做的事就是把这些矛盾给调和了。
01/ 预编译第一步:
首先生成了一个AO对象,叫创建AO对象,翻译过来Activation Object叫活跃对象
这个活跃对象(AO)简单的说就是我们理解的作用域,其实叫执行期上下文(这个执行期间上下文是由这个函数执行而产生的存储空间库)。
AO{
}
第一步不重要,重要的在第二步
注意:现在学的是函数的预编译,然后在学全局的预编译,全局的特别简单
02/ 第二步:
找函数里面的形参 和 变量声明,并且将变量声明的名 和 形参的名 作为AO对象的"属性名",属性值统一都是undefined
1). 函数里有形参名 a
2). 变量声明有 var a = 123,a名出现了两次,在AO对象里挂一次就行了
3). 还有 var b = function(){} 也是变量声明,这个虽然是函数,但变量的类型是值决定的,
但第一步就是找的是变量声明,b是一个变量的声明
4). 他们的值统一全是undefined
AO{
a : undefined,
b : undefined
}
03/ 第三步:
将"实参值" 和 "形参"相统一,实参是1,形参是a。也就是将实参的值放到形参里面去
AO{
a : 1,
d : undefined
}
04/ 第四步:
找函数体里面的函数声明
1). 函数声明有 function a(){} 还有 function d(){}
这个 var b = function(){} 叫函数表达式,函数声明和函数表达式是两回事
2). 然后将函数声明的名,作为AO对象的属性名也挂到AO里面,
a的名已经有了不用挂了,但是函数名d还没有,把d挂到AO上面,挂上之后他需要什么样的属性值
AO{
a : 1,
b : undefined,
d :
}
3). 属性值是把 函数体 的整体赋到值里面去,
这时候 a的属性值 和 d的属性值,相应的变成 a函数体function a(){} 和 d的函数体function d(){},
也就是把 函数体 全部复制到AO里面。
a原来的值是1,现在被function a(){} 覆盖了,因为第四部的优先级是最高的
AO{
a : function a(){},
b : undefined,
d : function d(){}
}
现在,AO对象创建完了:
AO对象中文名是“执行期上下文”,他的作用就是我们理解的作用域。
AO对象创建完之后马上就要执行函数了,因为预编译发生在执行前一刻
执行第一行:
打印console.log(a),到AO对象里面找这个a,a是的值function a(){},所以第一次打印a就是 function a(){}
然后第二行:
执行这句 var a = 123 准确的说是执行 a = 123,
1). 因为在预编译的第二步就找形参和变量声明,并且把形参和变量声明的"名",作为AO对象的"属性名"了,
2). 这个过程其实就是变量提升的过程,变量声明已经把 var a 提升上去了,
预编译已经把 var a 声明的过程看过了,不用看了,但是 a = 123 这个语句还没有读
3). 这次操作的是AO对象里面的a,把AO对象里a的值改成123
AO{
a : 123,
b : undefined,
d : function d(){}
}
第三行 :
再打印console.log(a),到AO里面找a,a已经是123了,打印结果是123
第四行:
function a(){} 这句忽略不看,因为在预编译的时候已经提升上去了,预编译看过的地方就不用再看了
第五行:
console.log(a) 再打印a结果还是123
第六行:
var b = function(){}
var b 已经提升上去了,现在执行剩下 b = function(){}。AO里的b的值undefined,现在b的值变function (){}
AO{
a : 123,
b : function(){},
d : function d(){}
}
第七行:
console.log(b) 打印b输出 function(){}
执行结果:
2、接着,看下面这道题,打印的都是什么?
function test(a ,b){ console.log(a); c = 0; var c; a = 3; b = 2; console.log(b); function b() {} function d() {} console.log(b); } test(1);
预编译四部:
第一步:生成AO对象
AO{
}
第二步:找"形参"和"变量"的名,把形参名a、b和变量名c,作为AO属性名,值为undefined
AO{
a : undefined,
b : undefined,
c : undefined
}
第三步:实参值和形参相统一,a变成1就完事了
AO{
a : 1,
b : undefined,
c : undefined
}
第四步:
找函数声明,函数声明有两个,一个声明了b、一个声明了d,
把b和d都放到AO上,b已经有了把d放上来,值是函数体,把值全部赋上来,
b的值原来是undefined现在换成function b(){}
AO{
a : 1,
b : function b(){},
c : undefined,
d : function d(){}
}
AO完成了,执行:
第一行:console.log(a); 打印a输出 1
第二行:执行c = 0; ,c的原来是undefined,现在变成0
AO{
a : 1,
b : function b(){},
c : 0,
d : function d(){}
}
第三行:var c; 不用看了,已经提升了
第四行:a = 3; a的值由原来是1,现在等于3了
AO{
a : 3,
b : function b(){},
c : 0,
d : function d(){}
}
第五行:b = 2; b刚才是函数体,现在变成 2
AO{
a : 3,
b : 2,
c : 0,
d : function d(){}
}
第六行:console.log(b); 访问b输出2
第七行:function b() {} 这句忽略,都提升上去了
第八行:function d() {} 这句忽略,都提升上去了
第九行:onsole.log(b); 再访问b还是输出 2
执行结果:1
2
2
3、再练练
先口算一下,第一行 console.log(a) ,a打印什么?
其实可以记住一个规律,一旦有重名的,比如下面这道题有变量a、有函数a,在第一行访问的还是a,那a一定是输出函数体,
因为函数在预编译的第四步,函数权限最高,不管a之前填什么值,第四步都被函数体给覆盖了。
function test(a ,b){ console.log(a); console.log(b); var b = 234; console.log(b); a = 123; console.log(a); function a(){}; var a; b = 234; var b = function(){}; console.log(a); console.log(b); } test(1);
预编译:
第一步:AO{}
第二步:
找形参和变量声明。
没有变量声明就两个形参a、b,
虽然函数里面一大堆,声明了变量a,声明了变量b,但就是a和b这两个
AO{
a : undefined,
b : undefined
}
第三步:
形成实参相统一,a变成1拉倒
AO{
a : 1,
b : undefined
}
第四步:
找函数声明,
这里面只有一个函数声明function a() {},
这个叫 var b = function(){} 函数表达式,只有函数声明能提升,函数表达式不能提升
a的值原来是1,现在变成function a(){}
AO{
a : function a() {},
b : undefined
}
开始执行:
第一行:console.log(a) 打印 function a() {}
第二行:console.log(b) 打印 undefined
第三行:
var b = 234
var b不用看了,b = 234,
上AO里找b,然后AO里的b变成234
AO{
a : function a() {},
b : 234
}
第四行:console.log(b) 打印 234
第五行:a = 123 AO里的a变成123
AO{
a : 123,
b : 234
}
第六行:console.log(a) 打印 123
第七行:function a(){} 这行不看了
第八行:var a; 这行也不用看了
第九行:b = 234; AO里面的b还是234
AO{
a : 123,
b : 234
}
第十行:var b = function (){} AO里的b变成函数 function (){}
AO{
a : 123,
b : function (){}
}
第十一行:console.log(a) 访问a还是打印123
第十二行:console.log(b) 访问b打印函数 function (){}
输出:function a() {}
undefined
234
123
123
function(){} // 注意这里是函数没有名字的
4、全局的预编译
上面是函数体系里的预编译,预编译不只发生在函数体系里,预编译还发生在全局。
在全局声明一变量a等于123,在声明变量a上面访问这个a行不行?输出结果是undefined,因为变量提升了
console.log(a); // undefined var a = 123;
全局里面已经有一个变量a了,在全局里面再定义一个a函数,在a函数声明和a变量声明的上面打印a返回的是什么?
返回的是a的函数体 ƒ a(){} ,因为全局也有预编译环节
console.log(a); // ƒ a(){} var a = 123; function a(){}
四、全局预编译
全局预编译环节和函数里的一样,只不过少了一个环节,全局没有参数所以就三步,但是全局的第一步会发生点变化,变化是生成了一个叫GO的对象。
第一步:生成一个GO对象,Global object叫全局的执行期上下文
第二步:找变量声明,将变量名作为GO属性名,值为undefined
第三步:找函数声明,值赋予函数体
1、看一个小例子
var a = 123; function a(){} console.log(a); // 123
预编译:
第一步:
生成GO{}
第二步:
GO对象里面,a的属性值是undefined
GO{
a : undefined
}
然后直接跳到第四部:
a函数体整体提升,a属性值变成function a(){}
GO{
a : function a(){}
}
执行:
第一句:
var a = 123
a = 123,GO里a的属性值就变成123了
GO{
a : 123
}
第二句:function a(){} 提升了不用看
第三句:console.log(a) 访问a输出123
2、window是什么?
window就是GO
GO是一个对象,window也是一个对象,这两个对象就是一个东西,GO就是window,是一个人的两个名,
所以我们定义完的所有变量都要放到GO里去储存,放到到GO里面去储存就是放到window里面去存储,所以window上面自然就有了我们定义变量的引用。
GO就等于window(window.a == GO.a)
下面GO.a和window.a是一回事,直接打印 window.a 和直接打印 a 两条语句是一样的,访问a到GO里去找,访问window.a更直接了,直接到window里去取
// GO{ // a : 123 // } var a = 123; function a(){} console.log(window.a); console.log(a);
任何全局变量都是window上的属性,换句话说全局变量肯定是GO里面的东西,所以一定是window的属性。
3、再和暗示全局变量的知识点在结合一下
暗示全局变量说,一个变量如果没经声明就赋值了,也要归window所有。
换句话说如果这个变量没经声明就赋值了,是放到GO里去预编译的。
比如下面代码中,b就是一个暗示全局变量,
1). 执行test()的时候生成一个test的AO,但是生成AO的时候发生一个有意思的现象,
2). AO能把a拿出来,但是对b无动于衷拿不出来,
3). 所以这就涉及到暗示全局变量的知识,b没声明就赋值了,b归全局的GO所有。
// GO{ // b : 123 // } function test(){ var a = b = 123; } test(); // AO{ // a : undefined // }
既然b归GO所有,GO又是window,所以在函数里打印window.b 是可以输出123的,因为window是GO可以调用window.b
function test(){ var a = b = 123; console.log(window.b); } test();
但是要在函数里访问window.a,GO里面没有a输出的是undefined
function test(){ var a = b = 123; console.log(window.a); // undefined } test();
4、先生成GO,还是先生成AO?
先生成的是GO
函数的预编译发生在函数的执行的前一刻,
那全局的GO一定发生在全局刚刚要执行的前一刻,script标签它就是全局,把script标签抽象一下也相当于一个大的function,所以一定是先生成GO
5、下面看一个真正恶心的
console.log(test); function test(test) { console.log(test); var test = 234; console.log(test); function test(){ } } test(1); var test = 123;
GO预编译:
第一步:生成GO{}
第二步:
变量var test提升,属性是undefined
GO{
test: undefined
}
第三步:
test函数整体提升,test属性变成test函数体(函数体里面有点复杂,用……代替)
GO{
test: function test(){
......
}
}
开始执行:
第一行:console.log(test),在全局输出的是GO里面的test函数体 function() test{......}
第二行:test函数提升了就不看了
第三行:
开始执行 test(1) 函数,
1). 在执行函数的时候,有执行的前一刻,先生成AO,
2). AO里面是变量test提升,值是undefined
AO{
test: undeined
}
test函数AO第三步:
形参实参相统一,test属性值变成1
AO{
test: 1
}
test函数AO第四步:
还有一个test函数整体提升,AO对象的test属性值变成test函数体
AO{
test: function test(){}
}
执行
test函数里第一行:
console.log(test) 输出函数体function test (){}
这时候有一个小问题,GO里面有test,AO里面也有test,打印谁里面的test?
1). 打印AO里面的test,几层嵌套关系,内层自己有的就用内层自己的,内层自己没有再往上找,
2). 这个时候AO和GO会形成一个链式关系,这个链式关系的访问顺序是可近的来,
AO上有就用AO的,AO上没有看GO上有没有
如果AO上没有test,会义无反顾的到GO上去找test
所以这里打印AO里面的test函数体 function test(){}
test函数里第二行:
test = 234 ,test的值变成234
AO{
test: 234
}
test函数里第三行: console.log(test) 再访问test输出234
最后结果:
6、由浅入深,再看一个简单的
下面打印结果为什么是100
var global = 100; function fn(){ console.log(global); } fn();
全局预编译
第一步:一开始生成GO{}
第二步:
GO里面只有global,值是undefined
GO{
global : undefined
}
第三步:
函数fn整体提升,值是函数体
GO{
global : undefined,
fn : function fn(){
console.log(global);
}
}
开始执行:
第一行:global的值变成100
GO{
global : 100,
fn : function fn(){
console.log(global);
}
}
第二行:fn函数已经提升上去不看了
然后:fn()执行,
执行fn()函数:生成fn函数对应的AO,AO里面什么都没有空的
AO{
}
执行fn函数里面的语句:console.log(global)
1). 首先到fn自己的AO上面,fn自己AO找什么都没有,
2). AO自己没有往上找,往上到GO上去找,
3). GO上有global:100,所以打印GO里面的global属性的值,输出100
如果fn函数里定义了一个变量global变量值是200呢?AO里有就不会访问GO的了,可近的来近的优先,打印输出200,
var global = 100; function fn(){ var global = 200; console.log(global); } fn(); // 200
7、加大难度,真正变态的
global = 100; function fn(){ console.log(global); global = 200; console.log(global); var global = 300; } fn(); var global; console.log(global);
第一步:先生成GO对象
GO{
}
第二步:找变量声明,变量global提升值是undefined
GO{
global : undefined
}
第三步:函数fn整体提升
GO{
global : undefined,
fn : function(){......}
}
执行
第一句:global的值变成100
GO{
global : 100,
function : function(){...}
}
第二句:执行fn()函数
AO预编译:
第一步:生成AO对象
第二步:fn函数里面找形参和变量声明,形参没有但是有变量声明global
AO{
global : undefined
}
第三步:没有实参,不与形参统一
第四部:没有可提升的函数
执行fn函数
第一行:console.log(global) 打印undefined
有fn函数有字节的global变量,值是undefined,所以找的是fn函数自己的global
第二行:global = 200,AO的global变200
AO{
global : 200
}
第三行:console.log(global) 打印global输出 200
第四行:global = 300,AO内的global的值变300
AO{
global : 300
}
执行全局:console.log(global) 输出100
一定要记住AO预编译的四句语法,这四条规律从语文的语法上、结构上没有任何问题。
8、再加大难度
function test(){ console.log(b); if(a){ var b = 100; } console.log(b); c = 234; console.log(c); } var a; test(); a = 10; console.log(c);
01/
GO{
a: undefined,
test: function test(){......}
}
02/
test()函数执行的时候,生成一个AO
AO{
}
03/
找AO里面的东西,找形参变量声明,
1). 不看if条件,if执行的时候才判断,if(a)决定if里面能不能执行,预编译根本就不看
2). 有变量var b,直接提升
刚才的语法是完全没有问题的
AO{
b: undefined
}
04/
执行test函数里第一句:console.log(b); 打印undfined
05/
执行test函数:if(a)
1). AO里面没有变量a
2). 变量a到GO里面找,此时的a是undefined,
3). a的值是undefined,判断体里面 b = 100 不能走,
06/
执行text函数:console.log(b); 打印b还是undefined
07/
然后执行:c = 234; AO里面没有c,c是暗示全局变量归GO
GO{
a: undefined,
test: function test(){......},
C: 234
}
08/
text函数里面打印c:console.log(c); AO里没有变量c,往上到找GO里面找c输出234
09/
test(); 执行完,
10/
a = 10 GO里面a属性的值变10
GO{
a: 10,
test: function test(){......},
C: 234
}
11/
console.log(c); 最后访问c是234
结果:undefined
undefined
234
234
五、百度2013年的两个笔试题
第一个题
function bar(){ return foo; foo = 10; function foo(){ } var foo = 11; } console.log(bar()); // foo(){}
1. 在函数内最上面有return foo,就相当于在最上面console.log(foo),
2. 在最上面 return foo 有一条谨记,如果下面有函数声明的名是foo的话,就什么也不用看了,一定打印的是函数,
3. 因为函数提升是在第四步权限最高。
所以这题打印函数体
第二个题
1. return foo在最下面也有一条谨记,
2. 只要这个foo名的上面被赋过值,直接输出这个值就好了,
3. 因为它执行顺序是最下面的,不管怎么提升覆盖,return上面foo=11,foo就输出11。
console.log(bar()); // 11 function bar() { foo = 10; function foo() { } var foo = 11; return foo; }