Vue.js設(shè)計與實(shí)現(xiàn)之權(quán)衡的藝術(shù)
1.寫在前面
本文便帶領(lǐng)大家進(jìn)入《Vue.js設(shè)計與實(shí)現(xiàn)》描述的宇宙,開啟探索框架設(shè)計的思想的旅程。
2.框架設(shè)計里到處都體現(xiàn)了權(quán)衡的藝術(shù)
作者在文章中寫到『框架設(shè)計里到處都體現(xiàn)了權(quán)衡的藝術(shù)』,的確在進(jìn)行設(shè)計模式和技術(shù)選型的時候,我們都會去綜合考慮性能和開發(fā)效率,去權(quán)衡各方面因素從而得到盡可能完善的框架。
框架是由各個模塊組成的,彼此關(guān)聯(lián)又相互獨(dú)立,要做到實(shí)現(xiàn)當(dāng)前的功能,又要考慮到后續(xù)的模塊拆分和拓展。作為框架的設(shè)計者,需要站在全局的角度去思考和設(shè)計,需要對整體的設(shè)計思路有著清晰的掌控。實(shí)現(xiàn)細(xì)節(jié)是在設(shè)計的時候不用太過于在意的視點(diǎn),不要囿于高山的霧層,畢竟它只是整個框架的冰山一角。
在Vue框架的設(shè)計中,最能體現(xiàn)這種權(quán)衡思想的可能是『命令式和聲明式』、『編譯時和運(yùn)行時』等之間的權(quán)衡,需要了解彼此的差異、汲取兩者的優(yōu)點(diǎn)。
3.命令式和聲明式
正如你所知道的,在計算機(jī)編程范式中有三種:命令式編程,聲明式編程和函數(shù)式編程。
命令式編程:是關(guān)注計算機(jī)執(zhí)行的步驟,即一步一步告訴計算機(jī)先做什么再做什么。
聲明式編程:以數(shù)據(jù)結(jié)構(gòu)的形式來表達(dá)程序執(zhí)行的邏輯。它的主要思想是告訴計算機(jī)應(yīng)該做什么,但不指定具體要怎么做。
函數(shù)式編程:是與聲明式編程關(guān)聯(lián)的,只關(guān)注做什么而不是怎么做。但函數(shù)式編程不僅僅局限于聲明式編程。
命令式
對于前端開發(fā)從業(yè)者而言,JQuery框架并不會陌生,它其實(shí)就是最經(jīng)典的命令式的框架設(shè)計,它關(guān)注計算機(jī)執(zhí)行的步驟,即關(guān)注過程。命令式編程其實(shí)就是寫給計算機(jī)看的,讓我們的自然語言能與代碼進(jìn)行一一對應(yīng),更符合我們做事邏輯。
$("#app")//獲取id為app的標(biāo)簽元素
.text("hello pingping")//設(shè)置標(biāo)簽的文本內(nèi)容
.on("click",()=>console.log("hello onechuan"));//給id為app的便簽綁定事件
等價于原生js的代碼:
const div = document.querySelector("#app");
div.innerText = "hello pingping";
div.addEventListener("click",()=>console.log("hello onechuan"))
聲明式
而聲明式更關(guān)注實(shí)現(xiàn)結(jié)果,具體的實(shí)現(xiàn)過程并不是使用者所在意的,這也很大程度地降低了認(rèn)知成本,關(guān)注表層邏輯提升使用效率。
事實(shí)上,Vue.js的設(shè)計并不是簡單使用純粹的命令式或是聲明式編程,而是結(jié)合兩者的優(yōu)點(diǎn)。在內(nèi)部實(shí)現(xiàn)使用命令式告知計算機(jī)如何運(yùn)行,對外暴露的API等則是采用的聲明式編程,能夠用人話讓使用者讀懂結(jié)果。
<div @click="()=>console.log('hello onechuan')">hello pingping<div>
性能和可維護(hù)性
在《編譯原理》書中,了解到命令式代碼的性能優(yōu)于聲明式代碼,這是因?yàn)槁暶魇酱a需要經(jīng)過編譯成計算機(jī)能夠讀懂的命令式代碼。但是呢,聲明式代碼更像是人類能夠讀懂的人話,在盡可能犧牲少量性能的同時降低代碼的維護(hù)成本。
Vue.js框架就是結(jié)合兩者的優(yōu)點(diǎn),對命令式代碼進(jìn)行了封裝,對使用者提供可維護(hù)性更高的聲明式代碼。
4.真實(shí)DOM和虛擬DOM
對于聲明式代碼的更新性能消耗而言:聲明式代碼的更新性能消耗 = 找出差異的性能消耗 + 直接修改的性能消耗,如果我們找到能夠讓找出差異的性能消耗最小化的算法,那么就能夠?qū)⒙暶魇酱a的性能消耗無限趨近于命令式代碼性能消耗。
我們分別從創(chuàng)建頁面和更新頁面兩方面,對真實(shí)DOM和虛擬DOM操作的性能消耗進(jìn)行分析:
狀態(tài) | 虛擬DOM(純JS創(chuàng)建VNODE) | 真實(shí)DOM(渲染HTML字符串) |
創(chuàng)建頁面 | 新建所有的DOM對象 | 新建所有的DOM對象 |
更新頁面 | 必要的DOM更新 | -銷毀所有的舊DOM,新建所有的新DOM |
關(guān)于性能:真實(shí)DOM<虛擬DOM<原生JS。
此處簡要的進(jìn)行總結(jié),后續(xù)文章將會有更詳細(xì)的數(shù)據(jù)分析。
5.編譯時和運(yùn)行時
在框架設(shè)計時還要考慮是選擇:純運(yùn)行時、純編譯時還是運(yùn)行時+編譯時,這需要結(jié)合你所期望的待設(shè)計框架的特征做出合適的決策。
運(yùn)行時
所謂運(yùn)行時,就是計算機(jī)所運(yùn)行時的代碼,不需要經(jīng)歷額外的處理,便能夠?qū)崿F(xiàn)我們所期許的結(jié)果。
例如,我們需要將提供的樹形結(jié)構(gòu)的數(shù)據(jù)對象,渲染到渲染成dom樹,那么我們需要設(shè)計一個Render函數(shù)直接進(jìn)行渲染,這樣就能得到我們想要的結(jié)果:
const obj = {
tag:"div",
children:[{
tag:"span",
children:"hello world"
}]
}
Render(obj, document.body)
function Render(obj, root){
const el = document.createElement(obj.tag);
if(typeof obj.children === "string"){
const text = document.createTextNode(obj.children);
el.appendChild(text)
}else if(obj.children){
// 如果是數(shù)組,就進(jìn)行遞歸調(diào)用render,使用el作為root參數(shù)
obj.children.forEach(child=>Render(child, el))
}
// 最后將元素添加到根元素
root.appendChild(el)
}
瀏覽器顯示如下:
編譯時
那么,編譯就是一種轉(zhuǎn)換技術(shù),將高級語言轉(zhuǎn)換低級語言,Vue.js將HTML標(biāo)簽通過編譯轉(zhuǎn)換成樹形結(jié)構(gòu)的數(shù)據(jù)對象。
這樣我們需要編寫一個Compiler函數(shù),用于將HTML標(biāo)簽通過編譯換成樹形結(jié)構(gòu)的數(shù)據(jù)對象。如下:
const html = `
<div>
<span>hello world</span>
</div>`
const obj = compoler(html)
Render(obj, document.body)
這樣就能將:
<div>
<span>hello pingping</span>
</div>
編譯成:
const obj = {
tag: 'div',
children: [
{tag: 'span', children: 'hello world'}
]
}
結(jié)合Render函數(shù)進(jìn)行渲染,這樣我們就初步設(shè)計了一個運(yùn)行時+編譯時的框架了。
運(yùn)行時+編譯時
所謂在Vue.js是運(yùn)行時+編譯時框架,其實(shí)指的是:
支持運(yùn)行時:使用者可以直接提供樹形結(jié)構(gòu)的數(shù)據(jù)對象而無需編譯;
支持編譯時:使用者可以提供HTML字符串,將其編譯成樹形結(jié)構(gòu)的數(shù)據(jù)對象后再交給運(yùn)行時處理。
為什么Vue.js要設(shè)計成運(yùn)行時+編譯時框架?
這所以這樣設(shè)計也是開源團(tuán)隊進(jìn)行權(quán)衡的結(jié)果,運(yùn)行時無法分析用戶提供的內(nèi)容,而加入編譯后就可以對用戶內(nèi)容進(jìn)行分析和編譯。在編譯的時候提取這些用戶內(nèi)容的信息,再通過Render函數(shù)進(jìn)行渲染。
當(dāng)然,將框架設(shè)計成純編譯時,可以分析用戶內(nèi)容直接編譯成可執(zhí)行的JS代碼,在保證性能的同時犧牲了框架的靈活性和可維護(hù)性,對用戶而言必須對內(nèi)容編譯后才能使用。
對此,Vue.js的設(shè)計是綜合考量,才用的運(yùn)行時+編譯時的框架設(shè)計,在保留運(yùn)行時的靈活性的同時,盡可能不犧牲性能。
6.寫在最后
在本文中,了解到開源團(tuán)隊對于命令式和聲明式、真實(shí)DOM和虛擬DOM、運(yùn)行時和編譯時的權(quán)衡選擇,在盡可能減少性能損耗的同時提供最好的用戶體驗(yàn)和可維護(hù)性、靈活性。