• Vue2.0源码阅读笔记--生命周期


    一、Vue2.0的生命周期

    Vue2.0的整个生命周期有八个:分别是 1.beforeCreate,2.created,3.beforeMount,4.mounted,5.beforeUpdate,6.updated,7.beforeDestroy,8.destroyed。

    用官方的一张图就可以清晰的了解整个生命周期:

    Vue最新源码下载:地址

    二:源码分析

    1.先看new Vue实例的方法

    创建Vue实例的文件是: src/core/instance/index.js

    function Vue (options) {
      if (process.env.NODE_ENV !== 'production' &&
        !(this instanceof Vue)) {
        warn('Vue is a constructor and should be called with the `new` keyword')
      }
      this._init(options)
    }

    Vue的构造函数调用了this._init()方法,this._init()方法存在Vue的原型链中。在src/core/instance/init.js文件中:

    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
        vm._uid = uid++
        // a flag to avoid this being observed
        vm._isVue = true
        // merge options 第一步: options参数的处理
        if (options && options._isComponent) {
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options)
        } else {
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }
        /* istanbul ignore else 第二步:renderProxy */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm)
        } else {
          vm._renderProxy = vm
        }
        // expose real self
        vm._self = vm
        // 第三步:vm的生命周期相关变量初始化
        initLifecycle(vm)
        // 第四步:vm的事件监听初始化
        initEvents(vm)
        // 第五步: render
        initRender(vm)
        callHook(vm, 'beforeCreate')
        // 第六步:vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化
        initState(vm)
        callHook(vm, 'created')
        if (vm.$options.el) {
          vm.$mount(vm.$options.el)
        }
      }
    }

    接下来继续分析每一步的详细实现。

    第一步: options参数的处理

     // merge options 第一步: options参数的处理
        if (options && options._isComponent) {
          // optimize internal component instantiation 优化内部组件实例
          // since dynamic options merging is pretty slow, and none of the 因为动态options融合比较慢,而内部组件options不需要特别处理
          // internal component options needs special treatment.
          initInternalComponent(vm, options)
        } else {
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }

    initInternalComponent的方法为:

    function initInternalComponent (vm: Component, options: InternalComponentOptions) {
      const opts = vm.$options = Object.create(vm.constructor.options)
      // doing this because it's faster than dynamic enumeration. 做这些是因为它比动态计数要快
      opts.parent = options.parent
      opts.propsData = options.propsData
      opts._parentVnode = options._parentVnode
      opts._parentListeners = options._parentListeners
      opts._renderChildren = options._renderChildren
      opts._componentTag = options._componentTag
      opts._parentElm = options._parentElm
      opts._refElm = options._refElm
      if (options.render) {
        opts.render = options.render
        opts.staticRenderFns = options.staticRenderFns
      }
    }

    Vue是一套组件化系统,子组件的options必然受到父组件的影响、即使是同一个组件,我们也有公用的options(挂载在构造器上)和差异的options(实例传入的options),因此处理options时我们要处理四个相关的options:

    • 父组件构造器上的options
    • 父组件实例上的options
    • 当前组件构造器上的options
    • 当前组件实例化传入的options
    vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )

    resolveConstructorOptions的方法为:

    export function resolveConstructorOptions (Ctor: Class<Component>) {
      let options = Ctor.options
      if (Ctor.super) { // 如果有父级
        const superOptions = Ctor.super.options // 获取父级的options
        const cachedSuperOptions = Ctor.superOptions // 获取父级缓存的options
        const extendOptions = Ctor.extendOptions // 获取自身的options
        if (superOptions !== cachedSuperOptions) { // 如果父级options有变化
          // super option changed
          Ctor.superOptions = superOptions // 更新缓存
          extendOptions.render = options.render
          extendOptions.staticRenderFns = options.staticRenderFns
          extendOptions._scopeId = options._scopeId
          options = Ctor.options = mergeOptions(superOptions, extendOptions)
          if (options.name) {
            options.components[options.name] = Ctor
          }
        }
      }
      return options
    }

    接下来就重点看mergeOptions(文件位置在srccoreutiloptions.js)的实现了:

    /**
     * Merge two option objects into a new one. 将两个参数融合成一个
     * Core utility used in both instantiation and inheritance. 核心公用的会被用于实例和继承中
     */
    export function mergeOptions (
      parent: Object,
      child: Object,
      vm?: Component
    ): Object {
      if (process.env.NODE_ENV !== 'production') {
        checkComponents(child)
      }
      // 统一props格式
      normalizeProps(child)
      // 统一directives的格式
      normalizeDirectives(child)
      const extendsFrom = child.extends
      // 如果存在child.extends
      if (extendsFrom) {
        parent = typeof extendsFrom === 'function'
          ? mergeOptions(parent, extendsFrom.options, vm)
          : mergeOptions(parent, extendsFrom, vm) // 递归调用该方法
      }
      if (child.mixins) {
        //如果存在child.mixins
        for (let i = 0, l = child.mixins.length; i < l; i++) {
          let mixin = child.mixins[i]
          if (mixin.prototype instanceof Vue) {
            mixin = mixin.options
          }
          parent = mergeOptions(parent, mixin, vm)
        }
      }
      //针对不同的键值,采用不同的merge策略
      const options = {}
      let key
      for (key in parent) {
        mergeField(key)
      }
      for (key in child) {
        if (!hasOwn(parent, key)) {
          mergeField(key)
        }
      }
      function mergeField (key) {
        const strat = strats[key] || defaultStrat
        options[key] = strat(parent[key], child[key], vm, key)
      }
      return options
    }

    上面采取了对不同的field采取不同的策略,Vue提供了一个strats对象,其本身就是一个hook,如果strats有提供特殊的逻辑,就走strats,否则走默认merge逻辑。

    /**
     * Option overwriting strategies are functions that handle
     * how to merge a parent option value and a child option
     * value into the final value.
     */
    const strats = config.optionMergeStrategies

     第二步:renderProxy 

    主要是定义了vm._renderProxy,这是后期为render做准备的

    /* istanbul ignore else 第二步:renderProxy */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm)
        } else {
          vm._renderProxy = vm
        }

    作用是在render中将this指向vm._renderProxy。一般而言,vm._renderProxy是等于vm的,但在开发环境,Vue动用了Proxy这个新API

    看下initProxy(存放于srccoreinstanceproxy.js) 这个方法

     initProxy = function initProxy (vm) {
        if (hasProxy) {
          // determine which proxy handler to use 确定用哪个proxy handler
          const options = vm.$options
          const handlers = options.render && options.render._withStripped
            ? getHandler
            : hasHandler // getHandler和hasHandler在上面有定义
          vm._renderProxy = new Proxy(vm, handlers)
        } else {
          vm._renderProxy = vm
        }
      }
    Proxy 学习资源

    ES6规范定义了一个全新的全局构造函数:代理(Proxy)。它可以接受两个参数:目标对象(vm)和句柄对象(handlers)。

    一个简单示例:

    var target = {}, handler = {};
    var proxy = new Proxy(target, handler);

    1.代理和目标对象之间的关系:

    代理的行为很简单:将代理的所有内部方法转发至目标。简单来说,如果调用proxy.[[Enumerate]](),就会返回target.[[Enumerate]]()

    现在,让我们尝试执行一条能够触发调用proxy.[[Set]]()方法的语句。

    此时target的结果看看

    2.代理和句柄对象的关系:

    句柄对象的方法可以覆写任意代理的内部方法。举个例子,定义一个handler.set()方法来拦截所有给对象属性赋值的行为:

     var target = {};
        var handler = {
          set: function (target, key, value, receiver) {
            throw new Error("请不要为这个对象设置属性。");
          }
        };
        var proxy = new Proxy(target, handler);

    结果:

    第三步:vm的生命周期相关变量初始化

    // 第三步:vm的生命周期相关变量初始化
        initLifecycle(vm)

    initLifecycle该方法存在于srccoreinstancelifecycle.js文件中

    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
    }

    第四步:vm的事件监听初始化

    // 第四步:vm的事件监听初始化
        initEvents(vm)

    initEvents方法存在于srccoreinstanceevent.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)
      }
    }
    export function updateComponentListeners (
      vm: Component,
      listeners: Object,
      oldListeners: ?Object
    ) {
      target = vm
      updateListeners(listeners, oldListeners || {}, add, remove, vm)
    }

    updateListeners方法存在于srccorevdomhelperupdate-listeners.js

    export function updateListeners (
      on: Object,
      oldOn: Object,
      add: Function,
      remove: Function,
      vm: Component
    ) {
      let name, cur, old, event
      for (name in on) {
        cur = on[name]
        old = oldOn[name]
        event = normalizeEvent(name)
        if (!cur) {
          process.env.NODE_ENV !== 'production' && warn(
            `Invalid handler for event "${event.name}": got ` + String(cur),
            vm
          )
        } else if (!old) {
          // 新添加的listener
          if (!cur.invoker) {
            cur = on[name] = createEventHandle(cur)
          }
          add(event.name, cur.invoker, event.once, event.capture)
        } else if (cur !== old) {
          // 替换旧的事件监听
          old.fn = cur
          on[name] = old
        }
      }
      // 删除无用的listeners
      for (name in oldOn) {
        if (!on[name]) {
          event = normalizeEvent(name)
          remove(event.name, oldOn[name].invoker, event.capture)
        }
      }
    }

    第五步: render 

    // 第五步: render 
        initRender(vm)

    initRender存放于srccoreinstance ender.js

    export function initRender (vm: Component) {
      vm.$vnode = null // the placeholder node in parent tree 在父级树上的提示节点?
      vm._vnode = null // the root of the child tree 子级的根节点
      vm._staticTrees = null
      const parentVnode = vm.$options._parentVnode
      const renderContext = parentVnode && parentVnode.context
      vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
      vm.$scopedSlots = emptyObject
      // bind the createElement fn to this instance 绑定创建元素到这个实例
      // so that we get proper render context inside it. 以方便我们能获得正确的渲染内容
      // args order: tag, data, children, normalizationType, alwaysNormalize  参数提供: tag,data, children,normalizationType,alwaysNormalize
      // internal version is used by render functions compiled from templates 内部版本用来编译从templates来的函数?
      vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // 箭头函数,相当于function(a,b,c,d) { return createElement(vm, a, b, c, d, false) }
      // normalization is always applied for the public version, used in
      // user-written render functions.  统一化?
      vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
    }

    createElement方法存放于srccorevdomcreate-element.js

    // wrapper function for providing a more flexible interface 封装方法用来提供一个可扩展性的接口
    // without getting yelled at by flow 不是流程式
    export function createElement (
      context: Component,
      tag: any,
      data: any,
      children: any,
      normalizationType: any,
      alwaysNormalize: boolean
    ): VNode {
      if (Array.isArray(data) || isPrimitive(data)) {
        normalizationType = children
        children = data
        data = undefined
      }
      if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE
      return _createElement(context, tag, data, children, normalizationType)
    }

    _createElement方法也在这个文件中

    export function _createElement (
      context: Component,
      tag?: string | Class<Component> | Function | Object,
      data?: VNodeData,
      children?: any,
      normalizationType?: number
    ): VNode {
      if (data && data.__ob__) {
        process.env.NODE_ENV !== 'production' && warn(
          `Avoid using observed data object as vnode data: ${JSON.stringify(data)}
    ` +
          'Always create fresh vnode data objects in each render!',
          context
        )
        return createEmptyVNode()
      }
      if (!tag) {
        // in case of component :is set to falsy value
        return createEmptyVNode()
      }
      // support single function children as default scoped slot
      if (Array.isArray(children) &&
          typeof children[0] === 'function') {
        data = data || {}
        data.scopedSlots = { default: children[0] }
        children.length = 0
      }
      if (normalizationType === ALWAYS_NORMALIZE) {
        children = normalizeChildren(children)
      } else if (normalizationType === SIMPLE_NORMALIZE) {
        children = simpleNormalizeChildren(children)
      }
      let vnode, ns
      if (typeof tag === 'string') {
        let Ctor
        ns = config.getTagNamespace(tag)
        if (config.isReservedTag(tag)) {
          // platform built-in elements
          vnode = new VNode(
            config.parsePlatformTagName(tag), data, children,
            undefined, undefined, context
          )
        } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
          // component
          vnode = createComponent(Ctor, data, context, children, tag)
        } else {
          // unknown or unlisted namespaced elements
          // check at runtime because it may get assigned a namespace when its
          // parent normalizes children
          vnode = new VNode(
            tag, data, children,
            undefined, undefined, context
          )
        }
      } else {
        // direct component options / constructor
        vnode = createComponent(tag, data, context, children)
      }
      if (vnode) {
        if (ns) applyNS(vnode, ns)
        return vnode
      } else {
        return createEmptyVNode()
      }
    }
    View Code

    文件中引用了VNode这个类和createEmptyVNode方法,这两个东西存放于srccorevdomvnode.js

    export default class VNode {
      tag: string | void;
      data: VNodeData | void;
      children: ?Array<VNode>;
      text: string | void;
      elm: Node | void;
      ns: string | void;
      context: Component | void; // rendered in this component's scope
      functionalContext: Component | void; // only for functional component root nodes
      key: string | number | void;
      componentOptions: VNodeComponentOptions | void;
      componentInstance: Component | void; // component instance
      parent: VNode | void; // component placeholder node
      raw: boolean; // contains raw HTML? (server only)
      isStatic: boolean; // hoisted static node
      isRootInsert: boolean; // necessary for enter transition check
      isComment: boolean; // empty comment placeholder?
      isCloned: boolean; // is a cloned node?
      isOnce: boolean; // is a v-once node?
    
      constructor (
        tag?: string,
        data?: VNodeData,
        children?: ?Array<VNode>,
        text?: string,
        elm?: Node,
        context?: Component,
        componentOptions?: VNodeComponentOptions
      ) {
        this.tag = tag
        this.data = data
        this.children = children
        this.text = text
        this.elm = elm
        this.ns = undefined
        this.context = context
        this.functionalContext = undefined
        this.key = data && data.key
        this.componentOptions = componentOptions
        this.componentInstance = undefined
        this.parent = undefined
        this.raw = false
        this.isStatic = false
        this.isRootInsert = true
        this.isComment = false
        this.isCloned = false
        this.isOnce = false
      }
    
      // DEPRECATED: alias for componentInstance for backwards compat.
      /* istanbul ignore next */
      get child (): Component | void {
        return this.componentInstance
      }
    }
    View Code

    createEmptyVNode方法

    export const createEmptyVNode = () => {
      const node = new VNode()
      node.text = ''
      node.isComment = true
      return node
    }

    第六步:vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化 

     // 第六步:vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化
        initState(vm)

    initState存放于srccoreinstancestate.js

    export function initState (vm: Component) {
      vm._watchers = []
      const opts = vm.$options
      if (opts.props) initProps(vm, opts.props)
      if (opts.methods) initMethods(vm, opts.methods)
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data = {}, true /* asRootData */)
      }
      if (opts.computed) initComputed(vm, opts.computed)
      if (opts.watch) initWatch(vm, opts.watch)
    }

    vm的状态初始化是整个初始化中最复杂的异步,其data、props、methods、computed、watch都在这一步进行初始化,因此这一步也是Vue真正的创建。

    initProps
    function initProps (vm: Component, propsOptions: Object) {
      const propsData = vm.$options.propsData || {}
      const props = vm._props = {}
      // cache prop keys so that future props updates can iterate using Array
      // instead of dynamic object key enumeration.
      const keys = vm.$options._propKeys = []
      const isRoot = !vm.$parent
      // root instance props should be converted
      observerState.shouldConvert = isRoot
      for (const key in propsOptions) {
        keys.push(key)
        const value = validateProp(key, propsOptions, propsData, vm)
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
          if (isReservedProp[key]) {
            warn(
              `"${key}" is a reserved attribute and cannot be used as component prop.`,
              vm
            )
          }
          defineReactive(props, key, value, () => { // 监控prop的变化
            if (vm.$parent && !observerState.isSettingProps) {
              warn(
                `Avoid mutating a prop directly since the value will be ` +
                `overwritten whenever the parent component re-renders. ` +
                `Instead, use a data or computed property based on the prop's ` +
                `value. Prop being mutated: "${key}"`,
                vm
              )
            }
          })
        } else {
          defineReactive(props, key, value)
        }
        // static props are already proxied on the component's prototype
        // during Vue.extend(). We only need to proxy props defined at
        // instantiation here.
        if (!(key in vm)) {
          proxy(vm, `_props`, key)
        }
      }
      observerState.shouldConvert = true
    }
    View Code
    initMethods
    function initMethods (vm: Component, methods: Object) {
      for (const key in methods) {
        vm[key] = methods[key] == null ? noop : bind(methods[key], vm) // 作用域重新绑定
        if (process.env.NODE_ENV !== 'production' && methods[key] == null) {
          warn(
            `method "${key}" has an undefined value in the component definition. ` +
            `Did you reference the function correctly?`,
            vm
          )
        }
      }
    }
    View Code
    initData
    function initData (vm: Component) {
      let data = vm.$options.data
      data = vm._data = typeof data === 'function'
        ? data.call(vm)
        : data || {}
      if (!isPlainObject(data)) {
        // 保证data必须为纯对象
        data = {}
        process.env.NODE_ENV !== 'production' && warn(
          'data functions should return an object:
    ' +
          '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
      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 if (!isReserved(keys[i])) {
          proxy(vm, `_data`, keys[i]) //将属性代理到vm上
        }
      }
      // observe data 将data转换为监控对象
      observe(data, true /* asRootData */)
    }
    View Code
    initComputed
    const computedWatcherOptions = { lazy: true }
    
    function initComputed (vm: Component, computed: Object) {
      const watchers = vm._computedWatchers = Object.create(null)
    
      for (const key in computed) {
        const userDef = computed[key]
        const getter = typeof userDef === 'function' ? userDef : userDef.get
        // create internal watcher for the computed property.
        watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
    
        // component-defined computed properties are already defined on the
        // component prototype. We only need to define computed properties defined
        // at instantiation here.
        if (!(key in vm)) {
          defineComputed(vm, key, userDef)
        }
      }
    }
    
    export function defineComputed (target: any, key: string, userDef: Object | Function) {
      if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = createComputedGetter(key)
        sharedPropertyDefinition.set = noop
      } else {
        sharedPropertyDefinition.get = userDef.get
          ? userDef.cache !== false
            ? createComputedGetter(key)
            : userDef.get
          : noop
        sharedPropertyDefinition.set = userDef.set
          ? userDef.set
          : noop
      }
      Object.defineProperty(target, key, sharedPropertyDefinition)
    }
    View Code

    computed其实本身也是一种特殊的并且lazy的watcher,在get时它作为所计算的属性依赖而被收集,同时它把依赖自己的watcher也添加到属性的依赖中去,这样当原属性变化时,就会通知到依赖computed的依赖重新获取最新值。

    function createComputedGetter (key) {
      return function computedGetter () {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
          if (watcher.dirty) {
            // 将自己添加到属性的依赖列表中去
            watcher.evaluate()
          }
          if (Dep.target) {
            // 将依赖watcher的依赖也收集到属性依赖列表中去
            watcher.depend()
          }
          return watcher.value
        }
      }
    }
    initWatch
    function initWatch (vm: Component, watch: Object) {
      for (const key in watch) {
        const handler = watch[key]
        // 可以是数组,为key创建多个watcher
        if (Array.isArray(handler)) {
          for (let i = 0; i < handler.length; i++) {
            createWatcher(vm, key, handler[i])
          }
        } else {
          createWatcher(vm, key, handler)
        }
      }
    }
    
    function createWatcher (vm: Component, key: string, handler: any) {
      let options
      if (isPlainObject(handler)) {
        options = handler
        handler = handler.handler
      }
      // 如果handle传入为字符串,则直接找vm上的方法,一般是methods中定义的方法,这也是methods的初始化要先于watch初始化的原因
      if (typeof handler === 'string') {
        handler = vm[handler]
      }
      vm.$watch(key, handler, options) // 没找到$watch在原型上的定义
    }
    View Code

    经过这些初始化的步骤,一个组件就被创造出来了,紧接着就可以callHook(vm, 'created')。

    至此,我们主要了解了Vue整个生命周期,以及Vue创建(created)前的过程,从生命周期图中可以看到还有重要一步 Observe Data没有分析,状态初始化过程中用到监控对象如observe(data),依赖收集Dep等等,分析完Observe Data 应该就可以了解到Vue的数据绑定原理,这个分析留给下一篇文章。

    参考资料:http://blog.cgsdream.org/2016/11/11/vue-source-analysis-2/

  • 相关阅读:
    汇编学习笔记(25)
    在QT C++中调用 Python并将软件打包发布(裸机可运行)
    集合类
    解决python matplotlib 中文字体的显示乱码-Windows系统;RuntimeWarning: Glyph
    元数据库设计
    解决方案sln工程csproj
    托管代码和非托管代码的区别
    IIS安装和使用URL重写工具-URL Rewrite
    SSL证书及使用
    rpc和rpcwithcert两个虚拟目录
  • 原文地址:https://www.cnblogs.com/wj204/p/6406907.html
Copyright © 2020-2023  润新知