一、引言
JavaScript 这门动态弱类型语言,它的类型系统看似简单,却有许多易忽略的细节。同时,理解 V8 引擎如何执行代码、如何在内存中用堆与栈存储不同类型的数据,便可以让我们能轻松应对内存管理等常见问题。
云宝将本文的主体分为两部分:先梳理 JavaScript 中的两大类型的阵营,再深入 V8 引擎的执行过程与栈堆内存如何在底层调用。
二、JavaScript 中的类型
JavaScript 的类型分为两大类:基本类型(原始类型) 和 引用类型(复杂类型) 。
1、基本类型(原始类型)
1. string
首先我们来声明一个简单的字符串的变量str:let str = 'hello'
这时如果我们想把这个字符串变为“hello javascript”便有了以下两种方法,都可以用console.log(str2)输出hello js
- let str2 = str + ' js'
- let str2 = '${str} js'
下面是几个字符串有关的不同的输出表达式来满足对应的输出需求
- console.log(str)-----------------直接输出str字符串
- console.log(str[0])--------------输出第一个字符(即h)
- console.log(str.at(0))-----------输出第一个字符(即h)
- console.log(str.slice(0,7))------输出前7个字符,遵循左闭右开规则,此处输出第0123456个字符(即hello w)
2. number//含NaN
一样的我们先声明并赋值一个简单的numberlet num = 123;
基于此我们便可以进行简单的数学运算:
- let num2 = num + 0.1 //此处注意在js里0.1是number类型,无浮点类
此外依然有一些零散知识碎片:
console.log(num.toString())--将number类转string类
console.log(num + '5')--------此时v8会悄悄将num转为string类型,因此结果为’1235‘,而不是进行数学运算相加
console.log(Number(m))------字符串转数字(注意Number里N大写)
let n = 1; let m = 'm';
此时console.log(Number(n+m))输出NaN,因为此时不能转化为实际数字,只能转化为特殊的number类型NaN
- 此外在计算2的53次方大的数字时会失真
另外if( )里只有0和NaN才会被动转换成flase,其他都为ture
云宝此外进行拓展一下:
- let b = 1
- let c = '1'
- console.log(b == c)
- console.log(b === c)
==是相等运算符,会进行类型转换:将字符串'1'转为数字1,与1比较,结果为true。===是严格相等运算符,不进行类型转换:number和string类型不同,直接返回false。
3. boolean
布尔类型即boolean只有ture和false两个值,下边这个代码输出结果便很直白,为‘云宝’
- if(ture){ console.log('云宝') }
4. undefined
这是一个特殊类型,我们若声明一个undefined类型的值let u = undefined,此时u的值是undefined,类型也是undefined
5. null
null 代表 “空值” 或 “没有对象” 。它通常用于主动表示一个变量当前没有指向任何对象。
注意:null 与 undefined 的区别:
undefined表示变量已声明但未赋值。null表示变量已赋值,但值为“空”。
6. bigInt
bigint 是引入的新基本类型,用于表示任意精度的大整数。它解决了 number 类型无法精确表示超过 2^53 - 1 的整数的问题。两个bigint类型可以进行运算!
const bigNum1 = 9007199254740993n; // 超过 2^53 - 1
const bigNum2 = 12345678901234567890n; // 更大的大整数
// 相加
const sum = bigNum1 + bigNum2;
// 输出结果
console.log(sum); // 12354687900489318883n
7. Symbol
这是一个特殊的基本类型,该类型各个值都不会相等,每一个值都不会一样,举个例子:
- let s = symbol('hello')
- let p = symbol('hello') //s与p不相等
这样可以有效防止定义参名时重复套用哦~
2、引用类型(复杂类型)
1. Array
数组像是进行线性存储的管道,我们首先创建一个数组,并对它进行使用:
var arr = ['a','b',1,2 ]
arr.push(3)//尾部推入3
console.log(arr)//输出a,b,1,2,3
arr.pop()//尾部删除3
console.log(arr)//输出a,b,1,2
arr.unshift(0)//头部加入0
console.log(arr)//输出0,a,b,1,2
arr.shift()//头部删除
console.log(arr)//输出a,b,1,2
arr.splice(1,1)//用于删除元素,第一个数是位置,第二个是长度
console.log(arr)//输出a,1,2
arr.splice(2,0,0)//用于增加元素,找到2号下标,移除0个值,插入0进去
console.log(arr)//输出a,1,0,2
arr[2] = 10//将第三个数改为10;
console.log(arr)//输出a,1,0,10
这就像一个无限长的存储条,可以进行任意操作,但是过长的话会大幅增加操作的时间复杂度
2. Object
是一组键值对的集合,理论上可以进行无限嵌套,没有对象?我们先来创建一个!
var a = 1
var b = 'hello'
var obj = {
name:'rainbow',
age: 18,
like:{
one:'eat',
two:{
sports:['1','2']
}
}
}
obj.Friend = 'applejack'
delete obj.Friend
console.log(obj)的话会把该对象的所有属性值都罗列。
3. Function
function函数依然理论上可以在里面塞入无限运行逻辑与语句,是一段可重复执行的代码块
var fn = function(){
}
4. Date
特殊的引用类型,我们可以在浏览器查看页面中看到:
三、V8 引擎的底层执行机制
1. 创建调用栈
当 JavaScript 代码开始执行时,V8 首先创建一个调用栈。它是一个 后进先出 的数据结构,用于管理函数调用的执行上下文。
2. 存入执行上下文
- 每当执行一段全局代码时,会创建一个全局执行上下文并压入栈底。
执行上下文中包含了变量环境、词法环境等重要信息。
3. 执行代码并分配内存
在执行代码的过程中,V8 根据变量类型决定内存分配方式:
栈(Stack)中存放:
- 基本类型的值:直接保存在栈内存中。
- 引用类型的引用地址:即指向堆内存中实际对象的指针。
例如:
javascript
let name = "rainbow"; // 栈中存 "rainbow"
let age = 19; // 栈中存 19
let obj = { name: "applejack", age: 20 }; // 栈中存引用地址,比如 #001
堆(Heap)中存放:
- 引用类型的实际数据:
obj指向的对象{ name: "applejack", age: 20 }存储在堆内存中。
为什么要这样设计?
- 栈的内存大小有限,且访问速度快,适合存放大小固定、生命周期明确的数据(如基本类型和引用地址)。如果把所有对象都塞进栈,很容易导致爆栈。
- 堆的空间大且灵活,适合存放大小不确定或生命周期较长的对象。通过引用地址的方式,栈只保留一个小小指针,既保证了栈的轻量高效,又不会使栈内存过度膨胀。
**** ** 今日学习完毕,云宝给大家点赞 ******