一、执行栈
JS运行的环境称之为宿主环境。
执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。(单线程不容易冲突,但是效率低)
如下函数:
function a() {
console.log("a")
b();
}
function b() {
console.log("b");
c();
}
function c() {
console.log("c")
}
console.log("global");
a();
- 函数a执行,将a放入执行期上下文
- log(‘a’)执行,将log(‘a’)放入
- log(‘a’)执行完毕,将log(‘a’)移除
- 函数b执行,将b放入执行期上下文
…
n-1. 函数b执行完毕,将b移除
n. 函数a执行完毕,将a移除
对于递归函数:
function getFeibo(n) {
if (n === 1 || n === 2) {
return 1;
}
return getFeibo(n - 1) + getFeibo(n - 2);
};
console.log(getFeibo(4));
- log(getFeibo(4))执行,将log(getFeibo(4))放入
- getFeibo(4)执行,将
getFeibo(3)
+getFeibo(2)
放入getFeibo(3)
执行,将getFeibo(2)+getFeibo(1)放入
…- getFeibo(2)+getFeibo(1)执行完毕,将
getFeibo(3)
移除getFeibo(2)
执行,将getFeibo(2)
放入getFeibo(2)
执行完毕,将getFeibo(2)
移除- getFeibo(4)执行完毕,将getFeibo(4)移除
- log(getFeibo(4))执行完毕,将log(getFeibo(4))移除
JS引擎永远执行的是执行栈的最顶部。
二、异步函数
异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。
浏览器宿主环境中包含5个线程:
- JS引擎:负责执行执行栈的最顶部代码
- GUI线程:负责渲染页面 (js线程与GUI线程会相互等待,一者工作一者等待)
- 事件监听线程:负责监听各种事件
- 计时线程:负责计时
- 网络线程:负责网络通信
当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列
的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。
JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。
例1:
<div>
<button id="btn">点击</button>
</div>
<script>
document.getElementById("btn").onclick = function A() {//这里A只是后面好讲解
console.log("按钮被点击了");
};
</script>
- 全局上下文
- getElementById执行,getElementById放入
- getElementById执行完毕,getElementById移除
a. 浏览器宿主 发现 点击事件,将 点击函数A 放入事件队列- 全局执行完毕,执行栈中已经没有了任何内容后
b. 将 点击函数A 加入到执行栈中执行
例2:
console.log("a")
setTimeout(() => {
console.log("b")
}, 0);
for (let i = 0; i < 1000; i++) {
console.log("c")
};
- 全局上下文
- log(‘a’)执行,放入
- log(‘a’)完毕,移除
a. 浏览器宿主 发现 计时线程,将 setTimeout 放入事件队列- for循环事件执行,放入
- for循环事件完毕,移除
- 全局执行完毕,执行栈中已经没有了任何内容后
b. 将 计时线程 加入到执行栈中执行
- 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
- 微任务(队列):MutationObserver,Promise产生的回调进入微队列
当执行栈执行完毕后,会先在微队列中找需要执行的事件
let count = 1;
const ul = document.getElementById("container");
document.getElementById("btn").onclick = function A() {
setTimeout(function C() {//宏队列
console.log("添加了一个li")
}, 0);
var li = document.createElement("li")
li.innerText = count++;
ul.appendChild(li);
};
//监听ul,该函数就会进入微队列
const observer = new MutationObserver(()=> {
//当监听的dom元素发生变化时运行的回调函数
console.log("ul元素发生了变化")
});
//监听ul
observer.observe(ul, {
attributes: true, //监听属性的变化
childList: true, //监听子元素的变化
subtree: true //监听子树的变化(子元素的后代)
});
//不想监听后可以取消监听
// observer.disconnect();
let count = 1;
const ul = document.getElementById("container");
document.getElementById("btn").onclick = function A() {
var li = document.createElement("li")
li.innerText = count++;
ul.appendChild(li);
console.log("添加了一个li");
};
//监听ul,该函数就会进入微队列
const observer = new MutationObserver(()=> {
//当监听的dom元素发生变化时运行的回调函数
console.log("ul元素发生了变化")
});
//监听ul
observer.observe(ul, {
attributes: true, //监听属性的变化
childList: true, //监听子元素的变化
subtree: true //监听子树的变化(子元素的后代)
});
//不想监听后可以取消监听
// observer.disconnect();