最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue數(shù)據(jù)控制視圖源碼解析

 更新時(shí)間:2018年03月28日 08:53:16   作者:rehapun  
本篇內(nèi)容給大家詳細(xì)分析了關(guān)于vue數(shù)據(jù)控制視圖的源碼以及重點(diǎn)做了注釋,有興趣的朋友參考學(xué)習(xí)下。

分析vue是如何實(shí)現(xiàn)數(shù)據(jù)改變更新視圖的.

前記

三個(gè)月前看了vue源碼來分析如何做到響應(yīng)式數(shù)據(jù)的, 文章名字叫vue源碼之響應(yīng)式數(shù)據(jù), 最后分析到, 數(shù)據(jù)變化后會(huì)調(diào)用Watcher的update()方法. 那么時(shí)隔三月讓我們繼續(xù)看看update()做了什么. (這三個(gè)月用react-native做了個(gè)項(xiàng)目, 也無心總結(jié)了, 因?yàn)楹孟裉唵瘟?.

本文敘事方式為樹藤摸瓜, 順著看源碼的邏輯走一遍, 查看的vue的版本為2.5.2. 我fork了一份源碼用來記錄注釋.

目的

明確調(diào)查方向才能直至目標(biāo), 先說一下目標(biāo)行為: 數(shù)據(jù)變化以后執(zhí)行了什么方法來更新視圖的. 那么準(zhǔn)備開始以這個(gè)方向?yàn)槟繕?biāo)從vue源碼的入口開始找答案.

從之前的結(jié)論開始

先來復(fù)習(xí)一下之前的結(jié)論:

vue構(gòu)造的時(shí)候會(huì)在data(和一些別的字段)上建立Observer對象, getter和setter被做了攔截, getter觸發(fā)依賴收集, setter觸發(fā)notify.

另一個(gè)對象是Watcher, 注冊watch的時(shí)候會(huì)調(diào)用一次watch的對象, 這樣觸發(fā)了watch對象的getter, 把依賴收集到當(dāng)前Watcher的deps里, 當(dāng)任何dep的setter被觸發(fā)就會(huì)notify當(dāng)前Watcher來調(diào)用Watcher的update()方法.

那么這里就從注冊渲染相關(guān)的Watcher開始.

找到了文件在src/core/instance/lifecycle.js中.

new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)

mountComponent

渲染相關(guān)的Watcher是在mountComponent()這個(gè)方法中調(diào)用的, 那么我們搜一下這個(gè)方法是在哪里調(diào)用的. 只有2處, 分別是src/platforms/web/runtime/index.js和src/platforms/weex/runtime/index.js, 以web為例:

Vue.prototype.$mount = function (
 el?: string | Element,
 hydrating?: boolean
): Component {
 el = el && inBrowser ? query(el) : undefined
 return mountComponent(this, el, hydrating)
}

原來如此, 是$mount()方法調(diào)用了mountComponent(), (或者在vue構(gòu)造時(shí)指定el字段也會(huì)自動(dòng)調(diào)用$mount()方法), 因?yàn)閣eb和weex(什么是weex?之前別的文章介紹過)渲染的標(biāo)的物不同, 所以在發(fā)布的時(shí)候應(yīng)該引入了不同的文件最后發(fā)不成不同的dist(這個(gè)問題留給之后來研究vue的整個(gè)流程).

下面是mountComponent方法:

