欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談vue的第一個commit分析

 更新時間:2020年06月08日 16:13:03   作者:沐曉  
這篇文章主要介紹了淺談vue的第一個commit分析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

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

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

目錄結構

本文以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數據,執(zhí)行渲染
│  ├── observer        // 數據訂閱發(fā)布的實現
│  │  ├── array.js      // 實現array變異方法,$set $remove 實現
│  │  ├── batcher.js     // watch執(zhí)行隊列的收集,執(zhí)行
│  │  ├── 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        // 節(jié)點數據分析(元素節(jié)點,文本節(jié)點)
│    ├── index.js      // vdom主入口
│    ├── modules      // 不同屬性處理函數
│    │  ├── attrs.js    // 普通attr屬性處理
│    │  ├── class.js    // class處理
│    │  ├── events.js   // event處理
│    │  ├── props.js    // props處理
│    │  └── style.js    // style處理
│    ├── patch.js      // node樹的渲染,包括節(jié)點的加減更新處理,及對應attr的處理
│    └── vnode.js      // 返回最終的節(jié)點數據
└── 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)
}

當我們初始化項目的時候,即會執(zhí)行構造函數,該函數向我們展示了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的轉化生成了構建節(jié)點html的函數,在genElement將對if for 等進行判斷并轉化( 指令的具體處理將在后面做分析,先關注主流程代碼),最后都會執(zhí)行genData函數

// 生成節(jié)點主函數
export function generate (ast) {
 const code = genElement(ast)
 // 執(zhí)行code代碼,并將this作為code的global對象。所以我們在template中的變量將指向為實例的屬性 {{name}} -> this.name 
 return new Function (`with (this) { return $[code]}`)
}

// 解析單個節(jié)點 -> 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 自身屬性 子節(jié)點數據
  return `__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })`
 }
}

我們可以看看在genData中都做了什么。上面的parse函數將html字符串轉化為ast,而在genData中則將節(jié)點的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,實際上在這里還包括了對文本節(jié)點的處理。

// 遍歷子節(jié)點 -> 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 函數利用上面得到的節(jié)點數據得到 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)) {
  // 子節(jié)點遞歸處理
  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)
 }
 // 子節(jié)點為文本節(jié)點
 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()來建立對數據更改的訂閱,在很多地方也稱之為數據劫持。下面我們來學習從零開始建立這樣一個數據的訂閱發(fā)布體系。

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

function observe(data, callback) {
 if (!data || typeof data !== 'object') {
  return
 }

 // 遍歷key
 Object.keys(data).forEach((key) => {
  let value = data[key];

  // 遞歸遍歷監(jiān)聽深度變化
  observe(value, callback);

  // 監(jiān)聽單個可以的變化
  Object.defineProperty(data, key, {
   configurable: true,
   enumerable: true,
   get() {
    return value;
   },
   set(val) {
    if (val === value) {
     return
    }

    value = val;

    // 監(jiān)聽新的數據
    observe(value, callback);
    
    // 數據改變的回調
    callback();
   }
  });
 });
}

// 使用observe函數監(jiān)聽data
const data = {};
observe(data, () => {
 console.log('data修改');
})

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

// 首先是訂閱中心
class Dep {
 constructor() {
  this.subs = []; // 訂閱者數組
 }

 addSub(sub) {
  // 添加訂閱者
  this.subs.push(sub);
 }

 notify() {
  // 發(fā)布通知
  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;
  // 執(zhí)行表達式 -> 觸發(fā)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];
 
   // 遞歸遍歷監(jiān)聽深度變化
   const observe = new Observe(value);
 
   // 監(jiān)聽單個可以的變化
   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;
 
     // 監(jiān)聽新的數據
     new Observe(value);
     
     // 數據改變的回調
     dep.notify();
    }
   });
  });
 }
}

// 監(jiān)聽數據中某個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

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

  1. 建立觀察者觀察data數據的更改 (new Observe)
  2. 在編譯的時候,當某個代碼片段或節(jié)點依賴data數據,為該節(jié)點建議訂閱者,訂閱data中某些數據的更新(new Watch)
  3. 當dada數據更新時,通過訂閱中心通知數據更新,執(zhí)行節(jié)點更新函數,新建或更新節(jié)點(dep.notify())

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

// ...
// 為數據建立數據觀察
this._ob = observe(options.data)
this._watchers = []
// 添加訂閱者 執(zhí)行render 會觸發(fā) getter 訂閱者訂閱更新,數據改變觸發(fā) setter 訂閱中心通知訂閱者執(zhí)行 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)
 }
 // 存儲關聯(lián)的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

 // 實現遞歸監(jiān)聽屬性 val = obj[key]
 // 深度優(yōu)先遍歷 先為子屬性設置 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 {
  // 執(zhí)行 expOrFn,此時會觸發(fā) getter => dep.depend() 將watch實例添加到對應 obj[key] 的 dep
  value = this.getter.call(scope, scope)
 }
 if (this.deep) {
  // 深度watch
  // 觸發(fā)每個key的getter watch實例將對應多個dep
  traverse(value)
 }
 // ...
 this.afterGet()
 return value
}

// 觸發(fā)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聯(lián)系
 Dep.target = null
 var i = this.deps.length
 while (i--) {
  var dep = this.deps[i]
  if (!this.newDepIds[dep.id]) {
   // 移除不在expOrFn表達式中關聯(lián)的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')
  }
  // 添加到待執(zhí)行池
  pushWatcher(this)
 }
}

