一. 響應式系統升級
1. Vue.js 2.x 中響應式系統的核心是 Object.definePropertry
2. Vue.js 3.x 中使用 Proxy 對象重寫響應式系統
- 可以一次性監聽所有屬性
- 可以監聽動態新增的屬性
- 可以監聽刪除的屬性
- 可以監聽數組的索引和length屬性
詳細解釋:
Object.defineProperty Vs Proxy
1. Object.defineProperty 無法一次性監聽所有屬性, Proxy 可以
const personInfo = { name: 'zhangsan', age: 18, sex: '男' } const proxy = new Proxy(personInfo, { get(target, key) { }, set(target, key, newValue) { } }) Object.keys(personInfo).forEach(key => { Object.defineProperty(personInfo, key, { set() { }, get() { } }) })
2. Object.defineProperty 無法監聽動態新增的屬性, Proxy 可以
const personInfo = { name: 'zhangsan', age: 18, sex: '男' } const proxy = new Proxy(personInfo, { get(target, key) { console.log('get', key) }, set(target, key, newValue) { target[key] = newValue return true } })
Object.keys(personInfo).forEach(key => { Object.defineProperty(personInfo, key, { set() { }, get() { } }) })
personInfo.from = '上海'
console.log(proxy) // Proxy 生效 Object.defineProperty 不生效
3. 可以監聽刪除的屬性?
const personInfo = { name: 'zhangsan', age: 18, sex: '男' } const proxy = new Proxy(personInfo, { get(target, key) { console.log('get', key) }, set(target, key, newValue) { target[key] = newValue return true } }) Object.keys(personInfo).forEach(key => { Object.defineProperty(personInfo, key, { set() { }, get() { } }) }) delete personInfo.age console.log(personInfo) // proxy 生效 Object.defineProperty不生效
4. 可以監聽數組的索引和length屬性
const personInfo = [1, 2, 3, 4] const proxy = new Proxy(personInfo, { get(target, key) { console.log('get', key)
},
set(target, key, newValue) {
target[key] = newValue
return true
}
})
personInfo.forEach((item, index) => {
Object.defineProperty(personInfo, index, {
set() { },
get() { }
})
})
personInfo[0] = 8 // 都生效
personInfo[5] = 6 // proxy 生效
personInfo.push(99) // proxy 生效
二. 編譯優化
1. Vue.js 2.x 通過標記靜態節點,優化 diff 的過程
2. Vue.js 3.x 通過標記和提升所有的靜態根節點,diff 的時候只需要對比動態節點內容
- Fragments ( 升級 Vetur 插件 )
- 靜態提升
- Patch flag
- 緩衝事件處理函數
詳細解釋:
此處我們用到線上編譯器來查看 vue 2.x 與 vue3.x 的編譯區別~
1. 首先看一下,當文件內部,不包含任何內容時,Vue2.x 編譯是空的,Vue 3.x 編譯內部包含 render 函數,返回爲null
2. 我們先放入一個 Dom, 可以看到 vue2.x 和 vue 3.x 編譯的部分完完全全重構了,之前 Vue2.x 採用,_c 的模式創建標籤,_v 爲 Vnode 節點, 而當前的 Vue 3.x 通過 _createBlock 生成 block tree
- Vue 2.x 數據更新並觸發重新渲染的粒度是組件級的,單個組件內部需要遍歷該組件的整個 vnode 樹
- Vue.js 3.0 做到了通過編譯階段對靜態模板的分析,編譯生成了 Block tree。Block tree 是一個將模版基於動態節點指令切割的嵌套區塊,每個區塊內部的節點結構是固定的。每個區塊只需要追蹤自身包含的動態節點。
3. 新引入Fragments(片段)特性:Vue 3.x 模板中不需要再創建一個唯一的根節點,模板裏可以直接放文本內容或者很多同級的標籤, Vue2.x 需要唯一的節點
4. 靜態提升:靜態節點都會被提升到render 的外部,只有初始化時會被創建,再次調用render時不會再次創建,可以直接重用這些靜態節點對應的vnode
5. Patch flag
6. 緩存事件處理函數減少了不必要的更新操作
三. 源碼體積的優化
1. Vue.js 3.x 移除了一些不常用的API
- 例如:inline-template, filter 等
2. Tree-shaking
詳細解釋:移除一些不常用的API這裏我們很好解釋,在這裏我們就重點講一下 Tree-shaking, Tree-shaking就是把無用的模塊進行“剪枝”,很多沒有用到的API就不會打包到最後的包裏。
tree-shaking的原理是: 依賴 ES2015 模塊語法的靜態結構(即 import 和 export),通過編譯階段的靜態分析,找到沒有引入的模塊並打上標記。
舉例說明一下:
1. import 的引用
// src/assets/index.js export function testA() { console.log('A') } export function testB() { console.log('B') } // src/assets/data.json { "name": "colin", "gender": "male" } // src/main.js import { createApp } from 'vue' import App from './App.vue' // 測試代碼開始 import { testA } from './assets/js/index' import { name } from './assets/data.json' console.log(name) // 測試代碼結束 createApp(App).mount('#app')
以上代碼,我們將 testA 方法引用,但未使用,data.json 引用,同時使用後,npm run build 打包,我們會發現,data.json 文件
內容已被打印,但testA 在打包後的文件內,沒有發現被引用的情況
修改 main.js
import { createApp } from 'vue'
import App from './App.vue'
// 測試代碼開始
import { testA } from './assets/js/index'
import { name } from './assets/data.json'
console.log(testA())
console.log(name)
// 測試代碼結束
createApp(App).mount('#app')
進行再次打包,我們會看到 testA 已經被成功的打印,但在打包的js中,我們並沒有找到 testB 方法,並且也沒有找到 assets/data.json 下的 gender 字段,所以此處我們的按需加載已經完成了,但這只是Tree-sharking的一部分
2. require 引用
修改 main.js 文件
import { createApp } from 'vue'
import App from './App.vue'
// 測試代碼開始
const test = require('./assets/js/index')
// 測試代碼結束
createApp(App).mount('#app')
打包後,我們會發現 我們通過 require 引用的文件,即使未使用,但還是打包在壓縮包內
從這上面一點,我們發現,Tree-sharking 只支持 import 或者 export 的形式,也就是 依賴ES6 內部的靜態分析。
所謂靜態分析就是不執行代碼,從字面量上對代碼進行分析,ES6之前的模塊化,比如我們可以動態require一個模塊,只有執行後才知道引用的什麼模塊,這個就不能通過靜態分析去做優化。
這是 ES6 modules 在設計時的一個重要考量,也是爲什麼沒有直接採用 CommonJS,正是基於這個基礎上,才使得 tree-shaking 成爲可能,這也是爲什麼 rollup 和 webpack 2 都要用 ES6 module syntax 才能 tree-shaking。