在说运行之前,我们需要明确几个概念
在 JavaScript 中,没有严格意义的堆内存和栈内存。但是某些场景我需要了解栈数据结构,因为 JavaScript 的执行上下文就是借用了栈数据结构的存储方式。
栈的定义及介绍:
下面看一下栈数据结构的简单实现
function Stack() {
this.dataStore = []; //保存栈内元素
this.top = 0; //标记课插入新元素的位置 栈内压入元素该变量变大 弹出元素 变量变小
this.push = push; // 入栈操作
this.pop = pop; // 出栈操作
this.peek = peek; //返回顶元素
this.clear = clear; // 清空栈
this.length = length; //栈的长度
}
function push(element) {
this.dataStore[this.top++] = element;
}
function pop() {
return this.dataStore[--this.top]
}
function peek() {
return this.dataStore[this.top - 1]
}
function length() {
return this.top;
}
function clear() {
this.top = 0;
this.dataStore=[];
}
堆数据结构是一种树状结构。
好比书架,我们只要知道书的名字,就可以取到想要的书,好比在 JSON 的数据中,key-value 是可以无序的,我们只要知道 key,就可以拿到对应的 value。
队列这个数据结构虽然暂时不会出现在 JavaScript 的运行机制中,但是后面的事件循环(Event Loop),我们会用到。
队列是一种先进先出的数据结构。好比一根水管,一端流入一端流出。
下面简单看一下 js 实现队列的简单代码:
function Queue() {
this.dataStore = [];
this.enqueue = enqueue; //入队
this.dequeue = dequeue; //出对=队
this.front = front; //对首
this.back = back; //队尾
this.isEmpty = isEmpty;
this.toString = toString;
}
function enqueue(element) {
this.dataStore.push(element);
}
function dequeue() {
return this.dataStore.shift();
}
function front() {
return this.dataStore[0];
}
function back() {
return this.dataStore[this.dataStore.length - 1];
}
function isEmpty() {
return this.dataStore.length === 0 ? true : false;
}
function toString() {
var str = '';
for (var i = 0; i < this.dataStore.length; i++) {
str += this.dataStore[i] + '\n'
}
return str
}
JavaScript 在执行上下文生成后,会创建一个叫变量对象的特殊对象(下面会详细说),JavaScript 的基本数据类型都会保存在变量对象上
基础数据类型都是简单的数据段,JavaScript 中,目前有 7 中基本数据类型,分别是 Null、Undefined、Number、String、Boolean、Symbol、BigInt。
我们知道,在 JavaScript 中,引用数据类型都是存在堆内存当中的,引用类型的值是按引用传递访问的。这里的引用,其实举手变量对象中保存的地址,该地址指向堆内存中的实际内存。
每当 JavaScript 编译阶段结束,都会进入一个执行上下文,执行上下文(EC)也可以理解为当前代码的执行环境。具体执行机制,到下面会完整的列出。
既然是代码的执行环境,那么必定会有多个执行上下文,我们下面通过图例来看:
var color = 'blue';
function changeColor() {
var anotherColor = 'red';
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}
changeColor();
这段代码,配合下面的图,我们会清楚的知道执行上下文是什么。
首先,第一步,我们知道,当代吗开始执行之前,会有一个全局上下文环境入栈,即 window
紧接着,可执行代码开始执行,将 changeColor 的执行上下文入栈
第三步,就是 swapColors 的执行上下文入栈
当 swapColors 执行完,开始出栈
我们知道先入后出原则,一次出栈
比较特殊的是,全局上下文并不会出栈,它会等到浏览器关闭后出栈
上面的概念中,我们有两次提到了变量对象,那么变量到底是什么,变量对象译为 Variable Object(VO)
目前我们先看一下 VO 到底是什么,下面会说 js 的运行机制,一切就豁然开朗了
变量对象(VO)的创建过程:
VO 分为两个阶段:
什么是 AO
可以理解为 AO 就是 VO,当执行上下文是函数执行环境时,AO===VO,这里只要明白这个就够用了
我们具体看一下 VO 和 AO 这个特殊的对象长什么样
//以window为例
windowEC = {
VO: Window,
scopeChain: {},
this: Window
}
下面看一个可执行代码的 AO/VO
function test() {
console.log(foo);
console.log(bar);
var foo = 'Hello';
console.log(foo);
var bar = function () {
return 'world';
}
function foo() {
return 'hello';
}
}
test();
我们分别看一下创建阶段以及执行阶段
// 创建阶段
EC(test):{
ScopeChain:{ Scope },
AO = {
arguments: {...},
foo: <foo reference>,
bar: undefined
}
}
// 执行阶段
EC(test):{
ScopeChain:{ Scope },
VO = {
arguments: {...},
foo: 'Hello',
bar: <bar reference>,
this: Window
}
}
其实上面已经说过了,我们来看一段代码就会知道
function func1(){
console.log('func1');
}
function func2(){
console.log('func2');
}
function func3(){
console.log('func3');
}
func1();
//此时的EC:
ECStack:{
func1,
globalContext
}
我们想一下,如果函数式异步的,那么执行又是怎样的
什么是闭包?
我的理解很短,闭包就是函数执行完毕,内部变量仍可以被外部访问。
可能说的太笼统,下面用一张图看一下
此处借鉴于掘金的 块级作用域的默认变量
直接上代码以及注释吧
var a;
if(true){
a=5;
function a(){}
a=0;
console.log(a); //0
}
console.log(a); //5
//因为在块级作用域内,变量不会提升到全局作用域顶层,而是在运行后,反射到全局作用域
//而函数直接可以从块级作用域提升到全局作用域
console.log(a); // undefined
if(true){
console.log(a,window.a); // function a() undefined
function a(){}
console.log(a); // functiona()
}
//重新说明,块级作用域和函数作用域完全不同,函数要等到执行完重写window,而块级作用域却不是,只会进行有意义的重写一次
//那么什么是有意义的重写,就是外层作用域没有声明及赋值
//在块级作用域,提升这些无异于其他作用域,函数提升到当前作用域顶层,提升到作用域顶层,并不代表会重写外层变量,是否重写,要看外层变量是否有实际存在意义