歡迎您光臨本站 註冊首頁

淺談vue的第一個commit分析

←手機掃碼閱讀     qp18502452 @ 2020-06-11 , reply:0

為什麼寫這篇vue的分析文章?
 

對於天資愚鈍的前端(我)來說,閱讀源碼是件不容易的事情,畢竟有時候看源碼分析的文章都看不懂。每次看到大佬們用了1~2年的vue就能掌握原理,甚至精通源碼,再看看自己用了好幾年都還在基本的使用階段,心中總是羞愧不已。如果一直滿足於基本的業務開發,怕是得在初級水平一直待下去了吧。所以希望在學習源碼的同時記錄知識點,可以讓自己的理解和記憶更加深刻,也方便將來查閱。

目錄結構
 

本文以vue的第一次 commit a879ec06 作為分析版本

  ├── build  │  └── build.js        // `rollup` 打包配置  ├── dist              │  └── vue.js    ├── package.json  ├── src            // vue源碼目錄  │  ├── compiler        // 將vue-template轉化為render函數  │  │  ├── codegen.js     // 遞歸ast提取指令,分類attr,style,class,並生成render函數  │  │  ├── html-parser.js   // 通過正則匹配將html字符串轉化為ast  │  │  ├── index.js      // compile主入口  │  │  └── text-parser.js   // 編譯{{}}  │  ├── config.js       // 對於vue的全局配置文件  │  ├── index.js        // 主入口  │  ├── index.umd.js      // 未知(應該是umd格式的主入口)  │  ├── instance        // vue實例函數  │  │  └── index.js      // 包含了vue實例的初始化,compile,data代理,methods代理,watch數據,執行渲染  │  ├── observer        // 數據訂閱發佈的實現  │  │  ├── array.js      // 實現array變異方法,$set $remove 實現  │  │  ├── batcher.js     // watch執行隊列的收集,執行  │  │  ├── dep.js       // 訂閱中心實現  │  │  ├── index.js      // 數據劫持的實現,收集訂閱者  │  │  └── watcher.js     // watch實現,訂閱者  │  ├── util          // 工具函數  │  │  ├── component.js  │  │  ├── debug.js  │  │  ├── dom.js  │  │  ├── env.js       // nexttick實現  │  │  ├── index.js  │  │  ├── lang.js  │  │  └── options.js  │  └── vdom  │    ├── dom.js       // dom操作的封裝  │    ├── h.js        // 節點數據分析(元素節點,文本節點)  │    ├── index.js      // vdom主入口  │    ├── modules      // 不同屬性處理函數  │    │  ├── attrs.js    // 普通attr屬性處理  │    │  ├── class.js    // class處理  │    │  ├── events.js   // event處理  │    │  ├── props.js    // props處理  │    │  └── style.js    // style處理  │    ├── patch.js      // node樹的渲染,包括節點的加減更新處理,及對應attr的處理  │    └── vnode.js      // 返回最終的節點數據  └── webpack.config.js     // webpack配置

 

從template到html的過程分析
 

