这篇文章是对上次堆栈考察题的一个概念补充,我们知道,基本类型是保存在栈内存中,复杂类型保存在堆内存中
堆和栈
定义
- 栈(stack):栈会自动分配内存空间,会自动释放,存放基本类型(String,Number,Boolean,Null,Undefined,BigInt),简单的数据段,占据固定大小的空间。
- 堆(heap):动态分配的内存,大小不定也不会自动释放,存放引用类型(Function,Array,Object),指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量,实际上保存的不是变量本身,而是指向该对象的指针。
区别
-
栈:所有在方法中定义的变量都是放在栈内存中,随着方法的执行结束,这个方法的内存栈也自然销毁。
- 优点:存取速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享;
- 缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
-
堆:堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。创建对象是为了反复利用,这个对象将被保存到运行时数据区。
栈和堆的溢出
- 栈:可以递归调用方法,这样随着栈深度的增加,JVM维持着一条长长的方法调用轨迹,知道内存不够分配,产生栈溢出。
- 堆:循环创建对象,通俗点就是不断的new 一个对象。
js中执行上下文的创建
运行环境的创建
执行上下文的创建离不开JS
的运行环境,JS
的运行环境包括全局环境、函数环境和eval
执行部分:
- 在第一次载入
js
代码时,会首先创建一个全局环境。全局环境位于最外层,直到应用程序退出后才会被销毁。 - 每个函数也会有自己的运行环境,当函数被调用时,就会进入该函数的运行环境。当函数执行完毕后,该环境会被销毁。不同的函数会创建不同的环境,同一个函数被调用多次时也会创建不同的函数环境。
执行上下文
每当进入一个不同的环境时,都会创建一个新的执行上下文(下面称为EC
),它包括:
-
建立作用域链(
Scope chain
)
作用域链可以简单地理解为在哪执行,哪就是它的当前作用域,从当前作用域一直向上找,直到全局作用域,这条链就可以称为作用域链 -
创建变量对象(
Variable Object, 简称VO
)
在编译阶段,会创建VO
,这个VO
会保存当前上下文中定义的所有变量和函数声明,在执行阶段,js会对变量进行赋值,此时变量对象VO
会转化活动对象AO(Active Object)
,转换后的AO
才可被访问,这就是VO->AO
的过程 -
确定this的指向
this
指向执行当前代码对象的所有者- 在全局环境中:
this
指向全局对象(浏览器window
,node
中global
) - 函数内部:this指向取决于当前函数的调用方式,如果是构造函数,指向的是实例对象;如果是直接调用,指向全局对象;如果是被某对象调用,指向调用它的对象
- 在箭头函数中:指向它被创建的环境
- 在全局环境中:
在下面的例子中,father的作用域链[[ scope ]]
就是全局[[ scope ]]
,而son[[ scope ]]
包括:father[[ scope ]]
和全局[[ scope ]]
function father() {
console.dir(son)
var x = 1;
function son() {
x = 2;
}
}
father()
console.dir(father)

在执行的过程我们会遇到函数或各种块级作用域,所以为了区分他们,会为它们创建不同的环境,也称为xx
执行上下文EC(xx)
(其中xx
是对应的函数或块级),其过程产生的私有变量,包括形参、变量声明,都在放在其作用域AO(xx)
执行过程中,变量对象VO
(VARIABLE OBJECT
)会变成AO
(ACTIVITED OBJECT激活对象
),如果在自身作用域中找不到变量,会向其父作用域上查找,如果父作用域也没有,会一级一级继续向上作用域查找,直到全局作用域,如果都没有就会返回undefined
js中堆栈中一些要注意的事
对象的key如果是引用值,那么在赋值时,会将key先转换成string类型,然后在赋值
let a = {
m: 1
}
let b ={
x: 2
}
let obj = {}
obj[a] = "小花"
obj[b] = "小明"
console.log(a) // 小明
// 实际上做以下处理:
// 1. obj[a] => obj[a.toString()] => obj['[object object]']
// 2. obj[b] => obj[a.toString()] => obj['[object object]']
// 3. obj[a] == obj[b] == "小明"
网友评论