消息系统

一个tab有几个进程

  1. 浏览器进程,负责页面显示,用户交互,子进程管理等
  2. 渲染进程,将html,css,js转换为网页
  3. GPU进程,用来渲染UI界面
  4. 网络进程,负责网页资源加载
  5. 插件进程,主要负责插件的运行

进程,线程,协程(纤程)

消息系统

  1. 一个进程就是一个程序的运行实例

  2. 线程是依附进程的,而进程中的多线程并行处理能提高运算效率
    消息系统

  3. 进程中的任意线程执行出错,都会导致整个进程崩溃

  4. 线程之间共享进程中的数据
    消息系统

  5. 当一个进程关闭之后,操作系统会回收进程所占用的内存

  6. 进程之间内容是相互隔离的
    消息系统

  7. 协程是一种比线程更轻量级的存在,协程不是被操作系统内核所管理,完全由程序控制(也就是在用户态执行)不会像线程切换那样消耗资源!消息系统

消息队列和事件循环

消息系统

  1. 消息队列是先进先出的执行方式如何处理高优先级的任务,当修改了DOM后如何即时更新,立即执行会影响当前任务的执行时长,以异步消息添加到队尾会影响实时性,消息队列中的任务称为宏任务,每个宏任务中包含了微任务队列,在执行宏任务过程中,如果DOM有变化,那么就会将该变化添加到微任务列表中,这样就不会影响到宏任务的执行,等宏任务主要功能执行完后,执行当前宏任务的微任务,这样就解决了执行效率和实时性问题
  2. 如何解决单个任务执行时长过久问题,通过回调来让js任务滞后执行

setTimeout缺陷

  1. 执行不精确,由于同步任务执行过长,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()

消息系统

  1. 嵌套调用时,调用超过5次以上,后面每次调用的间隔时间为4ms

function cb() { setTimeout(cb, 0); }
setTimeout(cb, 0);
  1. 未激活的页面,setTimtout执行最小间隔是1000ms

  2. 延时执行时间有最大值,32bit,只有31bit表示数,另外1bit表示符号位

function showName(){
  console.log("aaa")
}
var timerID = setTimeout(showName,2147483648);//会被理解调用执行
  1. this指向window

var name= 1;
var MyObj = {
  name: 2,
  showName: function(){
    console.log(this.name);
  }
}
setTimeout(MyObj.showName,1000)

XMLHttpRequest运行机制

消息系统

宏任务与微任务

消息系统

  1. 宏任务控制不精确,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>

异步回调带来的问题

消息系统

  1. 异步回调代码逻辑不连续

//执行状态
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)
    })
  1. 接口依赖形成回调地狱

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)
      })
  1. 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)
  1. 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()

消息系统

  1. 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)

异步回调执行过程

消息系统

  1. UI线程会从消息队列中取出一个任务,并分析该任务
  2. 分析过程发现该任务是一个下载请求,那么主线程会将该任务交给网络线程去执行
  3. 网络线程接到请求之后,便会和服务器服务器端建立连接,并发出下载请求
  4. 网络线程不断的收到服务器端传过来的数据
  5. 网络线程每次接收到数据时,都会将设置的回调函数和返回的数据信息,如大小,返回了多少字节,返回的数据存在内存中存在的位置封装成一个新事件,并将该事件放到消息队列中
  6. UI线程继续循环的读取消息队列中的事件,如果是下载状态的事件,那么UI线程会执行回调函数
  7. 接收到下载结束事件,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)

一帧内合理分配任务

浏览器每一帧都需要完成哪些任务

消息系统

消息系统

  1. 处理用户的交互
  2. js解析执行
  3. 帧开始,窗口尺寸变更
  4. rAF
  5. 布局绘制

刷新率

  1. 表示每秒钟更新多少次显示器的屏幕图像,60Hz,每秒60张画面,刷新率越高画面越流畅

帧率

  1. 每秒由显卡渲染生成的静态图像数量,单位是FPS,帧率越高,画面更顺畅

帧率与刷新率

  1. 刷新率与帧数不匹配就会出现卡断
  2. 60Hz页面要想不卡顿,1s就要执行60帧(FPS)与刷新率保持一致,每一帧1000/60 = 16ms,每一帧就需要控制在16ms内完成!消息系统

如果渲染进程生成的帧速比屏幕的刷新率慢,那么屏幕会在两帧中显示同一个画面,如果持续发生,用户就会感到明显的卡顿

如果渲染进程生成的帧速率实际上比屏幕刷新率快,比如当帧速率在 100fps 而刷新率只有 60Hz 的时候,GPU 所渲染的图像并非全都被显示出来,这就会造成丢帧现象。

setTimeout,setInterval无法准确执行

消息系统

消息系统

  1. 没有在16ms完成,帧率低于刷新率,造成2个画面绘制一帧造成卡顿
  2. setTimeout是由用户控制的,绘制时机很难和VSync时钟保持一致
  3. 还会出现丢帧现象

requestAnimationFrame

消息系统

消息系统

  1. 它由系统决定回调函数的执行时机,回请求浏览器在下一次重新渲染页面之前执行回调函数,无论设备的刷新率是多少,它会紧跟屏幕刷新一次所需要的时间,例如某一设备的刷新率是 75 Hz,那这时的时间间隔就是 13.3 ms(1 秒 / 75 次),但是也要注意在函数内不要做太多任务,如果太多同步阻塞代码,也会让任务的执行时间变长而掉帧
  2. requestAnimationFrame用来和VSync时钟保持一致
  3. 基本稳定在16ms没有掉帧的现象

requestIdleCallback

消息系统
消息系统

  1. 如果一帧内有空闲,会执行
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('执行任务');
}
上一篇:Ajax异步对象XMLHttpRequest属性和方法


下一篇:主席树Ⅱ