美文网首页原创的~Vue.js
6.最俗学习之-Vue源码学习-数据篇(上)

6.最俗学习之-Vue源码学习-数据篇(上)

作者: 木子tar | 来源:发表于2018-02-05 19:27 被阅读30次

    源码地址

    这篇重点学习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>
    
    

    <h3 style="color: red;">最后主角要登场了!!!噔噔蹬</h3>

    这里还是看大神的文章,主线路还是这里的

    Vue2.1.7源码学习

    
    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 {}
    
    // 这个是判断当前的环境能否使用这个东东,然后决定取那个方法
    
    

    这里还是推荐这位大神的文章,分析的非常到位,而且图文并茂

    JavaScript实现MVVM之我就是想监测一个普通对象的变化

    
    // 首先是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的东东没有说,后面再看!

    <p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">剩下的几个问题</p>

    
    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)                                       // 水平有限,看不懂 - -#
    
    

    相关文章

      网友评论

        本文标题:6.最俗学习之-Vue源码学习-数据篇(上)

        本文链接:https://www.haomeiwen.com/subject/wjvizxtx.html