• vue 阅读一【待完结】


    初步方案:从第一个commit开始到到最近的commit,从代码的大纲开始到细节,利用思维导图。
    注意: 源码阅读是一件比较枯燥的事情,要有别的东西一起做,源码只是偶尔看看,经常发呆的话,非常浪费时间。

    写在前面: 阅读源码并不能产生什么实际的价值,而阅读的源码的过程中,你学到的思路,分析方法,总结,才是你花大时间阅读源码所能产生的实际价值。

    阅读源码还是缺乏一点思考,没有结合到实际项目中源码是怎么产生作用的!!!

    阅读源码的疑问:

    • definePrototype是如何生效的
    • 双向绑定的通知机制是如何做的
    • 底层源码个组件之间的通信
    • 观察者模式的流程大概弄懂了,但是细节部分是怎么驱动生效的呢?

    可能的答案:
    关键可能是dep,每个待观察的对象都有一个Observer实例,实例都具有一个dep,每个dep都有一个notify;set方法的时候会通知notify,notify直接调用待观察对象的update方法。这个逻辑链路是符合观察者模式的设计模式的。
    观察者模式:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化。

    dev init a879ec0 第一版本

    内部结构图

    一、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)
      }
    

    源码编写挺遵守规范的,类的首字母大写啊,观察者模式啊等等。

    二、observe 详解

    数据的观察者,每个data属性上面都会有一个观察者

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

    三、compile 详解

    主要是用来解析vue的字符串模板和vue内置的语法为可处理的对象

    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.js

    
    **
     * 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) })`
      }
    }
    
    

    四、watcher 详解

    解析表达式收集依赖,当值变化的时候回调

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

    五、utils 工具集

    这个简直就是js的工具箱宝库,什么工具类都有。判断IE,判断是否是数组,定义defineProperty,debounce 输入延迟触发;

    六、惊艳的写法

    其实阅读源码另外有一个很有意思的地方,就是有些语法你会,但你没看到别人这么用,你可能永远或者很长一段时间都不会这么地用,也就是惊艳!!!

    将类型判断与定义放在一起

    (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)))
    
  • 相关阅读:
    创建一个Flex工程
    WebORB手动配置要点
    热血三国地图增强版
    vs2008 sp1无法加载实体模型工具的解决办法
    Berkeley DB for .NET使用
    FluorineFx使用自定义VO(实现IExternalizable接口)
    PD生成数据表时出现“未能准备语句”
    替换AsWing的JTree组件中的图标
    参数传递
    c# 32位16位MD5加密
  • 原文地址:https://www.cnblogs.com/djh-create/p/7941169.html
Copyright © 2020-2023  润新知