Go to comments

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 执行的过程


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;

这是那两句话不能解决的,那两句话只把“预编译过程”的两个现象给抽象出来当做方法来用,那不是知识点,学完“预编译”这些问题就都能轻松解决。

学习预编译前先铺垫点知识点,第一个知识点“暗示全局变量”,这个知识点和预编译没什么太大关系但必须得懂。


二、暗示全局变量


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) 

接下来是预编译过程


三、预编译过程(四部曲)


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(){}


执行结果

image.png


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的对象。


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


最后结果

    image.png

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;
}



Leave a comment 0 Comments.

Leave a Reply

换一张