// manifest.json
"host_permissions": ["https://www.exmaple.com/*"],
"declarative_net_request": {
"rule_resources": [{
"id": "sample_rules",
"enabled": true,
"path": "sample_rules.json"
}]
},
"permissions": ["declarativeNetRequest", "declarativeNetRequestFeedback"]
// sample_rules.json
[
{
"id": 1,
"priority": 1,
"condition": {
"urlFilter": "*.exmaple.com/*",
"resourceTypes": ["xmlhttprequest"]
},
"action": {
"type": "modifyHeaders",
"responseHeaders": [{
"header": "Access-Control-Allow-Origin",
"operation": "set",
"value": "*"
}]
}
}
]
最新的 Chrome 浏览器包括:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程
- 浏览器进程:主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
- 渲染进程:核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的页面,排版引擎 Blink 和 Javascript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
- GPU 进程:GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。
- 网络进程:主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
- 插件进程:主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响
渲染进程中的线程
- GUI 渲染线程:负责渲染页面,解析 html 和 CSS、构建 DOM 树、CSSOM 树、渲染树、和绘制页面,重绘重排也是在该线程执行
- JS 引擎线程:一个 tab 页中只有一个 JS 引擎线程(单线程),负责解析和执行 JS。它 GUI 渲染进程不能同时执行,只能一个一个来,如果 JS 执行过长就会导致阻塞掉帧
- 计时器线程:指 setInterval 和 setTimeout,因为 JS 引擎是单线程的,所以如果处于阻塞状态,那么计时器就会不准了,所以需要单独的线程来负责计时器工作
- 异步 http 请求线程: XMLHttpRequest 连接后浏览器开的一个线程,比如主线程中需要发送数据请求,就会把这个任务交给异步 HTTP 请求线程去执行,等请求数据返回之后,再将 callback 里需要执行的 JS 回调交给 JS 引擎线程去执行。也就是说,浏览器才是真正执行发送请求这个任务的角色,而 JS 只是负责执行最后的回调处理。所以这里的异步不是 JS 自身实现的,而是浏览器为其提供的能力。
- 事件触发线程:主要用来控制事件循环,比如 JS 执行遇到计时器,AJAX 异步请求等,就会将对应任务添加到事件触发线程中,在对应事件符合触发条件触发时,就把事件添加到待处理队列的队尾,等 JS 引擎处理
JavaScript 在遇到异步任务时,会将此任务交给其他线程来执行(比如遇到 setTimeout 任务,会交给定时器触发线程去执行,待计时结束,就会将定时器回调任务放入任务队列等待主线程来取出执行),主线程会继续执行后面的同步任务。
对于微任务,比如 promise.then,当执行 promise.then 时,浏览器引擎不会将异步任务交给其他浏览器的线程去执行,而是将任务回调存在一个队列中,当执行栈中的任务执行完之后,就去执行 promise.then 所在的微任务队列。
所以,宏任务和微任务的本质区别如下:
- 微任务:不需要特定的异步线程去执行,没有明确的异步任务去执行,只有回调;
- 宏任务:需要特定的异步线程去执行,有明确的异步任务去执行,有回调;
Node.js 中的 Event Loop 和浏览器中的是完全不相同的东西。Node.js 使用 V8 作为 js 的解析引擎,而 I/O 处理方面使用了自己设计的 libuv,libuv 是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的 API,事件循环机制也是它里面的实现的。
libuv 引擎中的事件循环分为 6 个阶段,它们会按照顺序反复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段。
- timers 阶段:执行 timer(setTimeout、setInterval)的回调,由 poll 阶段控制;
- I/O callbacks 阶段:主要执行系统级别的回调函数,比如 TCP 连接失败的回调;
- idle, prepare 阶段:仅 Node.js 内部使用,可以忽略;
- poll 阶段:轮询等待新的链接和请求等事件,执行 I/O 回调等;
- check 阶段:执行 setImmediate() 的回调;
- close callbacks 阶段:执行关闭请求的回调函数,比如 socket.on('close', ...)
注意:上面每个阶段都会去执行完当前阶段的任务队列,然后继续执行当前阶段的微任务队列,只有当前阶段所有微任务都执行完了,才会进入下个阶段,这里也是与浏览器中逻辑差异较大的地方。
其中,这里面比较重要的就是第四阶段:poll,这一阶段中,系统主要做两件事:
- 回到 timer 阶段执行回调
- 执行 I/O 回调
1、在进入该阶段时如果没有设定了 timer 的话,会出现以下情况: (1)如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制; (2)如果 poll 队列为空时,会出现以下情况:
- 如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调;
- 如果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去;
2、当设定了 timer 且 poll 队列为空,则会判断是否有 timer 超时,如果有的就会回到 timer 阶段执行回调。
- node 中常见的宏任务:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作等。
- node 中常见的微任务:process.nextTick、new Promise().then(回调)等。
setTimeout(() => {
console.log("timeout");
}, 0);
Promise.resolve().then(() => {
console.error("promise");
});
process.nextTick(() => {
console.error("nextTick");
});
// nextTick
// promise
// timeout
Chrome DevTools 控制台上有一个的 API 叫 queryObjects(),它可以从原型树上反查所有直接或间接的继承了某个对象的其它对象。 比如 queryObjects(Array.prototype)可以拿到所有的数组对象,queryObjects(Object.prototype)则基本上可以拿到页面里的所有对象了(除了继承自 Object.create(null)的对象之外)。而且关键是这个 API 会在内存里搜索对象前先进行一次垃圾回收。
- queryObjects(Promise): Returns all Promises.
- queryObjects(HTMLElement): Returns all HTML elements.
- queryObjects(foo): where foo is a function name. Returns all objects that were instantiated via new foo().
queryObjects(Constructor) 可以去查找 currently-selected execution context 中指定 Constructor 的所有实例。
execution context 是代码的执行环境,默认为 top。其他的 execution context 可以来自内嵌的 iframe、Extensions、Service Worker。