export function mountComponent (
 vm: Component,
 el: ?Element,
 hydrating?: boolean
): Component {
 vm.$el = el // 放一份el到自己的屬性里
 if (!vm.$options.render) { // render應(yīng)該經(jīng)過處理了, 因?yàn)槲覀兘?jīng)常都是用template或者vue文件
 // 判斷是否存在render函數(shù), 如果沒有就把render函數(shù)寫成空VNode來避免紅錯(cuò), 并報(bào)出黃錯(cuò)
 vm.$options.render = createEmptyVNode
 if (process.env.NODE_ENV !== 'production') {
  /* istanbul ignore if */
  if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
  vm.$options.el || el) {
  warn(
   'You are using the runtime-only build of Vue where the template ' +
   'compiler is not available. Either pre-compile the templates into ' +
   'render functions, or use the compiler-included build.',
   vm
  )
  } else {
  warn(
   'Failed to mount component: template or render function not defined.',
   vm
  )
  }
 }
 }
 callHook(vm, 'beforeMount')

 let updateComponent
 /* istanbul ignore if */
 if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
 // 不看這里的代碼了, 直接看else里的, 行為是一樣的
 updateComponent = () => {
  const name = vm._name
  const id = vm._uid
  const startTag = `vue-perf-start:${id}`
  const endTag = `vue-perf-end:${id}`

  mark(startTag)
  const vnode = vm._render()
  mark(endTag)
  measure(`vue ${name} render`, startTag, endTag)

  mark(startTag)
  vm._update(vnode, hydrating)
  mark(endTag)
  measure(`vue ${name} patch`, startTag, endTag)
 }
 } else {
 updateComponent = () => {
  vm._update(vm._render(), hydrating)
 }
 }

 // we set this to vm._watcher inside the watcher's constructor
 // since the watcher's initial patch may call $forceUpdate (e.g. inside child
 // component's mounted hook), which relies on vm._watcher being already defined
 // 注冊一個(gè)Watcher
 new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
 hydrating = false

 // manually mounted instance, call mounted on self
 // mounted is called for render-created child components in its inserted hook
 if (vm.$vnode == null) {
 vm._isMounted = true
 callHook(vm, 'mounted')
 }
 return vm
}

這段代碼其實(shí)只做了3件事:

  • 調(diào)用beforeMount鉤子
  • 建立Watcher
  • 調(diào)用mounted鉤子

(哈哈哈)那么其實(shí)核心就是建立Watcher了.

看一下Watcher的參數(shù): vm是this, updateComponent是一個(gè)函數(shù), noop是空, null是空, true代表是RenderWatcher.

在Watcher里看了isRenderWatcher:

if (isRenderWatcher) {
  vm._watcher = this
 }

是的, 只是復(fù)制了一份用來在watcher第一次patch的時(shí)候判斷一些東西(從注釋里看的, 我現(xiàn)在還不知道是干嘛的).

那么只有一個(gè)問題沒解決就是updateComponent是個(gè)什么東西.

updateComponent

在Watcher的構(gòu)造函數(shù)的第二個(gè)參數(shù)傳了function, 那么這個(gè)函數(shù)就成了watcher的getter. 聰明的你應(yīng)該已經(jīng)猜到, 在這個(gè)updateComponent里一定調(diào)用了視圖中所有的數(shù)據(jù)的getter, 才能在watcher中建立依賴從而讓視圖響應(yīng)數(shù)據(jù)的變化.

updateComponent = () => {
  vm._update(vm._render(), hydrating)
 }

那么就去找vm._update()和vm._render().

在src/core/instance/render.js找到了._render()方法.

Vue.prototype._render = function (): VNode {
 const vm: Component = this
 const { render, _parentVnode } = vm.$options // todo: render和_parentVnode的由來

 // reset _rendered flag on slots for duplicate slot check
 if (process.env.NODE_ENV !== 'production') {
  for (const key in vm.$slots) {
  // $flow-disable-line
  vm.$slots[key]._rendered = false
  }
 }

 if (_parentVnode) {
  vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
 }

 // set parent vnode. this allows render functions to have access
 // to the data on the placeholder node.
 vm.$vnode = _parentVnode
 // render self
 let vnode
 try {
  vnode = render.call(vm._renderProxy, vm.$createElement)
 } catch (e) {
  // catch其實(shí)不需要看了, 都是做異常處理, _vnode是在vm._update的時(shí)候保存的, 也就是上次的狀態(tài)或是null(init的時(shí)候給的)
  handleError(e, vm, `render`)
  // return error render result,
  // or previous vnode to prevent render error causing blank component
  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
  if (vm.$options.renderError) {
   try {
   vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
   } catch (e) {
   handleError(e, vm, `renderError`)
   vnode = vm._vnode
   }
  } else {
   vnode = vm._vnode
  }
  } else {
  vnode = vm._vnode
  }
 }
 // return empty vnode in case the render function errored out
 if (!(vnode instanceof VNode)) {
  if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
  warn(
   'Multiple root nodes returned from render function. Render function ' +
   'should return a single root node.',
   vm
  )
  }
  vnode = createEmptyVNode()
 }
 // set parent
 vnode.parent = _parentVnode
 return vnode
 }
}

