亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

vue數據控制視圖源碼解析

2019-11-19 14:06:06
字體:
來源:轉載
供稿:網友

分析vue是如何實現數據改變更新視圖的.

前記

三個月前看了vue源碼來分析如何做到響應式數據的, 文章名字叫vue源碼之響應式數據, 最后分析到, 數據變化后會調用Watcher的update()方法. 那么時隔三月讓我們繼續看看update()做了什么. (這三個月用react-native做了個項目, 也無心總結了, 因為好像太簡單了).

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

目的

明確調查方向才能直至目標, 先說一下目標行為: 數據變化以后執行了什么方法來更新視圖的. 那么準備開始以這個方向為目標從vue源碼的入口開始找答案.

從之前的結論開始

先來復習一下之前的結論:

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

另一個對象是Watcher, 注冊watch的時候會調用一次watch的對象, 這樣觸發了watch對象的getter, 把依賴收集到當前Watcher的deps里, 當任何dep的setter被觸發就會notify當前Watcher來調用Watcher的update()方法.

那么這里就從注冊渲染相關的Watcher開始.

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

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

mountComponent

渲染相關的Watcher是在mountComponent()這個方法中調用的, 那么我們搜一下這個方法是在哪里調用的. 只有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()方法調用了mountComponent(), (或者在vue構造時指定el字段也會自動調用$mount()方法), 因為web和weex(什么是weex?之前別的文章介紹過)渲染的標的物不同, 所以在發布的時候應該引入了不同的文件最后發不成不同的dist(這個問題留給之后來研究vue的整個流程).

下面是mountComponent方法:

export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean): Component { vm.$el = el // 放一份el到自己的屬性里 if (!vm.$options.render) { // render應該經過處理了, 因為我們經常都是用template或者vue文件 // 判斷是否存在render函數, 如果沒有就把render函數寫成空VNode來避免紅錯, 并報出黃錯 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 // 注冊一個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}

這段代碼其實只做了3件事:

  • 調用beforeMount鉤子
  • 建立Watcher
  • 調用mounted鉤子

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

看一下Watcher的參數: vm是this, updateComponent是一個函數, noop是空, null是空, true代表是RenderWatcher.

在Watcher里看了isRenderWatcher:

if (isRenderWatcher) {  vm._watcher = this }

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

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

updateComponent

在Watcher的構造函數的第二個參數傳了function, 那么這個函數就成了watcher的getter. 聰明的你應該已經猜到, 在這個updateComponent里一定調用了視圖中所有的數據的getter, 才能在watcher中建立依賴從而讓視圖響應數據的變化.

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其實不需要看了, 都是做異常處理, _vnode是在vm._update的時候保存的, 也就是上次的狀態或是null(init的時候給的)  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 }}

這個方法做了:

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

所以核心是這句:

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

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

先看vm._renderProxy: 是initMixin()的時候設置的, 在生產環境返回vm, 開發環境返回代理, 那么我們認為他是一個可以debug的vm(就是vm), 細節之后再看.

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

render有點復雜, 能不能以后研究, 總之就是把template或者vue單文件和mount目標parse成render函數.

小總結: vm._render()的返回值是VNode, 根據當前vm的render函數

接下來看vm._update()

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this if (vm._isMounted) {  callHook(vm, 'beforeUpdate') } // 記錄update之前的狀態 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創建新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. }

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

結論

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

遺留

