亮神知识库 亮神知识库
首页
  • 手写代码

    • 手写代码系列
  • 基础知识

    • 基础
    • JS底层
    • CSS
  • 原理
  • 浏览器
  • HTTP
  • 网络安全
  • babel
  • webpack基础
  • webpack进阶
  • Vite
  • TypeScript
  • Vue2
  • Vue3
  • Node基础

    • glob
    • 模块化机制
    • 事件循环
    • KOA2框架原理
    • Node子进程
    • cluster原理(了解)
  • 教育行业2021

    • B端
    • C端
    • 工具
  • 游戏行业2025
  • 刷题
  • 杂(待整理)
  • 学习
  • 面试
  • 实用技巧
  • 心情杂货
  • 年度总结
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
  • 收藏
GitHub (opens new window)

亮神

前程明亮,未来可期
首页
  • 手写代码

    • 手写代码系列
  • 基础知识

    • 基础
    • JS底层
    • CSS
  • 原理
  • 浏览器
  • HTTP
  • 网络安全
  • babel
  • webpack基础
  • webpack进阶
  • Vite
  • TypeScript
  • Vue2
  • Vue3
  • Node基础

    • glob
    • 模块化机制
    • 事件循环
    • KOA2框架原理
    • Node子进程
    • cluster原理(了解)
  • 教育行业2021

    • B端
    • C端
    • 工具
  • 游戏行业2025
  • 刷题
  • 杂(待整理)
  • 学习
  • 面试
  • 实用技巧
  • 心情杂货
  • 年度总结
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
  • 收藏
GitHub (opens new window)
  • 基础

  • 手写代码

  • JS底层深入

    • JS底层
      • 变量提升
      • 执行上下文与变量环境
      • 考一考
      • 为什么 JS 代码会出现栈溢出?
      • var缺陷以及如何解决?
      • 块级作用域与词法环境
      • 作用域链与词法作用域
      • 闭包【重要】
      • this的设计缺陷及应对方案
      • 考一考
      • 垃圾数据是如何自动回收的
      • 描述一下 V8 执行一段JS代码的过程
        • v8如何执行一段代码
      • 考一考
  • CSS

  • 基础
  • JS底层深入
0zcl
2021-11-09
目录

JS底层

# 变量提升

JS 引擎把变量的声明部分和函数声明部分 提升到代码开头的行为。变量被提升后,会给变量设置默认值: undefined

实际上变量和函数声明在代码里的位置是不会改变的,而且是在编译阶段被 JS 引擎放入内存中

showName()
console.log(myname)
var myname = '极客时间'
function showName() {
    console.log('函数showName被执行');
}
// 测试
// 函数showName被执行
// undefined
1
2
3
4
5
6
7
8
9

hoisting

# 执行上下文与变量环境

输入一段代码,经过编译,生成两部分

  1. 执行上下文。变量和函数会被存放到变量环境中
  2. 可执行代码

# 考一考

注意

执行下面代码,打印出啥?

showName()
var showName = function() {
    console.log(2)
}
showName()
function showName() {
    console.log(1)
}
1
2
3
4
5
6
7
8

答:

编译过程:
var showName = undefined
function showName() {
  console.log(1)
}
执行阶段:
showName() // 输出1
showName = function() {
  console.log(2)
}
showName() // 输出2
// 如果后面再有showName执行的话,就输出2因为这时候函数引用已经变了
1
2
3
4
5
6
7
8
9
10
11
12

变量提升和函数提升的优先级,函数提升高于变量提升,当存在同名时,在变量赋值之前函数声明还是函数声明,不会被覆盖,当变量赋值之后,函数声明将被变量覆盖。JavaScript变量提升和函数提升 (opens new window)

# 为什么 JS 代码会出现栈溢出?

每调用一个函数,JS引擎为其创建执行上下文,并压入栈中,而调用栈是有大小的,当栈的执行上下文超过一定数量时,就会出现栈溢出。

# var缺陷以及如何解决?

var缺陷:

  1. 变量提升
  2. 污染全局变量
function foo(){
  for (var i = 0; i < 7; i++) {
  }
  console.log(i); 
}
foo() // 7
1
2
3
4
5
6

解决var缺陷: ES6引入 let 和 const 关键字, 使其拥有块级作用域

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // 不同的变量
    console.log(x);  // 2
  }
  console.log(x);  // 1
}
1
2
3
4
5
6
7
8

# 块级作用域与词法环境

在编译阶段,通过 var 声明的变量放到了变量环境中,通过 let、const 声明的变量放到了词法环境中

function foo(){
  var a = 1
  let b = 2
  {
    let b = 3
    var c = 4
    let d = 5
    console.log(a) // 1
    console.log(b) // 3
  }
  console.log(b) // 2
  console.log(c) // 4
  console.log(d) // 出错
}   
foo()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. 词法环境中也维护了一个栈结构,栈底是函数最外层的变量
  2. 沿着词法环境的栈顶向下查询,如果没找到,在变量环境中查找
  3. 当作用域执行结束后,该作用域的信息就会从栈顶弹出

# 作用域链与词法作用域

function bar() {
  console.log(myName)
}
function foo() {
  var myName = "极客邦"
  bar()
}
var myName = "极客时间"
foo() // 极客时间
1
2
3
4
5
6
7
8
9

在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为outer

当使用了一个变量时,js 引擎首先在当前上下文中查找,找不到就继续在outer指向的上下文中查找。这个查找的链条就作用域链

