目录
禁止跨线程访问DOM:webworker线程不能直接操作DOM
宏任务macrotask: 定时事件(setTimeout,setInterval),Ajax,DOM/用户交互事件,HTML解析, I/O
优先级:js>setImmediate(Node环境)>setTimeout
微任务microtask(异步):Promise 、async/await、alert、MutationObserver,process.nextTick,queueMicrotask
优先级:process.nextTick>其他微任务>alert>Promise=async/await
async隐式返回Promise,会产生一个微任务await xx;后的代码在微任务时执行
promise>MutationObserver>setImmediate>setTimeout
setTimeout(delay=0)=setImmediate:下个Event Loop执行
js单线程
竞态:DOM操作冲突
竞态条件(race condition)和死锁(deadlock):一个删除DOM,另外一个访问DOM
禁止跨线程访问DOM:webworker线程不能直接操作DOM
安全性:JSt主要是在浏览器端执行,多线程的设计可能引发安全隐患,例如跨线程访问共享数据容易导致数据不一致或非法访问。
事件回调简单
单线程按顺序执行JS时,遇到事件触发时,将事件回调函数添加到队列中。
为了解决高耗时操作就带来的进程阻塞问题,
Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)
同步:JS
日常的 DOM 操作通常是同步的,并不直接分为异步模式(宏任务或微任务):
修改元素属性:如更改 className
、id
或 style
属性
document.body.style.backgroundColor = "red"; // 修改背景颜色
宏任务
在主线程上排队执行的任务,顺序执行
由宿主(浏览器、Node)发起
宏任务macrotask: 定时事件()setTimeout,setInterval定时事件(),Ajax,DOM/用户交互事件,HTML解析, I/O
优先级:js
>setImmediate(Node环境)>setTimeout
浏览器对页面的渲染和重绘被优化为异步任务以提高性能:
宏任务:用户交互事件(如点击事件、滚动等)
微任务:视图更新请求;微任务队列会在当前宏任务(例如一个点击事件的处理函数)完成后,且在下一个宏任务开始前执行执行所有的视图更新。
微任务
不进入主线程、而进入"微任务列表"的任务
由 JS 自身发起
微任务microtask(异步):Promise 、async/await、alert、MutationObserver,process.nextTick,queueMicrotask
优先级:
process.nextTick>其他微任务>alert>Promise=
async/await
queueMicrotask(JS)
MutationObserver( 浏览器端 )
MutationObserver
是浏览器提供的一个用于监测 DOM 变化的接口。它允许开发者在 DOM 树发生变化时进行异步处理。MutationObserver
可以观察到节点的增删改等操作
// 创建一个 MutationObserver 实例,指定回调函数
const observer = new MutationObserver((mutations, observer) => {
mutations.forEach((mutation) => {
// 处理每个变化(mutation)
console.log(mutation.type); // 变化的类型(attributes、childList、characterData)
console.log(mutation.target); // 受影响的节点
});
});
// 配置观察选项
const config = {
attributes: true, // 观察属性的变化
childList: true, // 观察子节点的变化
subtree: true, // 观察所有后代节点的变化
characterData: true, // 观察字符数据的变化
// 其他配置项...
};
// 开始观察目标节点
observer.observe(targetNode, config);
// 在不需要观察时,可以调用 disconnect 方法停止观察
// observer.disconnect();
async、await事件轮询执行时机
async隐式返回Promise,会产生一个微任务
await xx;后的代码在微任务时执行
//1.script start(同步)
console.log("script start");
async function async1() {
await async2(); // await 隐式返回promise
console.log("async1 end"); // 这里的执行时机:在执行微任务时执行
}
async function async2() {
console.log("async2 end"); // 这里是同步代码
}
//2.async2 end(同步)
//微任务队列:[async1 end]
async1();
//宏任务队列:[setTimeout],setTimeOut进入下一loop
setTimeout(function() {
console.log("setTimeout");
}, 0);
//3.Promise(同步)
//宏任务队列:[setTimeout]
//微任务队列:[async1 end,promise1]
new Promise(resolve => {
console.log("Promise"); // 这里是同步代码
resolve();
})
.then(function() {
console.log("promise1");
})
.then(function() {
console.log("promise2");
});
//4.script end(同步)
console.log("script end");
//当前loop的宏任务(都是同步代码)都执行完毕
//执行所有微任务[async1 end,promise1]
//执行promise1完后碰到了promise2,加入微任务队列,接着执行
//当前所有微任务都执行完毕,开始执行宏任务队列[setTimeout]
// 打印结果: script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout
nextTick(Vue):DOM更新后执行回调函数
function nextTick(callback?: () => void): Promise<void>
vue2:
promise>MutationObserver>setImmediate>setTimeout
执行优先级:微任务(
MutationObserver>promise)>宏任务(
setImmediate>setTimeout)
setImmediate
在当前事件循环的结束后立即执行
setTimeout
则是在指定的延迟时间后才执行。
因此,setImmediate
的执行时机比 setTimeout
更早,更接近于微任务的执行时机,响应更快
MutationObserver开销高于promise
vue3:promise(vue3
基于 Proxy 响应式,而Proxy基于promise)
实现nectTick
promise>MutationObserver>setImmediate>setTimeout
-
Promise:如果浏览器支持
Promise
,nextTick
会优先使用Promise.then
来创建微任务,以确保回调函数在下一个微任务队列中执行。 -
MutationObserver:否则。
MutationObserver
变动观察器)是一种Web API,它允许开发者监视DOM树的变化并在这些变化发生时执行回调函数允许监视DOM树的变化,因此它也可以用于异步任务的调度。 -
setImmediate:否则。
setImmediate
是一种宏任务,通常比setTimeout
执行得更早,因此它用于创建宏任务级别的异步任务。 -
setTimeout:如果以上方法都不可用,
nextTick
会回退到使用setTimeout
来创建异步任务。setTimeout
是一种宏任务,但是优先级较低,可能在其他异步任务之后执行。
// 定义nextTick的回调队列
let callbacks = [];
// 批量执行nextTick的回调队列
function flushCallbacks() {
callbacks.forEach((cb) => cb());
callbacks = [];
pending = false;
}
//定义异步方法,优先使用微任务实现
let timerFunc;
// 优先使用promise 微任务
if (Promise) {
timerFunc = function () {
return Promise.resolve().then(flushCallbacks);
};
// 如不支持promise,再使用MutationObserver 微任务
} else if (MutationObserver) {
timerFunc = function () {
const textNode = document.createTextNode('1');
const observer = new MutationObserver(() => {
flushCallbacks();
observer.disconnect();
});
const observe = observer.observe(textNode, { characterData: true });
textNode.textContent = '2';
};
// 微任务不支持,再使用宏任务实现
} else if (setImmediate) {
timerFunc = function () {
setImmediate(flushCallbacks);
};
} else {
timerFunc = function () {
setTimeout(flushCallbacks);
};
}
// 定义nextTick方法
export function nextTick(cb) {
callbacks.push(cb);
if (!pending) {
pending = true;
timerFunc();
}
}
process.nextTick( Node 端 )
console.log("start");
//定时进入下一loop,宏任务队列:[timeout]
setTimeout(() => {
console.log("timeout");
}, 0);
//微任务队列:[promise]
Promise.resolve().then(() => {
console.log("promise");
});
//process.nextTick在微任务队首
//微任务队列:[nextTick,promise]
process.nextTick(() => {
console.log("nextTick");
Promise.resolve().then(() => {
console.log("promise1");
});
});
console.log("end");
// 执行结果 start end nextTick promise promise1 timeout
事件轮询机制
0.在执行宏任务过程中,遇到微任务,依次加入微任务队列
1.当前宏任务执行完后,会判断微任务列表中是否有任务
1.5.如果有,会把所有微任务放到主线程中并执行
因此微任务优先级高于宏任务,用于处理更紧急的工作
1.5.如果没有,就继续执行下一个宏任务
重复上面loop
setTimeout(delay=0)=setImmediate:下个Event Loop执行
//宏任务队列:[]
//微任务队列:[promise0]
Promise.resolve()
.then(function() {
console.log("promise0");
})
.then(function() {
console.log("promise5");
});
//定时的setTimeout(delay=0)=setImmediate:下个Event Loop执行
//宏任务队列:[timer1]
//微任务队列:[promise0]
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function() {
console.log("promise2");
});
Promise.resolve().then(function() {
console.log("promise4");
});
}, 0);
//宏任务队列:[timer1,timer2]
//微任务队列:[promise0]
setTimeout(() => {
console.log("timer2");
Promise.resolve().then(function() {
console.log("promise3");
});
}, 0);
//宏任务队列:[timer1,timer2]
//微任务队列:[promise0,promise1]
Promise.resolve().then(function() {
console.log("promise1");
});
//执行start
console.log("start");
//执行当前所有微任务队列:[promise0,promise1]
//执行promise0时将promise5放入了微任务队列:[promise1,promise5]
//接着执行微任务队列:输出promise1,promise5
//当微任务队列为空,开始执行宏任务队列[timer1,timer2]队首的timer1
//执行timer1时碰到了微任务promise2,放进微任务队列[promise2]
//宏任务timer1执行完了,开始执行所有当前所有微任务:[promise2]
//执行promise2完碰到微任务promise4,放进微任务队列:[promise4]
//当前微任务队列不为空,接着执行promise4
//微任务队列为空,接着执行宏任务队列队首[timer2]
//执行timer2时碰到了微任务promise3,放进微任务队列[promise3]
//宏任务timer2执行完了,开始执行所有当前所有微任务:[promise3]
// 打印结果: start promise0 promise1 promise5 timer1 promise2 promise4 timer2 promise3
event loop 与 浏览器更新渲染时机
宏任务 → 微任务 → 渲染更新
浏览器更新渲染会在event loop中的 宏任务 和 微任务 完成后进行,即宏任务 → 微任务 → 渲染更新
(先宏任务 再微任务,然后再渲染更新)
大量宏任务时,可以将DOM->微任务
宏任务队列中,如果有大量任务等待执行时,将dom的变动作为微任务,能更快的将变化呈现给用户
,这样就可以在这一次的事件轮询中更新dom