js运行机制及异步编程(一)
相信大家在面试的过程中经常遇到查看执行顺序的问题,如setTimeout,promise,async await等等,各种组合,是不是感觉头都要晕掉了,其实这些问题最终还是考察大家对js的运行机制是否掌握牢固,对promise,async的原理是否掌握,万变不离其宗,这次就来彻底搞懂它。
1 js引擎的运行原理
js引擎也是程序,是属于浏览器的一部分,由浏览器厂商自行开发。从头到尾负责整个JavaScript程序的编译及执行过程
浏览器在渲染的过程中,首先按顺序加载由<script>标签分割的js代码块,加载js代码块完毕后,需要js引擎进行解析。无论是外部脚本文件(不异步加载)还是内部脚本代码块,都是一样的原理,并且都在同一个全局作用域中。
JavaScript被归类为“动态”或“解释执行”语言,所以它无需提前编译,而是由解释器实时运行
js引擎执行过程分为三个阶段:
- JS的解释阶段
- JS的预处理(编译)阶段及执行阶段
1.1 JS的解释阶段
js脚本代码块加载完毕后,会首先JS的解释阶段。该阶段主要过程如下:
- 词法分析——这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)
- 语法分析——这个过程是将词法单元流(数组)转化成抽象语法树(Abstract Syntax Tree)
- 使用翻译器(translator),将代码转为字节码(bytecode)
- 使用字节码解释器(bytecode interpreter),将字节码转为机器码
最终计算机执行的就是机器码。
为了提高运行速度,现代浏览器一般采用即时编译(JIT-Just In Time compiler)
即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存(inline cache)
这样整个程序的运行速度能得到显著提升。
而且,不同浏览器策略可能还不同,有的浏览器就省略了字节码的翻译步骤,直接转为机器码(如chrome的v8)
1.2 JS的预处理(编译)阶段及执行阶段
这里我理解为js为解释型语言,由解释器实时运行,通俗的说就是预处理完之后马上执行,一边编译一边执行
1.2.1 js的执行环境主要有三种:
- 全局环境
- 函数环境
- eval(不建议使用,会有安全,性能问题)
1.2.2 以下段例子说明js的预编译与执行过程
function bar() {
var B_context = "Bar EC";
function foo() {
var f_context = "foo EC";
}
foo()
}
bar()
这段函数经过词法解析,语法解析阶段之后,就开始进入预编译并执行,如下:
- 首先,进入全局环境,就会先进行预处理,然创建全局上下文执行环境(Global ExecutionContext),会对var声明的变量和函数声明进行预处理,window对象就是全局执行上下文的变量对象,所有的变量和函数都是window对象的属性方法。所以函数声明提前和变量声明提升是在创建变量对象中进行的,且函数声明优先级高于变量声明。然后推入stack栈中。预处完成之后,开始执行js
- 当执行bar()时,就会进入bar函数运行环境,就会先进行预处理,创建bar函数执行上下文(bar Execution Context),推入stack栈中,预处理完后,开始执行foo()
- 在bar函数内部调用foo函数,则再进入foo函数运行环境,创建foo函数执行上下文(foo Execution Context),推入stack栈中
- 此刻栈底是全局执行上下文(Global Execution Context),栈顶是foo函数执行上下文(foo Execution Context),如上图,由于foo函数内部没有再调用其他函数,那么则开始出栈
- foo函数执行完毕后,栈顶foo函数执行上下文(foo Execution Context)首先出栈
- bar函数执行完毕,bar函数执行上下文(bar Execution Context)出栈
- Global Execution Context则在浏览器或者该标签页关闭时出栈。
1.2.3 执行上下文
分析一段简单的代码,帮助我们理解创建执行上下文的过程,如下:
function fun(a, b) {
var num = 1;
function test() {
console.log(num)
}
}
fun(2, 3)
这里我们在全局环境调用fun函数,创建fun执行上下文,这里为了方便大家理解,暂时不讲解作用域链以及this指向,如下:
funEC = {
//变量对象
VO: {
//arguments对象
arguments: {
a: undefined,
b: undefined,
length: 2
},
//test函数
test: <test reference>,
//num变量
num: undefined
},
//作用域链
scopeChain:[],
//this指向
this: window
}
- funEC表示fun函数的执行上下文(fun Execution Context简写为funEC)
- funE的变量对象中arguments属性,上面的写法仅为了方便大家理解,但是在浏览器中展示是以类数组的方式展示的
- <test reference>表示test函数在堆内存地址的引用
注:创建变量对象发生在预编译阶段,但尚未进入执行阶段,该变量对象都是不能访问的,因为此时的变量对象中的变量属性尚未赋值,值仍为undefined,只有进入执行阶段,变量对象中的变量属性进行赋值后,变量对象(Variable
Object)转为活动对象(Active Object)后,才能进行访问,这个过程就是VO –> AO过程。
建立作用域链
作用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
理清作用域链可以帮助我们理解js很多问题包括闭包问题等,下面我们结合一个简单的例子来理解作用域链,如下:
var num = 30;
function test() {
var a = 10;
function innerTest() {
var b = 20;
return a + b
}
innerTest()
}
test()
在上面的例子中,当执行到调用innerTest函数,进入innerTest函数环境。全局执行上下文和test函数执行上下文已进入执行阶段,innerTest函数执行上下文在预编译阶段创建变量对象,所以他们的活动对象和变量对象分别是AO(global),AO(test)和VO(innerTest),而innerTest的作用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,如下:
innerTestEC = {
//变量对象
VO: {b: undefined},
//作用域链
scopeChain: [VO(innerTest), AO(test), AO(global)],
//this指向
this: window
}
在这里我们顺便思考一下,什么是闭包?
我们先看下面一个简单例子,如下:
function foo() {
var num = 20;
function bar() {
var result = num + 20;
return result
}
bar()
}
foo()
我这里直接以浏览器解析,以浏览器理解的闭包为准来分析闭包,如下图:
如上图所示,chrome浏览器理解闭包是foo,那么按浏览器的标准是如何定义闭包的,我总结为三点:
- 在函数内部定义新函数
- 新函数访问外层函数的局部变量,即访问外层函数环境的活动对象属性
- 新函数执行,创建新的函数执行上下文,外层函数即为闭包
确定this指向
在全局环境下,全局执行上下文中变量对象的this属性指向为window;函数环境下的this指向却较为灵活,需根据执行环境和执行方法确定
来源:https://segmentfault.com/a/1190000017394625
js运行机制及异步编程(一)的更多相关文章
- 面试 09-02.js运行机制:异步和单线程
09-02.js运行机制:异步和单线程 #前言 面试时,关于同步和异步,可能会问以下问题: 同步和异步的区别是什么?分别举一个同步和异步的例子 一个关于 setTimeout 的笔试题 前端使用异步的 ...
- js 运行机制
<script> console.log(1) setTimeout(function(){ console.log(3) },0) console.log(2) </script& ...
- 如何通过setTimeout理解JS运行机制详解
setTimeout()函数:用来指定某个函数或某段代码在多少毫秒之后执行.它返回一个整数,表示定时器timer的编号,可以用来取消该定时器. 例子 ? 1 2 3 4 5 console.log(1 ...
- Js 运行机制 (重点!!)
一.引子 本文介绍JavaScript运行机制,这一部分比较抽象,我们先从一道面试题入手: 这一题看似很简单,但如果你不了解JavaScript运行机制,很容易就答错了.题目的答案是依次输出1 2 3 ...
- Js 运行机制 event loop
Js - 运行机制 (Even Loop) Javascript 的单线程 - 引用思否的说法: JavaScript的一个语言特性(也是这门语言的核心)就是单线程.什么是单线程呢?简单地说就是同一时 ...
- js 运行机制简单了解
一.如何理解 JS 的单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScrip ...
- 从浏览器多进程到JS单线程,JS运行机制的一次系统梳理
前言 见解有限,如有描述不当之处,请帮忙及时指出,如有错误,会及时修正. ----------超长文+多图预警,需要花费不少时间.---------- 如果看完本文后,还对进程线程傻傻分不清,不清楚浏 ...
- 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
前言 来源:https://dailc.github.io/2018/01/21/js_singlethread_eventloop.html 见解有限,如有描述不当之处,请帮忙及时指出,如有错误,会 ...
- js异步梳理:1.从浏览器的多进程到JS的单线程,理解JS运行机制
大家很早就知道JS是一门单线程的语言.但是也时不时的会看到进程这个词.首先简单区分下线程和进程的概念 1. 简单理解进程 - 进程是一个工厂,工厂有它的独立资源 - 工厂之间相互独立 - 线程是工厂中 ...
随机推荐
- 常州模拟赛d4t1 立方体
题目描述 立方体有 6 个面,每个面上有一只奶牛,每只奶牛都有一些干草.为了训练奶牛的合作精神,它 们在玩一个游戏,每轮:所有奶牛将自己的干草分成 4 等份,分给相邻的 4 个面上的奶牛. 游戏开始, ...
- 【2018.10.1】【JSOI2016】最佳团体(bzoj4753)
一看到“比值”最大(性价比最高)就知道跟分数规划有关系了.(这里讲过分数规划) 然后看到 要选一个候选人 必须选他的前置,画画图就知道是一棵树. 所以这道题是二分比值,每个点的权值就是战斗力-费用*比 ...
- VS的一些错误解决方法记录
1.errorC2664 "bool CMarkup::AddElem(MCD_CSTR,MCD_CSTR,int)":不能将参数1从“constchar [7]” 转换位&quo ...
- Linux(13):期中架构(5)--- 前端部分:keepalived高可用 & HTTPS & iptables防火墙
keepalived 高可用集群 1. keepalived服务概念说明 # 1.1 keepalived软件的作用? Keepalived软件起初是专为LVS负载均衡软件设计的, 用来管理并监控LV ...
- J2ME开发入门
原文发布时间为:2008-07-31 -- 来源于本人的百度文章 [由搬家工具导入] J2ME开发入门J2ME方面开发的资料,确实是少之又少,一般给新手推荐的都是王森先生的《PDA与手机开发入门》一书 ...
- Codeforces Round #291 (Div. 2) D. R2D2 and Droid Army [线段树+线性扫一遍]
传送门 D. R2D2 and Droid Army time limit per test 2 seconds memory limit per test 256 megabytes input s ...
- C++练习,计算间隔天数
time_t time(time_t *time) 从1970年1月1日到到现在的秒数,如果系统时间不存在,返回1char *ctime(const time_t *time) 返回以:day mon ...
- 531. Lonely Pixel I
Given a picture consisting of black and white pixels, find the number of black lonely pixels. The pi ...
- 二叉树的层次遍历 II
给定一个二叉树,返回其节点值自底向上的层次遍历. (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 例如:给定二叉树 [3,9,20,null,null,15,7], 3 / \ 9 20 ...
- SpringMVC拦截器详解[附带源码分析](转)
本文转自http://www.cnblogs.com/fangjian0423/p/springMVC-interceptor.html 感谢作者 目录 前言 重要接口及类介绍 源码分析 拦截器的配置 ...