【JavaScript高级】JavaScript的运行原理:V8引擎,JS代码执行原理,作用域和作用域链面试题

V8引擎

浏览器内核是由两部分组成的,以webkit为例:

  • WebCore:负责HTML解析、布局、渲染等等相关的工作
  • JavaScriptCore:解析、执行JavaScript代码。

有一个强大的JavaScript引擎就是V8引擎。

架构

Parse模块会将JavaScript代码转换成AST(抽象语法树),因为解释器并不直接认识JavaScript代码。如果函数没有被调用,是不会被转成AST的。

Ignition是一个解释器,会将AST转换成ByteCode(字节码)。同时会收集TurboFan优化所需要的信息(如函数参数的类型信息)。如果函数只调用一次,Ignition会解释执行ByteCode;

TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码

  • 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能。
  • 机器码也会被还原为ByteCode,因为如果后续执行函数的过程中,类型发生了变化,之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码

如:

function sum(num1,num2){
	return num1+num2
}

若多次调用sum(10,20),则它会被标记为热点函数,且优化为机器码。这里的数据类型都是Number,执行的操作是加法。
但这里也可以传入两个字符串,执行的操作是字符串拼接,若如此,之前优化的机器码就要逆向地转换成字节码。

在这里插入图片描述

JavaScript代码执行原理

初始化全局对象

js引擎会在执行代码之前,会在内存中创建一个全局对象Global Object(GO)

  • 该对象 所有的作用域(scope)都可以访问
  • 里面会包含Date、Array、String、Number、setTimeout、setInterval等
  • 有一个window属性指向自己

执行上下文 Execution Contexts

js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈。执行的是全局的代码块:

  • 全局的代码块为了执行会构建一个全局执行上下文 Global Execution Context(GEC)
  • GEC会 被放入到ECS中 执行

GEC被放入到ECS中里面包含两部分内容:

  • 代码执行前,在parser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值——这个过程也称之为变量的作用域提升(hoisting)
  • 在代码执行中,对变量赋值,或者执行其他的函数

VO对象

每一个执行上下文会关联一个VO(Variable Object,变量对象),变量和函数声明会被添加到这个VO对象中

举个例子:

var message = "Global Message"

function foo() {
    message = "foo message"
}

var num1 = 30
var num2 = 20
var res = num1 + num2
console.log(res);

这段代码被parse(即转化为AST)时,会有一个VO对象。这段代码里的message、foo、num1、num2、res的声明都会被放到这个VO对象中(注意,函数是提前声明的),但是还没赋值。相当于:

VO:{
//创建函数对象,值(键值对的值)是指向函数对象的指针
	foo:0xa00,
	message:undefined,	
	num1:undefined,
	num2:undefined,
	res:undefined,
}

如果执行上下文是全局执行上下文的话,VO就是GO。

函数的执行

执行一个函数时,会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC),并且压入到EC Stack中。

这里的VO是AO(Activation Object),它会使用arguments作为初始化,并且初始值是传入的参数,AO会存放变量的初始化。

作用域和作用域链的面试题

看看这个:Js作用域与作用域链详解

面试题:

1

var n = 100
function foo() {
    n = 200
}
foo()
console.log(n);

答:

200

2

function foo() {
    console.log(n);
    n = 200
    console.log(n);
}
var n = 100
foo()

答:

100
200

3

var n = 100
function foo1() {
   console.log(n);
}
function foo2() {
   var n = 200
   console.log(n);
   foo1()
}

foo2()
console.log(n);

答:

200
100
100

解析:

  1. foo2里的n是200
  2. 调用foo1,foo1里没有n,所以去它的上层作用域找:它的上层作用域是全局——与调用的位置无关,与定义的位置有关,所以全局里的n是100
  3. 全局里的n是100

4

var a = 100
function foo() {
   console.log(a);
   return
   var a = 100
}

foo()

答:

undefined

解析:foo里有a!代码其实相当于:

var a = 100
function foo() {
   var a
   console.log(a);
   //return 后的不会执行
   //return
   //a = 100
}

foo()

所以输出是undefined。

5

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

解析:

  1. 访问不到a,因为a只在foo函数作用域里,在全局里没有
  2. 访问得到b,因为b没有用var声明,于是会被js解析成全局变量,b为100

参考

coderwhy的课
JavaScript代码到底是怎么执行的?
站在‘上帝 ’角度,透视v8 执行 js 的过程
Js作用域与作用域链详解

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

karshey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值