一步一步的理解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. 将实参值和形参值统一 在函数体内找函数声明,值赋予函数体. 权重按顺序依次增加.以下例子即可体现上述规则 ...
随机推荐
- .NET 跨域问题解决
后端处理:var callback=context.Request.QueryString["callback"].ToString(); context.Response.Wri ...
- python中pip添加国内镜像源后显著加速下载
python中pip添加国内镜像源后显著加速下载 更换pip源到国内镜像,很多国外的库下载非常慢,添加国内镜像后安装下载速度提升非常明显(亲测有些可以由几十kb加速到几MB) pip国内的一些镜像阿里 ...
- css中的baseline
这是css中的一个容易被人忽略的概念,今天在知乎上看到一个问题,这个问题应该是关于baseline,才去补习了一下关于baseline的知识,首先我来还原一下问题: <div style=&qu ...
- 基础系列(2)--- css1
css组成 css语法组成 选择器 和 声明 (多个声明用分号隔开) 声明包括 属性和属性值(多个属性值用空格隔开) 语法: 选择器{ 属性: 属性值; 属性: 属性值1 属性值2; } css样式表 ...
- BUUCTF--checkin
文件上传文件上传一般验证方式:1.本地js验证(客户端)2.MIME验证(服务端)3.拓展名验证(服务端)4.脚本内容(文件头)验证(服务端) 通常会用到exif_imagetype()函数,这个函数 ...
- C# 测试网络速度例子
using System.Net.NetworkInformation; namespace PingExample { public partial class Form1 : Form { pub ...
- Pinctrl子系统之一了解基础概念【转】
转自:https://blog.csdn.net/u012830148/article/details/80609337 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请 ...
- C++和C的相互调用
C++和C相互调用实际工程中C++和C代码相互调用是不可避免的C++编译器能够兼容C语言的编译方式C++编译器会优先使用C++编译的方式extern关键字能强制让C++编译器进行C方式的编译 exte ...
- Python之flask框架2
Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务.本文参考自Flask官方文档,大部分代码引用自官方文档. 安装flask 首先我们来安装F ...
- djang小项目过程中的小问题 02(跳转界面)
我觉着自己生下来就是解决问题的 ##1. 今天在使用登录注册模板时,输入后缀index,正常显示登录界面,但是点击 立即注册 之后不会跳转到注册页面 因为我观察到后缀名发生变化了,但是出发点是错的,前 ...