我們的代碼是從new Vue()開始的,Vue的構造函數如下:

  constructor (options) {   // options就是我們對於vue的配置   this.$options = options   this._data = options.data   // 獲取元素html,即template   const el = this._el = document.querySelector(options.el)   // 編譯模板 -> render函數   const render = compile(getOuterHTML(el))   this._el.innerHTML = ''   // 實例代理data數據   Object.keys(options.data).forEach(key => this._proxy(key))   // 將method的this指向實例   if (options.methods) {    Object.keys(options.methods).forEach(key => {     this[key] = options.methods[key].bind(this)    })   }   // 數據觀察   this._ob = observe(options.data)   this._watchers = []   // watch數據及更新   this._watcher = new Watcher(this, render, this._update)   // 渲染函數   this._update(this._watcher.value)  }

 

當我們初始化項目的時候,即會執行構造函數,該函數向我們展示了vue初始化的主線:編譯template字符串 => 代理data數據/methods的this綁定 => 數據觀察 => 建立watch及更新渲染

1. 編譯template字符串
 

  const render = compile(getOuterHTML(el))

 

其中compile的實現如下:

  export function compile (html) {   html = html.trim()   // 對編譯結果緩存   const hit = cache[html]   // parse函數在parse-html中定義,其作用是把我們獲取的html字符串通過正則匹配轉化為ast,輸出如下 {tag: 'div', attrs: {}, children: []}   return hit || (cache[html] = generate(parse(html)))  }

 

接下來看看generate函數,ast通過genElement的轉化生成了構建節點html的函數,在genElement將對if for 等進行判斷並轉化( 指令的具體處理將在後面做分析,先關注主流程代碼),最後都會執行genData函數

  // 生成節點主函數  export function generate (ast) {   const code = genElement(ast)   // 執行code代碼,並將this作為code的global對象。所以我們在template中的變量將指向為實例的屬性 {{name}} -> this.name    return new Function (`with (this) { return $[code]}`)  }    // 解析單個節點 -> genData  function genElement (el, key) {   let exp   // 指令的實現,實際就是在模板編譯時實現的   if (exp = getAttr(el, 'v-for')) {    return genFor(el, exp)   } else if (exp = getAttr(el, 'v-if')) {    return genIf(el, exp)   } else if (el.tag === 'template') {    return genChildren(el)   } else {    // 分別為 tag 自身屬性 子節點數據    return `__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })`   }  }

 

我們可以看看在genData中都做了什麼。上面的parse函數將html字符串轉化為ast,而在genData中則將節點的attrs數據進一步處理,例如class -> renderClass style class props attr 分類。在這裡可以看到 bind 指令的實現,即通過正則匹配 : 和 bind,如果匹配則把相應的 value值轉化為 (value)的形式,而不匹配的則通過JSON.stringify()轉化為字符串('value')。最後輸出attrs的(key-value),在這裡得到的對象是字符串形式的,例如(value)等也僅僅是將變量名,而在generate中通過new Function進一步通過(this.value)得到變量值。

  function genData (el, key) {   // 沒有屬性返回空對象   if (!el.attrs.length) {    return '{}'   }   // key   let data = key ? `{key:${ key },` : `{`   // class處理   if (el.attrsMap[':class'] || el.attrsMap['class']) {    data += `class: _renderClass(${ el.attrsMap[':class'] }, "${ el.attrsMap['class'] || '' }"),`   }   // attrs   let attrs = `attrs:{`   let props = `props:{`   let hasAttrs = false   let hasProps = false   for (let i = 0, l = el.attrs.length; i < l; i++) {    let attr = el.attrs[i]    let name = attr.name    // bind屬性    if (bindRE.test(name)) {     name = name.replace(bindRE, '')     if (name === 'class') {      continue     // style處理     } else if (name === 'style') {      data += `style: ${ attr.value },`     // props屬性處理     } else if (mustUsePropsRE.test(name)) {      hasProps = true      props += `"${ name }": (${ attr.value }),`      // 其他屬性     } else {      hasAttrs = true      attrs += `"${ name }": (${ attr.value }),`     }    // on指令,未實現    } else if (onRE.test(name)) {     name = name.replace(onRE, '')    // 普通屬性    } else if (name !== 'class') {     hasAttrs = true     attrs += `"${ name }": (${ JSON.stringify(attr.value) }),`    }   }   if (hasAttrs) {    data += attrs.slice(0, -1) + '},'   }   if (hasProps) {    data += props.slice(0, -1) + '},'   }   return data.replace(/,$/, '') + '}'  }

 

而對於genChildren,我們可以猜到就是對ast中的children進行遍歷調用genElement,實際上在這裡還包括了對文本節點的處理。

  // 遍歷子節點 -> genNode  function genChildren (el) {   if (!el.children.length) {    return 'undefined'   }   // 對children扁平化處理   return '__flatten__([' + el.children.map(genNode).join(',') + '])'  }    function genNode (node) {   if (node.tag) {    return genElement(node)   } else {    return genText(node)   }  }    // 解析{{}}  function genText (text) {   if (text === ' ') {    return '" "'   } else {    const exp = parseText(text)    if (exp) {     return 'String(' + escapeNewlines(exp) + ')'    } else {     return escapeNewlines(JSON.stringify(text))    }   }  }

 

genText處理了text及換行,在parseText函數中利用正則解析{{}},輸出字符串(value)形式的字符串。

現在我們再看看__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })中__h__函數

  // h 函數利用上面得到的節點數據得到 vNode對象 => 虛擬dom  export default function h (tag, b, c) {   var data = {}, children, text, i   if (arguments.length === 3) {    data = b    if (isArray(c)) { children = c }    else if (isPrimitive(c)) { text = c }   } else if (arguments.length === 2) {    if (isArray(b)) { children = b }    else if (isPrimitive(b)) { text = b }    else { data = b }   }   if (isArray(children)) {    // 子節點遞歸處理    for (i = 0; i < children.length; ++i) {     if (isPrimitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i])    }   }   // svg處理   if (tag === 'svg') {    addNS(data, children)   }   // 子節點為文本節點   return VNode(tag, data, children, text, undefined)  }

 

到此為止,我們分析了const render = compile(getOuterHTML(el)),從el的html字符串到render函數都是怎麼處理的。

2. 代理data數據/methods的this綁定
 

  // 實例代理data數據  Object.keys(options.data).forEach(key => this._proxy(key))  // 將method的this指向實例  if (options.methods) {   Object.keys(options.methods).forEach(key => {    this[key] = options.methods[key].bind(this)   })  }

 

實例代理data數據的實現比較簡單,就是利用了對象的setter和getter,讀取this數據時返回data數據,在設置this數據時同步設置data數據

  _proxy (key) {   if (!isReserved(key)) {    // need to store ref to self here    // because these getter/setters might    // be called by child scopes via    // prototype inheritance.    var self = this    Object.defineProperty(self, key, {     configurable: true,     enumerable: true,     get: function proxyGetter () {      return self._data[key]     },     set: function proxySetter (val) {      self._data[key] = val     }    })   }  }

 

3. Obaerve的實現
 

Observe的實現原理在很多地方都有分析,主要是利用了Object.defineProperty()來建立對數據更改的訂閱,在很多地方也稱之為數據劫持。下面我們來學習從零開始建立這樣一個數據的訂閱發佈體系。

從簡單處開始,我們希望有個函數可以幫我們監聽數據的改變,每當數據改變時執行特定回調函數

  function observe(data, callback) {   if (!data || typeof data !== 'object') {    return   }     // 遍歷key   Object.keys(data).forEach((key) => {    let value = data[key];      // 遞歸遍歷監聽深度變化    observe(value, callback);      // 監聽單個可以的變化    Object.defineProperty(data, key, {     configurable: true,     enumerable: true,     get() {      return value;     },     set(val) {      if (val === value) {       return      }        value = val;        // 監聽新的數據      observe(value, callback);            // 數據改變的回調      callback();     }    });   });  }    // 使用observe函數監聽data  const data = {};  observe(data, () => {   console.log('data修改');  })

 

上面我們實現了一個簡單的observe函數,只要我們將編譯函數作為callback傳入,那麼每次數據更改時都會觸發回調函數。但是我們現在不能為單獨的key設置監聽及回調函數,只能監聽整個對象的變化執行回調。下面我們對函數進行改進,達到為某個key設置監聽及回調。同時建立調度中心,讓整個訂閱發佈模式更加清晰。

  // 首先是訂閱中心  class Dep {   constructor() {    this.subs = []; // 訂閱者數組   }     addSub(sub) {    // 添加訂閱者    this.subs.push(sub);   }     notify() {    // 發佈通知    this.subs.forEach((sub) => {     sub.update();    });   }  }    // 當前訂閱者,在getter中標記  Dep.target = null;    // 訂閱者  class Watch {   constructor(express, cb) {    this.cb = cb;    if (typeof express === 'function') {     this.expressFn = express;    } else {     this.expressFn = () => {      return new Function(express)();     }    }        this.get();   }     get() {    // 利用Dep.target存當前訂閱者    Dep.target = this;    // 執行表達式 -> 觸發getter -> 在getter中添加訂閱者    this.expressFn();    // 及時置空    Dep.taget = null;   }     update() {    // 更新    this.cb();   }     addDep(dep) {    // 添加訂閱    dep.addSub(this);   }  }    // 觀察者 建立觀察  class Observe {   constructor(data) {    if (!data || typeof data !== 'object') {     return    }       // 遍歷key    Object.keys(data).forEach((key) => {     // key => dep 對應     const dep = new Dep();     let value = data[key];        // 遞歸遍歷監聽深度變化     const observe = new Observe(value);        // 監聽單個可以的變化     Object.defineProperty(data, key, {      configurable: true,      enumerable: true,      get() {       if (Dep.target) {        const watch = Dep.target;        watch.addDep(dep);       }       return value;      },      set(val) {       if (val === value) {        return       }          value = val;          // 監聽新的數據       new Observe(value);              // 數據改變的回調       dep.notify();      }     });    });   }  }    // 監聽數據中某個key的更改  const data = {   name: 'xiaoming',   age: 26  };    const observe = new Observe(data);    const watch = new Watch('data.age', () => {   console.log('age update');  });    data.age = 22

 

現在我們實現了訂閱中心,訂閱者,觀察者。觀察者監測數據的更新,訂閱者通過訂閱中心訂閱數據的更新,當數據更新時,觀察者會告訴訂閱中心,訂閱中心再逐個通知所有的訂閱者執行更新函數。到現在為止,我們可以大概猜出vue的實現原理:

  1. 建立觀察者觀察data數據的更改 (new Observe)

  2. 在編譯的時候,當某個代碼片段或節點依賴data數據,為該節點建議訂閱者,訂閱data中某些數據的更新(new Watch)

  3. 當dada數據更新時,通過訂閱中心通知數據更新,執行節點更新函數,新建或更新節點(dep.notify())

上面是我們對vue實現原理訂閱發佈模式的基本實現,及編譯到更新過程的猜想,現在我們接著分析vue源碼的實現:
 在實例的初始化中
 

  // ...  // 為數據建立數據觀察  this._ob = observe(options.data)  this._watchers = []  // 添加訂閱者 執行render 會觸發 getter 訂閱者訂閱更新,數據改變觸發 setter 訂閱中心通知訂閱者執行 update  this._watcher = new Watcher(this, render, this._update)  // ...

 

vue中數據觀察的實現
 

  // observe函數  export function observe (value, vm) {   if (!value || typeof value !== 'object') {    return   }   if (    hasOwn(value, '__ob__') &&    value.__ob__ instanceof Observer   ) {    ob = value.__ob__   } else if (    shouldConvert &&    (isArray(value) || isPlainObject(value)) &&    Object.isExtensible(value) &&    !value._isVue   ) {    // 為數據建立觀察者    ob = new Observer(value)   }   // 存儲關聯的vm   if (ob && vm) {    ob.addVm(vm)   }   return ob  }    // => Observe 函數  export function Observer (value) {   this.value = value   // 在數組變異方法中有用   this.dep = new Dep()   // observer實例存在__ob__中   def(value, '__ob__', this)   if (isArray(value)) {    var augment = hasProto     ? protoAugment     : copyAugment    // 數組遍歷,添加變異的數組方法    augment(value, arrayMethods, arrayKeys)    // 對數組的每個選項調用observe函數    this.observeArray(value)   } else {    // walk -> convert -> defineReactive -> setter/getter    this.walk(value)   }  }    // => walk  Observer.prototype.walk = function (obj) {   var keys = Object.keys(obj)   for (var i = 0, l = keys.length; i < l; i++) {    this.convert(keys[i], obj[keys[i]])   }  }    // => convert  Observer.prototype.convert = function (key, val) {   defineReactive(this.value, key, val)  }    // 重點看看defineReactive  export function defineReactive (obj, key, val) {   // key對應的的訂閱中心   var dep = new Dep()     var property = Object.getOwnPropertyDescriptor(obj, key)   if (property && property.configurable === false) {    return   }     // 兼容原有setter/getter   // cater for pre-defined getter/setters   var getter = property && property.get   var setter = property && property.set     // 實現遞歸監聽屬性 val = obj[key]   // 深度優先遍歷 先為子屬性設置 reactive   var childOb = observe(val)   // 設置 getter/setter   Object.defineProperty(obj, key, {    enumerable: true,    configurable: true,    get: function reactiveGetter () {     var value = getter ? getter.call(obj) : val     // Dep.target 為當前 watch 實例     if (Dep.target) {      // dep 為 obj[key] 對應的調度中心 dep.depend 將當前 wtcher 實例添加到調度中心      dep.depend()      if (childOb) {       // childOb.dep 為 obj[key] 值 val 對應的 observer 實例的 dep       // 實現array的變異方法和$set方法訂閱       childOb.dep.depend()      }        // TODO: 此處作用未知?      if (isArray(value)) {       for (var e, i = 0, l = value.length; i < l; i++) {        e = value[i]        e && e.__ob__ && e.__ob__.dep.depend()       }      }     }     return value    },    set: function reactiveSetter (newVal) {     var value = getter ? getter.call(obj) : val     // 通過 getter 獲取 val 判斷是否改變     if (newVal === value) {      return     }     if (setter) {      setter.call(obj, newVal)     } else {      val = newVal     }     // 為新值設置 reactive     childOb = observe(newVal)     // 通知key對應的訂閱中心更新     dep.notify()    }   })  }

 

訂閱中心的實現
 

  let uid = 0    export default function Dep () {   this.id = uid++   // 訂閱調度中心的watch數組   this.subs = []  }    // 當前watch實例  Dep.target = null    // 添加訂閱者  Dep.prototype.addSub = function (sub) {   this.subs.push(sub)  }    // 移除訂閱者  Dep.prototype.removeSub = function (sub) {   this.subs.$remove(sub)  }    // 訂閱  Dep.prototype.depend = function () {   // Dep.target.addDep(this) => this.addSub(Dep.target) => this.subs.push(Dep.target)   Dep.target.addDep(this)  }    // 通知更新  Dep.prototype.notify = function () {   // stablize the subscriber list first   var subs = this.subs.slice()   for (var i = 0, l = subs.length; i < l; i++) {    // subs[i].update() => watch.update()    subs[i].update()   }  }

 

訂閱者的實現
 

  export default function Watcher (vm, expOrFn, cb, options) {   // mix in options   if (options) {    extend(this, options)   }   var isFn = typeof expOrFn === 'function'   this.vm = vm   // vm 的 _watchers 包含了所有 watch   vm._watchers.push(this)   this.expression = expOrFn   this.cb = cb   this.id = ++uid // uid for batching   this.active = true   this.dirty = this.lazy // for lazy watchers   // deps 一個 watch 實例可以對應多個 dep   this.deps = []   this.newDeps = []   this.depIds = Object.create(null)   this.newDepIds = null   this.prevError = null // for async error stacks   // parse expression for getter/setter   if (isFn) {    this.getter = expOrFn    this.setter = undefined   } else {    warn('vue-lite only supports watching functions.')   }   this.value = this.lazy    ? undefined    : this.get()   this.queued = this.shallow = false  }    Watcher.prototype.get = function () {   this.beforeGet()   var scope = this.scope || this.vm   var value   try {    // 執行 expOrFn,此時會觸發 getter => dep.depend() 將watch實例添加到對應 obj[key] 的 dep    value = this.getter.call(scope, scope)   }   if (this.deep) {    // 深度watch    // 觸發每個key的getter watch實例將對應多個dep    traverse(value)   }   // ...   this.afterGet()   return value  }    // 觸發getter,實現訂閱  Watcher.prototype.beforeGet = function () {   Dep.target = this   this.newDepIds = Object.create(null)   this.newDeps.length = 0  }    // 添加訂閱  Watcher.prototype.addDep = function (dep) {   var id = dep.id   if (!this.newDepIds[id]) {    // 將新出現的dep添加到newDeps中    this.newDepIds[id] = true    this.newDeps.push(dep)    // 如果已在調度中心,不再重複添加    if (!this.depIds[id]) {     // 將watch添加到調度中心的數組中     dep.addSub(this)    }   }  }    Watcher.prototype.afterGet = function () {   // 切除key的getter聯繫   Dep.target = null   var i = this.deps.length   while (i--) {    var dep = this.deps[i]    if (!this.newDepIds[dep.id]) {     // 移除不在expOrFn表達式中關聯的dep中watch的訂閱     dep.removeSub(this)    }   }   this.depIds = this.newDepIds   var tmp = this.deps   this.deps = this.newDeps   // TODO: 既然newDeps最終會被置空,這邊賦值的意義在於?   this.newDeps = tmp  }    // 訂閱中心通知消息更新  Watcher.prototype.update = function (shallow) {   if (this.lazy) {    this.dirty = true   } else if (this.sync || !config.async) {    this.run()   } else {    // if queued, only overwrite shallow with non-shallow,    // but not the other way around.    this.shallow = this.queued     ? shallow      ? this.shallow      : false     : !!shallow    this.queued = true    // record before-push error stack in debug mode    /* istanbul ignore if */    if (process.env.NODE_ENV !== 'production' && config.debug) {     this.prevError = new Error('[vue] async stack trace')    }    // 添加到待執行池    pushWatcher(this)   }  }    // 執行更新回調  Watcher.prototype.run = function () {   if (this.active) {    var value = this.get()    if (     ((isObject(value) || this.deep) && !this.shallow)    ) {     // set new value     var oldValue = this.value     this.value = value     var prevError = this.prevError     // ...     this.cb.call(this.vm, value, oldValue)    }    this.queued = this.shallow = false   }  }    Watcher.prototype.depend = function () {   var i = this.deps.length   while (i--) {    this.deps[i].depend()   }  }

 

wtach回調執行隊列
 

在上面我們可以發現,watch在收到信息更新執行update時。如果非同步情況下會執行pushWatcher(this)將實例推入執行池中,那麼在何時會執行回調函數,如何執行呢?我們一起看看pushWatcher的實現。

  // batch.js  var queueIndex  var queue = []  var userQueue = []  var has = {}  var circular = {}  var waiting = false  var internalQueueDepleted = false    // 重置執行池  function resetBatcherState () {   queue = []   userQueue = []   // has 避免重複   has = {}   circular = {}   waiting = internalQueueDepleted = false  }    // 執行執行隊列  function flushBatcherQueue () {   runBatcherQueue(queue)   internalQueueDepleted = true   runBatcherQueue(userQueue)   resetBatcherState()  }    // 批量執行  function runBatcherQueue (queue) {   for (queueIndex = 0; queueIndex < queue.length; queueIndex++) {    var watcher = queue[queueIndex]    var id = watcher.id    // 執行後置為null    has[id] = null    watcher.run()    // in dev build, check and stop circular updates.    if (process.env.NODE_ENV !== 'production' && has[id] != null) {     circular[id] = (circular[id] || 0) + 1     if (circular[id] > config._maxUpdateCount) {      warn(       'You may have an infinite update loop for watcher ' +       'with expression "' + watcher.expression + '"',       watcher.vm      )      break     }    }   }  }    // 添加到執行池  export function pushWatcher (watcher) {   var id = watcher.id   if (has[id] == null) {    if (internalQueueDepleted && !watcher.user) {     // an internal watcher triggered by a user watcher...     // let's run it immediately after current user watcher is done.     userQueue.splice(queueIndex + 1, 0, watcher)    } else {     // push watcher into appropriate queue     var q = watcher.user      ? userQueue      : queue     has[id] = q.length     q.push(watcher)     // queue the flush     if (!waiting) {      waiting = true      // 在nextick中執行      nextTick(flushBatcherQueue)     }    }   }  }

 

4. patch實現
 

上面便是vue中數據驅動的實現原理,下面我們接著回到主流程中,在執行完watch後,便執行this._update(this._watcher.value)開始節點渲染

  // _update => createPatchFunction => patch => patchVnode => (dom api)    // vtree是通過compile函數編譯的render函數執行的結果,返回了當前表示當前dom結構的對象(虛擬節點樹)  _update (vtree) {   if (!this._tree) {    // 第一次渲染    patch(this._el, vtree)   } else {    patch(this._tree, vtree)   }   this._tree = vtree  }    // 在處理節點時,需要針對class,props,style,attrs,events做不同處理  // 在這裡注入針對不同屬性的處理函數  const patch = createPatchFunction([   _class, // makes it easy to toggle classes   props,   style,   attrs,   events  ])    // => createPatchFunction返回patch函數,patch函數通過對比虛擬節點的差異,對節點進行增刪更新  // 最後調用原生的dom api更新html  return function patch (oldVnode, vnode) {   var i, elm, parent   var insertedVnodeQueue = []   // pre hook   for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()     if (isUndef(oldVnode.sel)) {    oldVnode = emptyNodeAt(oldVnode)   }     if (sameVnode(oldVnode, vnode)) {    // someNode can patch    patchVnode(oldVnode, vnode, insertedVnodeQueue)   } else {    // 正常的不復用 remove insert    elm = oldVnode.elm    parent = api.parentNode(elm)      createElm(vnode, insertedVnodeQueue)      if (parent !== null) {     api.insertBefore(parent, vnode.elm, api.nextSibling(elm))     removeVnodes(parent, [oldVnode], 0, 0)    }   }     for (i = 0; i < insertedVnodeQueue.length; ++i) {    insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i])   }     // hook post   for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()   return vnode  }

 

結尾
 

以上分析了vue從template 到節點渲染的大致實現,當然也有某些地方沒有全面分析的地方,其中template解析為ast主要通過正則匹配實現,及節點渲染及更新的patch過程主要通過節點操作對比來實現。但是我們對編譯template字符串 => 代理data數據/methods的this綁定 => 數據觀察 => 建立watch及更新渲染的大致流程有了個比較完整的認知。


[qp18502452 ] 淺談vue的第一個commit分析已經有382次圍觀

http://coctec.com/docs/vue-js/show-post-237973.html