render函數是在哪里被編譯的
vue源碼發布時引入不同平臺最后打成dist的流程是什么
__patch__和VNode的分析

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美大片网站在线观看| 91在线视频成人| 久久精品国产亚洲精品2020| 久久精品国产成人精品| 亚洲a成v人在线观看| 最近2019免费中文字幕视频三| 亚洲嫩模很污视频| 欧美又大又粗又长| 久久久久五月天| 亚洲欧美日韩精品久久| 日韩电影在线观看永久视频免费网站| 中文字幕日韩电影| 日韩一区二区三区xxxx| 欧美性猛交xxxx黑人猛交| 欧美日韩精品国产| 亚洲福利小视频| 欧美国产第二页| 久久久这里只有精品视频| 国产99久久精品一区二区 夜夜躁日日躁| 久久久在线观看| 日韩精品视频观看| 日韩av成人在线观看| 欧美日韩成人在线观看| 国产成人精品电影久久久| 久久国产精品视频| 欧美在线视频播放| 精品亚洲永久免费精品| 日韩在线视频导航| 成人性生交大片免费看视频直播| 亚洲深夜福利在线| 91香蕉嫩草神马影院在线观看| 亚洲女在线观看| 日韩在线观看网址| 国产精品久久一区| 国产亚洲精品va在线观看| 国产精品日韩在线| 亚洲精品98久久久久久中文字幕| 中文字幕久久久av一区| 91九色视频导航| 国产精品久在线观看| 精品国产自在精品国产浪潮| 亚洲国产精品99| 久久99久国产精品黄毛片入口| 国产一区二区黑人欧美xxxx| 国产亚洲精品久久久久久777| 91香蕉国产在线观看| 精品在线欧美视频| 日韩精品在线视频| 精品电影在线观看| 亚洲欧美激情在线视频| 51精品在线观看| 亚洲国产精品久久久久秋霞蜜臀| 亚洲第一综合天堂另类专| 欧美一级电影免费在线观看| 日本一区二区三区在线播放| 国产精品永久免费观看| 亚洲电影免费观看高清完整版| 欧美一区二区三区免费观看| 国产精品久久在线观看| 国产成人aa精品一区在线播放| 国产成人亚洲精品| 国产成人a亚洲精品| 国内伊人久久久久久网站视频| 日韩电影在线观看中文字幕| 国产精品久久久久久av福利软件| 欧美人与物videos| 国产一区二区日韩精品欧美精品| 欧美综合国产精品久久丁香| 欧美性在线观看| 在线观看亚洲视频| 欧美黄色www| 国产精品视频专区| 国产精品观看在线亚洲人成网| 亚洲欧美在线一区| 亚洲高清av在线| 日韩视频免费在线观看| 亚洲女人被黑人巨大进入| 中文字幕国内精品| 91精品国产综合久久久久久蜜臀| 97在线看免费观看视频在线观看| 亚洲欧美在线看| 欧美黑人巨大精品一区二区| 一区二区欧美在线| 中日韩美女免费视频网址在线观看| 国产精品精品久久久久久| 欧美大肥婆大肥bbbbb| 中文字幕欧美日韩| 高清在线视频日韩欧美| 美女国内精品自产拍在线播放| 久久深夜福利免费观看| 国产精品普通话| 日韩精品免费在线播放| 国产精品久久久久久网站| 久久精品国产免费观看| 国产精品视频色| 亚洲最大激情中文字幕| 国产精品久久久久久久久男| 亚洲人成五月天| 欧美国产精品va在线观看| 亚洲精品短视频| 亚洲第一区第二区| 黑人巨大精品欧美一区免费视频| 欧美激情精品久久久久久久变态| 日韩精品中文在线观看| 日韩黄色av网站| 影音先锋日韩有码| 欧美激情第三页| 色偷偷噜噜噜亚洲男人的天堂| 黑人极品videos精品欧美裸| 国产自产女人91一区在线观看| 欧美成人免费全部| 欧美性猛交xxxx黑人猛交| 国产精品永久免费观看| 精品一区电影国产| 黑人巨大精品欧美一区二区一视频| 日韩在线视频网站| 亚洲欧美在线磁力| 97精品欧美一区二区三区| 奇米4444一区二区三区| 亚洲石原莉奈一区二区在线观看| 精品国内产的精品视频在线观看| 亚洲色无码播放| 欧美国产日韩免费| 国产91精品久久久久| 丝袜美腿精品国产二区| 91免费电影网站| 久久久久久久久久久国产| 亚洲视屏在线播放| 国产精品久久久久久影视| 丝袜亚洲欧美日韩综合| 亚洲午夜精品久久久久久久久久久久| 欧美激情中文网| 欧美高清视频在线| 日韩国产在线播放| 欧美另类在线播放| 国产精品久久久久秋霞鲁丝| 久久不射热爱视频精品| 亚洲精品网站在线播放gif| 欧美丝袜一区二区| 久久久www成人免费精品张筱雨| 亚洲第一网中文字幕| 日韩电影中文字幕在线| 97激碰免费视频| 国产+人+亚洲| 日韩成人在线视频网站| 国产欧美日韩91| 亚洲精品一区二三区不卡| 亚洲精品视频网上网址在线观看| 日韩国产欧美精品在线| 成人a视频在线观看| 久久精品国产一区| 国产一区红桃视频| 欧美极品少妇xxxxⅹ裸体艺术| 国产精品国内视频| 国产成人+综合亚洲+天堂| 久久久久久国产免费| 亚洲午夜av久久乱码| 欧美性高跟鞋xxxxhd| 精品国产福利视频| 久热99视频在线观看| 欧美日韩免费一区| 亚洲视频一区二区| 精品香蕉在线观看视频一| 久久6免费高清热精品|