Vue3 對比 Vue2 做了那些改進?
1. 響應式系統
- vue2 中使用的 Object.defineProperty 實現的響應式,劫持整個對象,遞歸遍歷所有屬性,給每個屬性添加 getter 和 setter
- vue3 中使用的 Proxy 實現的響應式
2. 編譯階段
- Fragment
Vue3 增加了一個 Fragment
抽象組件,本身不會被渲染到 DOM 中。主要的作用是:模板裏面不再需要創建唯一根節點。可以直接放同級標籤
- 靜態節點提升
Vue2 中,每次數據更新重新渲染時,靜態節點,也會在虛擬 DOM 樹中重新創建一次。執行 diff 算法來比較舊的虛擬 DOM 樹和新的虛擬 DOM 樹,通過對靜態節點打上標記,來優化 diff 的過程
Vue3 中,是通過將靜態節點提升到渲染函數之外,渲染函數內部只會保持對靜態節點的引用。這樣重新渲染時,並不會創建靜態的虛擬節點,從而減少了不必要的計算和操作,提高了渲染性能
下面用一段代碼說明,假設我們有如下模板:
<div>
<p>static text</p>
<p>{{ title }}</p>
</div>
在沒有靜態提升的情況下,它對應的渲染函數是:
function render() {
return (
openClock(),
createClock('div', null, [
createVNode('p', null, 'static text'), // 靜態節點被重新創建
createVNode('p', null, ctx.title, 1)
])
)
}
有了靜態提升的情況:
const hoist1 = createVNode('p', null, 'text')
function render() {
return (
openBlock(),
createBlock('div', null, [
hoist1, // 靜態節點引用
createVNode('p', null, ctx.title, 1)
])
)
}
3. 源碼體積
- Vue3 中移除了一些不常用的 API,整體體積變小了
- 使用的函數方法,如 ref、reactive、computed 等,只有再用到的時候纔會進行打包
Vue3 有哪些變化?
- 響應式系統
用 Proxy 代替 Object.defineProperty 重構了響應式系統,可以監聽到數組下標的變化,對象屬性的變化
- 生命週期
用 setup 代替了 beforeCreate 和 created 這兩個生命週期
- 指令
- 新增指令 v-memo 可以緩存 html 模板
- 插槽 slot 和 slot-scope 屬性已被廢棄,用 v-slot 代替
- 內置 API
移除了 Vue.filter、Vue.mixin、Vue.set 和 Vue.delete
- 編譯
組件模板中不再需要唯一根節點,不需要根元素包裹
- 組合式 API
新增 Composition API 可以更好的組織代碼,同一功能的代碼不至於像以前一樣太分散。Vue2 中可以用 minxin 來實現複用代碼,但也會存在一些問題,比如方法或屬性名會衝突,代碼來源不明顯等
組合式 API 有什麼優缺點?
優點:
- 提供了靈活性和邏輯複用方式,相關的邏輯代碼組織在一起,方便了閱讀
缺點:
- 對於初學者或已經習慣於響應式 API 的開發者來說,切換到組合式 API 可能需要一些時間來熟悉用法和設計思想
- 組合式 API 提供了更強大的靈活性,開發者可以自由的組織代碼和邏輯處理。但這可能會導致代碼結構不一致,代碼更難以理解和維護
選項式 API 有什麼優缺點?
優點:
- 易於學習和使用,寫代碼的位置已經固定好
缺點:
- 當組件變得複雜時,同一個功能的邏輯被拆分,關注點會變得很分散
- 相似的邏輯代碼不便於複用
選項式 API 和 組合式 API 的區別?
- 選項式 API:Vue2 中的
Options API
就是選項式 API。在一個文件內,只能固定使用data
、methods
、computed
等選項來組織代碼。在項目變得複雜時,一個功能的屬性和方法會存在文件的各個地方,很分散,變的越來越難維護。使用 mixin 重用共用代碼,也會有命名衝突,數據來源不清晰的問題 - 組合式 API:Vue3 中的
Composition API
就是組合式 API。通常把一個功能所定義的屬性、方法放在一起,可以更靈活的組織邏輯。解決了選項式 API 不好拆分和重用的問題
Vue3 響應式原理 和 Vue2 的區別?
Vue2
數據響應式是通過 Object.defineProperty
劫持屬性,在數據變化時發佈消息給訂閱者,觸發相應的監聽回調,這其中存在幾個問題:
- 初始化數據需要遞歸遍歷對象所有
key
,性能不好 - 通知更新需要維護大量的
dep
實例和watcher
實例,佔用額外的內存 - 無法監聽到數組元素的變化,只能通過重寫數組方法
- 動態新增、刪除對象屬性無法攔截,只能通過特定
set
、delete
API 代理 - 不支持
Map
、Set
等數據結構
在Vue3
中爲了解決這些問題:
- 使用原生
proxy
代理,可以監聽到數組下標的變化,對象屬性的變化,多達 13 種攔截方法 - 新增數據結構全部支持
- 對象嵌套屬性只代理第一層,只有訪問某個屬性的時候,纔會遞歸代理下一級的屬性
- 也不需要維護特別多的依賴關係
Object.defineProperty 和 Proxy 的區別?
Object.defineProperty
是es5
的方法。Proxy
是es6
的方法Object.defineProperty
不能監聽到數組下標變化和對象新增屬性。Proxy
可以Object.defineProperty
是劫持對象屬性。Proxy
是代理整個對象Object.defineProperty
是遞歸遍歷對象屬性,只能監聽單個屬性。Proxy
對象嵌套屬性運行時遞歸,用到的時候再進行代理,也不需要維護特別多的依賴關係,性能提升很大,且首次渲染更快Object.defineProperty
會污染原對象,修改時是修改原對象。Proxy
是對原對象進行代理並會返回一個新的代理對象,修改的是代理對象Object.defineProperty
不兼容 IE8。Proxy
不兼容 IE11
談談你對 setup 函數的瞭解?
- setup 會在 beforeCreate() 之前執行
- setup 不能使用 this
- setup 內部的屬性、方法,必須 return 暴露出來。否則沒法使用
- setup 內部數據不具有響應式
- setup 不能調用生命週期相關函數,但生命週期函數可以調用 setup 內的函數
setup 方法和 setup 語法糖的區別?
// 方法
setup(props.context) {}
// 語法糖
<script setup></script>
ref 和 reactive 區別?
- 定義數據
ref
通常用來定義基本類型數據,也可以傳入引用類型的數據,內部會通過reactive
轉化爲代理對象,只是不推薦這麼做
reactive
用來定義引用類型數據。不可以傳入基本類型數據,如果傳入則不會返回proxy
對象,由於reactive
是基於proxy
實現響應式的,如果返回的不是proxy
對象,則該數據不會具有響應式 - 實現原理
ref
通過Object.defineProperty
來實現數據代理
reactive
使用proxy
實現數據代理,並且通過reflect
操作原對象內部的數據 - 操作數據
ref
在 js 中,獲取和修改數據需要.value
,template
中不需要
reactive
不需要
怎麼重置 reactive 數據?
- 第一種
import { reactive } from 'vue'
class InitFormData {
userName: string = ''
age: number = 0
}
let formData = reactive(new InitFormData())
// 重置數據
Object.assign(formData, new InitFormData())
- 第二種
import { reactive } from 'vue'
const initFormData = () => {
return {
userName: '',
age: 0
}
}
let formData = reactive(initFormData())
// 重置數據
Object.assign(formData, initFormData())
- 第三種
import {reactive} from 'vue'
const initFormData = {
userName: '',
age: 0
}
const _initFormData = Object.assign({}, initFormData)
let formData = reactive{formData: initFormData}
// 重置數據
formData.formData = Object.assign({}, _initFormData)
原理
打包
說說 wepack 和 vite 的區別?
https://www.inte.net/news/281889.html
https://worktile.com/kb/p/53704
- 打包過程
webpack
會對項目進行全量構建,首先會分析各個模塊之間的依賴關係,形成一個依賴樹,然後打包成bundle.js
文件,將打包後的代碼在本地服務器進行渲染。這樣存在的問題就是,隨着模塊的增多,打包體積變大,影響熱更新的速度vite
不需要向webpack
一樣先打包再渲染。它可以直接啓動本地服務器進行渲染,它利用瀏覽器原生的 ES 模塊加載功能(現代瀏覽器本身支持ES-Module
),會自動向依賴的模塊發出請求,按需動態編譯顯示(每個模塊可以獨立的進行編譯和緩存)
- 熱更新方面
webpack
當改動了某個模塊時,需要將模塊以及依賴的模塊全部編譯一次vite
只需讓瀏覽器重新請求該模塊
結果:通過比較可以看出vite
在啓動的時候不需要打包,不用分析模塊之間的依賴關係,瀏覽器請求某個模塊時,再對模塊進行編譯。當項目越複雜,模塊越多的情況,vite
越優於webpack
怎麼開啓 gzip?
1. Nginx 在線壓縮
通過 CPU 負載換取寬度,客戶端請求靜態資源時,通過 Nginx 在線壓縮 gz 文件返回瀏覽器,瀏覽器自動解壓縮(支持 gzip 的瀏覽器)
gzip on;
gzip_min-length 1K;
gzip_buffers 4 16K;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
2. Koa2 開啓 gzip
node 直接對源文件進行 gzip 壓縮,返回壓縮後的資源
[koa-compress]https://www.npmjs.com/package/koa-compress
2.1 安裝 koa-compress 中間件
npm install koa-compress --save
2.2 配置 koa-compress 中間件
const app = require('koa')()
const compress = require('koa-compress')
const options = { threshold: 2048 }
app.use(compress(options))
3. webpack 開啓 gzip 壓縮
服務端進行壓縮,如果壓縮的文件比較大,壓縮的這個過程也比較耗時,體驗也不是太好
可以在前端打包的時候,直接打包成 .gz
的文件
3.1 安裝 compression-webpack-plugin
npm install compression-webpack-plugin --save-dev
3.2 Vue.config.js 配置
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
configureWebpack: {
plugins: [
new CompressionPlugin({
algorithm: 'gzip', // 使用 gzip 壓縮
test: /\.(js|css|html)?$/i, // 匹配文件名
filename: '[path][base].gz[query]', // 壓縮後的文件名(保持原文件名,後綴加.gz)
minRatio: 0.8, // 默認 0.8,壓縮率小於 1 纔會進行壓縮
threshold: 10 \* 1024, // 超過 10K 的文件會進行壓縮
deleteOriginalAssets: false // 是否刪除未壓縮的源文件。如果希望提供非 gzip 的資源,可不設置或設置爲 false
})
]
}
}
3.3 nginx 配置
gzip_static on;
4. vite 開啓 gzip 打包
4.1 安裝
npm i vite-plugin-compression
4.2 vite.config.js 配置
import viteCompression from 'vite-plugin-compression'
const plugins = [
vue(),
vueJsx(),
viteCompression({
threshold: 10 * 1024 // 對大於 10K 的文件進行壓縮
})
]