欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue源碼之批量異步更新策略的深入解析

 更新時間:2021年05月14日 09:59:58   作者:MiemieWan  
這篇文章主要給大家介紹了關(guān)于vue源碼之批量異步更新策略的相關(guān)資料,關(guān)于vue異步更新是我們?nèi)粘i_發(fā)中經(jīng)常遇到的一個功能,需要的朋友可以參考下

vue異步更新源碼中會有涉及事件循環(huán)、宏任務(wù)、微任務(wù)的概念,所以先了解一下這幾個概念。

一、事件循環(huán)、宏任務(wù)、微任務(wù)

1.事件循環(huán)Event Loop:瀏覽器為了協(xié)調(diào)事件處理、腳本執(zhí)行、網(wǎng)絡(luò)請求和渲染等任務(wù)而定制的工作機(jī)制。

2.宏任務(wù)Task: 代表一個個離散的、獨(dú)立的工作單位。瀏覽器完成一個宏任務(wù),在下一個宏任務(wù)開始執(zhí)行之前,會對頁面重新渲染。主要包括創(chuàng)建文檔對象、解析HTML、執(zhí)行主線JS代碼以及各種事件如頁面加載、輸入、網(wǎng)絡(luò)事件和定時器等。

3.微任務(wù):微任務(wù)是更小的任務(wù),是在當(dāng)前宏任務(wù)執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)。如果存在微任務(wù),瀏覽器會在完成微任務(wù)之后再重新渲染。微任務(wù)的例子有Promise回調(diào)函數(shù)、DOM變化等。

執(zhí)行過程:執(zhí)行完宏任務(wù) => 執(zhí)行微任務(wù) => 頁面重新渲染 => 再執(zhí)行新一輪宏任務(wù)

 任務(wù)執(zhí)行順序例子:

//第一個宏任務(wù)進(jìn)入主線程
console.log('1');
//丟到宏事件隊列中
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
//微事件1
process.nextTick(function() {
    console.log('6');
})
//主線程直接執(zhí)行
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    //微事件2
    console.log('8')
})
//丟到宏事件隊列中
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
 
// 1,7,6,8,2,4,3,5,9,11,10,12

解析:

第一個宏任務(wù) 

  1. 第一個宏任務(wù)進(jìn)入主線程,打印1
  2. setTimeout丟到宏任務(wù)隊列
  3. process.nextTick丟到微任務(wù)隊列
  4. new Promise直接執(zhí)行,打印7
  5. Promise then事件丟到微任務(wù)隊列
  6. setTimeout丟到宏任務(wù)隊列

第一個宏任務(wù)執(zhí)行完,開始執(zhí)行微任務(wù)

  1. 執(zhí)行process.nextTick,打印6
  2. 執(zhí)行Promise then事件,打印8

微任務(wù)執(zhí)行完,清空微任務(wù)隊列,頁面渲染,進(jìn)入下一個宏任務(wù)setTimeout

  1. 執(zhí)行打印2
  2. process.nextTick丟到微任務(wù)隊列
  3. new Promise直接執(zhí)行,打印4
  4. Promise then事件丟到微任務(wù)隊列

第二個宏任務(wù)執(zhí)行完,開始執(zhí)行微任務(wù)

  1. 執(zhí)行process.nextTick,打印3
  2. 執(zhí)行Promise then事件,打印5

微任務(wù)執(zhí)行完,清空微任務(wù)隊列,頁面渲染,進(jìn)入下一個宏任務(wù)setTimeout,重復(fù)上述類似流程,打印出9,11,10,12

二、Vue異步批量更新過程

1.解析:當(dāng)偵測到數(shù)據(jù)變化,vue會開啟一個隊列,將相關(guān)的watcher存入隊列,將回調(diào)函數(shù)存入callbacks隊列,異步執(zhí)行回調(diào)函數(shù),遍歷watcher隊列進(jìn)行渲染。

異步:Vue 在更新 DOM 時是異步執(zhí)行的,只要偵聽到數(shù)據(jù)變化,vue將開啟一個隊列,并緩沖  在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)  的變更。

批量:如果同一個watcher被多次觸發(fā),只會被推入到隊列中一次。去重可以避免不必要的計算和DOM操作。然后在下一個的事件循環(huán)“tick”中,vue刷新隊列執(zhí)行實際工作。

