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

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

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

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

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

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

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

function test() {
console.log(123456);
}
test();

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

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

test();
function test() {
console.log(123456);
}

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

再看下边这段代码:

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

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

但是,如果直接这样写

console.log(num);

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

再换一下写法:

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

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

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

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

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

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

下边来看一个实例:

  function test(a) {
console.log(a);
console.log(b);
console.log(c);
var a = 123;
console.log(a);
function a() {};
console.log(a);
var b = function () {};
console.log(b);
function c() {};
console.log(c);
}
test(1);

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

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

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

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

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

AO{

}

这就是一个AO对象。

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

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

所以对于上边的函数:

 AO{

  a:undefined,

  b:undefined

}

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

此时

AO{

   a:1,

   b:undefined

}

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

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

所以此时

AO{

  a:function a(){},

  b:undefined,

  c :function c(){}

}

以上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;此时:

AO{

  a:123,

  b:undefined,

  c :function c(){}

}

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() {};此时:

AO{

  a:123,

  b: function () {},

  c :function c(){}

}

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

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

运行结果如下图所示:

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

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

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

        console.log(a);
console.log(b);
var a = 123;
var b = function (){};
console.log(a);
function a() {};
console.log(a);
console.log(b);

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

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

GO{

}

这就是一个GO对象

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

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

此时GO是这样的:

GO{
a:undefined,
b:undefined
}

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

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

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

所以此时:

GO{
a: function a() {},
b: undefined
}

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

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;此时:

GO{
  a:123,
b:undefined
}

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

GO{
  a:123,
b:function() {}
}

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所有。什么意思呢?我们看下边的实例

 function f() {
var a = b = 6;
c = 8;
}
f();
console.log(a);

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

但是这样:

function f() {
var a = b = 6;
c = 8;
}
f();
console.log(b);
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. NopCommerce源代码分析之用户验证和权限管理

    目录 1.  介绍 2.  UML 2.1  实体类UML图 2.2  业务相关UML图 3.  核心代码分析 3.1  实体类源代码 3.2  业务相关源代码 3.3  相关控制器源代码 3.4  ...

  2. 团队展示&选题 (白衣天使队)

    作业详见此地址:    https://www.cnblogs.com/bbplus/p/11735449.html

  3. Redis缓存和MySQL数据一致性方案(转)

    需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景,主要 ...

  4. SpringBoot 整合Mybatis操作数据库

    1.引入依赖: <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId> ...

  5. kerberos&LDAP实现免密码登录搭建

    kerberos && openldap 1.install openldap & kerberos server: yum install db4 db4-utils db4 ...

  6. 【洛谷P5331】 [SNOI2019]通信

    洛谷 题意: \(n\)个哨站排成一列,第\(i\)个哨站的频段为\(a_i\). 现在每个哨站可以选择: 直接连接到中心,代价为\(w\): 连接到前面某个哨站\(j(j<i)\),代价为\( ...

  7. 201871010118-唐敬博《面向对象程序设计(java)》第十二周学习总结

    博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.co ...

  8. pipenv安装包时一直卡在Locking [packages] dependencies…,换pypi源

    Pipfile 中 source 源是 python 官网,服务器在国外,加载比较慢. 将 url 修改为清华的 pypi 源 https://pypi.tuna.tsinghua.edu.cn/si ...

  9. 【转】前后端分离的项目如何部署发布到Linux

    前后端分离的项目如何部署发布到Linux 前期准备 1.服务器的基本配置信息2.本机远程连接服务器的工具(xshell.xftp或者mobaXterm等等,看你自己喜欢) 第一步:部署环境 1.安装j ...

  10. leetcode2. 两数相加

    使用迭代的方式 class Solution{ public: ListNode *addTwoNumbers(ListNode* l1,ListNode *l2) { ListNode *res=) ...