// 執(zhí)行更新回調
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回調執(zhí)行隊列

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

// batch.js
var queueIndex
var queue = []
var userQueue = []
var has = {}
var circular = {}
var waiting = false
var internalQueueDepleted = false

// 重置執(zhí)行池
function resetBatcherState () {
 queue = []
 userQueue = []
 // has 避免重復
 has = {}
 circular = {}
 waiting = internalQueueDepleted = false
}

// 執(zhí)行執(zhí)行隊列
function flushBatcherQueue () {
 runBatcherQueue(queue)
 internalQueueDepleted = true
 runBatcherQueue(userQueue)
 resetBatcherState()
}

// 批量執(zhí)行
function runBatcherQueue (queue) {
 for (queueIndex = 0; queueIndex < queue.length; queueIndex++) {
  var watcher = queue[queueIndex]
  var id = watcher.id
  // 執(zhí)行后置為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
   }
  }
 }
}

// 添加到執(zhí)行池
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中執(zhí)行
    nextTick(flushBatcherQueue)
   }
  }
 }
}

4. patch實現

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

// _update => createPatchFunction => patch => patchVnode => (dom api)

// vtree是通過compile函數編譯的render函數執(zhí)行的結果,返回了當前表示當前dom結構的對象(虛擬節(jié)點樹)
_update (vtree) {
 if (!this._tree) {
  // 第一次渲染
  patch(this._el, vtree)
 } else {
  patch(this._tree, vtree)
 }
 this._tree = vtree
}

// 在處理節(jié)點時,需要針對class,props,style,attrs,events做不同處理
// 在這里注入針對不同屬性的處理函數
const patch = createPatchFunction([
 _class, // makes it easy to toggle classes
 props,
 style,
 attrs,
 events
])

// => createPatchFunction返回patch函數,patch函數通過對比虛擬節(jié)點的差異,對節(jié)點進行增刪更新
// 最后調用原生的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 到節(jié)點渲染的大致實現,當然也有某些地方沒有全面分析的地方,其中template解析為ast主要通過正則匹配實現,及節(jié)點渲染及更新的patch過程主要通過節(jié)點操作對比來實現。但是我們對編譯template字符串 => 代理data數據/methods的this綁定 => 數據觀察 => 建立watch及更新渲染的大致流程有了個比較完整的認知。

到此這篇關于淺談vue的第一個commit分析的文章就介紹到這了,更多相關vue commit內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • vue 獲取及修改store.js里的公共變量實例

    vue 獲取及修改store.js里的公共變量實例

    今天小編就為大家分享一篇vue 獲取及修改store.js里的公共變量實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11
  • Vue2打包部署后可動態(tài)修改后端接口地址的解決方法

    Vue2打包部署后可動態(tài)修改后端接口地址的解決方法

    本篇文章將介紹使用Vue2開發(fā)前后端分離項目時,前端打包部署后可動態(tài)修改后端接口地址的解決方法,文中通過圖文結合的方式介紹的非常詳細,需要的朋友可以參考下
    2024-07-07
  • vue+iview使用樹形控件的具體使用

    vue+iview使用樹形控件的具體使用

    這篇文章主要介紹了vue+iview使用樹形控件的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • 前端架構vue動態(tài)組件使用基礎教程

    前端架構vue動態(tài)組件使用基礎教程

    這篇文章主要為大家介紹了前端架構vue動態(tài)組件使用的基礎教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2022-02-02
  • vue修改數據的時候,表單值回顯不正確問題及解決

    vue修改數據的時候,表單值回顯不正確問題及解決

    這篇文章主要介紹了vue修改數據的時候,表單值回顯不正確的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Vue如何接入hls/m3u8的直播視頻詳解

    Vue如何接入hls/m3u8的直播視頻詳解

    項目中有一個需求,需要實現直播功能,后端接口返回的是m3u8數據流,下面這篇文章主要給大家介紹了關于Vue如何接入hls/m3u8直播視頻的相關資料,需要的朋友可以參考下
    2022-07-07
  • vue.js基于v-for實現批量渲染 Json數組對象列表數據示例

    vue.js基于v-for實現批量渲染 Json數組對象列表數據示例

    這篇文章主要介紹了vue.js基于v-for實現批量渲染 Json數組對象列表數據,結合實例形式分析了vue.js使用v-for遍歷json格式數據渲染列表相關操作技巧,需要的朋友可以參考下
    2019-08-08
  • vue嵌入第三方頁面幾種常見方法

    vue嵌入第三方頁面幾種常見方法

    在Vue中嵌入第三方頁面可以采用多種方法,例如使用<iframe>、Vue插件、動態(tài)加載第三方腳本或WebComponents,不同方法適用于不同類型的內容和項目需求,如<iframe>適用于整個網頁,而動態(tài)腳本和WebComponents適合特定功能,選擇合適的方法可以有效整合外部資源
    2024-09-09
  • 詳解處理Vue單頁面應用SEO的另一種思路

    詳解處理Vue單頁面應用SEO的另一種思路

    這篇文章主要介紹了詳解處理Vue單頁面應用SEO的另一種思路,本文主要針對 vue 2.0 單頁面 Meta SEO 優(yōu)化展開介紹,非常具有實用價值,需要的朋友可以參考下
    2018-11-11
  • vant IndexBar實現的城市列表的示例代碼

    vant IndexBar實現的城市列表的示例代碼

    這篇文章主要介紹了vant IndexBar實現的城市列表的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-11-11

最新評論