文章目录
( 部分蓝色字体可点击跳转,查看对应叙述)
预编译
预编译指的是在函数执行之前系统会浏览一遍代码,对一些低级语法错误会报错,并且在这个过程中会产生程序执行的环境(比如变量声明提升,函数整体提升等)
预编译的效果:
预编译会将函数声明整体提升到函数调用之前。即使函数调用在前,声明在后代码也可正常执行。
预编译会将变量声明提升到使用之前,但变量赋值仍然会在使用之后,比如下面代码执行结果,第一次输出为undefined因为预编译只提升了变量的声明(此时存值为undefined),并没有赋值。
<script>
console.log(a);
var a = 12;
console.log(a);
</script>
![](https://img-blog.csdnimg.cn/20190816211431149.png)
预编译前奏:(发生在函数执行前)
- 任何变量未经声明就赋值,则此变量为全局对象(window)所有。
- 一切声明的全局变量全是window的属性。
总结:window就是全局的域 var a = 123;
相当于window { a:123; }
,我们在访问a
时会到window
中寻找有没有对应的属性。
【例】此处b便是全局对象的属性
<script> function test() { var a = b = 123; } test(); </script>
再来反过来看预编译四部曲(只是一个执行顺序,重在最终的结果)
1. 创建AO Activation Object(不重要)(执行期上下文,理解为作用域)
AO { }
2. 找形参和变量声明,将变量和形参名作为AO属性名,值为
undefined
(注意此处虽然有a函数和a变量,但只出现一次)AO { a : undefined; b : undefined; }
3. 将实参值和形参统一
AO { a : 2; b : undefined; }
AO { a : function a(){};//由2变为函数体 b : undefined; d : function d(){} }
结合以上可以判断以下代码输出
<script> 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(2); </script>
解析
在预编译完成之后才会执行代码,首先到AO {}
中找到a
的值,为ƒ a(){}
,然后到var a = 123;
,注意这里不会再声明a
,因为预编译已经使得 变量声明提升了,所以只会对a
赋值 123,所以第二项输出123,接下来function a(){}
忽略,因为预编译已经 该函数声明整体提升了,故输出仍然时123,最后输出 b变量,值为其函数体
【练习】以下代码会怎么输出?<script> function fn(a , b){ console.log("a="+a); console.log("c="+c); c = 0; console.log("c="+c); var c; a = 3; console.log("b="+b); b = 2; console.log("a="+a); console.log("b="+b); function b(){} function d(){} console.log("b="+b); } fn(2); </script>
同时需注意此处
说明了预编译不仅发生在函数内部,同时也放生在全局,但不一样的地方在于生成的不是AO对象,而是 GO
(Global Object, 注 实际上就是window,并且GO一定在AO之前生成)
对象,同时没有预编译的 第三步,因为它没有实参传值,回到 此处,它实际的存储是这样的AO { a:undefined } GO { b:123 }
可用如下代码验证
function test() { console.log("1a="+a); console.log("2window.a="+window.a); console.log("3window.b="+window.b); var a = b = 123; console.log("4a="+a); console.log("5window.a="+window.a); console.log("b="+b); console.log("6window.b="+window.b); } test(); console.log("7window.a="+window.a); console.log("8b="+b); console.log("9window.b="+window.b);
【例1】以下两份代码如何输出?
console.log("1="+test); function test(test){ console.log(test); var test = 345; console.log(test); function test(){} } test(1); var test = 123;
可将第二份代码
tr()
中的变量声明去掉,看看又是如何输出,会让你对GO和AO的关系理解更深var a = 123; function tr(){ console.log(a); var a = 23; console.log(a); } tr();
【例2】以下代码先输出undefined,在输出200,300。
ad = 100; function fb(){ console.log(ad); ad = 200; console.log(ad); var ad = 300; console.log(ad); } fb(); var ad;
作用域
作用域是啥?
作用域是根据名称查找变量的一套规则。负责收集并维护有所有声明的标识符(变量)组成的一系列查询,并实施一套严格规则,确定当前执行的代码对这些标识符的访问权限。
在预编译的基础上
[[scope]]:
每个javascript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供javascript引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了执行期上下文的集合。- 作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
- 运行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行期上下文被销毁。
- 查找变量:从作用域链的顶端依次向下查找
结合以下代码理解
function a() { function b() { var b = 234; } var a = 123; b(); } var glob = 100; a();
function a(){ function b(){ function c(){ } c(); } b(); } a();
以下为预编译顺序
a定义:a.[[scope]]——> 0 : GO
a执行:a.[[scope]]——> 0 : a.AO
——> 1 : GO在a的基础上
b定义:b.[[scope]]——> 0 : a.AO
——> 1 : GO
b执行:b.[[scope]]——> 0 : b.AO
——> 1 : a.AO
——> 2 : GO在a、b的基础上
c定义:a.[[scope]]——> 0 : b.AO
——> 1 : a.AO
——> 2 : GO
c执行:a.[[scope]]——> 0 : c.AO
——> 1 : b.AO
——> 2 : a.AO
——> 3 : GO- 查找变量时,会在当前函数的作用域链中从顶端开始查找,从a的AO到a的GO
- 在a执行时会定义b,b在执行时会在a的作用域链的基础上在创建自己的执行器上下文,这里b所用的只是a创建时的作用域链的引用,所以说他们是同一个东西,在一个地方改变,另一个地方也会改变
- 每次调用函数都会创建一个独一无二的执行期上下文,执行完毕就会销毁执行期上下文
附:关于作用域的补充
取自《你不知道的JavaScript》上卷
LHS查询:变量出现在赋值操作符左侧,试图找到变量的容器本身,从而对其进行赋值
——>赋值操作的目标是谁?RHS查询:变量出现在赋值操作符右侧(理解为非左侧),试图取得变量的源值
——>谁是赋值操作的源头(要把谁赋给变量)?【例】此处都有什么查询?
function foo (a) { console.log(a); } foo(2);
foo引用,找到函数体 RHS 找到a的源值,传给console.log() RHS 对a进行查询,查看它传参操作的目标(foo的形参a) LHS 对console查询 RHS 对console对象进行查询,检查是否有log方法 RHS foo(2)函数传参,即为赋值 LHS 假设log()函数的原生实现中可接受参数,在将2赋值给第一个参数(实参列表某一位)之前,需进行引用查询 LHS 【例】如下代码有几处查询?
function foo(a) { var b = a; return a + b; } var c = foo(2);
共有七处 3LHS、4RHS 把返回的结果赋给c LHS foo(2)传参(隐式赋值) LHS b=a; LHS 查询foo函数 RHS =a;需要找到a的值 RHS a+ 需要找到a的值 RHS +b;需要找到b的值 RHS