一步一步的理解javascript的预编译
首先,我们要知道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的预编译的更多相关文章
- javascript的预编译和执行顺序
原文:javascript的预编译和执行顺序 最近在复习javascript的事件处理时发现了一个问题,然后也是我来写javascript的预编译和执行顺序的问题 代码: 代码一<html> ...
- javascript引擎执行的过程的理解--语法分析和预编译阶段
一.概述 js是一种非常灵活的语言,理解js引擎的执行过程对于我们学习js是非常有必要的.看了很多这方便文章,大多数是讲的是事件循环(event loop)或者变量提升的等,并没有全面分析其中的过程. ...
- JavaScript的预编译和执行
JavaScript引擎,不是逐条解释执行javascript代码,而是按照代码块一段段解释执行.所谓代码块就是使用<script>标签分隔的代码段. 整个代码块共有两个阶段,预编译阶段和 ...
- JavaScript 之 预编译 作用域,作用域链
第一次写博客,本来是学习jQuery遇到闭包问题,发现并没有理解闭包,发现闭包牵扯的知识点太多.复习了一遍(发现自己该记住的全忘了)写在博客里,自己也是小白,希望大神们指点迷津,必将感激不尽. 我们知 ...
- JavaScript之预编译
javascript是一种解释性弱类型语言,在浏览器中执行时,浏览器会先预览某段代码进行语法分析,检查语法的正确与否,然后再进行预编译,到最后才会从上往下一句一句开始执行这段代码,简单得来说可以表示为 ...
- 还原真实,javascript之预编译 / 预解析
今天在群里吹水时,有群友提出一个问题.我一看很简单,就立马给出了答案:因为存在变量提升,所以输出undefined.本以为无人反驳,可确招来口诛笔伐.作为写实派的我,一贯以来坚持真实是我的使命,岂能容 ...
- Javascript的"预编译"思考
今天工作需要,搜索下JS面试题,看到一个题目,大约是这样的 <script> var x = 1, y = z = 0; function add(n) { n = n+1; } y = ...
- 带你玩转Visual Studio——带你理解微软的预编译头技术
原文地址:http://blog.csdn.net/luoweifu/article/details/49010627 不陌生的stdafx.h 还记得带你玩转Visual Studio——带你新建一 ...
- JavaScript函数——预编译
四部曲 创建AO对象 找形参和变量声明,将变量和形参名作为AO属性名,值为undefined. 将实参值和形参值统一 在函数体内找函数声明,值赋予函数体. 权重按顺序依次增加.以下例子即可体现上述规则 ...
随机推荐
- C# 去除数字中多于的0
decimal i = decimal.Parse(Console.ReadLine()); Console.WriteLine((i).ToString(")); Console.Writ ...
- .NET Core RabbitMQ探索(2)——RabbitMQ的Exchange
实际上,RabbitMQ的生产者并不会直接把消息发送给队列,甚至生产者都不知道消息是否会被发送给一个队列.对于生产者而言,它们只能把消息发送到Exchange,一个Exchange所完成的工作相当简单 ...
- Windows 下MongoDB复制集配置
1.下载服务.https://www.mongodb.com/ 点击products 下拉第二列MongoDB server 选择 4.0.6 2.下载下来后 有限管理员运行 一路安装,可以不用 ...
- maven 学习---Maven项目模板
Maven提供用户,使用原型的概念,不同类型的项目模板(以数字614)是一个非常大的列表. Maven帮助用户快速开始使用以下命令创建新的Java项目 mvn archetype:generate 什 ...
- Retrofit+Okhttp+RxJava打造网络请求之Post
之前一直在准备Android培训的事情,所幸的是终于完事啦,在这过程中真的发现了自身无论从沟通能力还是技术能力上很多的不足,就用一句 路漫漫其修远兮,吾将上下而求索 来勉励自己吧.之前也在项目上用上O ...
- toString的本质 以及String.valueOf()
Object可以用toString转为字符串. Object.toString(); 但char[]不行,得用valueOf. String.valueOf(char[]); 如果用toString, ...
- C语言的暂停
#include<stdio.h> int main(void) { printf("Hello, World!\n"); system("pause&quo ...
- Nginx 环境搭建 (windows)
Nginx 环境搭建 (windows) 资源 # nginx在线文档和支持 For online documentation and support please refer to nginx.or ...
- VIJOS-P1234 口袋的天空
洛谷 P1195 口袋的天空 https://www.luogu.org/problemnew/show/P1195 JDOJ 1374: VIJOS-P1234 口袋的天空 https://neoo ...
- electron自定义桌面应用的外观
1. 控制应用视窗大小 构建桌面应用时,我们要考虑我们的应用程序需要如何让用户来使用,那么我们需要提供一个视窗,那么该视窗可以最大化展示,也可以最小化展示,当然我们也希望可以全屏运行. 在electr ...