引言
JavaScript 的变量存储与执行机制,是理解这门语言的关键。本文将从数据类型划分入手,详解基本类型与引用类型的存储差异,并结合 V8 引擎的执行过程,带你读懂 JS 代码运行的底层逻辑。
一、JavaScript 的数据类型:两类核心划分
JavaScript 里的所有数据,都被分为两大类:基本类型(原始类型) 和 引用类型(复杂类型) ,它们的存储方式和特性完全不同。
二、什么是基本类型?
基本类型也叫原始类型,是 JavaScript 里最基础、最简单的数据形式,它们的特点是:
- 数据本身是不可拆分的原子值(不能再拆成更小的数据单元)
- 直接存储在栈内存中,存的是值本身。
JavaScript 中的 7 种基本类型
1.string 字符串
-
作用:用来表示文本内容
-
特点:被单引号、双引号或反引号包裹
-
例子:
let str = 'hello world' //单引号
let str1 = ''hello world'' //双引号
let str2 = `hello world` //反引号
let str3 = str + 'js'//第一种拼接方式
console.log(str3) //输出结果:hello worldjs
let str4 = `${str} js` //另一种拼接的方式,注意这里的引号是反引号,
console.log(str4) //输出结果:hello world js
注意str3和str4的输出结果不一样,空格也是一个字符,字符串里面的每个元素都叫一个字符。
2.number 数字
-
作用:表示所有数值,包括整数、小数、正负、科学计数法
-
特点:不区分整数和浮点数,统一用 64 位浮点数存储
-
例子:
let num = 123 //number类型
let num2 = num + 0.1 //
console.log(num2) //输出结果:123.1
console.log(num2.toString()); //number 类型转换为 string 类型。
console.log(num2 + '5'); //任何+字符串都变成字符串,输出结果为:123.14
>如何分别数字和字符串:数字呈现黄色,字符串呈现黑色
```js
let n = 1
let m = '2m'
console.log(Number(m)); //字符串转为数字 Number类型
console.log(1 + Number(m)); //运行显示NaN数字类型
//NaN意思是not a number,非数字,但 NaN 这个输出值本身是一个 number 类型值。
console.log(9007199254740992 + 1); //number 类型有一个上限,就是 2**53 ,超过这个值就不能增加了。
3.boolean 布尔值
-
作用:表示逻辑上的 “真” 或 “假”,只有两个值
-
特点:只有
true和false两个值,常用来做条件判断 -
例子:
if (ture) { // boolean 布尔类型
console.log('小杰'); //判断成功,输出结果:小杰
}
if (false) {
console.log('小杰'); //判断失败,不输出结果
}
4.undefined(未定义)
-
作用:表示变量声明了,但没有赋值的状态
-
特点:不是主动赋值的,是 JS 自动给的默认值
-
例子:
let a; // 只声明,没赋值
console.log(a); // 输出结果:undefined
let u = undefined + ' 小杰'
console.log(u) //输出结果:undefined 小杰
5.null 空值
-
作用:表示 “这里什么都没有”,是开发者主动给变量赋的空值
-
特点:表示变量的指向为空,常用来清空对象
-
例子:
let n = null
💡 小补充:
undefined是 JS 给的默认状态,null是开发者主动设置的空值。
6.bigint 大整数
-
作用:用来表示超过
number安全范围的超大整数 -
特点:数字末尾加
n,解决number精度丢失问题 -
例子:
let b = 123123123n // bigInt 类型
let c = 9007199254740992n /末尾带n
console.log(b + c); //输出结果:9007199377864115n
7.Symbol 符号
-
作用:创建一个独一无二的值,用来避免对象属性名冲突
-
特点:每次调用
Symbol()生成的值都是唯一的,即使描述相同也不相等 -
例子:
let s = Symbol(1)
let p = Symbol(1)
console.log(s == p); //输出结果:false
💡 小补充:等号=的用法:
“=”一个等号为赋值号,把右边的值,存到左边的变量里,不是 “等于”,是存进去。
“==” 两个等号为相等(不严格),只比较值是否一样,不看类型。值一样就行,类型不一样也会当成一样。
“===” 三个等号为严格相等(最常用、最安全),值 和 类型 必须完全一样
三、什么是引用类型?
引用类型也叫复杂类型,和基本类型完全相反:它不是一个简单的值,而是由多个值组合而成的复杂数据结构。
你可以这么理解:
- 基本类型 = 一个单独的东西(一个数字、一个名字)
- 引用类型 = 一整套东西(一个人、一组列表、一个功能)
它最核心的特点有 3 个:
1.不直接存在栈里,而是存在堆内存中
2. 变量存的不是值,而是内存地址(引用)
3.赋值时不是复制内容,而是复制地址,所以会互相影响
最常见的 4 种引用类型
1.Array 数组
用来存放一组有序的数据,用 [] 表示。
例子:
var arr=['a','b',1,2]
arr.push(3) //向数组尾部添加一个值3
arr.pop() //移除数组尾部的一个值
arr.unshift(0) //向数组头部添加一个值0
arr.shift() //向数组头部删掉一个值
arr.splice(1,2) //从下标1开始,删除2个
arr.splice(2) //移除下标2之后的所有值
arr.splice(2,0,true) //从下标2开始,移除0个值即不移除,在这个位置加入ture
arr [2] = 10 // 将下标 2 的变量改为 10
2.Object 对象
用来描述一个事物的各种特征,用 {} 表示。
例子:
var obj = {
name: 'hisoka',
age: 20,
like: {
one: 'eat',
two: {
sports: ['魔术', '战斗']
}
}
}
obj.girlFriend = '小杰' //添加一个对象
delete obj.girlFriend //删除一个对象
3.Function 函数
可以被执行的代码块,用来实现某个功能。
例子:
var fn = function() {}
4.Date 时间日期
Date 是 JavaScript 里专门用来处理时间和日期的引用类型。只要你想获取当前时间、年、月、日、时、分、秒,都要用 Date。
四、V8引擎是怎么执行 JavaScript 的?
我们写的 JS 代码,浏览器里的 V8 引擎会帮我们执行,它的执行过程和内存设计,藏着很多巧思,核心流程分三步:
-
创建调用栈(用来执行预编译的过程)
调用栈就像一个 “先进后出” 的工作台,专门用来存放代码执行的上下文(比如当前执行到哪一行、变量的值),也是所有基本类型数据的 “直接存放地”。 -
在调用栈中,存入 xx 执行上下文
每一段代码(比如全局代码、函数)开始执行时,V8 都会在调用栈中放入对应的执行上下文,让代码有独立的运行空间。 -
数据存储:栈存地址,堆存真实数据,效率拉满
执行代码,将原始值存入栈中:
-
遇到基本类型数据时,直接把值本身存在调用栈里,读取和修改都很快。
-
遇到引用类型数据时,不会把整个复杂数据直接塞进栈里,而是将值存入堆空间并生成一个引用地址,将引用地址存入栈中
这种设计的好处非常明显:
- 栈不需要设计得很大,保证了 V8 执行速度更快、效率更高;
- 复杂数据都放在堆里,不会占用栈空间,有效避免栈溢出。
💡 举个生活中的例子帮你理解
你可以把调用栈理解成 “你的口袋”,堆空间理解成 “你的储物柜”:
- 基本类型就像硬币,直接装在口袋里,随时拿随时用;
- 引用类型就像一个大行李箱,口袋装不下,只能放在储物柜里,口袋里只装储物柜的钥匙(也就是内存地址)。
- 这样既不用把口袋做得巨大(影响携带效率),也能装下大行李箱,完美解决了空间和效率的矛盾。