JavaScript的Event Loop概念
JavaScript 的 Event Loop(事件循环)是其执行模型的核心机制,确保了异步操作和事件的处理能够在单线程环境下顺利进行。让我们详细解释 Event Loop 的工作原理,并探讨在 setTimeout
和 Promise
同时存在时,谁先执行。
Event Loop 的工作原理
-
调用栈(Call Stack):
- JavaScript 使用调用栈来执行代码。当函数被调用时,它会被推入栈中,函数执行完毕后会从栈中弹出。
-
任务队列(Task Queue / Callback Queue):
- 任务队列存储需要被执行的异步任务。常见的任务有
setTimeout
、setInterval
和 I/O 事件处理等。
- 任务队列存储需要被执行的异步任务。常见的任务有
-
微任务队列(Microtask Queue / Job Queue):
- 微任务队列主要用于处理 Promise 的回调函数。微任务具有比宏任务(如
setTimeout
)更高的优先级。
- 微任务队列主要用于处理 Promise 的回调函数。微任务具有比宏任务(如
-
事件循环(Event Loop):
- 事件循环不断检查调用栈是否为空。如果调用栈为空,事件循环会从任务队列中取出任务执行。
setTimeout
和 Promise
的执行顺序
在 JavaScript 中,当 setTimeout
和 Promise
同时存在时,它们的执行顺序受以下因素影响:
-
宏任务(Macrotask):
setTimeout
属于宏任务。宏任务在任务队列中执行,并且在每次事件循环的宏任务阶段执行。
-
微任务(Microtask):
- Promise 的回调函数(如
then
、catch
、finally
)属于微任务。微任务会在当前任务执行完成后,调用栈为空时立即执行。
- Promise 的回调函数(如
执行顺序
-
同步代码:
- 直接在调用栈中执行。
-
微任务:
- 在当前同步代码执行完毕后,且在任何宏任务之前,微任务会被执行。
-
宏任务:
- 之后会处理宏任务队列中的任务,如
setTimeout
。
- 之后会处理宏任务队列中的任务,如
示例代码
console.log('Start');setTimeout(() => {console.log('Timeout');
}, 0);Promise.resolve().then(() => {console.log('Promise');
});console.log('End');
解释:
console.log('Start')
被立即执行。setTimeout
回调函数被设置为 0 毫秒后执行,并且会被添加到宏任务队列中。Promise.resolve().then
的回调函数被添加到微任务队列中。console.log('End')
被立即执行。
执行顺序:
console.log('Start')
- 立即执行。console.log('End')
- 立即执行。- 微任务(
Promise
) - 在当前执行上下文完成后,微任务会被处理,打印Promise
。 - 宏任务(
setTimeout
) - 微任务执行完后,事件循环会处理宏任务,打印Timeout
。
所以,Promise 的回调函数会在 setTimeout
的回调函数之前执行,即使 setTimeout
的延迟时间设置为 0 毫秒。微任务总是会在宏任务之前执行,这是 Event Loop 的设计决定的。
总结
- Event Loop 确保了 JavaScript 的异步操作能够在单线程环境下正确执行。
- 微任务(如 Promise 的回调)优先于 宏任务(如
setTimeout
)执行。 - 当
setTimeout
和Promise
同时存在时,Promise 的回调会先于setTimeout
的回调执行。
setTimout中执行死循环和promise.then执行死循环哪个会卡死
都会卡死,两者的主要区别在于它们的任务队列优先级和影响范围:
-
setTimeout
中的死循环:- 优先级:
setTimeout
的回调属于宏任务队列。宏任务在微任务(Promise.then
)之后执行。 - 影响:当主线程在执行
setTimeout
回调中的死循环时,所有其他宏任务和微任务都会被延迟,直到当前的宏任务(即死循环)完成。
- 优先级:
-
Promise.then
中的死循环:- 优先级:
Promise.then
的回调属于微任务队列。微任务在当前执行栈完成后、下一个宏任务之前执行。 - 影响:当主线程在执行
Promise.then
回调中的死循环时,所有微任务会被阻塞,因此后续的微任务和宏任务(如setTimeout
)也会被延迟,直到当前的微任务(即死循环)完成。
- 优先级:
简单来说,setTimeout
中的死循环会影响所有的任务,包括微任务和宏任务,但 Promise.then
中的死循环主要影响微任务的执行,并间接影响宏任务的开始。
什么是CSP
Content Security Policy (CSP) 通过定义明确的资源加载策略来防止 XSS(跨站脚本攻击)。它的核心思想是限制网页能够从哪些来源加载资源,如脚本、样式、图片等。以下是 CSP 如何实现防止 XSS 的具体机制:
1. 限制脚本来源
CSP 允许开发者指定哪些来源的脚本可以被加载和执行。这通过 script-src
指令来实现。例如:
Content-Security-Policy: script-src 'self'
这条策略表示只允许从当前网站加载脚本。其他来源(例如,外部广告脚本、第三方分析脚本)将被禁止,从而减少了 XSS 攻击的可能性。
2. 禁用内联脚本和事件处理器
CSP 可以通过禁用内联脚本和内联事件处理器来防止攻击者通过注入恶意脚本来执行 XSS 攻击。通过使用 'strict-dynamic'
和 'nonce-<random>'
或 'hash-<base64>'
,可以防止未授权的内联脚本执行。
-
'nonce-<random>'
:允许只有在页面生成时设置的特定 nonce 值的脚本执行。例如:html复制代码<script nonce="random123">console.log('safe script');</script>
CSP 头部设置为:
http复制代码Content-Security-Policy: script-src 'self' 'nonce-random123'
-
'strict-dynamic'
:允许在加载脚本时,只有在严格模式下(如来自授权来源的脚本)才执行该脚本。这会进一步限制未被授权的脚本的执行。
3. 限制样式来源
类似于脚本的限制,CSP 允许限制样式表的来源,通过 style-src
指令。例如:
Content-Security-Policy: style-src 'self'
这条策略会阻止加载来自未授权来源的样式表,减少了 CSS 中可能存在的 XSS 漏洞。
4. 阻止不安全的资源
CSP 可以阻止加载不安全的资源,如不加密的 HTTP 请求。通过设置 upgrade-insecure-requests
指令,所有的 HTTP 请求会被自动升级为 HTTPS,减少中间人攻击的风险:
Content-Security-Policy: upgrade-insecure-requests
5. 资源完整性检查
CSP 允许开发者为脚本和样式定义哈希值或使用 integrity
属性进行完整性检查。这确保了加载的资源未被篡改。
6. 防止数据注入
通过 frame-ancestors
指令,可以指定允许嵌套页面的来源,这有助于防止点击劫持攻击(clickjacking)。例如:
Content-Security-Policy: frame-ancestors 'none'
这表示禁止任何页面嵌套当前页面。
总结
CSP 通过细粒度的控制策略限制网页加载和执行的资源,防止了恶意脚本和数据注入,从而大大减少了 XSS 攻击的风险。开发者可以通过设置和调整 CSP 策略来保护他们的应用免受各种攻击。
CSP 不能阻止浏览器插件的注入。浏览器插件通常有更高的权限,可以绕过 CSP 的限制。插件的代码运行在浏览器的不同上下文中,因此 CSP 对它们的行为没有直接影响。