•是要增加自己技术的深度,也就是懂得JavaScript的运行机制。
•现在在前端领域各种技术层出不穷,掌握底层原理,可以让自己以不变,应万变。
•应对各大互联网公司的面试,懂其原理,题目任其发挥。
•栈,堆,队列
•同步,异步任务
•宏任务,微任务
•Async/await语法糖
Event Loop即事件循环,是指浏览器或Node的一种解决javaScript
单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
js引擎负责解析并编译js代码。制定作用域标准,分配内存,创建执行上下文调用栈…。编译好的代码放到运行环境中去运行,而运行环境会维护异步任务。
对浏览器而言,浏览器是多线程的,它可以分配线程去倒计时定时器,发送请求,事件监听等。当定时器中的事件倒计时完毕,将其扔到也是由浏览器维护的消息队列中,当遇到其他异步时,浏览器会分配进程去处理,处理完毕也是扔到消息队列中。等待js引擎去执行。
堆是一种数据结构,是利用完全二叉树维护的一组数据,堆分为两种,一种为最大堆,一种为最小堆,将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆是线性数据结构,相当于一维数组,有唯一后继。
如最大堆:
栈在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。 栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。
栈是只能在某一端插入和删除的特殊线性表。
特殊之处在于它只允许在表的前端( front )进行删除操作,而在表的后端( rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO-first in first out )
同步是js任务进入任务栈按顺序等待主线程执行的过程,简单来讲就是js从上到下代码的运行的过程,如果中间有个同步任务出现了死循环,那么浏览器可能就会出现js无响应,意思就是无法继续向下执行。
但是我们读取本地文件数据,或者获取服务器接口数据时,花费的时间无法确定,这样会导致页面看起来非常卡顿,严重影响用户体验。这个时候就出现了异步,即不进入主线程任务栈,而是先进入event Table并注册回调函数,然后按顺序进入异步事件队列,在主线程的任务栈执行完后调用(队列先进先出,所以按注册函数进入队列的顺序来调用)
•console.log()
•new Promise()
•函数调用
•…
•Ajax请求
•setTimeOut、setInterval
•promise.then()
•Dom事件
•…
Javascript有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。
JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。
Javascript单线程任务被分为同步任务和异步任务,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。任务队列Task Queue,即队列,是一种先进先出的一种数据结构。
宏任务(Macro-take)
微任务(Micro-take)
script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI Rendering。
Process.nextTick (Node独有)、Promise 、Async/Await(实际是Promise),object.observe(废弃)、Mutation0bserver
事件的执行顺序,是先执行宏任务,然后执行微任务,这个是基础,任务可以有同步任务和异步任务,同步的进入主线程,异步的进入Event Table并注册函数,异步事件完成后,会将回调函数放入Event Queue中(宏任务和微任务是不同的Event Queue),同步任务执行完成后,会从Event Queue中读取事件放入主线程执行,回调函数中可能还会包含不同的任务,因此会循环执行上述操作。
**注意:**setTimeOut并不是直接的把你的回掉函数放进上述的异步队列中去,而是在定时器的时间到了之后,把回掉函数放到执行异步队列中去。如果此时这个队列已经有很多任务了,那就排在他们的后面。这也就解释了为什么setTimeOut为什么不能精准的执行的问题了。setTimeOut执行需要满足两个条件:
1.主进程必须是空闲的状态,如果到时间了,主进程不空闲也不会执行你的回调函数
2.这个回调函数需要等到插入异步队列时前面的异步函数都执行完了,才会执行
var p=new Promise(resolve=>{
console.log(4)
resolve(5)
})
function f1(){
console.log(1);
}
function f2(){
setTimeout(()=>{
console.log(2);
})
f1()
p.then(resolve=>{
console.log(resolve);
})
.then(()=>{
console.log(6);
})
console.log(3);
}
f2()
//4 1 3 5 6 2
async function async1(){
console.log("async1begin");
let async2res= await async2()
console.log(async2res);
console.log("async1end");
}
async function async2(){
console.log("async2begin");
return "async2end"
}
async1()
console.log("end");
function async1(){
console.log("async1begin");
return new Promise((resolve)=>{
console.log("async2begin");
resolve("async2end")
}).then((res)=>{
console.log(res);
console.log("async1end");
})
}
async1()
console.log("end");
async1begin
async2begin
end
async2end
async1end
async function async1(){
console.log("A");
await async2()
console.log("B");
}
async function async2(){
console.log("C");
}
console.log("D");
setTimeout(function(){
console.log("E");
},0)
async1()
new Promise(function (resolve){
console.log("F");
resolve()
}).then(function(){
console.log("G");
})
console.log("H");
D A C F H B G E
<body>
<button >buttonbutton>
body>
<script>
let button=document.getElementsByTagName('button')[0];
button.addEventListener("click",()=>{
Promise.resolve().then(()=>{console.log("M 1");})
console.log("L 1");
})
button.addEventListener("click",()=>{
Promise.resolve().then(()=>{console.log("M 2");})
console.log("L 2");
})
button.click()
script>
L 1
L 2
M 1
M 2
点击后:
L1
M1
L2
M2
button.click()会把两个函数合起来用,类似:
function fn1(){
Promise.resolve().then(()=>{console.log("M 1");})
console.log("L 1");
Promise.resolve().then(()=>{console.log("M 2");})
console.log("L 2");
}
fn1()
点击事件会连着执行两个函数,类似:
function fn2(){
Promise.resolve().then(()=>{console.log("M 1");})
console.log("L 1");
}
function fn3(){
Promise.resolve().then(()=>{console.log("M 2");})
console.log("L 2");
}
new Promise((resolve)=>{
fn2()
resolve()
}).then(()=>{
fn3()
})