1. 程式人生 > >6.最俗學習之-Vue原始碼學習-資料篇(上)

6.最俗學習之-Vue原始碼學習-資料篇(上)

原始碼地址

這篇重點學習Vue的資料響應系統,檔案路徑src/core/instance


// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
callHook(vm, 'beforeCreate')
initState(vm)
callHook(vm, 'created')
initRender(vm)

// 先看event.js,只有這麼一段

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
// init parent attached events const listeners = vm.$options._parentListeners if (listeners) { updateComponentListeners(vm, listeners) } } // 這裡只做了兩件事,至於這個vm.$options._parentListeners暫時是沒有的,作用暫時不明 vm._events = Object.create(null) vm._hasHookEvent = false // 再看lifecycle.js,同樣的這個if語句相關的也是不會執行的,因為沒有這個parent,也是在vm例項上
// 新增各種內部的屬性 export function initLifecycle (vm: Component) { const options = vm.$options // locate first non-abstract parent let parent = options.parent if (parent && !options.abstract) { while (parent.$options.abstract && parent.$parent) { parent = parent.$parent } parent
.$children.push(vm) } vm.$parent = parent vm.$root = parent ? parent.$root : vm vm.$children = [] vm.$refs = {} vm._watcher = null vm._inactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false } // 然後到了最下面的callHook方法,執行生命週期,也就是上面兩步後執行callHook(vm, 'beforeCreate') export function callHook (vm: Component, hook: string) { const handlers = vm.$options[hook] if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { handlers[i].call(vm) } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } } // 這裡一開始不太明白handlers為什麼會是個陣列,後來想了下,是mixin的原因,下面的例子可以測試 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="app"> </div> </body> <script type="text/javascript" src="vue.js"></script> <script> var mixin = { beforeCreate () { console.log('beforeCreate from mixin') } } let vm = new Vue({ el: '#app', data: { a: 1, b: [1, 2, 3] }, beforeCreate () { console.log('beforeCreate 2') }, mixins: [mixin] }) console.log(vm) </script> </html> // 然而這個就有點陌生了vm._hasHookEvent,看了下構建後的原始碼,使用到這個的只有三處地方 // 這裡新增的vm._hasHookEvent function initEvents (vm) { vm._events = Object.create(null); vm._hasHookEvent = false; // init parent attached events var listeners = vm.$options._parentListeners; if (listeners) { updateComponentListeners(vm, listeners); } } // 這裡新增事件方法(vm._events[event] || (vm._events[event] = [])).push(fn); var hookRE = /^hook:/; Vue.prototype.$on = function (event, fn) { var vm = this;(vm._events[event] || (vm._events[event] = [])).push(fn); // optimize hook:event cost by using a boolean flag marked at registration // instead of a hash lookup if (hookRE.test(event)) { vm._hasHookEvent = true; } return vm }; // 這裡觸發這個vm._hasHookEvent function callHook (vm, hook) { var handlers = vm.$options[hook]; if (handlers) { for (var i = 0, j = handlers.length; i < j; i++) { handlers[i].call(vm); } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook); } } // 根據官方文件api,下面用一個例子測試下,這裡不能用beforeCreate鉤子,因為這個鉤子不會再執行 // 這個大概就是可以在例項上面註冊生命週期事件吧,幾乎沒怎麼用到過,暫時不明白這個設計得用途 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <div id="app"> <p>{{ a }}</p> </div> </body> <script type="text/javascript" src="vue.js"></script> <script> var mixin = { beforeCreate () { console.log('beforeCreate from mixin') } } let vm = new Vue({ el: '#app', data: { a: 1, b: [1, 2, 3] }, beforeCreate () { console.log('beforeCreate 2') }, mixins: [mixin] }) console.log(vm) vm.$on('hook:updated', function () { console.log('hook:updated') }) setInterval( ()=> { vm.a++ }, 1000) </script> </html>

最後主角要登場了!!!噔噔蹬

這裡還是看大神的文章,主線路還是這裡的


initState(vm)

// 我們用最簡單的例子說起,之後慢慢豐富內容

let vm = new Vue({
    el: '#app',
    data: {
        a: 1,
        b: [1, 2, 3]
    }
})

// 這裡只有data,則只會走initData(vm)

function initData (vm: Component) {
  // 獲取data
  let data = vm.$options.data
  // 獲取data,若是函式形式則執行,無值則是空物件
  data = vm._data = typeof data === 'function'
    ? data.call(vm)
    : data || {}
    // 檢測是否為object
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  let i = keys.length
  // 檢測data和props不能衝突
  while (i--) {
    if (props && hasOwn(props, keys[i])) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${keys[i]}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else {
    // 代理資料,這樣我們就能通過 this.a 來訪問 data.a 了
      proxy(vm, keys[i])
    }
  }
  // observe data
  observe(data, true /* asRootData */)   // 重點是這個
}

