理解JavaScript 预编译过程

307 阅读6分钟

讲预编译之前,首先我们先了解JavaScript运行的特点

你可能立马想到这几个词:单线程?解释型语言?翻译一句执行一句,其实还没有这么直观,读一句执行一句是到最后一步才去弄的。在JavaScript执行之前,分为三步:

第一步:语法分析(或语义分析)

系统会扫描一遍,看你有没有语法错误,少个花括号,中文逗号等等。这个通篇扫描的过程就叫做语法分析。通篇扫描完成之后就会进行第二步

第二步:预编译

第三步:解释执行

在讲预编译之前,我们先铺垫一点东西,请看下面代码:

function test() {

  console.log('a');

}

test();

能不能执行?当然能啊,打印输出 'a',那么再请看下面代码:

test();

function test() {

  console.log('a');

}

你可能会想,读一行执行一行,应该不会输出,但是由于预编译的存在,还是打印 'a'

var a = 123;

console.log(a);//此时输出 123

如果是下面这样呢?

console.log(a);//此时输出 undefined

var a = 123;

或者是这样?

console.log(a); // 变量没声明,当然报错啊

// var a = 123;

但是为什么在定义变量a赋值之前输出就是undefined的呢?

我们有两句话:

1.函数声明整体提升

2.变量 声明提升

首先来理解第一句:

不管在哪里写的函数声明,系统总是会把这个函数提升到逻辑的最前面,不管你是在函数声明的上面调用还是函数声明的下面调用,其本质都是在函数声明的下面调用。

现在来理解第二句:

变量  声明提升 我中间加了空格代表有意思的

var a = 123;

上面的操作是定义变量再加变量赋值,是不是两步?但是这么来写的话就相当于变量声明提升了

console.log(a);

var a = 123;

所以上面的代码相当如下

var a;//此时会被丢到程序的最前面

console.log(a);// undefined

a = 123;

讲到这里先记住上面总结的简单两句话:

1.函数声明整体提升

2.变量 声明提升

但是呢?这两句话太过于肤浅。不能只靠这两句话啊!看下面代码

console.log(a);

function a () {}

var a = 123;

我声明了一个函数a,又var定义一个变量赋值123,我在最上面输出的a是什么?

我在变一下

console.log(a);

function a(a) {

  var a = 234;

  var a = function() {}

  a();

}

var a = 123;

此时函数的参数也是a,里面也搞个var a = 234,又来个函数表达式 a 并且再下面调用,我现在问你怎么办?学无止尽,上面的只是带你热身,现在老衲继续深入探讨

预编译前奏

1.imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有

2.一切声明的全局变量,全是window的属性

console.log(a);

执行上面操作,报错,因为根本就没有a这个变量

a = 123;

如果我这样写呢?当然不报错,也能获取 a 的值,但是 这样的写法 和 var声明的变量a也是有区别的。**任何变量,如果变量未经声明就赋值,此变量就为全局对象所有。**这个全局对象是谁呢?是window。这个window很有意思

a = 10; ----->  window.a = 10任何变量,如果变量未经声明就赋值,此变量就为全局对象所有

但是如果你是使用var声明的var b = 234;window上还是有b这个变量的值。因为一切声明的全局变量,全是window的属性

window就是全局的域

var a = 123;就相当于在window上定义了一个属性a

window: {  

  a:123

}

如果在全局内访问a其实是访问window.a

现在我写了如下代码:

function test() {  

  var a = b = 123;

}

test();

console.log(window.a);

console.log(window.b);

这个中间过程先把123赋值给未经声明的b,然后再去声明a,把b的值赋值给a,这个过程导致一个问题就是b未经声明就赋值了,那是不是这个b就属于window了?(前面刚写的啊,不准忘),那么此时在全局访问window.a应该是没有,但是访问window.b就应该有值

趁热打铁,赶紧看下面代码:

function test() {

 var b = 123;

}

test();

console.log(window.b);这里输出什么?undefined,前面说过一切声明的全局变量是widnow的属性,但是我这个var b = 123; 是在哪里声明的?我是在声明的函数中,局部定义的变量,那就不能归window所有。就记住window就是全局。

好了好了,前面铺垫了这么多终于到了预编译的过程,泪目


预编译

下面举个例子来讲:先想下调用函数里面会输出那些内容

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

我们先搞清楚fn函数中,又有函数声明又有变量声明,肯定是会涉及到覆盖的问题,它们是按照一个什么样的原则覆盖的呢?最骚的是fn函数的参数也来插一脚也是a,这就是涉及到预编译的奇妙过程。

预编译发生在函数执行的前一刻:

1.**创建AO对象(Activation Object)**执行期上下文

AO {

}

**2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined**

找到一个新参的声明a,又找到一个var a ,一样的名字就写一个,还找到函数表达式的声明var b ,此时的AO对象

AO {

  a: undefined,

  b: undefined

}

**3.将实参和形参统一**

实参是1,现在形参在AO对象上是undefined,值统一,变为1 

AO {

  a: 1,

  b: undefined

}

**4.在函数体里面找函数声明,值赋予函数体**

找到 function a() {} 和 funtion d() {},并把他们的值赋予函数体

那么此时

AO对象中a的值为1 被替换为函数体了。

第四步结束后,开始执行函数,第一个输出的a是什么?AO对象中的a,那么第一次输出的是function,下面 var a = 123,已经在前面发生了预编译,剩下执行的是a = 123,AO对象里的a

也被赋值为123,然后再执行console.log(a);是不是就变成123了?接下来的function a() {}还需要用看吗?不用看了,在预编译的时候就已经提升上去了,接着下面的console.log(a);还是123,var b = function () {}还需要看吗?var b 是不是提升上去了?就剩一个 b = function() {},因此下面打印的b是一个function.因此依次打印的结果就是function,123,123,function

下面自己练习一下 答案去控制台找,如果写的有什么不足的地方欢迎在评论区补充哦。

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