這個(gè)方法做了:

  • 根據(jù)當(dāng)前vm的render方法來生成VNode. (render方法可能是根據(jù)template或vue文件編譯而來, 所以推論直接寫render方法效率最高)
  • 如果render方法有問題, 那么首先調(diào)用renderError方法, 再不行就讀取上次的vnode或是null.
  • 如果有父節(jié)點(diǎn)就放到自己的.parent屬性里.
  • 最后返回VNode

所以核心是這句:

vnode = render.call(vm._renderProxy, vm.$createElement)

其中的render(), vm._renderProxy, vm.$createElement都不知道是什么.

先看vm._renderProxy: 是initMixin()的時(shí)候設(shè)置的, 在生產(chǎn)環(huán)境返回vm, 開發(fā)環(huán)境返回代理, 那么我們認(rèn)為他是一個(gè)可以debug的vm(就是vm), 細(xì)節(jié)之后再看.

vm.$createElement的代碼在vdom文件夾下, 看了下是一個(gè)方法, 返回值一個(gè)VNode.

render有點(diǎn)復(fù)雜, 能不能以后研究, 總之就是把template或者vue單文件和mount目標(biāo)parse成render函數(shù).

小總結(jié): vm._render()的返回值是VNode, 根據(jù)當(dāng)前vm的render函數(shù)

接下來看vm._update()

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
 const vm: Component = this
 if (vm._isMounted) {
  callHook(vm, 'beforeUpdate')
 }
 // 記錄update之前的狀態(tài)
 const prevEl = vm.$el
 const prevVnode = vm._vnode
 const prevActiveInstance = activeInstance
 activeInstance = vm
 vm._vnode = vnode
 // Vue.prototype.__patch__ is injected in entry points
 // based on the rendering backend used.
 if (!prevVnode) { // 初次加載, 只有_update方法更新vm._vnode, 初始化是null
  // initial render
  vm.$el = vm.__patch__( // patch創(chuàng)建新dom
  vm.$el, vnode, hydrating, false /* removeOnly */,
  vm.$options._parentElm,
  vm.$options._refElm
  )
  // no need for the ref nodes after initial patch
  // this prevents keeping a detached DOM tree in memory (#5851)
  vm.$options._parentElm = vm.$options._refElm = null
 } else {
  // updates
  vm.$el = vm.__patch__(prevVnode, vnode) // patch更新dom
 }
 activeInstance = prevActiveInstance
 // update __vue__ reference
 if (prevEl) {
  prevEl.__vue__ = null
 }
 if (vm.$el) {
  vm.$el.__vue__ = vm
 }
 // if parent is an HOC, update its $el as well
 if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
  vm.$parent.$el = vm.$el
 }
 // updated hook is called by the scheduler to ensure that children are
 // updated in a parent's updated hook.
 }

我們關(guān)心的部分其實(shí)就是__patch()的部分, __patch()做了對dom的操作, 在_update()里判斷了是否是初次調(diào)用, 如果是的話創(chuàng)建新dom, 不是的話傳入新舊node進(jìn)行比較再操作.

結(jié)論

vue的視圖渲染是一種特殊的Watcher, watch的內(nèi)容是一個(gè)函數(shù), 函數(shù)運(yùn)行的過程調(diào)用了render函數(shù), render又是由template或者el的dom編譯成的(template中含有一些被observe的數(shù)據(jù)). 所以template中被observe的數(shù)據(jù)有變化觸發(fā)Watcher的update()方法就會(huì)重新渲染視圖.

遺留