異步策略:Vue的內(nèi)部對異步隊列嘗試使用原生的Promise.then、MutationObserver和 setImmediate,如果執(zhí)行環(huán)境不支持,則會采用 setTimeout(fn, 0) 代替。即會先嘗試使用微任務(wù)方式,不行再用宏任務(wù)方式。

異步批量更新流程圖:

 

三、vue批量異步更新源碼

異步更新:整個過程相當(dāng)于將臭襪子放到盆子里,最后一起洗。

1.當(dāng)一個Data更新時,會依次執(zhí)行以下代碼:

(1)觸發(fā)Data.set()

(2)調(diào)用dep.notify():遍歷所有相關(guān)的Watcher,調(diào)用watcher.update()。 

core/oberver/index.js:

notify () {
    const subs = this.subs.slice()
    // 如果未運(yùn)行異步,則不會在調(diào)度程序中對sub進(jìn)行排序
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // 排序,確保它們按正確的順序執(zhí)行
      subs.sort((a, b) => a.id - b.id)
    }
    // 遍歷相關(guān)watcher,并調(diào)用watcher更新
    for (let i = 0, l = subs.length; i < l; i++) { 
      subs[i].update()
    }
}

(3)執(zhí)行watcher.update(): 判斷是立即更新還是異步更新。若為異步更新,調(diào)用queueWatcher(this),將watcher入隊,放到后面一起更新。

core/oberver/watcher.js:

update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      //立即執(zhí)行渲染
      this.run()
    } else {
      // watcher入隊操作,后面一起執(zhí)行渲染
      queueWatcher(this)
    }
}

(4)執(zhí)行queueWatcher(this): watcher進(jìn)行去重等操作后,添加到隊列中,調(diào)用nextTick(flushSchedulerQueue)執(zhí)行異步隊列,傳入回調(diào)函數(shù)flushSchedulerQueue。

core/oberver/scheduler.js:

function queueWatcher (watcher: Watcher) {
  // has 標(biāo)識,判斷該watcher是否已在,避免在一個隊列中添加相同的 Watcher
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    // flushing 標(biāo)識,處理 Watcher 渲染時,可能產(chǎn)生的新 Watcher。
    if (!flushing) {
      // 將當(dāng)前 Watcher 添加到異步隊列
      queue.push(watcher)
    } else {
      // 產(chǎn)生新的watcher就添加到排序的位置
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    // waiting 標(biāo)識,讓所有的 Watcher 都在一個 tick 內(nèi)進(jìn)行更新。
    if (!waiting) {
      waiting = true
 
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      // 執(zhí)行異步隊列,并傳入回調(diào)
      nextTick(flushSchedulerQueue)
    }
  }
}

 (5)執(zhí)行nextTick(cb): 將傳進(jìn)去的 flushSchedulerQueue 函數(shù)處理后添加到callbacks隊列中,調(diào)用timerFunc啟動異步執(zhí)行任務(wù)。

core/util/next-tick.js:

function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 此處的callbacks就是隊列(回調(diào)數(shù)組),將傳入的 flushSchedulerQueue 方法處理后添加到回調(diào)數(shù)組
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    // 啟動異步執(zhí)行任務(wù),此方法會根據(jù)瀏覽器兼容性,選用不同的異步策略
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

 (6)timerFunc():根據(jù)瀏覽器兼容性,選用不同的異步方式去執(zhí)行flushCallbacks。由于宏任務(wù)耗費(fèi)的時間是大于微任務(wù)的,所以先選用微任務(wù)的方式,都不行時再使用宏任務(wù)的方式,

core/util/next-tick.js:

let timerFunc
 
// 支持Promise則使用Promise異步的方式執(zhí)行flushCallbacks
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // 實在不行再使用setTimeout的異步方式
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

(7)flushCallbacks:異步執(zhí)行callbacks隊列中所有函數(shù)

core/util/next-tick.js:

// 循環(huán)callbacks隊列,執(zhí)行里面所有函數(shù)flushSchedulerQueue,并清空隊列
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

(8)flushSchedulerQueue():遍歷watcher隊列,執(zhí)行watcher.run()

          watcher.run():真正的渲染

