hello,你們好,我是 132,很久不賤……vue
今天給你們帶來一篇源碼解析的文章,emm 是關於 vue3 的,vue3 源碼放出後,已經有不少文章來分析它的源碼,我以爲很快又要爛大街了,哈哈react
不過今天我要解析的部分是已經被廢除的 time slicing 部分,這部分源碼曾經出如今 vue conf 2018 的視頻中,可是源碼已經被移除掉了,以後可能也不會有人關注,因此應該不會爛大街git
閱讀源碼以前,須要先進行打包,打包出一份乾淨可調試的文件很重要github
vue3 使用的 rollup 進行打包,咱們須要先對它進行改造面試
import cleanup from 'rollup-plugin-cleanup'
plugins: [
cleanup() //增長了一個 cleanup 插件
tsPlugin,
aliasPlugin,
createReplacePlugin(isProductionBuild, isBunlderESMBuild, isCompat),
...plugins
],
複製代碼
增長 cleanup 插件主要目的是打包出無註釋的文件數組
以上,是我我的閱讀源碼的習慣,我以爲註釋和類型的做用就是礙眼的,因此先去掉再說app
咱們在讀源碼以前,須要先實現一個正確用例,可是我讀的這個版本的源碼,仍是 class 的,怎麼辦?dom
這個時候咱們能夠根據測試用例來猜想並給出代碼異步
function block () {
const start = performance.now()
while (performance.now() - start < 2) {
}
}
class Test extend Component {
render (props) {
block()
return h('li', props.msg)
}
}
class App extend Component {
msg = ''
render () {
const list = []
for (let i = 0; i < 200; i++) {
list.push(h(Test, { key: i, msg: this.msg }))
}
return [
h('input', {
onInput: e => {
this.msg = e.target.value
}
}),
h('div',list)
]
}
}
複製代碼
很好,如今咱們有了一個爭取,簡單的用例了,接下來就是一股腦調試函數
因爲我在 fre 中也實現了時間切片,因此我對它很是瞭解,我知道它的做用原理,因此咱們直接搜索宏任務,哈,果真有
window.addEventListener('message', event => {
if (event.source !== window || event.data !== key) {
return;
}
flushStartTimestamp = getNow();
try {
flush();
}
catch (e) {
handleError(e);
}
}, false);
function flushAfterMacroTask() {
window.postMessage(key, `*`);
}
複製代碼
這段代碼很是容易理解,就是在宏任務隊列裏執行了 flush 函數,繼續
而後關鍵就來了
function flush() {
let job;
while (true) {
job = stageQueue.shift();
if (job) {
stageJob(job);
}
else {
break;
}
{
const now = getNow();
if (now - flushStartTimestamp > frameBudget && job.expiration > now) {
break; // 此處爲關鍵,意思是超過16ms,或者任務過時,跳出循環
}
}
}
... 如下代碼省略...
複製代碼
上面的循環很關鍵,它作的事情很簡單的,從 stageQueue 裏出棧一個任務,而後執行 stateJob
stateJob 作的事情很簡單,就是往 commitQueue 裏 push 這個任務
function stageJob(job) {
if (job.ops.length === 0) {
currentJob = job;
job.cleanup = job();
currentJob = null;
commitQueue.push(job); //重點在這裏
job.status = 2;
}
}
複製代碼
到目前爲止,咱們源碼讀了一丟丟,可是已經幾乎讀完了能夠說
它的本質就是,在宏任務中,stageQueue 做爲低優先級任務隊列,不斷的出棧,而後分批次(16ms 的閾值)入棧到 commitQueue 裏
呼,其實若是不是寫文章,就能夠到此爲止了,可是寫文章爲了湊字數嘛,咱們繼續
上面咱們已經知道了兩個隊列,stageQueue 和 commitQueue,可是並不知道他們裏面都是什麼東西
是什麼東西被調度的呢?打印一下,你就知道:
console.log(stageQueue,commitQueue)
複製代碼
得出的結果是
function mountComponentInstance(){...}
複製代碼
看名字就知道是組件掛載函數,固然組件更新和卸載的函數也是同理
到如今,咱們也知道了參與調度的是組件掛載更新的函數,因此本質上,vue 的時間切片的基本單位是組件,也就是說,若是你的組件掛載須要一個小時,那你仍然要卡一小時
剩下的內容純屬湊字數,就是除了核心調度以外的東西
好比 commitQueue 是操做 dom 的,那它咋個操做
function commitJob(job) {
const { ops, postEffects } = job;
for (let i = 0; i < ops.length; i++) {
applyOp(ops[i]); // 重點在這裏
}
if (postEffects) {
postEffectsQueue.push(...postEffects);
}
resetJob(job);
job.status = 0;
}
複製代碼
如上,拿到 ops,而後進行操做,咱們看一下 ops 是啥就好了
[<div></div>, <li></li>, function CreactElement(){}]
複製代碼
湊合湊合,是個數組,包含了 dom 操做的方法和被操做的元素
而後這個過程是同步完成的,也就是所謂的高優先級任務,必須等到完全收集完畢,才能夠循環執行它
作完這個,postEffectQueue 主要是一些額外的反作用和清理工做,我實在湊字數無能,就不打印了
最後咱們用最直白的話,總結一下:
在宏任務隊列中,不斷的從 stageQueue 分批次(16ms)將組件的函數轉移到 commitQueue 裏,轉移完了,同步操做 dom
原理其實仍是利用了宏任務隊列,其實如今 vue 的作法和 fre 也有一點點相似,fre 是在宏任務中,儘量更多的去訪問 reconcile 大循環
如開頭提到的,time slicing 這部份內容已經在 master 分支被移除了,關於爲何廢除,我特意發了 issue,能夠戳這裏:(天啊,我和尤終於能夠和平地進行交談了)
簡單說,就是 time slicing 的收益不大,除了 issue 中提到的,它自己的場景就少的可憐
也由於 vue 如今的實現,因爲調度的基本單位是組件,因此它仍然會由於組件內部的邏輯而被阻斷
好比我把用例中用於阻斷的 block 函數改成 1s,就已經完全卡死了
從 issue 和源碼自己,咱們能夠思考一些問題,同時用來湊字數
答案是否認的,尤的回覆已經足夠充分了:github.com/vuejs/rfcs/…
大體有兩點:
那,fre 呢?
fre 的異步渲染,是否也存在這個問題,不得不認可,fre 雖然粒度很小,對於組件內部的阻斷能夠搞定,可是元素自己也能夠被阻斷
並且第一個問題也是存在的,就是沒有太多適用場景
可是 fre 源碼層面仍是意義重大的,即使這玩意搞出來,發現它做用不大,反作用不小,但 fre 做爲我我的的學習和研究的項目,它的價值歷來就不是業務層面的
只是我應該停下來,異步渲染搞定了,只是向你們展現它的源碼實現,將來不該該跟隨 react 去搞一堆業務 API,如 useTransition 等等
vue3 發版當天,源碼解讀就放出了,可是到目前爲止,全部的源碼解讀通通都是蹭熱度的
不久的未來,vue 的源碼又要爛大街了……
這種現象引發檢討,咱們讀源碼究竟是爲了什麼?爲了面試嗎?爲了更好的寫業務?
對我而言,僅僅只是感興趣,我對這部分源碼感興趣,我就去讀,而且只讀感興趣的部分
其實你們也看到了,我不多寫源碼解讀的文章,由於我一直反對所謂的【通讀源碼】
將閱讀源碼做爲一項工做,一樣的小函數,讀了一遍又一遍,重複勞動
這和糊 shi 有什麼區別呢?