JavaScript数据类型与V8内存运行机制

1 阅读6分钟

我们写JS代码时天天和各种数据打交道,可很少有人知道这些数据藏在哪、小 v8 又是怎么有条不紊跑起代码的。接下来我们一起弄懂数据分类搭配V8底层执行逻辑,真正吃透JS基础内核。

一、V8引擎完整执行流程

在讲数据类型之前,我们先来搞明白

V8作为JS专属运行引擎,做事条理十足,靠着一套固定流程推进代码运行,全程井然有序。

  1. 创建调用栈 代码开始运行的第一件事,就是搭建好调用栈,它就像一个有序收纳架,专门用来收纳管理各类预编译流程与运行环境。
  2. 压入执行上下文 根据代码运行状态,往调用栈里放入对应的执行上下文。运行全局代码就放入全局上下文,调用函数时就压入函数专属上下文,前期所有预编译准备工作,全都依托上下文完成。
  3. 逐行执行分配内存 正式运行代码时,引擎自动智能区分数据

我们来举一个例子

function foo(){
    function fn(){}
    fn()
}
foo()

上面这段代码的调用栈如图。

屏幕截图 2026-05-23 160529.png

运行全局代码后,先创建全局执行上下文。

我们在全局进行了foo函数的调用foo(),于是 v8 接着创建了 foo 函数执行上下文。 foo 函数中又进行了fn函数的调用fn(),所以接着创建 fn 函数执行上下文。

执行完毕后,从上到下进行销毁。

栈就像你牛仔裤的裤子口袋,从下往上堆东西,从上往下取东西,想要取到下面的东西,就必须把上面的东西先拿出来。

在这里,我们先省略内存的分配,在认识 JS 两大数据类型后,我们会对内存的分配有更深刻的理解。

二、JS两大数据类型

1. 基本类型

这类数据体量小巧玲珑,结构简单没有多余内容,就像随身带的小物件,轻便不占地方,直接存放于栈内存,存取速度飞快。

var声明的变量存放在变量环境中,letconst声明的变量存放在词法环境中。

一共包含七种:

  • 字符串 string

使用单引号'...' 用于存放字符

let str = 'hello world' // string 类型
let str2 = str + ' js'
//let str2 =`${str} js`  在字符串后面加字符的另一种方法

我们可以使用以下这些方式来读取字符

// 读取
console.log(str2);
console.log(str[0])
console.log(str.at(0))
console.log(str.slice(0,5)) // 左闭右开,截取字符串
  • 数字 number

JavaScript 中不区分intfloat等类型,整数和小数统一使用number

let num = 123 
let num2 = num + 0.1  

如果用number加字符,number会被 v8 偷偷的转化成 String类型

console.log(num2 + '5') // 输出123.15

这也太不公平了,为什么要把数字类型转成字符串类型呢,我们能不能把字符串类型转成数字类型呢?

答案也是可以的,对应将数字类型转成字符串类型的num2.toString(),我们可以用Number()将字符串类型转成数字类型

let m = '2'
console.log( 1 + Number(m)) // 字符串转换成数字

不过前提是被转换的字符串当中是一个数字,不然就会输出NaNNaN也被认为是一个数字

let p = '2p'
console.log( 1 + Number(p)) // NaN not a number 
  • 大整数 bigInt

number 类型 2^53 是最大安全值,为了存放大于安全值的数字,我们引入了大整数 bigInt,在整数后面加n,两个大整数相加也是大整数

let a = 4637647393478734563223233455197n
  • 布尔值 boolean

truefalse,放在if语句括号里面的值会被 v8 悄咪咪的转化成 boolean 类型,在所有数字里面,只有0NaN放在if中被认为是false

  • 唯一标识 Symbol

Symbol是全世界独一无二的值,用来做唯一标识,不能与其它类型进行运算

let s1 = Symbol('a')
let s2 = Symbol('a')
console.log(s1 == s2); // false
console.log(s1 === s2); // false
  • 未定义 undefined

表示 应该有值,但还没给 / 不存在

  • 空值 null

代表对象引用为空、无实际对象,可以用来清空对象,或者使用空数据占位

值得一提的是

null == undefined // true 松散相等 
null === undefined // false 严格不等

在这里,==是相等,===是全等,指的是在值相等之外,类型也要相等

2. 引用类型

这类数据体量可大可小,内部还能层层嵌套,如同体积庞大的大件物品,没办法直接塞进狭小的栈空间。

那么 v8 会怎么办呢?

引擎会把真实数据本体存放到宽敞的堆内存中,再生成一串简短的内存地址,最后只把这串地址放进栈内存里。

我们操作数据时,都是通过栈内地址,间接找到堆里的真实内容。

接下来我们来看一个综合一点的例子,便于理解 V8 引擎完整执行流程,和内存的分配:

var a=1
let b='hello'
var obj={
    name:'萧',
    age:18,
    like :{
        one:'eat',
        two:{
            sport:['篮球','跑步']
        }
    }
}
console.log(obj);

上面代码的调用栈如图:

屏幕截图 2026-05-23 165715.png

执行代码,v8 将原始类型的值存入中,遇到引用类型的值,则将值存入堆空间并生成一个引用地址,将引用地址存入栈中

常见引用类型有:

  • 函数Function
  • 日期对象Date
  • 普通对象Object
  • 数组Array

另外补充一些数组的增删改查

var arr =['a','b',1,2]
arr.push(3) // 在数组尾部添加元素
arr.pop() // 移除数组尾部元素
arr.unshift(0) // 在数组头部添加元素
arr.shift() // 移除数组头部元素
arr.splice(1,1) // 在中间移除元素,第一个参数是下标,第二个参数是移除个数 
arr.splice(2,0,2) // 在中间插入元素,第一个参数是下标,第二个参数是移除个数 ,第三个参数是要插入的元素
a[2]=10 // 更改元素值

三、栈堆分离存储的实用好处

V8采用这种内存分配方式,设计十分巧妙,优势格外明显。

  • 既不用刻意扩容栈内存,保持栈体小巧轻便,稳稳拉高引擎整体运行效率;
  • 又能把大容量复杂数据安置在堆内存,从根源上避免数据过多挤占空间引发爆栈问题;
  • 快慢内存相互配合,兼顾了基础数据的读取速度,和复杂数据的存储灵活性,让代码运行流畅又稳定。

总结

简单来说,小巧基础数据住栈内存,庞大复杂数据本体住堆、地址留栈

V8依靠调用栈规整管理执行环境,完成预编译后正式运行代码。

一快一稳的内存搭配模式,让JavaScript兼顾运行速度与内存利用率,也是代码能够稳定高效运行的底层关键。