vue 閱讀一【待完結】

初步方案:從第一個commit開始到到最近的commit,從代碼的大綱開始到細節,利用思惟導圖。
注意: 源碼閱讀是一件比較枯燥的事情,要有別的東西一塊兒作,源碼只是偶爾看看,常常發呆的話,很是浪費時間。javascript

寫在前面: 閱讀源碼並不能產生什麼實際的價值,而閱讀的源碼的過程當中,你學到的思路,分析方法,總結,纔是你花大時間閱讀源碼所能產生的實際價值。html

閱讀源碼仍是缺少一點思考,沒有結合到實際項目中源碼是怎麼產生做用的!!!vue

閱讀源碼的疑問:html5

  • definePrototype是如何生效的
  • 雙向綁定的通知機制是如何作的
  • 底層源碼個組件之間的通訊
  • 觀察者模式的流程大概弄懂了,可是細節部分是怎麼驅動生效的呢?

可能的答案:
關鍵多是dep,每一個待觀察的對象都有一個Observer實例,實例都具備一個dep,每一個dep都有一個notify;set方法的時候會通知notify,notify直接調用待觀察對象的update方法。這個邏輯鏈路是符合觀察者模式的設計模式的。
觀察者模式:當一個對象變化時,其它依賴該對象的對象都會收到通知,而且隨着變化。java

==========dev init a879ec0 初版本==========

內部結構圖

1、vue的構造器概覽

constructor (options) {
    this.$options = options
    this._data = options.data
    const el = this._el = document.querySelector(options.el)
    const render = compile(getOuterHTML(el)) //編譯vue模板
    this._el.innerHTML = ''
    Object.keys(options.data).forEach(key => this._proxy(key)) //利用Observer.defineProperty();重定義了屬性成get和set
    if (options.methods) {
      Object.keys(options.methods).forEach(key => {
        this[key] = options.methods[key].bind(this) //把methods 做用域綁定到this,也就是vue實例上面
      })
    }
    this._ob = observe(options.data) // 將數據轉化爲觀察者對象
    this._watchers = []
    this._watcher = new Watcher(this, render, this._update) // 解析表達式,收集依賴,當值變化的時候,通知回調
    this._update(this._watcher.value)
  }

源碼編寫挺遵照規範的,類的首字母大寫啊,觀察者模式啊等等。react

2、observe 詳解

數據的觀察者,每一個data屬性上面都會有一個觀察者git

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 *
 * @param {*} value
 * @param {Vue} [vm]
 * @return {Observer|undefined}
 * @static
 */

export function observe (value, vm) {// 此處的value 是options.data,通常來講是個json
  if (!value || typeof value !== 'object') {
    return
  }
  var ob
  if (
    hasOwn(value, '__ob__') && //hasOwn = hasOwnproperty ,檢查是否具備`__ob__`屬性
    value.__ob__ instanceof Observer //`__ob__` 是不是Observer的實例
  ) {
    ob = value.__ob__ //已經存在`__ob__`,則賦原值
  } else if (
    shouldConvert &&
    (isArray(value) || isPlainObject(value)) && //判斷是不是原生的數組或者是對象
    Object.isExtensible(value) && //判斷一個對象是否可擴展,添加屬性
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (ob && vm) {
    ob.addVm(vm) //??添加vm實例,代理keys
  }
  return ob
}

/**
 * Add an owner vm, so that when $set/$delete mutations
 * happen we can notify owner vms to proxy the keys and
 * digest the watchers. This is only called when the object
 * is observed as an instance's root $data.
 *
 * @param {Vue} vm
 */

Observer.prototype.addVm = function (vm) {
  (this.vms || (this.vms = [])).push(vm)
}


/**
 * Observer class that are attached to each observed
 * object. Once attached, the observer converts target
 * object's property keys into getter/setters that
 * collect dependencies and dispatches updates.
 *
 * @param {Array|Object} value
 * @constructor
 */

export function Observer (value) {
  this.value = value
  this.dep = new Dep()
  def(value, '__ob__', this) //??將value做爲obj,觀察者的關鍵
  if (isArray(value)) {
    var augment = hasProto
      ? protoAugment
      : copyAugment
    augment(value, arrayMethods, arrayKeys) //若是是數組,則攔截變異方法,通知更新
    this.observeArray(value) // 遍歷value ,observer實例化每一個value的值。
  } else {
    this.walk(value)
  }
}

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 * dep 就是一個能夠訂購多個指令的觀察者
 * @constructor
 */

export default function Dep () {
  this.id = uid++
  this.subs = []
}
/**
 * Notify all subscribers of a new value.
 */

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()
  }
}

/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 *
 * @param {Object|Array} target
 * @param {Object} src
 */

function protoAugment (target, src) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment an target Object or Array by defining
 * hidden properties.
 *
 * @param {Object|Array} target
 * @param {Object} proto
 */

function copyAugment (target, src, keys) {
  for (var i = 0, l = keys.length; i < l; i++) {
    var key = keys[i]
    def(target, key, src[key])
  }
}

export const arrayMethods = Object.create(arrayProto)

/**
 * Intercept mutating methods and emit events
 */

