本文将以chrome为基准讲述浏览器的多进程架构、事件循环、移动端webview、浏览器渲染原理以及浏览器下的JS。
并发
并行
串行 指任务按顺序一个一个的执行,只有当前一个任务执行完成,才能执行下一个任务。
并发与并行的关系
其他:
进程与线程的区别
协程与线程的区别
使用协程的示例
function* genDemo() { console.log(" 开始执行第一段 ") yield 'generator 2' console.log(" 开始执行第二段 ") yield 'generator 2' console.log(" 开始执行第三段 ") yield 'generator 2' console.log(" 执行结束 ") return 'generator 2' } console.log('main 0') let gen = genDemo() console.log(gen.next().value) console.log('main 1') console.log(gen.next().value) console.log('main 2') console.log(gen.next().value) console.log('main 3') console.log(gen.next().value) console.log('main 4')
jsasync function foo() {
console.log(1)
let a = await 100
console.log(a)
console.log(2)
}
console.log(0)
foo()
console.log(3)
父协程: A协程启动B协程,那么A是B的父协程
所谓死锁是值多个进程在运行过程中因争抢资源而造成的一种僵局,当线程处于这种僵持的状态时,无外力的作用,他们将无法再向前推进。系统中的资源可以分为两类
产生死锁的原因:
产生死锁的必要条件
预防死锁的方法:
先贴一张图,大致了解一下浏览器的组成
下面将从浏览器的发展历程捋一捋,了解浏览器架构为什么这么设计
单进程是指浏览器内所有功能和模块(网络、插件、JS运行环境、渲染引擎和页面)都是运行在一个进程中
产生的问题: 不稳定 不安全 不流畅
那么多进程浏览器是如何解决上述问题的?
目前的多进程架构
关于渲染进程,也可能会出现多个页面共享同一个渲染进程中
虽然多进程模型提升了浏览器的稳定性、流畅性和安全性,同样也带来了一些问题
chrome团队一直在寻找一种弹性方案,既可以解决资源占用高的问题,也可以解决复杂体系架构的问题----》面向服务的架构
chrome整体架构会朝向现在操作系统所采用的“面向服务架构”方向发展,原来的各模块会重构成独立的服务,每个服务都会在独立的进程中运行,访问服务必须使用定义好的接口,通过IPC来通信,从而构建一个更内聚、松耦合易于维护和扩展的系统
浏览器渲染引擎(浏览器内核)有两个最主要的部分 渲染引擎和JS引擎 它决定了如何现实网页内容及网页格式的信息
主流浏览器的引擎列表
浏览器 | 渲染内核 | JS引擎 |
---|---|---|
IE/Edge | Trident(<10) ; EdgeHTML | JScript(<IE9); Chakra(IE9+及Edge) |
Safari | Webkit/Webkit2 | JSCore/Nitro(4+) |
Chrome | Chromium(WebKit); Blink | V8 |
FireFox | Gecko | SpiderMonkey(<3.0); TraceMonkey(<3/6); JaegerMonkey(4.0+) |
Opera | Presto; Blink | Futhark(9.5~10.2) CaraKan(10.5) |
补充知识点:
uiwebview 目前已经推出历史舞台
2020年4月起App Store将不再接受使用UIWebView的新App上架、2020年12月起将不再接受使用UIWebView的App更新。
wkwebview的问题
wkwebview的好处
android系统的webview分系统webview和x5两种
android系统的webview即 google的 Android system webview
,它自带手机rom中,所有依赖系统webview的应用都调用这个webview
如何查看android机webview系统版本?
Android System Webview
手机默认浏览器和webview的区别
Android System Webview
Android机app内嵌h5更换 Webview方案
Android System Webview
,应用宝、华为应用市场、小米应用市场均可以下载这个apk, 可以设置wifi下自动更新本段将以JS的发展讲起浏览器事件循环机制为什么这样设计。
首先JS诞生之处 是为了一个需求:解决表单校验问题, 那个年代网络很慢, 为了避免用户表单时,因后端校验失败,而反复等待网路的问题,想通过脚本在前端校验通过在提交表单,JS的作者把js设计的非常简单(单线程、没有块级作用域)。
Q:为什要设计成单线程呢? A:简单!如果设计成多线程,那么就想后端操作数据库一样,需要引入锁和事物等机制。
单线程带来的问题就是JS阻塞DOM渲染,DOM解析渲染阻塞JS。
有三种方案
缺点: 并不是所有的任务都是在执行前统一安排好的,大部分情况下,新的任务是在线程运行过程中产生的。
虽然引入了事件循环,可以在执行过程中接受新的任务。但是所有的任务都是来自线程内部的。如果另外一个线程(比如IO线程)想让主线程执行一个任务,利用第二版线程模型是无法做到的。
渲染进程专门有一个IO线程用来接收其他进程传进来的消息,接收到消息后会将这些消息组装成任务发送给渲染主进程。
消息队列中有哪些任务类型呢?
比如监听DOM节点变化(增删改),然后根据这些变化来处理响应的业务逻辑。如果采用同步通知的方式,会影响到当前任务的执行效率,如果采用异步方式会影响到监控的实时性 通常我们把消息队列中的任务称为宏任务,每个宏任务中都包含一个微任务队列,在执行宏任务的过程中,如果DOM有变化,就会将该变化添加到微任务队列中,这样就不会影响宏任务继续执行,因而解决了执行效率的问题。 等到宏任务中的主要任务都执行完成之后,这时候渲染引擎并不着急去执行下一个宏任务,而是执行当前宏任务中的微任务因为DOM变化都保存在微任务中,这样也就解决了实时性问题。
JavaScript可以通过回调功能来规避这种问题,也就是让要执行的JavaScript滞后执行。
一般情况下浏览器接受到消息(DOM解析、JS执行、触发垃圾回收、窗口改变)会将事件添加到消息队列中,事件循环系统就会按照消息队列中的顺序来执行事件。
但是定时器这个比较特别,不能直接添加到消息队列中。其实浏览器中还有一个延时队列,存储了包括定时器及chromium内部一些需要延时执行的任务。
具体实现是 有一个ProcessDelayTask函数(专门用来处理延时执行任务函数的),处理完消息队列中的任务就会执行该函数,该函数会根据发起时间和延迟时间计算出要到期的任务,然后一次执行这些到期的任务。执行完成后再继续下一个循环过程。
设置一个定时器,JavaScript 引擎会返回一个定时器的 ID,在没有执行前可以通过clearTimeout函数取消该定时器
由此,setTimeout会有一些问题
setTimeout(MyObj.showName.bind(MyObj), 1000)
简单总结:
宏任务:
# | 浏览器 | Node |
---|---|---|
I/O | ✅ | ✅ |
setTimeout | ✅ | ✅ |
setInterval | ✅ | ✅ |
setImmediate | ❌ | ✅ |
requestAnimationFrame | ✅ | ❌ |
UI交互事件 | ✅ | ❌ |
postMessage | ✅ | ❌ |
补充说明
requestAnimationFrame
姑且也算是宏任务吧,requestAnimationFrame
在MDN的定义为,下次页面重绘前所执行的操作,而重绘也是作为宏任务的一个步骤来存在的,且该步骤晚于微任务的执行微任务
# | 浏览器 | Node |
---|---|---|
process.nextTick | ❌ | ✅ |
MutationObserver | ✅ | ❌ |
Promise.then catch finally | ✅ | ✅ |
await 后面 | ❌ | ✅ |
Object.observe | ❌ | ✅ |
渐进式
+ Web应用
。思考: 相对于APP,web缺少哪些能力?
针对上述问题 PWA提出 了两种解决方案
早期有个AppCache的方案,因暴露问题比较多&多方吐槽,最后被废弃了
Service Worker来自于Web Worker的一个核心思想。(Web Worker是在渲染进程中在开辟一个新线程它的生命周期是和页面关联的)。区别有以下两点
离线推送 消息推送也是通过Service Worker来实现的。因为消息推送过程中,浏览器页面或许并没有启动,这是就需要ServiceWorker来接收服务器推送的结果,并将消息一定程度展示给用户
安全
为避免HTTP明文传输存在的被窃听、篡改和劫持的风险,所以Service Worker必须采用https协议或者运行或在localhost域名下运行
除此之外 ServiceWoker 还需要同时支持Web页面默认的安全策略、存储同源策略、内容安全策略等
Service Worker生命周期
特点:
用途
manifest.json
导航栏 会出现 “安装”并打开
除了http浏览器缓存外,还有Server Worker
缓存,但是SW
缓存如果使用不当会造成永久无法更新问题, 只能更换域名 重定向了,下面是一个示例
jsconst CACHE_NAME = 'project-xx-cache'
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll([
// ... 要缓存资源
]);
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
if (response) {
return response;
}
return fetch(event.request).then(function (response) {
if (!response || response.status !== 200) {
return response;
}
// 缓存ajax
const toCache = response.clone();
caches.open(CACHE_NAME).then(function (cache) {
cache.put(event.request, toCache);
});
return response;
});
}
)
);
});
如果你的ServerWorker.js
没有被浏览器或代理服务器强缓存,那么还有补救错误,代价是需要刷新浏览器两次。
用户第一次刷新:等待页面加载完成,新的serverworker要去卸载老的servicework及缓存清理,但由于页面已经加载完成这时候老的缓存依旧有效
用户第二次刷新:启动新的serviceworker 这是才看到页面更新,下面是补救代码
jsconst CACHE_NAME = 'project-xx-cache'
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
return cache.addAll([
// 你要缓存的资源
]);
}),
);
self.skipWaiting(); // 1. 立即接管
});
// 2. 激活Service Worker并清理旧缓存
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cache) => {
if (cache !== PANDORA_NEXT_CACHE_NAME) {
console.log("Service Worker: Clearing Old Cache");
return caches.delete(cache);
}
}),
);
}),
);
self.clients.claim(); // 3. 接管旧的Service Worker控制的客户端
});
self.addEventListener("fetch", function (event) {
// 如果需要你可以在这里限制那些资源不走sw缓存
// if () {
// return event.respondWith(fetch(event.request));
// }
event.respondWith(
caches.match(event.request).then(function (response) {
if (response) {
return response;
}
return fetch(event.request).then(function (response) {
if (!response || response.status !== 200) {
return response;
}
const toCache = response.clone();
const cacheControl = response.headers.get("cache-control");
// 排除 no-cache不使用强缓存 no-store 不缓存 的情况
if (
cacheControl &&
!cacheControl.includes("no-cache") &&
!cacheControl.includes("no-store")
) {
caches.open(CACHE_NAME).then(function (cache) {
cache.put(event.request, toCache);
});
}
return response;
});
}),
);
});
如果你一上来用的就是这个代码, 那么serverworker就完美实现了缓存,也不用担心更新问题了。
本文作者:郭敬文
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!