编译阶段

词法分析(Lexing)

这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代 码块被称为词法单元(token)。

简单举个例子:c = b - a 转换为

  • NAME "c"
  • EQUALS
  • NAME "a"
  • MINUS
  • NAME "b"
  • SEMICOLON

语法分析(Parsing)

这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法 结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。

AST大概是下面的样子:

生成可执行代码

将 AST 转换为可执行代码的过程称被称为代码生成。

执行阶段

接下来,我们以一个简单例子进行分析。

var a = 2;

function bar() {
var b = 2; function foo() {
var c = 2;
} foo();
} bar();

1. JS引擎创建一个全局对象(Global Object)

这个对象全局只存在一份,它的属性在任何地方都可以访问,它的存在伴随着应用程序的整个生命周期。全局对象在创建时,将Math,String,Date,document 等常用的JS对象作为其属性。由于这个全局对象不能通过名字直接访问,因此还有另外一个属性window,并将window指向了自身,这样就可以通过window访问这个全局对象了。用伪代码模拟全局对象的大体结构如下:

//创建一个全局对象
var globalObject = {
Math:{},
String:{},
Date:{},
document:{}, //DOM操作
...
window:this //让window属性指向了自身
}

2. JS引擎会创建一个执行环境栈(Execution Context Stack)


  • 提到栈,小伙伴们都知道,栈是一种类似羽毛球筒存储羽毛球的数据结构,采用先进后出,后进先出的特点。

上图中的羽毛球1一定是先放入栈中,然后是羽毛球2,以此类推,而出栈时,一定是羽毛球5先拿出来,然后是羽毛球4,以此类推,这种方式和栈存取数据的方式如出一辙。


  • 堆数据类型类似与书架。书虽然也整齐的存放在书架上,但是我们只要知道书的名字,我们就可以很方便的取出我们想要的书。

好了好了,扯远了。我们接着往下说,在这只需知道执行环境栈是怎样存取数据的就行。

3. 创建全局执行上下文(Execution Context)

到这你可能会问,上下文是个啥玩意?

是啊,上下文是个什么鬼啊?

上下文不是玩意,也不是什么鬼。

执行上下文可以理解为当前代码的执行环境。JS所有代码都会在自己的上下文环境下运行。

说到上下文,你可能会有这样的疑惑:上下文不就是作用域吗?

老铁,我肯定的告诉你,上下文不是作用域。的确,在JS里,这还真是个很难区分的东东。不过现在我还不能马上道出他们的区别,因为作用域的知识,我们还没有涉及,彻底搞懂JavaScript作用域,通过这篇文章,你将彻彻底底了解关于作用域的一切。

那在JS中会有几种执行环境呢?

大概有3种:

  • 全局环境:JavaScript代码运行起来会首先进入该环境
  • 函数环境:当函数被调用执行时,会进入当前函数中执行代码
  • eval、with(不建议使用,可忽略)

因此在一个JavaScript程序中,必定会产生多个执行上下文。

go on...

4. 全局上下文推入执行环境栈底

5. 代码开始从上往下执行,这里我们暂且不谈标识符处理,当代码执行到bar(),生成bar执行上下文,推入栈中

6. 代码执行到foo(),生成foo执行上下文,推入栈中

7. foo()执行完,foo执行上下文出栈

8. bar()执行完,bar执行上下文出栈

9. 全局上下文执行上下文出栈

我们用图走一下js执行流程,是这样的:

小伙伴们,现在是不是对JS执行流程有了一个整体认识,下面我们来说点更有意思的。

上下文执行细节

我们先看整体了解下

创建阶段

1. 创建变量对象(Variable Object)

创建变量对象,依次经历了以下几个步骤

  1. 建立arguments对象。检测当前上下文参数,建立该对对象下的属性及属性值。(这里提一下,函数的参数是按值传递,我知道你是知道的)
  2. 检测关键词function函数声明。检测当前上下文中的函数声明,并挂载到变量对象上,其值是函数对象的引用。
  3. 检测var变量声明。检测当前上下文中的var声明,并赋值为undefined;如遇到同名var声明的变量,则会默认覆盖;如遇到同名函数声明,则默认忽略,这也就体现了函数声明的优先级要高于var声明。谁的大哥还是得分清的,哈哈。。。
