zoukankan      html  css  js  c++  java
  • 《一天读懂一个Vue源码文件》---init.js ,

    天下风云出我辈,一入江湖岁月催。 黄图霸业谈笑中,不胜人生一场醉。-- 《笑傲江湖》

    为什么会问这个问题

    上一篇文章大致了解了一下vnode,Component,vnodeData的数据结构,刚才大致数了一下vue源码src文件下所有的Js文件,大致一共有70个,按照一天弄明白一个文件的话,基本上两个月就可以把vue的源码理解个差不多了。

    今天先看src/core/instance/index.jssrc/core/instance/init.js这两个文件。

    从代码的角度思考Vue是什么

    项目中通常入口文件index.js中通常会有以下代码:

    new Vue({
      router,
      store,
      render: (h) => h(App),
    }).$mount("#pay");

    从这个代码中可以看到,使用new关键字,创建了一个对象,所以可以得出以下结论,Vue是个构造函数,这是毫无疑问的。既然是构造函数,那么一定有初始的参数。我们看下接口中定义的Vue都包含哪些参数。

    vue.d.ts中定义的Vue接口

    export interface Vue {
    // 只读属性
      readonly $el: HTMLElement;
      readonly $options: ComponentOptions<this>;
      readonly $parent: Vue;
      readonly $root: Vue;
      readonly $children: Vue[];
      readonly $refs: { [key: string]: Vue | Element | Vue[] | Element[] };
      readonly $slots: { [key: string]: VNode[] };
      readonly $scopedSlots: { [key: string]: ScopedSlot };
      readonly $isServer: boolean;
      readonly $data: Record<string, any>;
      readonly $props: Record<string, any>;
      readonly $ssrContext: any;
      readonly $vnode: VNode;
      readonly $attrs: Record<string, string>;
      readonly $listeners: Record<string, Function | Function[]>;
    // 其他属性
      $mount(elementOrSelector?: Element | String, hydrating?: boolean): this;
      $forceUpdate(): void;
      $destroy(): void;
      $set: typeof Vue.set;
      $delete: typeof Vue.delete;
      $watch(
        expOrFn: string,
        callback: (this: this, n: any, o: any) => void,
        options?: WatchOptions
      ): (() => void);
      $watch<T>(
        expOrFn: (this: this) => T,
        callback: (this: this, n: T, o: T) => void,
        options?: WatchOptions
      ): (() => void);
      $on(event: string | string[], callback: Function): this;
      $once(event: string, callback: Function): this;
      $off(event?: string | string[], callback?: Function): this;
      $emit(event: string, ...args: any[]): this;
      $nextTick(callback: (this: this) => void): void;
      $nextTick(): Promise<void>;
      $createElement: CreateElement;
    }

    可以看到,接口中定义的属性,都是我们日常开发中常用的属性。

    然后看src/core/instance/index.js文件

    这个文件中内容比较少,最容易理解

    // 重点关注 initMixin 
    import { initMixin } from './init'
    // 暂时不考虑这些
    import { stateMixin } from './state'
    import { renderMixin } from './render'
    import { eventsMixin } from './events'
    import { lifecycleMixin } from './lifecycle'
    import { warn } from '../util/index'

    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)
    }
    // 重点关注 initMixin
    initMixin(Vue)

    stateMixin(Vue)
    eventsMixin(Vue)
    lifecycleMixin(Vue)
    renderMixin(Vue)

    export default Vue

    可以看出Vue构造函数接受options作为参数,并且调用_init()方法进行初始化。随后又执行了initMixin(Vue)方法进行初始化。

    这里有一个非常巧妙的问题,就是构造函数中的_init方法初始化的问题。举个例子。

      function Vue(options){
        this._init(options)
      }
      
      let b = new Vue({data:'test'});
      // 会报错
      // Uncaught TypeError: this._init is not a function

    但是,initMixin(Vue)方法接受Vue,并在其原型上添加了_init方法,进行了初始化。这个非常巧妙。

    接下来看src/core/instance/init.js

    抛开其中以下几个方法,这个文件其实只做了一件事---合并options,处理参入的参数

        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        callHook(vm, 'beforeCreate')
        initInjections(vm) // resolve injections before data/props
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created')

    init.js源代码

    该文件的大体逻辑如下: initMixn()方法接受options作为参数,options如果是组件,则通过initInternalComponent(vm,options)方法将options参数合并到vm.options上。否则,将从vm的构造函数中解析出options进行合并。解析的过程中使用 dedupe()方法消除重复数据。

    接来来,调用initProxy(vm)对属性进行拦截。

    最后,调用vm.$mount(vm.$options.el)进行挂载。

    /* @flow */

    import config from '../config'
    import { initProxy } from './proxy'
    import { initState } from './state'
    import { initRender } from './render'
    import { initEvents } from './events'
    import { mark, measure } from '../util/perf'
    import { initLifecycle, callHook } from './lifecycle'
    import { initProvide, initInjections } from './inject'
    import { extend, mergeOptions, formatComponentName } from '../util/index'

    let uid = 0

    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
        vm._uid = uid++

        let startTag, endTag
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          startTag = `vue-perf-start:${vm._uid}`
          endTag = `vue-perf-end:${vm._uid}`
          mark(startTag)
        }

        // a flag to avoid this being observed
        vm._isVue = true
        // merge 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 */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm)
        } else {
          vm._renderProxy = vm
        }
        // expose real self
        vm._self = vm
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        callHook(vm, 'beforeCreate')
        initInjections(vm) // resolve injections before data/props
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created')

        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          vm._name = formatComponentName(vm, false)
          mark(endTag)
          measure(`vue ${vm._name} init`, startTag, endTag)
        }

        if (vm.$options.el) {
          vm.$mount(vm.$options.el)
        }
      }
    }

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

    export function resolveConstructorOptions (Ctor: Class<Component>) {
      let options = Ctor.options
      if (Ctor.super) {
        const superOptions = resolveConstructorOptions(Ctor.super)
        const cachedSuperOptions = Ctor.superOptions
        if (superOptions !== cachedSuperOptions) {
          // super option changed,
          // need to resolve new options.
          Ctor.superOptions = superOptions
          // check if there are any late-modified/attached options (#4976)
          const modifiedOptions = resolveModifiedOptions(Ctor)
          // update base extend options
          if (modifiedOptions) {
            extend(Ctor.extendOptions, modifiedOptions)
          }
          options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
          if (options.name) {
            options.components[options.name] = Ctor
          }
        }
      }
      return options
    }

    function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
      let modified
      const latest = Ctor.options
      const extended = Ctor.extendOptions
      const sealed = Ctor.sealedOptions
      for (const key in latest) {
        if (latest[key] !== sealed[key]) {
          if (!modified) modified = {}
          modified[key] = dedupe(latest[key], extended[key], sealed[key])
        }
      }
      return modified
    }

    // 消除重复
    function dedupe (latest, extended, sealed) {
      // compare latest and sealed to ensure lifecycle hooks won't be duplicated
      // between merges
      if (Array.isArray(latest)) {
        const res = []
        sealed = Array.isArray(sealed) ? sealed : [sealed]
        extended = Array.isArray(extended) ? extended : [extended]
        for (let i = 0; i < latest.length; i++) {
          // push original options and not sealed options to exclude duplicated options
          if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
            res.push(latest[i])
          }
        }
        return res
      } else {
        return latest
      }
    }

    明日复明日,明日何其多

    明天看initLifeCycle

    最后说两句

    1. 动一动您的小手,「点个赞吧」
    2. 都看到这里了,不妨 「加个关注」

    javascript基础知识总结javascript基础知识总结

  • 相关阅读:
    MyEclipse 2016CI破解版for Mac
    关于Mac系统中my sql 和navicat for mysql 的连接问题。
    二分总结
    递推总结
    区间DP学习笔记 6/20
    搜索考试
    模板整理
    防线 2020/3/31
    JS 循环遍历JSON数据
    pm am 12小时格式化
  • 原文地址:https://www.cnblogs.com/vali/p/14365499.html
Copyright © 2011-2022 走看看