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运行三部曲
学预编译之前先了解一下 js 执行的过程
1. 语法分析
2. 预编译
3. 解释执行
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);
预编译发生在函数执行的前一刻。
也就是函数刚刚要执行之前,预编译就完事了在那等着。然后函数执行的时候,预编译已经发生完了,预编译做的事就是把这些矛盾给调和了。
预编译第一步
首先生成了一个 AO 对象,叫创建 AO 对象,翻译过来 Activation Object 叫活跃对象
这个活跃对象(AO),简单的说就是我们理解的作用域,其实叫执行期上下文(这个执行期间上下文是由这个函数执行而产生的存储空间库)。
AO{
}
第一步不重要,重要的在第二步
注意,
现在学的是函数的预编译,然后在学全局的预编译,全局的特别简单
第二步
找函数里面的 形参 和 变量声明,并且将 变量声明的名 和 形参的名 作为 AO 对象的 属性名,属性值统一都是 undefined
1. 函数里有形参名 a
2. 变量声明有 var a = 123,a 名出现了两次,在 AO 对象里挂一次就行了
3. 还有 var b = function(){} 也是变量声明,这个虽然是函数,但变量的类型是值决定的,
但第一步就是找的是变量声明,b 是一个变量的声明
4. 他们的值统一全是 undefined
AO{
a: undefined,
b: undefined
}
第三步
将“实参值”和“形参”相统一,实参是 1,形参是 a。也就是将实参的值放到形参里面去
AO{
a: 1,
b: undefined
}
第四步
找函数体里面的函数声明
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() {} 这句忽略,都提升上去了
第九行 console.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); // 123 } 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、加大难度,真正变态的
第一步,先生成 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; }