首先,我们要知道javascript是单线程、解释性语言。所谓解释性语言,就是翻译一句执行一句。而不是通篇编译成一个文件再去执行。

其实这么说还没有这么直观,读一句执行一句那是到最后的事了。到JS执行前还有两大步骤。

那就是1.语法分析(或语意分析)→2.预编译→3.解释执行(真正的读一句执行一句)

第一步:语法分析(即扫描一下看一看有没有低级的语法错误,比如多个大括号啊,写个中文标点等等,只通篇检查语法,但不执行。这就是语法分析的过程。)

第二步:预编译过程(发生在函数执行时,也可说成执行的前一刻,下面重点讲解)

第三步:解释执行(解释一句执行一句)

好了,了解了js执行的三大步骤接着说一下js预编译。说预编译之前先看几段代码

  1. function test() {
  2. console.log(123456);
  3. }
  4. test();

上边这段代码毫无疑问可以执行,正常输出123456

接下来换一种写法,先写执行语句,再写函数体,如下:

  1. test();
  2. function test() {
  3. console.log(123456);
  4. }

这样依然可以正常执行。打印出123456

再看下边这段代码:

  1. var num = 123;
  2. console.log(num);

这个也毫无疑问可以执行,输出123。

但是,如果直接这样写

  1. console.log(num);

这样属于一个变量未经声明就被访问,会报错。

再换一下写法:

  1. console.log(num);
  2. var num = 123;

其实这也是一种变量未经声明就访问,但是这样写不但不会不报错,还可以打印出结果,打印结果为undefined。

这是为什么呢? 这时候,有些经验的人会让你记住两句话:

1.函数声明整体提升(意思是函数的声明无论写到那个位置,在执行的时候都会把函数声明的语句提到最前执行)。

2.变量的声明提升(意思是变量的声明无论写到什么位置,在执行的时候都会提到最前执行,这里注意是变量的声明,没有赋值什么事)。

这两句话虽然可以解决大部分问题,但是下面的实例它就解决不了了,要真正解释这两种现象就不得不说预编译了。学会了预编译以后上边那两句话永远不需要去记,轻松解决各种问题。

下边来看一个实例:

  1. function test(a) {
  2. console.log(a);
    console.log(b);
    console.log(c);
  3. var a = 123;
  4. console.log(a);
  5. function a() {};
  6. console.log(a);
  7. var b = function () {};
  8. console.log(b);
  9. function c() {};
  10. console.log(c);
  11. }
  12. test(1);

这段代码就是上边那两句话解决不了的。先思考一下这段代码的运行结果会是什么呢?

想要明白这个运行结果,首先我们得明白一个事,这里边既有函数,又有变量声明,还有形参,而且大家的名字还都一样,像打仗一样,都在抢的用。到底谁能抢过谁呢?

我们已经知道函数的预编译在函数执行的前一刻了,也就是说在函数运行之前函数的预编译就帮助我们调和了这个“打仗”的矛盾。

函数的预编译分为4大步骤。

第一步:生成一个Activation Object(执行期上下文)对象,简称AO对象。在访问函数中的变量的时候会直接从我们函数对应的的AO中获取

  1. AO{
  2.  
  3. }

这就是一个AO对象。

第二步:找形参和变量声明,将形参和变量名作为AO对象的属性名,值为undefined。

注意:var a=123;这条语句需要拆分成两部分,一部分为var a;(变量的声明) 一部分为a=123;(变量的赋值)。在这里我们找的是变量声明。所以a=123并没有在预编译  过程中发现。

所以对于上边的函数:

  1. AO{
  2.  
  3. a:undefined
  4.  
  5. b:undefined
  6.  
  7. }

第三步:将实参值和形参统一

此时

  1. AO{
  2.  
  3. a:1
  4.  
  5. b:undefined
  6.  
  7. }

第四步:在函数体里找函数声明,值赋予函数体

注意:这里找的是函数声明,而b=function () {};属于函数表达式,不是这里需要的。

所以此时

  1. AO{
  2.  
  3. afunction a(){},
  4.  
  5. bundefined
  6.  
  7. c function c(){}
  8.  
  9. }

以上AO就是函数的预编译全部完成之后的AO。

接下来该到了真正的读一句执行一句的时候了。

1.读console.log(a);语句,从AO中找到a的值:  function a(){},所以输出结果就为 function a(){} 。

2.读console.log(b);语句,从AO中找到b的值:  undefined,所以输出结果就为 undefined。

3.读console.log(c);语句,从AO中找到c的值:  function c(){},所以输出结果就为 function c(){} 。

4.读var a = 123;语句,var a = 123分为var a;和a=123;两部分,第一部分变量的声明看过,现在只看a=123;此时:

  1. AO{
  2.  
  3. a123
  4.  
  5. bundefined
  6.  
  7. c function c(){}
  8.  
  9. }

