一个tab有几个进程
- 浏览器进程,负责页面显示,用户交互,子进程管理等
- 渲染进程,将html,css,js转换为网页
- GPU进程,用来渲染UI界面
- 网络进程,负责网页资源加载
- 插件进程,主要负责插件的运行
进程,线程,协程(纤程)
-
一个进程就是一个程序的运行实例
-
线程是依附进程的,而进程中的多线程并行处理能提高运算效率
-
进程中的任意线程执行出错,都会导致整个进程崩溃
-
线程之间共享进程中的数据
-
当一个进程关闭之后,操作系统会回收进程所占用的内存
-
进程之间内容是相互隔离的
-
协程是一种比线程更轻量级的存在,协程不是被操作系统内核所管理,完全由程序控制(也就是在用户态执行)不会像线程切换那样消耗资源!
消息队列和事件循环
- 消息队列是先进先出的执行方式如何处理高优先级的任务,当修改了DOM后如何即时更新,立即执行会影响当前任务的执行时长,以异步消息添加到队尾会影响实时性,消息队列中的任务称为宏任务,每个宏任务中包含了微任务队列,在执行宏任务过程中,如果DOM有变化,那么就会将该变化添加到微任务列表中,这样就不会影响到宏任务的执行,等宏任务主要功能执行完后,执行当前宏任务的微任务,这样就解决了执行效率和实时性问题
- 如何解决单个任务执行时长过久问题,通过回调来让js任务滞后执行
setTimeout缺陷
- 执行不精确,由于同步任务执行过长,setTimeout被分配到了另一个task去执行,setTimeout回调被放入消息队列中并不是立即执行,要等当前任务执行完
function bar() {
console.log(‘bar‘)
}
function foo() {
setTimeout(bar, 0);
for (let i = 0; i < 5000; i++) {
let i = 5+8+8+8
console.log(i)
}
}
foo()
- 嵌套调用时,调用超过5次以上,后面每次调用的间隔时间为4ms
function cb() { setTimeout(cb, 0); }
setTimeout(cb, 0);
-
未激活的页面,setTimtout执行最小间隔是1000ms
-
延时执行时间有最大值,32bit,只有31bit表示数,另外1bit表示符号位
function showName(){
console.log("aaa")
}
var timerID = setTimeout(showName,2147483648);//会被理解调用执行
- this指向window
var name= 1;
var MyObj = {
name: 2,
showName: function(){
console.log(this.name);
}
}
setTimeout(MyObj.showName,1000)
XMLHttpRequest运行机制
宏任务与微任务
- 宏任务控制不精确,2个任务之间添加了其他的系统任务,如果添加的任务执行的时间过长,会影响到后面任务的执行
<!DOCTYPE html>
<html>
<body>
<div id=‘demo‘>
<ol>
<li>test</li>
</ol>
</div>
</body>
<script type="text/javascript">
function timerCallback2(){
console.log(2)
}
function timerCallback(){
console.log(1)
setTimeout(timerCallback2,0)
}
setTimeout(timerCallback,0)
</script>
</html>
异步回调带来的问题
- 异步回调代码逻辑不连续
//执行状态
function onResolve(response){console.log(response) }
function onReject(error){console.log(error) }
let xhr = new XMLHttpRequest()
xhr.ontimeout = function(e) { onReject(e)}
xhr.onerror = function(e) { onReject(e) }
xhr.onreadystatechange = function () { onResolve(xhr.response) }
//设置请求类型,请求URL,是否同步信息
let URL = ‘https://xxx.com‘
xhr.open(‘Get‘, URL, true);
//设置参数
xhr.timeout = 3000 //设置xhr请求的超时时间
xhr.responseType = "text" //设置响应返回的数据格式
xhr.setRequestHeader("X_TEST","time.geekbang")
//发出请求
xhr.send();
改造一下使其变得线性
//makeRequest用来构造request对象
function makeRequest(request_url) {
let request = {
method: ‘Get‘,
url: request_url,
headers: ‘‘,
body: ‘‘,
credentials: false,
sync: true,
responseType: ‘text‘,
referrer: ‘‘
}
return request
}
//[in] request,请求信息,请求头,延时值,返回类型等
//[out] resolve, 执行成功,回调该函数
//[out] reject 执行失败,回调该函数
function XFetch(request, resolve, reject) {
let xhr = new XMLHttpRequest()
xhr.ontimeout = function (e) { reject(e) }
xhr.onerror = function (e) { reject(e) }
xhr.onreadystatechange = function () {
if (xhr.status = 200)
resolve(xhr.response)
}
xhr.open(request.method, URL, request.sync);
xhr.timeout = request.timeout;
xhr.responseType = request.responseType;
//补充其他请求信息
//...
xhr.send();
}
XFetch(makeRequest(‘https://xxx.org‘),
function resolve(data) {
console.log(data)
}, function reject(e) {
console.log(e)
})
- 接口依赖形成回调地狱
XFetch(makeRequest(‘https://aaa.org/?category‘),
function resolve(response) {
console.log(response)
XFetch(makeRequest(‘https://bbb.org/column‘),
function resolve(response) {
console.log(response)
XFetch(makeRequest(‘https://ccc.org‘)
function resolve(response) {
console.log(response)
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
- Promise消灭嵌套,合并多个任务的错误处理
function XFetch(request) {
function executor(resolve, reject) {
let xhr = new XMLHttpRequest()
xhr.open(‘GET‘, request.url, true)
xhr.ontimeout = function (e) { reject(e) }
xhr.onerror = function (e) { reject(e) }
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
resolve(this.responseText, this)
} else {
let error = {
code: this.status,
response: this.response
}
reject(error, this)
}
}
}
xhr.send()
}
return new Promise(executor)
}
var x1 = XFetch(makeRequest(‘https://aaa.org/?category‘))
var x2 = x1.then(value => {
console.log(value)
return XFetch(makeRequest(‘https://bbb.org/column‘))
})
var x3 = x2.then(value => {
console.log(value)
return XFetch(makeRequest(‘https://ccc.org‘))
})
x3.catch(error => {
console.log(error)
})
Promise通过回调函数延迟绑定,回调函数返回值穿透和错误冒泡解决了问题
//创建Promise对象x1,并在executor函数中执行业务逻辑
function executor(resolve, reject){
resolve(100)
}
let x1 = new Promise(executor)
//x1延迟绑定回调函数onResolve
function onResolve(value){
console.log(value)
}
x1.then(onResolve)
function executor(resolve, reject) {
let rand = Math.random();
console.log(1)
console.log(rand)
if (rand > 0.5)
resolve()
else
reject()
}
var p0 = new Promise(executor);
var p1 = p0.then((value) => {
console.log("succeed-1")
return new Promise(executor)
})
var p3 = p1.then((value) => {
console.log("succeed-2")
return new Promise(executor)
})
var p4 = p3.then((value) => {
console.log("succeed-3")
return new Promise(executor)
})
p4.catch((error) => {
console.log("error")
})
console.log(2)
- async await 用线性的书写方式,不阻塞主线程的方式来写异步代码
fetch(‘https://aaa.org‘)
.then((response) => {
console.log(response)
return fetch(‘https://bbb.org/test‘)
}).then((response) => {
console.log(response)
}).catch((error) => {
console.log(error)
})
async function foo(){
try{
let response1 = await fetch(‘https://www.geekbang.org‘)
console.log(‘response1‘)
console.log(response1)
let response2 = await fetch(‘https://www.geekbang.org/test‘)
console.log(‘response2‘)
console.log(response2)
}catch(err) {
console.error(err)
}
}
foo()
- generator 协程可以暂停和恢复执行的特点,用同步的方式来写异步代码
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‘
async function foo() {
console.log(1)
let a = await 100
console.log(a)
console.log(2)
}
console.log(0)
foo()
console.log(3)
异步回调执行过程
- UI线程会从消息队列中取出一个任务,并分析该任务
- 分析过程发现该任务是一个下载请求,那么主线程会将该任务交给网络线程去执行
- 网络线程接到请求之后,便会和服务器服务器端建立连接,并发出下载请求
- 网络线程不断的收到服务器端传过来的数据
- 网络线程每次接收到数据时,都会将设置的回调函数和返回的数据信息,如大小,返回了多少字节,返回的数据存在内存中存在的位置封装成一个新事件,并将该事件放到消息队列中
- UI线程继续循环的读取消息队列中的事件,如果是下载状态的事件,那么UI线程会执行回调函数
- 接收到下载结束事件,UI线程会显示该页面下载完成
永不爆栈的宏任务
function foo() {
setTimeout(foo, 0)
}
foo()
函数内部触发的宏任务比函数内部触发的微任务优先
function bar(){
console.log(‘bar‘)
Promise.resolve().then(
(str) =>console.log(‘micro-bar‘)
)
setTimeout((str) =>console.log(‘macro-bar‘),0)
}
function foo() {
console.log(‘foo‘)
Promise.resolve().then(
(str) =>console.log(‘micro-foo‘)
)
setTimeout((str) =>console.log(‘macro-foo‘),0)
bar()
}
foo()
console.log(‘global‘)
Promise.resolve().then(
(str) =>console.log(‘micro-global‘)
)
setTimeout((str) =>console.log(‘macro-global‘),0)
永不执行的协程
function NeverResolvePromise(){
return new Promise((resolve, reject) => {})
}
async function getResult() {
let a = await NeverResolvePromise()
console.log(a)
}
getResult()
console.log(0)
function* getResult() {
yield ‘getUserID‘
yield ‘getUserName‘
return ‘name‘
}
let result = getResult()
console.log(result.next().value)
console.log(result.next().value)
console.log(result.next().value)
function HaveResolvePromise(){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(100)
}, 0);
})
}
async function getResult() {
console.log(1)
let a = await HaveResolvePromise()
console.log(a)
console.log(2)
}
console.log(0)
getResult()
console.log(3)
一帧内合理分配任务
浏览器每一帧都需要完成哪些任务
- 处理用户的交互
- js解析执行
- 帧开始,窗口尺寸变更
- rAF
- 布局绘制
刷新率
- 表示每秒钟更新多少次显示器的屏幕图像,60Hz,每秒60张画面,刷新率越高画面越流畅
帧率
- 每秒由显卡渲染生成的静态图像数量,单位是FPS,帧率越高,画面更顺畅
帧率与刷新率
- 刷新率与帧数不匹配就会出现卡断
- 60Hz页面要想不卡顿,1s就要执行60帧(FPS)与刷新率保持一致,每一帧1000/60 = 16ms,每一帧就需要控制在16ms内完成!
如果渲染进程生成的帧速比屏幕的刷新率慢,那么屏幕会在两帧中显示同一个画面,如果持续发生,用户就会感到明显的卡顿
如果渲染进程生成的帧速率实际上比屏幕刷新率快,比如当帧速率在 100fps 而刷新率只有 60Hz 的时候,GPU 所渲染的图像并非全都被显示出来,这就会造成丢帧现象。
setTimeout,setInterval无法准确执行
- 没有在16ms完成,帧率低于刷新率,造成2个画面绘制一帧造成卡顿
- setTimeout是由用户控制的,绘制时机很难和VSync时钟保持一致
- 还会出现丢帧现象
requestAnimationFrame
- 它由系统决定回调函数的执行时机,回请求浏览器在下一次重新渲染页面之前执行回调函数,无论设备的刷新率是多少,它会紧跟屏幕刷新一次所需要的时间,例如某一设备的刷新率是 75 Hz,那这时的时间间隔就是 13.3 ms(1 秒 / 75 次),但是也要注意在函数内不要做太多任务,如果太多同步阻塞代码,也会让任务的执行时间变长而掉帧
- requestAnimationFrame用来和VSync时钟保持一致
- 基本稳定在16ms没有掉帧的现象
requestIdleCallback
- 如果一帧内有空闲,会执行
requestIdleCallback(myNonEssentialWork, { timeout: 2000 });
?
// 任务队列
const tasks = [
() => {
console.log("第一个任务");
},
() => {
console.log("第二个任务");
},
() => {
console.log("第三个任务");
},
];
?
function myNonEssentialWork (deadline) {
// 如果帧内有富余的时间,或者超时
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && tasks.length > 0) {
work();
}
?
if (tasks.length > 0)
requestIdleCallback(myNonEssentialWork);
}
?
function work () {
tasks.shift()();
console.log(‘执行任务‘);
}