zoukankan      html  css  js  c++  java
  • vue2.x源码解析(第一部分)

    vue构造函数

    首先vue是一个构造函数,通过new操作符来生成实例的,那么首先得知道vue构造函数都干了什么

    vue构造函数原型

    打开entry-runtime-with-compiler.js可以看到它引入了运行时版的vue

    import Vue from './runtime/index'
    

    打开./runtime/index可以看到它引入的是core/index中导出的vue

    import Vue from 'core/index'
    

    打开core/index又可以看到它引入的是./instance/index中导出的vue

    import Vue from './instance/index'
    

    打开./instance/index发现终于看到vue构造函数了,现在来看看这个文件都干了些什么?

    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原型添加_init方法
    initMixin(Vue)
    /*为$data,$props添加拦截器来提示一些警告(这些时只读属性)
    初始化一些有关state的方法比如:$set、$delete、$watch*/
    stateMixin(Vue)
    /*为Vue原型添加$on、$once、$off、$emit四个方法*/
    eventsMixin(Vue)
    /*为Vue原型添加_update、$forceUpdate、$destroy三个方法*/
    lifecycleMixin(Vue)
    /*为vue原型添加$nextTick、_render*/
    renderMixin(Vue)
    
    export default Vue
    

    可以看到这个文件主要做的事情就是初始化了Vue构造函数和执行了5个函数。在vue构造函数中可以看到,它主要是对非vue实例提示 'vue是一个构造函数,应该用new关键字调用它',然后执行了_init函数,并将用户传入的options传进去。那么接下来看看这个五个函数都 干了些什么?

    1、首先来看initMixin(Vue)这个函数,这个函数是从/core/instance/init.js文件导出的,所以我们去看这个文件。发现这个函数主要是为了 初始化_init函数,也就是Vue构造函数中执行的函数,那么就预计到了这个函数中必定做了很多的东西,这个等后面再讲。

    //为原型添加了_init函数
    export function initMixin (Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {...}
    }
    

    2、stateMixin(Vue)这个函数是从core/instance/state.js中导出的,现在前往这个文件。

    export function stateMixin (Vue: Class<Component>) {
      // flow somehow has problems with directly declared definition object
      // when using Object.defineProperty, so we have to procedurally build up
      // the object here.
      const dataDef = {}
      /*设置一个输出值是data对象并且名为get的方法*/
      dataDef.get = function () { return this._data }
      const propsDef = {}
      /*设置一个输出值是props对象并且名为get的方法*/
      propsDef.get = function () { return this._props }
      /*当出现改变实例data属性时或props属性时提示错误警告*/
      if (process.env.NODE_ENV !== 'production') {
        dataDef.set = function (newData: Object) {
          warn(
            'Avoid replacing instance root $data. ' +
            'Use nested data properties instead.',
            this
          )
        }
        propsDef.set = function () {
          warn(`$props is readonly.`, this)
        }
      }
      /*为$data和$props添加读取和赋值的拦截器*/
      Object.defineProperty(Vue.prototype, '$data', dataDef)
      Object.defineProperty(Vue.prototype, '$props', propsDef)
    
      /*为vue实例添加$set和$delete和$watch方法*/
      Vue.prototype.$set = set
      Vue.prototype.$delete = del
    
      Vue.prototype.$watch = function (
        expOrFn: string | Function,//watch的key可以是函数
        cb: any,
        options?: Object
      ): Function {...}
    }
    

    可以看到主要是Vue原型添加了$data属性和$props属性,这两个属性分别代理的是_data和_props(其实就是用户传入的options中的data和props)。 并对当出现改变实例data属性时或props属性时提示错误警告'避免修改根实例的$data和$props是只读的',总之就是一 句话:'你休想修改我,你修改了就给你报个红,让你不爽'。

    然后就是为原型添加$set$delete$watch函数属性,字面意思很好理解,就是设置、删除、观察,具体等用到了在讲

    3、eventsMixin(Vue)这个函数是从core/instance/events.js中导出的,现在前往这个文件

    export function eventsMixin (Vue: Class<Component>) {
      const hookRE = /^hook:/
      /*
        作用:
              1、传入的事件名可以是数组
              2、为实例添加监听事件
              3、监听函数名存在hook:时,将_hasHookEvent设置为true,作用: 监听子组件的生命周期执行情况
      */
      Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {...}
      /*
        作用:
              1、只执行一次该fn,在执行一次后$off掉在实例中的该函数
      */
      Vue.prototype.$once = function (event: string, fn: Function): Component {...}
      /*
        作用:
              1、当没传参数时清空该实例的所有监听事件
              2、对传空fn的事件名值设置为null
              3、删除与fn对应的在_events[event]集合中的函数
      */
      Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {...}
      /*
          作用:
                1、对不规范的写法进行提示
                2、执行该监听函数对应的函数
      */
      Vue.prototype.$emit = function (event: string): Component {...}
    }
    

    可以看到主要就是为Vue原型添加$on、$once、$off、$emit这四个函数,用过Vue的应该知道这个几个函数时干什么用?

    $on:为实例添加监听事件

    $once:为实例添加一次性的监听事件

    $off:清空摸个或所有的监听事件

    $emit:触发某个监听事件

    4、lifecycleMixin(Vue)这个函数是从/core/instance/lifecycle.js中导出的,现在前往这个文件

    export function lifecycleMixin (Vue: Class<Component>) {
      Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {...}
      /*
        作用: 重新渲染一次该实例
      */
      Vue.prototype.$forceUpdate = function () {...}
      /*
         作用:
              1、该实例正在销毁时,return
              2、执行销毁前的生命周期函数beforeDestroy
              3、设置_isBeingDestroyed属性为true,表示正在销毁中
              4、存在父组件&&父组件还未销毁时&&父组件不为抽象组件时,将该组件从父组件的子集合中移除
              5、解除该实例观察者对属性的观察
              6、清空该实例中的所有属性观察者
              7、实例序号-1
              8、设置_isDestroyed为true,表示已销毁
              9、调用当前渲染树上的销毁钩子、执行destroyed钩子函数、清空所有实例侦听器、
                设置节点的__vue__为null、清空组件节点的父级(释放循环引用)
      */
      Vue.prototype.$destroy = function () {...}
    }
    

    可以看到其实就是未原型添加了_update$forceUpdate$destroy这三个函数,同样这三个函数时用来干什么的呢?

    _update: 将虚拟DOM渲染为真实DOM

    $forceUpdate: 强制重新渲染(其实就是重新执行渲染函数)

    $destroy: 销毁当前实例

    5、renderMixin(Vue)这个函数是从core/instance/render.js中导出的,现在前往这个文件

    export function renderMixin (Vue: Class<Component>) {
      // install runtime convenience helpers
      installRenderHelpers(Vue.prototype)
    
      Vue.prototype.$nextTick = function (fn: Function) {
        return nextTick(fn, this)
      }
    
      Vue.prototype._render = function (): VNode {...}
    }
    

    可以看到为原型添加了$nextTick和_render函数,这2个函数干了什么呢?

    $nextTick: 可以看到其调用的是nextTick函数,而这个函数的作用主要就是等所有的同步任务执行完以后再执行其传入的回调函数, 同时这个函数又可以使视图强制重新渲染一遍(具体等用到了在讲)

    _render: 执行render函数生成虚拟节点

    还有一个就是installRenderHelpers函数,这个函数传入了vue原型为参数,具体做了什么呢?

    export function installRenderHelpers (target: any) {
      target._o = markOnce
      target._n = toNumber
      target._s = toString
      target._l = renderList
      target._t = renderSlot
      target._q = looseEqual
      target._i = looseIndexOf
      target._m = renderStatic
      target._f = resolveFilter
      target._k = checkKeyCodes
      target._b = bindObjectProps
      target._v = createTextVNode
      target._e = createEmptyVNode
      target._u = resolveScopedSlots
      target._g = bindObjectListeners
    }
    

    可以看到为原型添加了很多方法,这些方法其实执行渲染函数的时候会去执行的,这里不做具体解释,等到编译阶段的时候在解释。

    添加全局API

    到这里就是/core/instance/index.js这个文件对Vue做的所有事情,那么前面几个文件还有事情没做完呢.所以我们继续追溯到core/index 这个文件,这个文件其实最主要的是为Vue构造函数添加全局API

    //为Vue添加全局API
    initGlobalAPI(Vue)
    //为vue原型添加只读属性$isServer,判断是vue运行在客户端还是服务端
    Object.defineProperty(Vue.prototype, '$isServer', {
      get: isServerRendering
    })
    //为vue原型添加只读属性$ssrContext,为服务端渲染专用
    Object.defineProperty(Vue.prototype, '$ssrContext', {
      get () {
        /* istanbul ignore next */
        return this.$vnode && this.$vnode.ssrContext
      }
    })
    // expose FunctionalRenderContext for ssr runtime helper installation
    //为vue构造函数添加属性FunctionalRenderContext,为服务端渲染准备的(ssr)
    Object.defineProperty(Vue, 'FunctionalRenderContext', {
      value: FunctionalRenderContext
    })
    //vue版本号,rollup 的 replace 插件会把'__VERSION__'替换成版本号
    Vue.version = '__VERSION__'
    export default Vue
    

    可以看到给Vue原型添加$isServer$ssrContext属性值,为构造函数的FunctionalRenderContext 属性添加值,这个值是个函数主要是为了生成功能性组件函数用的,后面会讲到这个函数的。

    接下来主要将initGlobalAPI(Vue)这个函数,这个函数是在/core/global-api/index.js这个文件中的。 首先来看这一段

    const configDef = {}
      configDef.get = () => config
      if (process.env.NODE_ENV !== 'production') {
        configDef.set = () => {
          warn(
            'Do not replace the Vue.config object, set individual fields instead.'
          )
        }
      }
      //为vue添加只读属性config
      Object.defineProperty(Vue, 'config', configDef)
    

    可以看到这一段是为Vue构造函数添加config属性,并为这个属性添加了拦截器,只要更换config属性就会提示错误信息'不要替换我, 可以改变我其中的某个属性'.

    接下来是这一段:

    /*不被认为是公共API的一部分,要避免依赖他们,但是你依然可以使用,只不过风险你要自己控制*/
      Vue.set = set
      Vue.delete = del
      Vue.nextTick = nextTick
      //设置options为原型为空的对象
      Vue.options = Object.create(null)
      //为options添加以上三个值为{}(原型为空的对象)的[属性+'s']
      ASSET_TYPES.forEach(type => {
        Vue.options[type + 's'] = Object.create(null)
      })
      // this is used to identify the "base" constructor to extend all plain-object
      // components with in Weex's multi-instance scenarios.
      Vue.options._base = Vue
      //混合builtInComponents对象中的属性到Vue.options.components中
      extend(Vue.options.components, builtInComponents)
    

    可以看到为构造函数添加了setdeletenextTick三个函数,这三个函数其实就是$set$delete$nextTick,但是这个构造函数上 的三个函数没有公开,因为存在风险,所以要避免使用它们。接着又为构造函数添加了options对象属性,并为这个对象设置了多个属性,并为 Vue.options.components对象添加了keep-alive组件

    export const ASSET_TYPES = [
      'component',
      'directive',
      'filter'
    ]
    // 循环添加以及添加keep-alive组件过后
    Vue.options = {
      components: {
          KeepAlive
      },//通用组件集合
      directives: {},//通过指令集合
      filters: {},//通用过滤器集合
      _base: Vue //储存vue构造函数
    }
    

    接下来是initGlobalAPI函数的最后一段代码:

    initUse(Vue)
    initMixin(Vue)
    initExtend(Vue)
    initAssetRegisters(Vue)
    

    具体来看看这四个函数都干了些什么?

    1、initUse(Vue): 这个函数在文件/core/global-api/use.js中,主要是用来为Vue添加use函数,添加插件用的

    Vue.use = function (plugin: Function | Object) {...}
    

    2、initMixin(Vue): 这个函数在文件core/global-api/mixin.js中,主要是为vue添加mixin函数用的。

    export function initMixin (Vue: GlobalAPI) {
      Vue.mixin = function (mixin: Object) {
        this.options = mergeOptions(this.options, mixin)
        return this
      }
    }
    

    3、initExtend(Vue): 这个函数在文件/core/global-api/extend.js中,主要为vue构造函数添加cid属性和extend函数属性, 具体有什么用等用到了再讲。

    export function initExtend (Vue: GlobalAPI) {
      /**
       * Each instance constructor, including Vue, has a unique
       * 每个实例构造函数,包括Vue,都有唯一的
       * cid. This enables us to create wrapped "child
       * cid。这使我们能够创建包装的“子元素”
       * constructors" for prototypal inheritance and cache them.
       * 用于原型继承并缓存它们的构造函数。
       */
      //为vue添加cid静态属性
      Vue.cid = 0
      let cid = 1
      Vue.extend = function (extendOptions: Object): Function {...}
    }
    

    4、initAssetRegisters: 这个函数在文件/core/global-api/extend.js中,可以看到是循环ASSET_TYPES变量为vue添加静态方法, 主要为其添加componentdirectivefilter函数,作用其实就是注册自定义组件、自定义指令、自定义过滤器

    export function initAssetRegisters (Vue: GlobalAPI) {
      /**
       * Create asset registration methods.
       */
      //为vue添加静态方法component、directive、filter(分别用来全局注册组件,指令和过滤器)
      ASSET_TYPES.forEach(type => {
        Vue[type] = function (
          id: string,
          definition: Function | Object
        ): Function | Object | void {...}
      })
    }
    

    Vue平台化

    以上是core/index.js文件所做的事情,接下来在往上追溯就是platforms/web/runtime/index.js文件了,这里主要是对vue做了平台化的 包装,在不同的平台添加不同的东西。

    // 判断标签的某个属性是否需要prop修饰符进行绑定(表示原生属性)
    Vue.config.mustUseProp = mustUseProp
    // 判断标签是否是保留的标签或者svg标签
    Vue.config.isReservedTag = isReservedTag
    // 判断标签属性是否存在style || class
    Vue.config.isReservedAttr = isReservedAttr
    // 判断标签是否为sug标签或math标签
    Vue.config.getTagNamespace = getTagNamespace
    // 判断标签是否为未知的标签
    Vue.config.isUnknownElement = isUnknownElement
    

    可以看到上面是对vue.config的属性添加值,这些工具函数后面会用到

    // 往Vue.options.directives中添加model和show这两个指令
    extend(Vue.options.directives, platformDirectives)
    // 往Vue.options.components中添加了2个组件,分别为Transition和TransitionGroup,主要是为了过渡动画用的。
    extend(Vue.options.components, platformComponents)
    // 这个函数应该是当组件更新时对比用的
    Vue.prototype.__patch__ = inBrowser ? patch : noop
    // 在这里定义了$mount函数
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      el = el && inBrowser ? query(el) : undefined
      return mountComponent(this, el, hydrating)
    }
    

    上面这段代码对vue构造函数的options对象添加KeepAlive, Transition, TransitionGroup这三个组件;为Vue.options.directives中添加了两个 model: inserted、componentUpdated;show:bind、update、unbind指令,等用到了在讲;并且为其原型链添加了$mount、__patch__方法, 其中$mount调用的是/core/instance/lifecycle.js中的mountComponent函数,这个用到了再讲。当进行过上面这段代码的处理过后就变成了以下情况:

    Vue.options = {
        directives: {
            model: {
                inserted: function() {...}
                componentUpdated: function(){...}
            },
            show: {
                bind: function () {...},
                update: fuction () {...},
                unbind: function () {...}
            }
        },
        components: {
            Transition,
            TransitionGroup,
            KeepAlive
        },
        filters: {},
        _base: Vue
    }
    

    完整版的vue

    完整版的vue就是多了一个编译

    //根据 id 获取元素的 innerHTML
    const idToTemplate = cached(id => {
      const el = query(id)
      return el && el.innerHTML
    })
    const mount = Vue.prototype.$mount
    //重新改写运行时的Vue原型的$mount方法
    Vue.prototype.$mount = function (
      el?: string | Element,
      hydrating?: boolean
    ): Component {
      ...
      return mount.call(this, el, hydrating)
    }
    ...
    //为vue添加静态方法compile
    Vue.compile = compileToFunctions
    export default Vue
    

    可以看到完整版主要做的事情就是添加编译板块,并且重写了$mount,当然这个函数最后输出的是运行时的$mount函数,具体这个重写的函数干了什么等 后面再讲。最后为vue添加了静态函数compile,该函数引用的是platforms/web/compiler/index.js中生成的compileToFunctions函数,这个 函数等将编译的时候在讲。

    到这里就讲完了完整版vue的所有初始化所做的事情,所有做的事情就是为vue构造函数或原型添加属性,为运行时或编译做准备,下面这段代码是上面的总结。

    Vue = {
        options: {
            directives: {
                model: {
                    inserted() {},
                    componentUpdated(){}
                },
                show: {
                    bind() {},
                    update() {},
                    unbind() {}
                }
            },
            components: {
                Transition,
                TransitionGroup,
                KeepAlive
            },
            filters: {},
            _base: Vue
        },
        compile() {},
        config: {
             mustUseProp(){},
             isReservedTag(){},
             isReservedAttr(){},
             getTagNamespace(){},
             isUnknownElement(){},
             // options各选项的处理函数
             optionMergeStrategies: {
                 beforeCreate(){},
                 created(){},
                 mounted(){},
                 beforeUpdate(){},
                 updated(){},
                 beforeDestroy(){},
                 destroyed(){},
                 activated(){},
                 deactivated(){},
                 errorCaptured(){},
                 components(){},
                 directives(){},
                 filters(){},
                 provide(){},
                 data(){},  
                 props(){},  
                 methods(){},  
                 inject(){},  
                 computed(){},  
                 watch(){},  
                 data(){},  
                 el(){},  
                 propsData(){},  
             },
             silent: false,
             productionTip: process.env.NODE_ENV !== 'production',
             devtools: process.env.NODE_ENV !== 'production',
             performance: false,
             errorHandler: null,
             warnHandler: null,
             ignoredElements: [],
             keyCodes: {},
             parsePlatformTagName: _ => _,
             _lifecycleHooks: [
                 'beforeCreate',
                 'created',
                 'beforeMount',
                 'mounted',
                 'beforeUpdate',
                 'updated',
                 'beforeDestroy',
                 'destroyed',
                 'activated',
                 'deactivated',
                 'errorCaptured'
             ]
        },
        FunctionalRenderContext,
        version,
        prototype: {
            $mount () {},
            __patch__(){},
            $isServer,
            $ssrContext, 
            _init(){},
            $data,
            $props,
            $set(){},
            $delete(){},
            $watch(){},
            $on(){},
            $once(){},
            $off(){},
            $emit(){},
            _update(){},
            $forceUpdate(){},
            $destroy(){},
            $nextTick(){},
            _render(){},
            _o(){},
            _n(){},
            _s(){},
            _l(){},
            _t(){},
            _q(){},
            _i(){},
            _m(){},
            _f(){},
            _k(){},
            _b(){},
            _v(){},
            _e(){},
            _u(){},
            _g(){},
        }, 
        util: {
            warn(){},
            extend(){},
            mergeOptions(){},
            defineReactive(){}
        },
        set(){},
        delete(){},
        nextTick(){},
        use(){},
        mixin(){},
        extend(){},
        cid,
        component(){},
        directive(){},
        filter(){}
    }
    

      下一章节我们来讲下当创建一个vue实例的时候,vue都干了些什么

    详细信息可访问https://github.com/maomao93/vue-2.x/tree/master/personal

  • 相关阅读:
    shell面试题整理
    用循环链表实现Josephus问题
    in与exists的区别
    单链表的建立/测长/打印/删除/排序/逆序/寻找中间值
    float在内存中的存放
    crontab定时任务详解
    螺旋队列问题之二
    螺旋队列问题之一
    android网络编程--从网络下载图片,并保存到内存卡
    android Shader类简介_渲染图像示例
  • 原文地址:https://www.cnblogs.com/maomao93/p/14142939.html
Copyright © 2011-2022 走看看