5.读console.log(a);语句,从AO中找到a的值:  123,所以输出结果就为 123 。

6.function a(){};语句在预编译时已经看过,现在不管,直接下一句console.log(a); 从AO中找到a的值:  123,所以输出结果就为 123 。

7.var b = function() {};语句同样也是分为var b;和 b = function() {};两部分,第一部分变量的声明看过,现在只看 b = function() {};此时:

  1. AO{
  2.  
  3. a123
  4.  
  5. b function () {},
  6.  
  7. c function c(){}
  8.  
  9. }

8.读console.log(b);语句,从AO中找到b的值: function() {},所以输出结果就为 function() {}。

9.function c(){};也在预编译中看过了,在这里不看,直接下一句console.log(c);从AO中找到c的值: function c() {},所以输出结果就为 function c() {}。

运行结果如下图所示:

以上四部曲说的是Javascript函数的预编译,预编译不仅发生在函数体,在全局也会发生预编译。全局的预编译相对于函数的就简单一些了。接着我们看一下全局的预编译。

全局的预编译只有三个步骤,因为在全局不会涉及到参数。

继续来看一个发生在全局的预编译的实例

  1. console.log(a);
  2. console.log(b);
  3. var a = 123;
  4. var b = function (){};
  5. console.log(a);
  6. function a() {};
  7. console.log(a);
  8. console.log(b);

思考一下这段代码的运行结果。同样也是有变量声明,函数名,只不过发生在全局不会有参数的出现,其实步骤与函数的预编译一致,只是去掉有关参数的部分即可。

第一步:生成一个Global Object(执行期上下文)对象,简称GO对象。因为是全局生成的不再叫AO,但是道理和AO一样,可以理解为换一种叫法而已。

  1. GO{
  2.  
  3. }

这就是一个GO对象

第二步:在全局中找变量声明,将变量名作为GO对象的属性名,值为undefined。

同样需要注意:var a=123;这条语句需要拆分成两部分,一部分为var a;(变量的声明) 一部分为a=123;(变量的赋值)。在这里我们找的是变量声明。所以a=123并没有在预编译过程中发现

此时GO是这样的:

  1. GO{
  2. a:undefined
  3. b:undefined
  4. }

由于没有参数,实参形参统一的步骤直接省略

第三步在全局中找函数声明,值赋予函数体

同样需要注意:这里找的是函数声明,而b=function () {};属于函数表达式,不是这里需要的。

所以此时:

  1. GO{
  2. a: function a() {},
  3. b: undefined
  4. }

接下来该到了真正的读一句执行一句的时候了。

1.读console.log(a);语句,在GO中找到a的值:function a(){},所以输出结果就为function a(){}。

2.读console.log(b);语句,在GO中找到b的值:undefined,所以输出结果就为undefined。

3.读var a = 123;语句,var a = 123分为var a;和a=123;两部分,第一部分变量的声明看过,现在只看a=123;此时:

  1. GO{
  2.   a:123
    b:undefined
  3. }

4.读var b = function() {};语句同样也是分为var b;和 b = function() {};两部分,第一部分变量的声明看过,现在只看 b = function() {};此时:

  1. GO{
  2.   a:123
  3. b:function() {}
  4. }

5.读console.log(a);语句,在GO中找到a的值:123,所以输出结果就为123。

6.function a() {};语句在预编译时已经看过,现在不管,直接下一句console.log(a); 从GO中找到a的值:  123,所以输出结果就为 123 。

7.读console.log(b);语句,在GO中找到b的值:function (){},所以输出结果就为function (){}。

运行结果如下图所示:

这里有一个特别的:未经声明的变量就直接赋值,该变量归GO所有。什么意思呢?我们看下边的实例

  1. function f() {
  2. var a = b = 6;
  3. c = 8;
  4. }
  5. f();
  6. console.log(a);

这段代码会报错,因为变量a在函数中声明,他归该函数的AO所有,当函数执行完AO被销毁,所以在全局找不到a。

但是这样:

  1. function f() {
  2. var a = b = 6;
  3. c = 8;
  4. }
  5. f();
  6. console.log(b);
  7. console.log(c);

运行结果:

在全局访问b和c不但没报错,而且还正确的打印出了运行结果。正如我们刚刚所说的未经声明的变量就直接赋值,该变量归GO所有。所以在全局可以访问到也是顺其自然的事情了。

好了,以上就是javascript的预编译过程。说了半天,学习这个预编译到底有什么用呢?在开发的时候我们也不可能这么命名变量与函数名的呀。其实在这里学习预编译主要是为了下面的作用域来做铺垫,理解了作用域之后再谈我们开发中常见的闭包。这样才能更深入的去理解闭包。