render函數(shù)是在哪里被編譯的
vue源碼發(fā)布時(shí)引入不同平臺(tái)最后打成dist的流程是什么
__patch__和VNode的分析

相關(guān)文章

  • vue.js頁面加載執(zhí)行created,mounted的先后順序說明

    vue.js頁面加載執(zhí)行created,mounted的先后順序說明

    這篇文章主要介紹了vue.js頁面加載執(zhí)行created,mounted的先后順序說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • vue自定義樹狀結(jié)構(gòu)圖的實(shí)現(xiàn)方法

    vue自定義樹狀結(jié)構(gòu)圖的實(shí)現(xiàn)方法

    這篇文章主要給大家介紹了關(guān)于vue自定義樹狀結(jié)構(gòu)圖的實(shí)現(xiàn)方法,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Vue實(shí)現(xiàn)上下層疊輪播圖

    Vue實(shí)現(xiàn)上下層疊輪播圖

    這篇文章主要介紹了Vue實(shí)現(xiàn)上下層疊輪播圖,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • 深入理解Vue Computed計(jì)算屬性原理

    深入理解Vue Computed計(jì)算屬性原理

    Computed 計(jì)算屬性是 Vue 中常用的一個(gè)功能,本篇文章主要介紹了Vue Computed 計(jì)算屬性原理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-05-05
  • vue+element實(shí)現(xiàn)下拉菜單并帶本地搜索功能示例詳解

    vue+element實(shí)現(xiàn)下拉菜單并帶本地搜索功能示例詳解

    這篇文章主要介紹了vue+element實(shí)現(xiàn)下拉菜單并帶本地搜索功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • vue+element?tree懶加載更新數(shù)據(jù)的示例代碼

    vue+element?tree懶加載更新數(shù)據(jù)的示例代碼

    這篇文章主要介紹了vue+element?tree懶加載更新數(shù)據(jù),文中給大家補(bǔ)充介紹了Vue Element Ui 樹形表懶加載新增、修改、刪除等操作后局部數(shù)據(jù)更新的詳細(xì)代碼,感興趣的朋友跟隨小編一起看看吧
    2022-09-09
  • vue?filter的幾種用法示例小結(jié)

    vue?filter的幾種用法示例小結(jié)

    Vue.filter函數(shù)用于注冊一個(gè)全局的過濾器,該過濾器可以在模板和組件中使用,這篇文章主要介紹了vue?filter的幾種用法示例小結(jié),需要的朋友可以參考下
    2024-08-08
  • vue自定義指令實(shí)現(xiàn)方法詳解

    vue自定義指令實(shí)現(xiàn)方法詳解

    這篇文章主要介紹了vue自定義指令實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了vue.js自定義指令相關(guān)定義、注冊、使用方法及操作注意事項(xiàng),需要的朋友可以參考下
    2019-02-02
  • webpack4打包vue前端多頁面項(xiàng)目

    webpack4打包vue前端多頁面項(xiàng)目

    這篇文章主要介紹了webpack4打包vue前端多頁面項(xiàng)目的相關(guān)知識,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-09-09
  • 解決vue與node模版引擎的渲染標(biāo)記{{}}(雙花括號)沖突問題

    解決vue與node模版引擎的渲染標(biāo)記{{}}(雙花括號)沖突問題

    這篇文章主要介紹了解決vue與node模版引擎的渲染標(biāo)記{{}}(雙花括號)沖突問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09

最新評論

齐齐哈尔市| 田林县| 武城县| 新邵县| 衡阳县| 宝应县| 新沂市| 禄劝| 前郭尔| 东乡族自治县| 台北县| 炎陵县| 海兴县| 于都县| 开鲁县| 宁国市| 屯昌县| 哈巴河县| 伊吾县| 内乡县| 东乡| 兰西县| 辽阳县| 临颍县| 沁源县| 秦安县| 昌黎县| 稻城县| 江口县| 吉林市| 镇坪县| 正安县| 柳林县| 大关县| 宜兰市| 旌德县| 班玛县| 普安县| 敦化市| 德江县| 丹凤县|