想要了解JS底层运行机制,首先要明白这几个概念:
为什么js能在浏览器中执行
众所周知,计算机是有内存的,计算机会在内存中开辟一块空间去供js执行,这个空间我们称之为执行栈
全局对象和全局变量对象是一样的吗
全局对象(Global Object),即window对象,存储着浏览器提供的内置方法,setTimeout,setInterval....
全局变量对象,在script标签内的代码执行时,会形成EC(G)的栈,EC(G)进栈(执行环境栈,EC Stack)执行,形成全局执行上下文(VO(G)),供给下级作用域
js中上下文有哪些
全局上下文(全局代码执行形成),记录为VO
私有上下文(函数执行形成),记录为AO
块级私有上下文
....
执行上下文
为了进行区域划分,会形成不同的执行上下文,全局环境会形成全局执行上下文,函数执行会形成函数执行上下文,通过作用域链scope chain形成链式关系
举个例子,var a = 12在底层是如何执行的
这里需要对栈内存和堆内存有个基本的了解
引用类型的值是存放在堆内存当中的
基本数据类型,也就是值类型,这种值是存放于栈内存当中
对于var a = 12,在计算机底层是先看等号右边的值,是引用类型还是值类型
需要了解一点,看似简单的赋值操作,实际上都可以归为三步操作
1.创建值
创建值的过程,又可以细分,需要看值是什么类型
如果是引用类型的话,那么就开辟一块堆内存,用来存放引用类型的值
如果是值类型,那么就在栈内存中直接存放该值
2.声明变量,declare
3.将变量和值关联起来 defined
这里的关联实际上,对计算机有所了解的,都会知道,这里是通过指针指向的行为进行关联
对于函数声明function fn(){}
底层会形成类似于fn = function (){},因为函数也是引用类型,故先创建堆内存,用来存放函数体内容,存放格式是字符串的形式
再次说明下对象值类型,以此对比,对象类型,也是创建堆内存,存放形式时键值对的形式
而对于数组值类型,也是创建堆内存,存放形式是,举个例子var arr = [1, 2] 存放形式(0 1, 1:2, length: 2)
函数在声明的时候,会生成其作用域[[scope]]指向当前函数声明所在的环境,如在全局中声明函数fn,则fn的作用域[[scope]]是EC(G),即指向全局执行上下文
对于函数执行fn()
1.形成私有化上下文,EC(fn)(包含了AO(fn)私有化变量对象,存放当前上下文中声明的变量),然后进栈执行,此时全局AO(G)压入栈底
2.代码执行之前,会进行预编译环节
初始化作用域链 scope chain
初始化this
初始化arguments
形参赋值
变量提升
3.函数体代码执行
4.出栈释放
EC/AO/VO/GO
EC(Execution call) 执行上下文
AO(active Object) 活跃对象,函数创建时会产生,同时确定其作用域[[scope]]
VO(variable Object) 变量对象
GO(global Object) 全局变量对象,也是VO(G)
let x = 1;
function A(y) {
let x = 2;
function B(z) {
console.log(x + y + z);
}
return B;
}
let c = A(2);
c(3);
ECS = [
EC(A) = {
// 链表初始化为 AO(A) --> VO(G)
[scope]: VO(G),
scope.chain: <AO(A), VO(G)>,
AO(A) : {
x: 2,
B: function (z) {}..,
B[[scope]]: AO(A), // 函数创建的时候就确定了其作用域,指向当前的环境
}
},
// A函数执行时,EC(G)被压入栈底
EC(G) = {
// 全局变量对象
VO(G) : {
x: 1,
A: function (y) {}..,
A[[scope]]: VO(G), // 函数创建的时候就确定了其作用域
c: A(2),
}
}
]