一步一步的理解javascript的预编译的更多相关文章

  1. javascript的预编译和执行顺序

    原文:javascript的预编译和执行顺序 最近在复习javascript的事件处理时发现了一个问题,然后也是我来写javascript的预编译和执行顺序的问题 代码: 代码一<html> ...

  2. javascript引擎执行的过程的理解--语法分析和预编译阶段

    一.概述 js是一种非常灵活的语言,理解js引擎的执行过程对于我们学习js是非常有必要的.看了很多这方便文章,大多数是讲的是事件循环(event loop)或者变量提升的等,并没有全面分析其中的过程. ...

  3. JavaScript的预编译和执行

    JavaScript引擎,不是逐条解释执行javascript代码,而是按照代码块一段段解释执行.所谓代码块就是使用<script>标签分隔的代码段. 整个代码块共有两个阶段,预编译阶段和 ...

  4. JavaScript 之 预编译 作用域,作用域链

    第一次写博客,本来是学习jQuery遇到闭包问题,发现并没有理解闭包,发现闭包牵扯的知识点太多.复习了一遍(发现自己该记住的全忘了)写在博客里,自己也是小白,希望大神们指点迷津,必将感激不尽. 我们知 ...

  5. JavaScript之预编译

    javascript是一种解释性弱类型语言,在浏览器中执行时,浏览器会先预览某段代码进行语法分析,检查语法的正确与否,然后再进行预编译,到最后才会从上往下一句一句开始执行这段代码,简单得来说可以表示为 ...

  6. 还原真实,javascript之预编译 / 预解析

    今天在群里吹水时,有群友提出一个问题.我一看很简单,就立马给出了答案:因为存在变量提升,所以输出undefined.本以为无人反驳,可确招来口诛笔伐.作为写实派的我,一贯以来坚持真实是我的使命,岂能容 ...

  7. Javascript的"预编译"思考

    今天工作需要,搜索下JS面试题,看到一个题目,大约是这样的 <script> var x = 1, y = z = 0; function add(n) { n = n+1;  } y = ...

  8. 带你玩转Visual Studio——带你理解微软的预编译头技术

    原文地址:http://blog.csdn.net/luoweifu/article/details/49010627 不陌生的stdafx.h 还记得带你玩转Visual Studio——带你新建一 ...

  9. JavaScript函数——预编译

    四部曲 创建AO对象 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined. 将实参值和形参值统一 在函数体内找函数声明,值赋予函数体. 权重按顺序依次增加.以下例子即可体现上述规则 ...

随机推荐

  1. .NET 跨域问题解决

    后端处理:var callback=context.Request.QueryString["callback"].ToString(); context.Response.Wri ...

  2. python中pip添加国内镜像源后显著加速下载

    python中pip添加国内镜像源后显著加速下载 更换pip源到国内镜像,很多国外的库下载非常慢,添加国内镜像后安装下载速度提升非常明显(亲测有些可以由几十kb加速到几MB) pip国内的一些镜像阿里 ...

  3. css中的baseline

    这是css中的一个容易被人忽略的概念,今天在知乎上看到一个问题,这个问题应该是关于baseline,才去补习了一下关于baseline的知识,首先我来还原一下问题: <div style=&qu ...

  4. 基础系列(2)--- css1

    css组成 css语法组成 选择器 和 声明 (多个声明用分号隔开) 声明包括 属性和属性值(多个属性值用空格隔开) 语法: 选择器{ 属性: 属性值; 属性: 属性值1 属性值2; } css样式表 ...

  5. BUUCTF--checkin

    文件上传文件上传一般验证方式:1.本地js验证(客户端)2.MIME验证(服务端)3.拓展名验证(服务端)4.脚本内容(文件头)验证(服务端) 通常会用到exif_imagetype()函数,这个函数 ...

  6. C# 测试网络速度例子

    using System.Net.NetworkInformation; namespace PingExample { public partial class Form1 : Form { pub ...

  7. Pinctrl子系统之一了解基础概念【转】

    转自:https://blog.csdn.net/u012830148/article/details/80609337 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请 ...

  8. C++和C的相互调用

    C++和C相互调用实际工程中C++和C代码相互调用是不可避免的C++编译器能够兼容C语言的编译方式C++编译器会优先使用C++编译的方式extern关键字能强制让C++编译器进行C方式的编译 exte ...

  9. Python之flask框架2

    Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务.本文参考自Flask官方文档,大部分代码引用自官方文档. 安装flask 首先我们来安装F ...

  10. djang小项目过程中的小问题 02(跳转界面)

    我觉着自己生下来就是解决问题的 ##1. 今天在使用登录注册模板时,输入后缀index,正常显示登录界面,但是点击 立即注册 之后不会跳转到注册页面 因为我观察到后缀名发生变化了,但是出发点是错的,前 ...