傳送門vue技術(shù)揭秘:https://ustbhuangyi.github.io/vue-analysis/v2/data-driven/new-vue.html
一、主要步驟
1.初始化
- vue初始化init的過程包含生命周期峦剔、事件档礁、props、methods吝沫、data呻澜、computed與watch等的初始化
其中最主要的兩個步驟是watch的初始化和data屬性的observer過程递礼,兩個過程是實(shí)現(xiàn)響應(yīng)式和依賴收集
2.編譯 - 編譯是將template轉(zhuǎn)變?yōu)閞ender function 的過程,包括:解析/優(yōu)化/生成三個步驟
解析:template->AST(抽象語法樹)
優(yōu)化:標(biāo)記AST中的靜態(tài)(static)節(jié)點(diǎn)
生成:AST->render function
3.render function 執(zhí)行 - render function 執(zhí)行后生成虛擬節(jié)點(diǎn)樹(VNode DOM Tree)
4.渲染展現(xiàn)頁面
二羹幸、依賴收集過程
整體的流程圖中render function 執(zhí)行開始的綠色箭頭指向的流程為依賴收集過程
1.render function 執(zhí)行中會依此調(diào)用使用到的data.attr的get方法
2.get方法調(diào)用Dep.add將Vue對象中的watch加入到attr.Dep數(shù)組里
3.整個頁面渲染完畢后脊髓,所有需要使用attr的組件Vue對象的watch都收集到attr.Dep,attr.Dep內(nèi)容即為template與data的依賴關(guān)系(attr是隨便起的一個組件名)
三栅受、響應(yīng)式原理
整體流程圖中attr.set()執(zhí)行開始的紅色箭頭指向的流程為響應(yīng)式原理
1.對data.attr賦值即調(diào)用attr.set方法
2.attr.set會調(diào)用Dep.notify(),notify方法是依次執(zhí)行attr.Dep數(shù)組中watch對象的update方法
3.update()是重新渲染視圖的過程将硝,中間生成的Vnode DOM Tree,供patch使用 (利用diff算法)
四屏镊、update中的patch
patch依疼,是將update產(chǎn)生的New Vnode節(jié)點(diǎn)與上一次渲染的Old Vnode進(jìn)行對比的過程,最終只有對比后的差異節(jié)點(diǎn)才會被更新到視圖上而芥,從而達(dá)到提高update性能的目的
原文鏈接:https://www.cnblogs.com/zs-note/p/8675755.html
五律罢、 new Vue 的操作
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue) //如果函數(shù)function Vue 被new了當(dāng)前的this就是Vue類型
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
process在node中有全局變量表示的是當(dāng)前的node進(jìn)程。process.env包含著關(guān)于系統(tǒng)環(huán)境的信息蔚出。但是process.env中不存在NODE_ENV這個東西弟翘,是用戶自定義的變量,在webpack中判斷生產(chǎn)環(huán)境和開發(fā)環(huán)境的骄酗。
_init中主要做了合并配置稀余,初始化生命周期,初始化事件中心趋翻,初始化渲染睛琳,初始化data、props踏烙、computed师骗、watcher等等
頁面使用
export default {
data(){
return {
name:'zmq'
}
},
mouted(){
console.log(this.name) //為什么data中得可以直接this得到,因?yàn)楸粧燧d到了vm上讨惩,vue實(shí)例上辟癌。主要實(shí)現(xiàn)得方法是proxy(代理)
//proxy(target,sourceKey,key)
}
}
target 傳遞得是vm sourcKey傳遞得是_data,key就是name
獲取得時候就是sharePropertyDefinition.get=function proxyGetter(){
return this [sourceKey][key]//this [_data][name]
}
總結(jié):vue初始化邏輯荐捻,把不同功能邏輯拆成一些單獨(dú)的函數(shù)執(zhí)行黍少,讓主線邏輯一目了然。
六处面、掛載的時候的操作
首先對el做了限制不能掛載在html和body上厂置。如果沒有定義render方法,則會把el或者template字符串轉(zhuǎn)換成render方法魂角;然后是調(diào)用vm.render方法生成虛擬node昵济,然后實(shí)例化watcher,回調(diào)updateComponent方法,更新dom访忿。瞧栗。當(dāng)掛載完成的時候會vm.isMounted=true 做標(biāo)記,當(dāng)前vm.$node(父虛擬node)為null醉顽。代表掛載完成
核心方法:vm.render 和vm.update
vm.update在修改數(shù)據(jù)得時候也會再次被調(diào)用
七沼溜、virtualDom和diff(vue實(shí)現(xiàn))
虛擬dom就是真實(shí)dom的一層抽象,用屬性描述真實(shí)dom的各個特性
例子:
<template>
<div id='dd'>
<p><span></span></p>
<p>abc</p>
<p>123</p>
</div>
</template>
var virtual=
{
dom:'div',
props:{
id:dd
},
children:[
{
dom:'p',
children:[
dom:'span',
children:[]
]
},
{
dom:'p',
children:[
]
},
{
dom:'p',
children:[
]
}
]
}
可以想象游添,最簡單粗暴的方法就是將整個dom結(jié)構(gòu)用innerHtml修改到頁面上系草,但是這樣進(jìn)行重繪整個視圖層是相當(dāng)消耗性能的。每次只更新被修改的部分是不是性能高一些唆涝。所以vue.js將dom抽象成一個以javascript對象為節(jié)點(diǎn)的虛擬dom樹找都,以vnode節(jié)點(diǎn)模擬真實(shí)dom,可以對這顆抽象樹進(jìn)行創(chuàng)建節(jié)點(diǎn)廊酣、刪除節(jié)點(diǎn)一級修改節(jié)點(diǎn)等操作能耻,在這個過程中都不需要操作真實(shí)dom,只需要操作javascript對象差異修改亡驰,這樣大大提升了性能晓猛。修改以后在經(jīng)過diff算法得出一些需要修改的最小單位,再將這些小單位的視圖進(jìn)行更新凡辱。這樣減少了很多不需要的dom操作戒职,大大提高了性能。
vue使用這種虛擬dom透乾,對真實(shí)dom的一層抽象洪燥,不依賴某個平臺,它可以是瀏覽器平臺乳乌,也可以是weex捧韵,甚至node平臺也可以對這樣一顆dom樹進(jìn)行創(chuàng)建刪除修改等操作。這也是為前后端同構(gòu)提供了可能汉操。
vue修改視圖
vue是通過雙向綁定來修改視圖再来,當(dāng)某個數(shù)據(jù)被修改的時候,set方法會讓閉包中的dep調(diào)用notify通知所有訂閱者watcher磷瘤,watcher通過get方法執(zhí)行
vm._update(vm._render(),hydrating)
diff算法
diff算法是通過同層的樹節(jié)點(diǎn)進(jìn)行比較而非對樹進(jìn)行逐層搜索遍歷的方式
兩張圖代表舊的和新的vnode進(jìn)行patch(修補(bǔ))的過程其弊,他們只是在同層級的vnode之間進(jìn)行比較得到變化(第二張圖中相同顏色的方塊代表互相進(jìn)行比較的vnode節(jié)點(diǎn)),然后修改變化的視圖膀斋,所以十分高校
_patch
當(dāng)oldvnode和vnode在samevnode的時候才會進(jìn)行patchvnode,也就是新舊節(jié)點(diǎn)判定為同一節(jié)點(diǎn)的時候才會進(jìn)行patchvnode這個過程痹雅,否則就是創(chuàng)建新的dom仰担,移除舊的dom
return function patch(oldVnode,vnode,hydrating,removeOnly,parentElm,refElm){
//vnode不存在的時候則直接調(diào)用銷毀鉤子(沒有新節(jié)點(diǎn),就把舊節(jié)點(diǎn)銷毀掉)
if(isUndef(vnode)){
if(isDef(oldVnode)) invokeDestoryHook(oldVnode)
return
}
let isInitialPatch=false
const insertedVnodeQueue=[]
if(isUndef(oldVnode)){
//如果沒有舊節(jié)點(diǎn)的時候就是沒有root節(jié)點(diǎn),就需要創(chuàng)建一個新的節(jié)點(diǎn)
isInitialPatch=true
createElm(vnode,insertedVnodeQueue,parentElm,refElm)
}else{
//查看當(dāng)前是否又nodeType摔蓝,也就子節(jié)點(diǎn)
const isRealElement=isDef(oldVnode.nodeType)
//當(dāng)沒有子節(jié)點(diǎn)并且是相同節(jié)點(diǎn)得時候赂苗,就直接修改現(xiàn)有的節(jié)點(diǎn)
if(!isRealElement&&sameVnode(oldVnode,vnodes)){
//當(dāng)沒有
patchVnode(oldVnode,vnode,insertedVnodeQueue,removeOnly)
}else{
//如果當(dāng)前又子節(jié)點(diǎn)
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
/*當(dāng)舊的VNode是服務(wù)端渲染的元素,hydrating記為true*/
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
/*需要合并到真實(shí)DOM上*/
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
/*調(diào)用insert鉤子*/
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
/*如果不是服務(wù)端渲染或者合并到真實(shí)DOM失敗贮尉,則創(chuàng)建一個空的VNode節(jié)點(diǎn)替換它*/
oldVnode = emptyNodeAt(oldVnode)
}
}
/*取代現(xiàn)有元素*/
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
if (isDef(vnode.parent)) {
// component root element replaced.
// update parent placeholder node element, recursively
/*組件根節(jié)點(diǎn)被替換拌滋,遍歷更新父節(jié)點(diǎn)element*/
let ancestor = vnode.parent
while (ancestor) {
ancestor.elm = vnode.elm
ancestor = ancestor.parent
}
if (isPatchable(vnode)) {
/*調(diào)用create回調(diào)*/
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode.parent)
}
}
}
if (isDef(parentElm)) {
/*移除老節(jié)點(diǎn)*/
removeVnodes(parentElm, [oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
/*Github:https://github.com/answershuto*/
/*調(diào)用destroy鉤子*/
invokeDestroyHook(oldVnode)
}
}
/*調(diào)用insert鉤子*/
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
samenode
/**判斷兩個vnode節(jié)點(diǎn)是否是同一個節(jié)點(diǎn),需要滿足一下條件猜谚,key相同败砂,
tag相同(當(dāng)前節(jié)點(diǎn)的標(biāo)簽名)
isComment(是否為注釋節(jié)點(diǎn))相同
是否data都有定義(當(dāng)前節(jié)點(diǎn)對應(yīng)的對象,包含了具體的一些數(shù)據(jù)信息)是一個vnodeData類型魏铅,可以參考vnodeData類型中的數(shù)據(jù)信息)
參當(dāng)標(biāo)簽<input>的時候昌犹,type必須相同
**/
function sameVnode (a, b) {
return (
a.key === b.key &&
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
)
}
/**
判斷當(dāng)標(biāo)簽是input的時候,type是否相同.
**/
八览芳、render斜姥、createElement、patch
render可以通過手寫沧竟,也可以通過編譯生成
render最終執(zhí)行得createElement方法返回得vnode
vue.js利用createElement方法創(chuàng)建Vnode
createElement實(shí)際上是對_createElement方法得封裝铸敏,讓傳入得參數(shù)更加靈活,在處理這些參數(shù)得時候調(diào)用真正得創(chuàng)建vnode得函數(shù)_createElement
創(chuàng)建得時候會檢測data悟泵,不能是響應(yīng)式得
_createElement方法5個參數(shù)杈笔,
context 表示Vnode得上下文環(huán)境,他是component類型
tag表示標(biāo)簽魁袜,它可以是一個字符串桩撮,也可以是compenent,這個時候就會創(chuàng)建一個組件峰弹。
data表示Vnode得數(shù)據(jù)店量,他是一個VNodeData類型,可以在flow/vnode.js中找到他的定義鞠呈,
children表示當(dāng)前VNode得子節(jié)點(diǎn)融师,他是任意類型得,他接下來需要被規(guī)范為標(biāo)準(zhǔn)得VNode數(shù)組蚁吝;
noramalizationType表示子節(jié)點(diǎn)規(guī)范得類型旱爆,類型不同規(guī)范得方法也就不一樣,它主要是參考render函數(shù)是編譯生成還是用戶手寫得
createElement函數(shù)得流程略微多窘茁,需要看的2個重點(diǎn)流程:children得規(guī)范化以及VNode得創(chuàng)建
children得規(guī)范化
用于Virtual DOM實(shí)際上是一個樹狀結(jié)構(gòu)怀伦,每一個vnode可能會有若干個子節(jié)點(diǎn),這些子節(jié)點(diǎn)應(yīng)該也是vnode類型山林。_createElement接收得第四個children是任意類型房待,因此我們需要把它門規(guī)范成vnode類型。
這里會根據(jù)normalizationType得不同,調(diào)用了noramalizeChildren(children)和simpleNoramalizeChildren(children)方法
simpleNormalizeChildren方法調(diào)用場景是render是編譯生成得桑孩。理論上編譯生成得children都已經(jīng)是vnode類型得拜鹤,這里有一個例外就是,functional compenent 組件返回得是一個數(shù)組而不是一個根節(jié)點(diǎn)流椒。所以會通過Array.prototype.concat方法把整個children數(shù)組打平敏簿,讓它得深度只有一層。
normalizeChildren方法得調(diào)用場景有2種宣虾,一個 場景是render函數(shù)是用戶手寫得惯裕,當(dāng)children只有一個節(jié)點(diǎn)得時候,vue.js從接口層面允許用戶把children寫成基礎(chǔ)類型用來創(chuàng)建單個簡單得文本節(jié)點(diǎn)安岂,這種情況會調(diào)用createTextNode創(chuàng)建一個文本節(jié)點(diǎn)得vnode轻猖;另一個場景是編譯slot、v-for得時候會產(chǎn)生嵌套數(shù)組得情況域那,會調(diào)用noramlizeArrayChildren方法咙边。
noramlizeArrayChildren接收2個參數(shù),children表示要規(guī)范得子節(jié)點(diǎn)次员,nestedIndex表示嵌套得索引败许,因?yàn)閱蝹€child可能一個數(shù)組類型。normalizeArrayChildren主要得邏輯就是遍歷children淑蔚,獲得單個節(jié)點(diǎn)c市殷,然后對c得類型判斷,如果是一個數(shù)組類型刹衫,則遞歸調(diào)用normalizeArrayChildren醋寝;如果是基礎(chǔ)類型,則通過createTextVNode方法轉(zhuǎn)換成VNode類型带迟,否則就已經(jīng)是Vnode類型了音羞。如果children是一個淚飆并且列表還存在嵌套得情況,則根據(jù)nestedIndex去更新他的key仓犬。這里需要注意一點(diǎn)嗅绰,在遍歷得過程中,對這3種情況都做了如下處理:如果存在兩個連續(xù)得text節(jié)點(diǎn)搀继,會把他們合并一個text節(jié)點(diǎn)窘面。
經(jīng)過children得規(guī)范化,children變成了一個類型為Vnode得Array
VNode創(chuàng)建
createElement創(chuàng)建VNode得時候會對tag做判斷叽躯,
如果是string類型财边,則接著判斷如果是內(nèi)置得一些節(jié)點(diǎn),則直接創(chuàng)建一個普通VNode点骑,如果是為已注冊得組件名酣难,則通過createComponent創(chuàng)建一個組件類型得Vnode们童,否則創(chuàng)建一個位置標(biāo)簽VNode。
如果tag是一個Component類型鲸鹦,則直接調(diào)用createComponent創(chuàng)建一個組件類型得VNode節(jié)點(diǎn)。
總結(jié)
createElement 創(chuàng)建 VNode 的過程跷跪,每個 VNode 有 children馋嗜,children 每個元素也是一個 VNode,這樣就形成了一個 VNode Tree吵瞻,它很好的描述了我們的 DOM Tree葛菇。
歸根節(jié)點(diǎn)是一個:const elm=docuemnt.createElement(tagName)
普通的patch和組件patch
普通的patch是對vnode(虛擬屬性)的判斷 ,新增子節(jié)點(diǎn)橡羞、刪除子節(jié)點(diǎn)眯停、新增節(jié)點(diǎn),移除當(dāng)前節(jié)點(diǎn)的操作卿泽。組件中的patch是發(fā)現(xiàn)組件莺债,就去createComponent->子組件初始化-》子組件render->子組件patch
patch
組件中patch
patch的整體流程:createComponent->子組件初始化-》子組件render->子組件patch
activeInstance為當(dāng)前激活的vm實(shí)例;vm.$vnode為組件的占位vnode签夭;vm._vnode為組件的渲染vnode
嵌套組件的插入順序是先子后父(遞歸)
九齐邦、update
update得有兩次:1.首次渲染得時候 2. 當(dāng)我們?nèi)ジ淖償?shù)據(jù)(更新)得時候
_update得核心就是調(diào)用vm.patch方法。這個方法在不同得平臺中定義是不一樣得第租,例如web和weex
在web中措拇,是否在服務(wù)端渲染會對這個方法產(chǎn)生影響。因?yàn)樵诜?wù)端渲染中慎宾,沒有真實(shí)dom環(huán)境丐吓,所以不需要把VNode最終轉(zhuǎn)換成Dom,因此是一個空函數(shù)趟据,而在瀏覽器端渲染中券犁,它指向了patch方法
patch方法是createPatchFunction方法得返回值,這里傳入一個對象之宿,包含nodeOps參數(shù)和modules參數(shù)族操。其中,nodeOps封裝了一系列Dom操作得方法比被,modules定義了一些模塊得鉤子函數(shù)得實(shí)現(xiàn)色难。
值得學(xué)習(xí)得地方:
因?yàn)閜atch是跟平臺相關(guān)得得,在web和weex環(huán)境等缀,他們把虛擬dom映射到“平臺Dom”得方法是不相同得枷莉,并且對Dom包括得屬性模塊創(chuàng)建和更新也盡不相同。因此每個平臺都是各自得nodeOps和modules尺迂。而不同平臺得主要邏輯是相同得笤妙。所以他們把公共得托管在core中冒掌。
差異化部分只需要通過參數(shù)來區(qū)別,這里用到了一個函數(shù)柯里化得技巧蹲盘,通過createPatchFunction把差異化參數(shù)提前固定化股毫,這樣不用每次調(diào)用patch得時候都傳遞nodeOps和modules了。
整個過程就是new Vue 然后初始化init 事件召衔,鉤子函數(shù)铃诬,data。watcher/等等苍凛,然后就是掛載趣席,編譯成render函數(shù)生成vnode,在通過patch醇蝴,insert插入dom元素變成真實(shí)dom宣肚。
十、createComponent
構(gòu)建子類構(gòu)造函數(shù)悠栓、安裝組件鉤子函數(shù)霉涨、實(shí)例化VNode,然后最后通過patch把VNode轉(zhuǎn)換成真正的Dom節(jié)點(diǎn)闸迷。
組件的創(chuàng)建最終執(zhí)行的是insert(parentElm,vnode.elm,refElm),插入順序是先子后父嵌纲,因?yàn)槔锩嬗幸粋€遞歸,如果碰到組件就去創(chuàng)建腥沽,初始化逮走,渲染。
十一今阳、合并配置
不同的key师溅,合并配置不同
鉤子函數(shù)的合并配置策略
父組件和子組件中的鉤子函數(shù)都是一樣的。如果有子組件盾舌,就將父子組件相同鉤子函數(shù)合并在一起墓臭,最后返回的是一個數(shù)組
三元運(yùn)算符:看當(dāng)前有沒有子元素,如果沒有就返回父元素妖谴,如果有子元素就把父元素和子元素合并窿锉,如果沒有父元素,就查看當(dāng)前子元素是不是一個數(shù)組膝舅,如果不是就變成數(shù)組嗡载,如果是就直接返回子元素數(shù)組
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook
})
例如:都定義了created()
vm.$options = {
parent: Vue /*父Vue實(shí)例*/,
propsData: undefined,
_componentTag: undefined,
_parentVnode: VNode /*父VNode實(shí)例*/,
_renderChildren:undefined,
__proto__: {
components: { },
directives: { },
filters: { },
_base: function Vue(options) {
//...
},
_Ctor: {},
created: [
function created() {
console.log('parent created')
}, function created() {
console.log('child created')
}
],
mounted: [
function mounted() {
console.log('child mounted')
}
],
data() {
return {
msg: 'Hello Vue'
}
},
template: '<div>{{msg}}</div>'
}
}
合并策略:
他們的合并策略都是 mergeHook 函數(shù)。這個函數(shù)的實(shí)現(xiàn)也非常有意思仍稀,用了一個多層 3 元運(yùn)算符洼滚,邏輯就是如果不存在 childVal ,就返回 parentVal技潘;否則再判斷是否存在 parentVal遥巴,如果存在就把 childVal 添加到 parentVal 后返回新數(shù)組千康;否則返回 childVal 的數(shù)組。所以回到 mergeOptions 函數(shù)铲掐,一旦 parent 和 child 都定義了相同的鉤子函數(shù)拾弃,那么它們會把 2 個鉤子函數(shù)合并成一個數(shù)組。
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
十二摆霉、組件注冊
全局注冊:
要注冊一個全局組件砸彬,可以使用vue.component(tabName,options)
Vue.component('my-component',{
//選項(xiàng)
})
命名規(guī)范取值的判斷:
export function resolveAsset (
options: Object,
type: string,
id: string,
warnMissing?: boolean
): any {
/* istanbul ignore if */
if (typeof id !== 'string') {
return
}
const assets = options[type]
// check local registration variations first
if (hasOwn(assets, id)) return assets[id]
const camelizedId = camelize(id)
if (hasOwn(assets, camelizedId)) return assets[camelizedId]
const PascalCaseId = capitalize(camelizedId)
if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
// fallback to prototype chain
const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
warn(
'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
options
)
}
return res
}
邏輯分析:先通過const assets=options[type]拿到assets,然后在嘗試assets[id],這里有一個順序斯入,先直接使用id拿,如果不存在蛀蜜,則把id變成駝峰的形式再拿刻两,如果仍然不存在則在駝峰的基礎(chǔ)上把首字母在變成大寫的形式再拿,如果仍然拿不到就報錯滴某。這樣就說明了我們在使用Vue.compoent(id,definition)全局注冊組件的時候磅摹,id可以是連字符、駝峰或首字母大寫的形式
局部注冊
import HelloWorld from './components/HelloWord';
export default{
components:{
HelloWorld
}
}
全局組件注冊是擴(kuò)展到Vue.options下霎奢,所以在所有組件創(chuàng)建的過程中户誓,都會從全局的Vue.options.componets擴(kuò)展到當(dāng)前組件的vm.$options.components下,這就是全局注冊的組件能被任意使用的原因幕侠。
異步組件(工廠函數(shù))
普通函數(shù)異步組件
里面又有個方法是
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
就是調(diào)用渲染watcher得update方法帝美,讓渲染watcher對應(yīng)得回調(diào)函數(shù)執(zhí)行,也就是觸發(fā)了組件得重新渲染晤硕。之所以這么做是因?yàn)関ue通常是數(shù)據(jù)驅(qū)動視圖重新渲染悼潭,但是在整個異步組件加載過程中是沒有數(shù)據(jù)發(fā)生變化得,所以通過執(zhí)行$forceUpdate可以強(qiáng)制組件重新渲染一次舞箍。
異步組件得3種實(shí)現(xiàn)方式
普通函數(shù)異步組件(工廠)
export function once (fn: Function): Function {
let called = false
return function () {
if (!called) {
called = true
fn.apply(this, arguments)
}
}
}
once邏輯非常簡單舰褪,傳入一個函數(shù),并返回一個新函數(shù)疏橄,它非常巧妙得利用閉包和一個標(biāo)志保證了它包裝得函數(shù)只會執(zhí)行一次占拍。(技巧值得學(xué)習(xí))
Promise異步組件
配合得是webpack +import 得語法
高級異步組件
高級異步組件非常得巧妙,它可以通過簡單得配置實(shí)現(xiàn)了loading捎迫、resolve晃酒、reject、timeout 4種狀態(tài)
const AsyncComp = () => ({
// 需要加載的組件立砸。應(yīng)當(dāng)是一個 Promise
component: import('./MyComp.vue'),
// 加載中應(yīng)當(dāng)渲染的組件
loading: LoadingComp,
// 出錯時渲染的組件
error: ErrorComp,
// 渲染加載中組件前的等待時間掖疮。默認(rèn):200ms。
delay: 200,
// 最長等待時間颗祝。超出此時間則渲染錯誤組件浊闪。默認(rèn):Infinity
timeout: 3000
})
Vue.component('async-example', AsyncComp)
優(yōu)點(diǎn):
- 異步組件可以減少包得大小恼布,一個性能優(yōu)化得技巧
- 按需加載組件
深入響應(yīng)式原理
前端有兩個工作:
- 一把數(shù)據(jù)渲染到頁面
- 另一個就是處理用戶交互
響應(yīng)式對象
響應(yīng)式對象核心:Object.defineProperty給數(shù)據(jù)添加了getter和setter,目的就是為了在我們訪問數(shù)據(jù)以及寫數(shù)據(jù)的時候能自動執(zhí)行一些邏輯搁宾,getter做的事情就是依賴收集折汞,setter做的事情就是派發(fā)更新。
依賴收集
依賴收集的目的是為了當(dāng)這些響應(yīng)式數(shù)據(jù)發(fā)生變化盖腿,觸發(fā)他們的setter的時候爽待,能知道應(yīng)該通知哪些訂閱者去做相應(yīng)的邏輯處理,我們把這個過程叫派發(fā)更新翩腐,watcher和Dep是一個非常經(jīng)典的觀察者設(shè)計模式