outer

问:foo 函数调用的 bar 函数,那为什么 bar 函数的外部引用是全局执行上下文,而不是 foo 函数的执行上下文?

答:JS的作用域链是由词法作用域决定的,词法作用域又由代码中函数声明的位置来决定的。词法作用域是代码编译阶段就决定好的,和函数怎么调用没有关系。

# 闭包【重要】

定义:在JS中,根据词法作用域的规则,内部函数总是可以访问其外部函数声明的变量。当通过调用一个外部函数返回一个内部函数后,即使外部函数已经执行结束了,但由于存在内部函数对外部函数变量的引用,因此这些变量依然保存在内存中,变量的集合就称为闭包

closure

闭包如何回收?

  • 引用闭包的函数是一个全局变量:闭包会一直存在直到页面关闭。
  • 引用闭包的函数是一个局部变量:函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存

提示

  1. 闭包不会造成内存泄漏。闭包可能会常驻在内存中。程序写错了才会造成内存泄漏
  2. 如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量

# this的设计缺陷及应对方案

执行上下文中包含了变量环境、词法环境、外部环境和this

  1. 嵌套函数中的 this 不会从外层函数中继承
var myObj = {
  name : "极客时间", 
  showThis: function(){
    console.log(this) // {name: "极客时间", showThis: ƒ}
    function bar(){console.log(this)} // window
    bar()
  }
}
myObj.showThis()
1
2
3
4
5
6
7
8
9
  • self=this
  • 箭头函数: 箭头函数没有自己的执行上下文,所以它会继承调用函数中的 this
  1. 普通函数中的 this 默认指向全局对象 window
  • 在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined

# 考一考

let userInfo = {
  name:"jack.ma",
  age:13,
  sex:male,
  updateInfo:function(){
    setTimeout(function(){
      this.name = "pony.ma"
      this.age = 39
      this.sex = female
    },100)
  }
}
userInfo.updateInfo()
1
2
3
4
5
6
7
8
9
10
11
12
13

问:通过 updateInfo 来更新 userInfo 里面的数据信息,但是这段代码存在一些问题,你能修复这段代码吗?

答:

let userInfo = {
  name:"jack.ma",
  age:13,
  sex:'male',
  updateInfo:function(){
    setTimeout(() => {
      this.name = "pony.ma"
      this.age = 39
      this.sex = 'female'
    },100)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 垃圾数据是如何自动回收的

function foo(){
  var a = 1
  var b = {name:"极客邦"}
  function showName(){
    var c = 2
    var d = {name:"极客时间"}
  }
  showName()
}
foo()
1
2
3
4
5
6
7
8
9
10
  1. 调用栈中的数据如何被销毁

答:当一个函数执行结束后,js引擎会通过向下移动ESP指针来销毁该函数保存在栈中的执行上下文

esp

  1. 堆中的数据如何被销毁 回收堆的垃圾数据,需要用到 JS 的垃圾回收器

代际假说

  • 大部分对象在内存中存活时间很短
  • 不死的对象会活的更久

因此,V8 把堆分为 新生代 和 老生代。

  1. 新生代中存放的是生存时间短的对象。新生区通常只支持 1~8M 的容量。新生代使用副垃圾回收器
  2. 老生代中存放的生存时间久的对象。老生区支持的容量就大很多了。老生代使用主垃圾回收器

垃圾回收处理逻辑

  1. 新生代:把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域; 新加入的对象都会存放到对象区域,当对象区域快被写满时,回收非存活的对象,再把存活的对象有序地放入空闲区域,对象区域与空闲区域进行角色翻转。经过两次垃圾回收依然还存活的对象,会被移动到老生区中。
  1. 老生代:
  • 标记 - 清除:回收不能到达的对象
  • 标记 - 整理:让所有存活的对象都向一端移动,不产生大量不连续的内存碎片

# 描述一下 V8 执行一段JS代码的过程

编译型语言和解释型语言

  1. 编译型语言: 在程序执行之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译了。比如 C/C++、GO 等都是编译型语言。
  2. 解释型语言: 在每次运行时都需要通过解释器对程序进行动态解释和执行。比如 Python、JAVA、JavaScript 等都属于解释型语言。

# v8如何执行一段代码

v8

  1. 生成AST抽象语法树和执行上下文。通过词法分析 -> 语法分析
  2. 生成字节码。字节码就是介于 AST 和机器码之间的一种代码;字节码需要通过解释器将其转换为机器码后才能执行。

code

机器码所占用的空间远远超过了字节码,所以使用字节码可以减少系统的内存使用。

  1. 执行代码。在执行字节码的过程中,如果发现有热点代码(HotSpot),比如一段代码被重复执行多次,这种就称为热点代码。把热点的字节码转换为机器码,并把转换后的机器码保存起来,以备下次使用

v8

总结:V8 依据 JavaScript 代码生成 AST 和执行上下文,再基于 AST 生成字节码,然后通过解释器执行字节码,通过编译器来优化编译字节码。

# 考一考

问:如何理解 V8 执行时间越久,执行效率越高

答:因为更多的代码成为热点代码之后,转为了机器码来执行

编辑 (opens new window)
上次更新: 2025/07/20, 08:30:18
手写Promise
盒子模型

← 手写Promise 盒子模型→

最近更新
01
2024年
07-20
02
2023年
07-20
03
2022年
07-20
更多文章>
Theme by Vdoing | Copyright © 2025-2025 亮神 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式