vue源碼思路解析

傳送門vue技術(shù)揭秘:https://ustbhuangyi.github.io/vue-analysis/v2/data-driven/new-vue.html

image.png

一、主要步驟

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)
      }
}
image.png
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)行逐層搜索遍歷的方式


img

img

兩張圖代表舊的和新的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了。
image.png

整個過程就是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è)計模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸟款,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子茂卦,更是在濱河造成了極大的恐慌何什,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件等龙,死亡現(xiàn)場離奇詭異处渣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蛛砰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門罐栈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泥畅,你說我怎么就攤上這事荠诬。” “怎么了位仁?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵浅妆,是天一觀的道長。 經(jīng)常有香客問我障癌,道長凌外,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任涛浙,我火速辦了婚禮康辑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轿亮。我一直安慰自己疮薇,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布我注。 她就那樣靜靜地躺著按咒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪但骨。 梳的紋絲不亂的頭發(fā)上励七,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天智袭,我揣著相機(jī)與錄音,去河邊找鬼掠抬。 笑死吼野,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的两波。 我是一名探鬼主播瞳步,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腰奋!你這毒婦竟也來了单起?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤劣坊,失蹤者是張志新(化名)和其女友劉穎馏臭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讼稚,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年绕沈,在試婚紗的時候發(fā)現(xiàn)自己被綠了锐想。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡乍狐,死狀恐怖赠摇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浅蚪,我是刑警寧澤藕帜,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站惜傲,受9級特大地震影響洽故,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盗誊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一时甚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧哈踱,春花似錦荒适、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至邪财,卻和暖如春陕壹,著一層夾襖步出監(jiān)牢的瞬間质欲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工帐要, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留把敞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓榨惠,卻偏偏與公主長得像奋早,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赠橙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容