function flushSchedulerQueue() {
  currentFlushTimestamp = getNow();
  flushing = true;
  let watcher, id;
 
  // 排序,先渲染父節(jié)點(diǎn),再渲染子節(jié)點(diǎn)
  // 這樣可以避免不必要的子節(jié)點(diǎn)渲染,如:父節(jié)點(diǎn)中 v -if 為 false 的子節(jié)點(diǎn),就不用渲染了
  queue.sort((a, b) => a.id - b.id);
 
  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  // 遍歷所有 Watcher 進(jìn)行批量更新。
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index];
    if (watcher.before) {
      watcher.before();
    }
    id = watcher.id;
    has[id] = null;
    // 真正的更新函數(shù)
    watcher.run();
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== "production" && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1;
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          "You may have an infinite update loop " +
            (watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`),
          watcher.vm
        );
        break;
      }
    }
  }
 
  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice();
  const updatedQueue = queue.slice();
 
  resetSchedulerState();
 
  // call component updated and activated hooks
  callActivatedHooks(activatedQueue);
  callUpdatedHooks(updatedQueue);
 
  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit("flush");
  }
}

(9)updateComponent():watcher.run()經(jīng)過一系列的轉(zhuǎn)圈,執(zhí)行updateComponent,updateComponent中執(zhí)行render(),讓組件重新渲染, 再執(zhí)行_update(vnode) ,再執(zhí)行 patch()更新界面。

(10)_update():根據(jù)是否有vnode分別執(zhí)行不同的patch。

四、Vue.nextTick(callback)

1.Vue.nextTick(callback)作用:獲取更新后的真正的 DOM 元素。

由于Vue 在更新 DOM 時是異步執(zhí)行的,所以在修改data之后,并不能立刻獲取到修改后的DOM元素。為了獲取到修改后的 DOM元素,可以在數(shù)據(jù)變化之后立即使用 Vue.nextTick(callback)。

2.為什么 Vue.$nextTick 能夠獲取更新后的 DOM?

因為Vue.$nextTick其實就是調(diào)用 nextTick 方法,在異步隊列中執(zhí)行回調(diào)函數(shù)。

Vue.prototype.$nextTick = function (fn: Function) {
  return nextTick(fn, this);
};

3.使用 Vue.$nextTick

例子1:

<template>
  <p id="test">{{foo}}</p>
</template>
<script>
 
export default{
  data(){
    return {
      foo: 'foo'
    }
  },
  mounted() {
    let test  = document.querySelector('#test');
    this.foo = 'foo1';
    // vue在更新DOM時是異步進(jìn)行的,所以此處DOM并未更新
    console.log('test.innerHTML:' + test.innerHTML);
 
    this.$nextTick(() => {
      // nextTick回調(diào)是在DOM更新后調(diào)用的,所以此處DOM已經(jīng)更新
      console.log('nextTick:test.innerHTML:' + test.innerHTML); 
    })
  }
}
</script>
執(zhí)行結(jié)果:
test.innerHTML:foo
nextTick:test.innerHTML:foo1

例子2:

<template>
  <p id="test">{{foo}}</p>
</template>
<script>
 
export default{
  data(){
    return {
      foo: 'foo'
    }
  },
  mounted() {
    let test  = document.querySelector('#test');
    this.foo = 'foo1';
    // vue在更新DOM時是異步進(jìn)行的,所以此處DOM并未更新
    console.log('1.test.innerHTML:' + test.innerHTML); 
 
    this.$nextTick(() => {
      // nextTick回調(diào)是在DOM更新后調(diào)用的,所以此處DOM已經(jīng)更新
      console.log('nextTick:test.innerHTML:' + test.innerHTML); 
    })
 
    this.foo = 'foo2';
    // 此處DOM并未更新,且先于異步回調(diào)函數(shù)前執(zhí)行
    console.log('2.test.innerHTML:' + test.innerHTML); 
  }
}
</script>
執(zhí)行結(jié)果:
1.test.innerHTML:foo
2.test.innerHTML:foo
nextTick:test.innerHTML:foo2
 

例子3:

<template>
  <p id="test">{{foo}}</p>
</template>
<script>
 
export default{
  data(){
    return {
      foo: 'foo'
    }
  },
  mounted() {
    let test  = document.querySelector('#test');
    this.$nextTick(() => {
      // nextTick回調(diào)是在觸發(fā)更新之前就放入callbacks隊列,
      // 壓根沒有觸發(fā)watcher.update以及以后的一系列操作,所以也就沒有執(zhí)行到最后的watcher.run()實行渲染
      // 所以此處DOM并未更新
      console.log('nextTick:test.innerHTML:' + test.innerHTML);
    })
    this.foo = 'foo1';
    // vue在更新DOM時是異步進(jìn)行的,所以此處DOM并未更新
    console.log('1.test.innerHTML:' + test.innerHTML); 
    this.foo = 'foo2';
    // 此處DOM并未更新,且先于異步回調(diào)函數(shù)前執(zhí)行
    console.log('2.test.innerHTML:' + test.innerHTML); 
  }
}
</script>
執(zhí)行結(jié)果:
1.test.innerHTML:foo
2.test.innerHTML:foo
nextTick:test.innerHTML:foo

4、 nextTick與其他異步方法

nextTick是模擬的異步任務(wù),所以可以用 Promise 和 setTimeout 來實現(xiàn)和 this.$nextTick 相似的效果。

例子1:

<template>
  <p id="test">{{foo}}</p>
</template>
<script>
 
export default{
  data(){
    return {
      foo: 'foo'
    }
  },
  mounted() {
    let test  = document.querySelector('#test');
    this.$nextTick(() => {
      // nextTick回調(diào)是在觸發(fā)更新之前就放入callbacks隊列,
      // 壓根沒有觸發(fā)watcher.update以及以后的一系列操作,所以也就沒有執(zhí)行到最后的watcher.run()實行渲染
      // 所以此處DOM并未更新
      console.log('nextTick:test.innerHTML:' + test.innerHTML); 
    })
    this.foo = 'foo1';
    // vue在更新DOM時是異步進(jìn)行的,所以此處DOM并未更新
    console.log('1.test.innerHTML:' + test.innerHTML); 
    this.foo = 'foo2';
    // 此處DOM并未更新,且先于異步回調(diào)函數(shù)前執(zhí)行
    console.log('2.test.innerHTML:' + test.innerHTML); 
 
    Promise.resolve().then(() => {
      console.log('Promise:test.innerHTML:' + test.innerHTML); 
    });
    setTimeout(() => {
        console.log('setTimeout:test.innerHTML:' + test.innerHTML);
    });
  }
}
</script>
執(zhí)行結(jié)果:
1.test.innerHTML:foo
2.test.innerHTML:foo
nextTick:test.innerHTML:foo
Promise:test.innerHTML:foo2
setTimeout:test.innerHTML:foo2

例子2:

​
<template>
  <p id="test">{{foo}}</p>
</template>
<script>
 
export default{
  data(){
    return {
      foo: 'foo'
    }
  },
  mounted() {
    let test  = document.querySelector('#test');
    // Promise 和 setTimeout 依舊是等到DOM更新后再執(zhí)行
    Promise.resolve().then(() => {
      console.log('Promise:test.innerHTML:' + test.innerHTML); 
    });
    setTimeout(() => {
        console.log('setTimeout:test.innerHTML:' + test.innerHTML);
    });
    this.$nextTick(() => {
      // nextTick回調(diào)是在觸發(fā)更新之前就放入callbacks隊列,
      // 壓根沒有觸發(fā)watcher.update以及以后的一系列操作,所以也就沒有執(zhí)行到最后的watcher.run()實行渲染
      // 所以此處DOM并未更新
      console.log('nextTick:test.innerHTML:' + test.innerHTML); 
    })
    this.foo = 'foo1';
    // vue在更新DOM時是異步進(jìn)行的,所以此處DOM并未更新
    console.log('1.test.innerHTML:' + test.innerHTML); 
    this.foo = 'foo2';
    // 此處DOM并未更新,且先于異步回調(diào)函數(shù)前執(zhí)行
    console.log('2.test.innerHTML:' + test.innerHTML); 
  }
}
</script>
執(zhí)行結(jié)果:
1.test.innerHTML:foo
2.test.innerHTML:foo
nextTick:test.innerHTML:foo
Promise:test.innerHTML:foo2
setTimeout:test.innerHTML:foo2

總結(jié)

到此這篇關(guān)于vue源碼之批量異步更新策略的文章就介紹到這了,更多相關(guān)vue批量異步更新策略內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論