;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  var original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    var i = arguments.length
    var args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    var result = original.apply(this, args)
    var ob = this.__ob__
    var inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})


/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 *
 * @param {Object} obj
 * @param {String} key
 * @param {*} val
 * @public
 */

export function set (obj, key, val) {
  if (hasOwn(obj, key)) {
    obj[key] = val
    return
  }
  if (obj._isVue) {
    set(obj._data, key, val)
    return
  }
  var ob = obj.__ob__
  if (!ob) {
    obj[key] = val
    return
  }
  ob.convert(key, val)
  ob.dep.notify() //更新只有,調用通知更新。
  if (ob.vms) {
    var i = ob.vms.length
    while (i--) {
      var vm = ob.vms[i]
      vm._proxy(key)
      vm._digest()
    }
  }
  return val
}

/**
 * Walk through each property and convert them into
 * getter/setters. This method should only be called when
 * value type is Object.
 *
 * @param {Object} obj
 */

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]])
  }
}


/**
 * Define a reactive property on an Object.
 *
 * @param {Object} obj
 * @param {String} key
 * @param {*} val
 */

export function defineReactive (obj, key, val) {
  var dep = new Dep()

  var property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get
  var setter = property && property.set

  var childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
        }
        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
      if (newVal === value) {
        return
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)
      dep.notify()
    }
  })
}

3、compile 詳解

主要是用來解析vue的字符串模板和vue內置的語法爲可處理的對象github

export function compile (html) {
  html = html.trim()
  const hit = cache[html]
  return hit || (cache[html] = generate(parse(html))) //若是沒有的話就生成一個
}

parse() 主要使用了一個第三方庫,將html元素轉化成了一個對象;能夠方便後一步generate處理vue內置語法v-for,v-if等等
htmlparser.jsexpress

**
 * Convert HTML string to AST
 *
 * @param {String} html
 * @return {Object}
 */

export function parse (html) {
  let root
  let currentParent
  let stack = []
  HTMLParser(html, {
    html5: true,
    start (tag, attrs, unary) {
      let element = {
        tag,
        attrs,
        attrsMap: makeAttrsMap(attrs),
        parent: currentParent,
        children: []
      }
      if (!root) {
        root = element
      }
      if (currentParent) {
        currentParent.children.push(element)
      }
      if (!unary) {
        currentParent = element
        stack.push(element)
      }
    },
    end () {
      stack.length -= 1
      currentParent = stack[stack.length - 1]
    },
    chars (text) {
      text = currentParent.tag === 'pre'
        ? text
        : text.trim() ? text : ' '
      currentParent.children.push(text)
    },
    comment () {
      // noop
    }
  })
  return root
}
// src/compile/codepen.js
// generate 主要是解析vue內置的語法,生成dom的。

export function generate (ast) {
  const code = genElement(ast)
  return new Function (`with (this) { return ${code}}`)
}

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 {
    return `__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })`
  }
}

4、watcher 詳解

解析表達式收集依賴,當值變化的時候回調json

**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 *
 * @param {Vue} vm
 * @param {String|Function} expOrFn
 * @param {Function} cb
 * @param {Object} options
 *                 - {Array} filters
 *                 - {Boolean} twoWay
 *                 - {Boolean} deep
 *                 - {Boolean} user
 *                 - {Boolean} sync
 *                 - {Boolean} lazy
 *                 - {Function} [preProcess]
 *                 - {Function} [postProcess]
 * @constructor
 */

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.push(this)
  this.expression = expOrFn
  this.cb = cb
  this.id = ++uid // uid for batching
  this.active = true
  this.dirty = this.lazy // for lazy watchers
  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()
  // state for avoiding false triggers for deep and Array
  // watchers during vm._digest()
  this.queued = this.shallow = false
}

/**
 * Prepare for dependency collection.
 */
Watcher.prototype.beforeGet = function () {
  Dep.target = this
  this.newDepIds = Object.create(null)
  this.newDeps.length = 0
}
/**
 * Add a dependency to this directive.
 *
 * @param {Dep} dep
 */

Watcher.prototype.addDep = function (dep) {
  var id = dep.id
  if (!this.newDepIds[id]) {
    this.newDepIds[id] = true
    this.newDeps.push(dep)
    if (!this.depIds[id]) {
      dep.addSub(this)
    }
  }
}

5、utils 工具集

這個簡直就是js的工具箱寶庫,什麼工具類都有。判斷IE,判斷是不是數組,定義defineProperty,debounce 輸入延遲觸發;

6、驚豔的寫法

其實閱讀源碼另外有一個頗有意思的地方,就是有些語法你會,但你沒看到別人這麼用,你可能永遠或者很長一段時間都不會這麼地用,也就是驚豔!!!

將類型判斷與定義放在一塊兒

(this.vms || (this.vms = [])).push(vm) 

至關於
一個建立了一個prototype擁有全部Array.prototype的object。
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

建立一個原型爲空的對象

const cache = Object.create(null)

若是hit沒有值,則返回gennerate以後的值。
  const hit = cache[html]
  return hit || (cache[html] = generate(parse(html)))
相關文章
相關標籤/搜索