变量提升

看到这,我觉得你对变量提升具体是什么以及如何实现的应该了解的一清二楚了。

是不是呢?

我们来一道题测试下

function foo() {
console.log(a);
console.log(baz); var a = 'inner';
var baz = 1; function baz() {}
} foo();

第一处是undefined,第二处是[Function: baz],是不是很简单?

下面我用代码简单模拟下上面的过程

function foo() {
function baz() {} var a = undefined; console.log(a);
a = 'inner'; console.log(baz);
} foo();

变量对象大概是这样的

 VO(foo) = {
arguments: {},
baz: <foo reference>, // 表示foo的地址引用
a: undefined
}

2. 确定作用域链

作用域链是由当前作用域与上层一系列父级作用域组成,作用域的头部永远是当前作用域,尾部永远是全局作用域。作用域链保证了当前上下文对其有权访问的变量的有序访问。

我们先简单了解下,详细的我们会在彻底搞懂JavaScript作用域中谈到。

var a = 1;
function foo() {
function baz() {
console.log( a );
} baz();
} foo(); // 1

上面的对于我们来说很简单,是吧?没错这就是作用域链的应用。

我们简单模拟下

EC(foo) = {
VO(foo): {...}, //省略
ScopeChain: [VO(foo), window],
this:
} EC(baz) = {
VO(baz): {...}, //省略
ScopeChain: [VO(baz), VO(foo), window],
this:
}

3. 确定this指向

谈到this,大家是不是感到很兴奋,平时写代码时,被这家伙整的晕头转向的,这回我们终于可以揭开this的神秘面纱了,搞清楚它在JS到底是怎样的存在,不过客官别着急,我们这里先不介绍this,因为关于this的内容太多了,我们得慢慢去品味它,这里先记住,this是在执行上下文创建阶段确定的。

this传送门

this真是一个淘气鬼

全局上下文

全局上下文有些特殊,其变量对象永远是window,this永远指向window(在浏览器中,Node中不是)。

EC(global) = {
VO: window,
ScopeChain: {},
this: window
}

执行阶段

在执行阶段变量对象(Variable Object)变为活动对象(Active Object)。
VO => AO

这样,如果再面试的时候被问到变量对象和活动对象有什么区别,就又可以自如的应答了,他们其实都是同一个对象,只是处于执行上下文的不同生命周期。不过只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变成活动对象。

执行阶段JS引擎会进行变量赋值、函数引用、执行其他代码,执行顺序取决于代码的位置。

我们就聊到这吧。

喝杯茶,

休息一下。

 
 

 prettyEcho added JS interesting labels 9 days ago

 

Pomelo1213 commented 3 hours ago

function foo() {
console.log(a);
console.log(baz); var a = 'inner';
var baz = 1; function baz() {}
} foo();

这个地方baz到底是怎样的?

VO(foo) = {
arguements: {},
a = undefined,
baz = reference Function,
baz = undefind,
}

这里baz不应该是undefined吗?相同名字的变量和函数,函数会声明前置,这个地方是覆盖baz吗?博主,能解下疑惑吗?