src/core/observer/index.js


/**
 * 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.
 */
function observe (value, asRootData) {
  // 若不是object,返回,注意這裡,這裡不是標準的檢測方式,這裡陣列也會被看做是object的
  if (!isObject(value)) {
    return
  }
  var ob;
  // 如果已經有這個屬性,則說明已經繫結過資料了
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    observerState.shouldConvert &&  // 這個是定義的一個變數,為true
    !isServerRendering() &&           // 是否為服務端渲染
    (Array.isArray(value) || isPlainObject(value)) &&      // 是Array或者是Object
    Object.isExtensible(value) &&    // 是否為可拓展屬性的物件
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {  // 如果是根資料,那麼ob這個例項的屬性vmCount++
    ob.vmCount++;
  }
  return ob
}

// 重點看這個ob = new Observer(value);

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value           // value就是data值
    this.dep = new Dep()         // 這個是另一個類,依賴收集器
    this.vmCount = 0             // 自身的一個屬性
    def(value, '__ob__', this)   // 定義一個屬性,用的def方法,後面再說
    if (Array.isArray(value)) {   // 如果是陣列,則走這裡處理
      const augment = hasProto
        ? protoAugment
        : copyAugment
      augment(value, arrayMethods, arrayKeys)
      this.observeArray(value)
    } else {                       // 非陣列走這裡
      this.walk(value)
    }
  }

  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    // 遍歷物件的所有屬性,執行defineReactive(obj, keys[i], obj[keys[i]])
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

// 按照例子

let vm = new Vue({
    el: '#app',
    data: {
        a: 1,
        b: [1, 2, 3]
    }
})

// 就是這個樣子了

ob = new Observer({
        a: 1,
        b: [1, 2, 3]
    })

// 然後就是

this.walk({
        a: 1,
        b: [1, 2, 3]
    })

// 再然後

defineReactive(obj, a, 1)
defineReactive(obj, b, [1, 2, 3])

// 找到defineReactive方法

function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter
) {
  var dep = new Dep();           // 先不管

  // getOwnPropertyDescriptor方法返回指定物件上一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該物件的屬性,不需要從原型鏈上進行查詢的屬性)
  var property = Object.getOwnPropertyDescriptor(obj, key);
  // 如果是不可操作的
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  // 獲取物件上的get和set方法,這個應該和Object.defineProperty有關,這個方法網上教程很多,後面也會簡單說下,這裡應該就是之前自定義的方法吧,註釋也說了pre-defined getter/setters,預定義的方法
  var getter = property && property.get;
  var setter = property && property.set;

  var childOb = observe(val);  // 檢測它的子屬性
  Object.defineProperty(obj, key, {   // 定義這個屬性的get和set方法
    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 (Array.isArray(value)) {
          dependArray(value);
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if ("development" !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = observe(newVal);
      dep.notify();
    }
  });
}

// 重點在這個var childOb = observe(val);  // 檢測它的子屬性,根據我們的例子這裡會這樣子

var childOb = observe(1);
var childOb = observe([1,2,3]);

// 然後再次進入observer方法,注意這個

// 若不是object,返回,注意這裡,這裡不是標準的檢測方式,這裡陣列也會被看做是object的
if (!isObject(value)) {
  return
}

// 所以第一個observe(1)直接就return了,重點在observe([1,2,3]),這裡會ob = new Observer(value)
// 即ob = new Observer([1,2,3]),這個時候數個數組就會走這一段

if (Array.isArray(value)) {
  const augment = hasProto
    ? protoAugment
    : copyAugment
  augment(value, arrayMethods, arrayKeys)
  this.observeArray(value)
}

// can we use __proto__?
export const hasProto = '__proto__' in {}

// 這個是判斷當前的環境能否使用這個東東,然後決定取那個方法

這裡還是推薦這位大神的文章,分析的非常到位,而且圖文並茂


// 首先是protoAugment,根據例子即

protoAugment([1,2,3], arrayMethods, arrayKeys)

// 然後找到arrayMethods和arrayKeys,分別如下

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

const arrayProto = Array.prototype
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
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    const result = original.apply(this, args)
    const ob = this.__ob__
    let 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
  })
})


// 這裡弄個自己理解的例子,這個有點不好說,有點繞,還是看大神的分析,不能看懂也不勉強,
// 反正可以理解為在呼叫原生的陣列方法前做點什麼事情,資料繫結更新什麼的等等

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">

  </div>
</body>
<script type="text/javascript" src="vue.js"></script>
<script>

