堆与栈
栈(stack)/ 堆(heap)/ 队列(queue)是js的三种数据结构
栈(stack)
栈的特点是 “LIFO,即后进先出(Last in, First out)”。数据存储时只能从顶部逐个存入,取出时也去要从顶部逐个取出。例如搭建积木
堆(heap)
堆的特点是 “无序” 的key-value “键值对”储存方式。例如书架存书
堆的存取方式跟顺序无关,不局限出入口
队列(queue)
队列的特点是 “FIFO,即先进先出(First in First out)”。数据存取时 “从尾部插入,从头部取出”
堆/栈/队列在js中堆应用
代码运行方式(栈应用/函数调用栈)
javascript中的函数的执行过程,其实就是一个入栈出栈的过程:
1.当脚本要调用一个函数时,js解释器把该函数推入栈中并执行
2.当函数运行结束后,ja解释器将它从堆栈中弹出
事件轮询(队列)
javascript中事件轮询(Event Loop)的执行机制,就是采用队列的存取方式
内存存储(堆/栈)
javascrip中变量类型有两种:
基础类型:string/number/boolean/null/undefined/symbol
引用类型:object
js中的基础数据类型,这些只都有固定的大小,往往都保存在栈内存中(闭包除外),由系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按照值访问,数据在内存中的存储与使用方式类似于数据结构中的堆栈数据结构,遵循后见先出的原则。
js中引用数据类型,比如数组,他们值的大小都是不固定的。引用数据类型的值是保存在堆内存中的对象。js不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以粗浅地理解为保存在栈内存中的一个地址,该地址与堆内存的实际值相关联。每次使用const或者let去初始化一个变量的时候,会首先便利当前的内存栈,看看有没有重名的变量,有的话就反悔错误。
赋值与赋址
引擎不能直接操作堆内存中的数据,这就造成了堆同一个变量赋不同的值,会出现完全不同的效果:为一个变量赋基本值时,实际上是创建一个新值,然后把该值赋给新的变量,可以说这是一种真正意义上的“赋值”;为一个变量赋引用值时,实际上是为新变量添加一个指针,指向堆内存中的一个对象,属于一种“赋址”操作。
// 基本值
var a = 1
var b = a
b = 2
console.log(a) // 1
console.log(b) // 2
// 引用值
var c = [0, 1, 2]
var d = c
c[0] = 5
console.log(c) // [5, 1, 2]
console.log(d) // [5, 1, 2]
浅拷贝
浅拷贝可以简单理解为,发生在栈中的拷贝行为,只能拷贝基本值和引用值的地址
Object.assign() 数组的slice/concat都属于浅拷贝
let a = {
name: 'Tom',
obj: {
age: 19
}
}
let b = Object.assign({}, a) // 浅拷贝
let c = a // 赋值
a.name = 'Amy'
a.obj.age = 20
console.log(a) // {name: 'Amy', obj: {age: 20}}
// 浅拷贝拷贝了基础值‘Tom’ 和 对象obj的地址
console.log(b) // {name: 'Tom', obj: {age: 20}}
// a为对象,所以在此是‘赋址’
console.log(c) // {name: 'Amy', obj: {age: 20}}
var arr1 = [0, [1], [2]]
var arr2 = arr1.slice(0)
arr1[0] = 8
arr1[1][0] = 9
arr1[2] = 10
console.log(arr1) // [8, [9], 10]
// 浅拷贝arr2拷贝了arr1[0]的值 8 ,
// arr1[1]的数组地址p1 = [1],
// arr1[2]的数组地址p2 = [2]
// 改变p1指的的数组的下标0的值为9,自然会同步到arr2
// 把arr1[2]的值由 p2 改为 10,并不影响arr2的值仍然是 p2,所以arr2[2] 依然是[2]
console.log(arr2) // [0, [9], [2]]
深拷贝
深拷贝可以理解为,同时发生栈中和堆中的拷贝行为,除了拷贝基本值和引用值的地址之外,地址指向的堆中的对象也会发生拷贝
function deepClone(target) {
if (typeof target !== 'object' || target === null) return target
var result = target instanceof Array ? [] : {}
for (let key in target) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
if (target[key] && typeof target === 'object') {
result[key] = deepClone(target[key])
} else {
result[key] = target[key]
}
}
}
return result
}
var a = [1, 2, 3, { a: 'a' }]
var b = deepClone(a)
a[3].a = 'b'
console.log(b) // [1, 2, 3, { a: 'a' }]