JavaScript内部是这样运行的更多相关文章

  1. JavaScript内部原理实践——真的懂JavaScript吗?(转)

    通过翻译了Dmitry A.Soshnikov的关于ECMAScript-262-3 JavaScript内部原理的文章, 从理论角度对JavaScript中部分特性的内部工作机制有了一定的了解. 但 ...

  2. JavaScript 对引擎、运行时、调用堆栈的概述理解

    JavaScript 对引擎.运行时.调用堆栈的概述理解  随着JavaScript越来越流行,越来越多的团队广泛的把JavaScript应用到前端.后台.hybrid 应用.嵌入式等等领域. 这篇文 ...

  3. JavaScript闭包的底层运行机制

    转自:http://blog.leapoahead.com/2015/09/15/js-closure/ 我研究JavaScript闭包(closure)已经有一段时间了.我之前只是学会了如何使用它们 ...

  4. 解读JavaScript 之引擎、运行时和堆栈调用

    转载自开源中国 译者:Tocy, 凉凉_, 亚林瓜子, 离诌 原文链接 英文原文:How JavaScript works: an overview of the engine, the runtim ...

  5. 解读 JavaScript 之引擎、运行时和堆栈调用

    https://www.oschina.net/translate/how-does-javascript-actually-work-part-1 随着 JavaScript 变得越来越流行,很多团 ...

  6. JavaScript忍者秘籍——运行时代码求值

    1. 代码求值机制 JavaScript中,有很多不同的代码求值机制. ● eval()函数 ● 函数构造器 ● 定时器 ● <script>元素 - 用eval()方法进行求值 作为定义 ...

  7. JavaScript内部原理系列-变量对象(Variable object)

    概要 我们总是会在程序中定义一些函数和变量,之后会使用这些函数和变量来构建我们的系统.然而,对于解释器来说,它又是如何以及从哪里找到这些数据的(函数,变量)?当引用一个对象的时候,在解释器内部又发生了 ...

  8. 如何用JavaScript判断前端应用运行环境(移动平台还是桌面环境)

    我们部署在某些云平台或者Web服务器上的前端应用,既可以用PC端浏览器访问,也可以用手机上的浏览器访问. 在前端应用里,有时候我们需要根据运行环境的不同做出对应处理.比如下面这段逻辑,如果判断出应用当 ...

  9. Javascript中计算脚本运行的时间

    console.time("timer名字") 其他脚本 console.timeEnd("timer名字"); 运行后结果为: timer名字: 运行时间

随机推荐

  1. django+xadmin在线教育平台(三)

    通过留言版功能回顾django基础知识 将对于django目录结构,使用Django快速搭建可以提交的表单页面,models.py , urls.py, views.py. 从数据库中取出数据展示到h ...

  2. Scrapy分布式爬虫打造搜索引擎- (二)伯乐在线爬取所有文章

    二.伯乐在线爬取所有文章 1. 初始化文件目录 基础环境 python 3.6.5 JetBrains PyCharm 2018.1 mysql+navicat 为了便于日后的部署:我们开发使用了虚拟 ...

  3. DevOps - 日志分析 -ELK

    wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-co ...

  4. oracle一些常用的数据类型

    字符数据类型 char数据类型 当需要固定长度时,使用char数据类型,此数据类型长度可以使1-2000字节.若是不指定大小默认占1字节,如果长度有空余时会以空格进行填充,如果大于设定长度 数据库则会 ...

  5. 多页应用 Webpack4 配置优化与踩坑记录

    前言 最近新起了一个多页项目,之前都未使用 webpack4 ,于是准备上手实践一下.这篇文章主要就是一些配置介绍,对于正准备使用 webpack4 的同学,可以做一些参考. webpack4 相比之 ...

  6. Ubuntu设置代理上网

    代理服务器(Proxy Server)是个人网络和Internet服务商之间的中间代理机构,它负责转发合法的网络信息,对转发进行控制和登记.代理服务器作为连接Internet(广域网)与Intrane ...

  7. Scrapy-redis分布式爬虫爬取豆瓣电影详情页

    平时爬虫一般都使用Scrapy框架,通常都是在一台机器上跑,爬取速度也不能达到预期效果,数据量小,而且很容易就会被封禁IP或者账号,这时候可以使用代理IP或者登录方式爬,然而代理IP很多时候都很鸡肋, ...

  8. 时间转换,django的时间设置,re模块简单校验密码和手机号

    时间转换和密码,手机的re模块简单校验 import re,time def check_userinfo(request): pwd = request.POST.get("pwd&quo ...

  9. 1022 D进制的A+B (20)(20 分)

    1022 D进制的A+B (20)(20 分) 输入两个非负10进制整数A和B(<=\(2^{30}-1\)),输出A+B的D (1 < D <= 10)进制数. 输入格式: 输入在 ...

  10. 快速排序算法(C)

    sort快排函数的基本版,效率n*logn,快排的完全版就是在递归之中夹杂对序列的预判断,最优的选择排序方法,快速排序算法只是其中之一. 简单的说明一下快速排序的思想,对于一个数列,首先选择一个基数( ...