function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)


/**
 * Intercept mutating methods and emit events
 */
;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    const result = original.apply(this, args)
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    return result
  })
})

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

console.log(arrayProto)
console.log(arrayMethods)

// ----------------------上面是官方的例子----------------------------------------

// ---------------------個人理解的一個例子---------------------------------------

var myArr = Object.create(Array.prototype);
var original = Array.prototype;

myArr.push = function (argument) {
  console.log('自己的方法,先做點什麼,最後在呼叫原生的方法')
  original.push.apply(this, [argument])
}

var arr = [1,2,3]

arr.__proto__ = myArr;

arr.push(7)

console.log(arr)

// ---------------------個人理解的一個例子,然後對每個方法做處理-------------------------------

const original_arr_methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];

var myArr2 = Object.create(Array.prototype);
var original2 = Array.prototype;

original_arr_methods.forEach(function (method) {
  myArr2[method] = function (argument) {
    console.log('自己的方法,先做點什麼,最後在呼叫原生的方法')
    original2[method].apply(this, [argument])
  }
})

var arr2 = [1,2,3,4,5,6,7,8,9]

arr2.__proto__ = myArr2;

arr2.push(0)
arr2.shift()
arr2.unshift(1)
arr2.reverse()
arr2.splice(3)

console.log(arr2)
</script>
</html>


// 回到例子protoAugment([1,2,3], arrayMethods, arrayKeys)
// 這裡arrayMethods我們已經知道了

arrayMethods = {
  pop: function () {}             // 自己重寫的方法
  push: function () {}             // 自己重寫的方法
  shift: function () {}             // 自己重寫的方法
  unshift: function () {}             // 自己重寫的方法
  splice: function () {}             // 自己重寫的方法
  sort: function () {}             // 自己重寫的方法
  reverse: function () {}             // 自己重寫的方法
  __proto__: {
    // 這裡面是原生陣列的方法,也就是Array.prototype
  }
}

arrayKeys = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"]

// 所以這個方法實際上就是,這個方法不需要第三個引數,也就是不需要arrayKeys

function protoAugment (target, src: Object) {
  target.__proto__ = src
}

[1,2,3].__proto__ = arrayMethods    // 這個操作類似我上面的那個例子的這一步

arr.__proto__ = myArr;
arr2.__proto__ = myArr2;


// 最後到了另一種情況就是,hasProto沒有這個東東的情況,那就是這個形式

copyAugment([1,2,3], arrayMethods, arrayKeys)

// 對應的方法是這個,這個方法需要arrayKeys這個值

function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

// 這裡的def方法也有說過,這一步的操作就不解釋了,如果到了這裡看不懂的話,就說明你上面的都沒看懂,
// 思路應該還是比較亂的,也可能是我表達不好0.0,這裡一定要弄懂,不然後面的陣列操作會很懵逼
// 再次回到主線路

if (Array.isArray(value)) {
  const augment = hasProto
    ? protoAugment
    : copyAugment
  augment(value, arrayMethods, arrayKeys)
  console.log(value.__proto__)   // 到了這裡打印出來看下其實就能明白了
  this.observeArray(value)
}

// 最後剩下這個this.observeArray(value)了,對陣列的每一項進行observe,又是這個東東- -#

observeArray (items) {
  for (let i = 0, l = items.length; i < l; i++) {
    observe(items[i])
  }
}

// 但是我們這個例子會被直接return的,因為裡面有這一段

if (!isObject(value)) {
  return
}

// 因為我們的例子是

b: [1,2,3]

// 假如是這樣的資料,則會繼續遞迴呼叫

b: [{key1: value1}, {key2: value2}, {key3: value3}]
b: [[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15]]

至此大概的思路應該都是這樣了,也不知道說的對不對,看到最後自己腦袋都有點懵逼了,希望各路大神指點指點,感激不盡,最後還有個Dep的東東沒有說,後面再看!

剩下的幾個問題


Vue.set = set                       // 涉及到Vue的資料響應式系統,先保留
Vue.delete = del                    // 涉及到Vue的資料響應式系統,先保留
Vue.nextTick = util.nextTick        // 水平有限,看不懂 - -#
initExtend(Vue)                     // 水平有限,看不懂 - -#


extend(Vue.options.directives, platformDirectives)  // 水平有限,看不懂 - -#
extend(Vue.options.components, platformComponents)  // 水平有限,看不懂 - -#
Vue.prototype.__patch__                             // 水平有限,看不懂 - -#
compileToFunctions                                  // 水平有限,看不懂 - -#


const extendsFrom = child.extends                   // 水平有限,看不懂 - -#

initProxy(vm)                                       // 水平有限,看不懂 - -#