zoukankan      html  css  js  c++  java
  • vue 源码之 Vue 定义

    本文地址: https://www.cnblogs.com/veinyin/p/14638541.html

    1 定义位置 core/instance/index

    在 core/instance/index 中,定义了 function Vue,作为构造器

    并向原型上绑定了一些方法,如下所示

     1 import { initMixin } from './init'
     2 import { stateMixin } from './state'
     3 import { renderMixin } from './render'
     4 import { eventsMixin } from './events'
     5 import { lifecycleMixin } from './lifecycle'
     6 import { warn } from '../util/index'
     7 
     8 function Vue (options) {
     9   if (process.env.NODE_ENV !== 'production' &&
    10     !(this instanceof Vue)
    11   ) {
    12     warn('Vue is a constructor and should be called with the `new` keyword')
    13   }
    14   this._init(options)
    15 }
    16 
    17 initMixin(Vue) // 在 Vue.prototype 上添加 _init 方法,做一些初始化的事情,一直到 mounted
    18 stateMixin(Vue) // 状态管理相关,也是添加到 Vue.prototype 上,包括 $data, $props, $set, $delete, $watch
    19 eventsMixin(Vue) // vue 自定义事件相关,$on, $once, $off, $emit
    20 lifecycleMixin(Vue) // 生命周期相关,_update, $forceUpdate, $destroy,这是放在原型上的,对每个实例会有相应的 initLifecycle 初始化生命周期钩子
    21 renderMixin(Vue) // 渲染相关,先给 vue.prototype 加上 runtime 常用方法,如 toNumber、createTextVnode 等,然后是 $nextTick 和 _render
    23 export default Vue // 把丰富原型方法之后的 Vue 导出

    疑问1:为什么不用 ES6 的 class,而是仍采用 ES5 的 function 形式

    A:在 vue 的开发场景下,class 添加原型方法没有 function 方便

    从上面代码可以看出,不同模块的代码是分开维护的,这样看上去清晰简洁,也便于维护

    class 的原型方法是在 class 内部定义的,如下所示,由于需要在 Vue 的原型对象上扩展很多属性和方法,如果使用 class,将在这个文件中糅合大量代码,看起来分层不够清晰,也不利于阅读和维护

    当然,也可以通过 vue.prototype._init = function () {} 给 class 的原型扩展,但这样就与 class 的使用初衷违背了,不如直接用 function 

    1 class Vue {
    2     constructor() {
    3         _init() {} 
    4     }
    5 
    6     // 原型方法要写在 class 内部,原型方法很多时,就都糅合在一个单文件里了  
    7 }

    2 增强_1  core/index

    在 1 基础上功能增强了,代码如下,重点加粗,主要是静态属性和方法的扩展,以及原型对象上属性的扩展

     1 import Vue from './instance/index'
     2 import { initGlobalAPI } from './global-api/index'
     3 import { isServerRendering } from 'core/util/env'
     4 import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
     5 
     6 initGlobalAPI(Vue) // 给 Vue 添加了一些静态属性和方法,util, set, delete, nextTick, observable, options. 其它文件做的导入的 use, mixin, extend, 这几个定义都是导入的 compoent, directive, filter
     7 
     8 Object.defineProperty(Vue.prototype, '$isServer', {
     9   get: isServerRendering
    10 })
    11 
    12 Object.defineProperty(Vue.prototype, '$ssrContext', {
    13   get () {
    14     /* istanbul ignore next */
    15     return this.$vnode && this.$vnode.ssrContext
    16   }
    17 })
    18 
    19 // expose FunctionalRenderContext for ssr runtime helper installation
    20 Object.defineProperty(Vue, 'FunctionalRenderContext', {
    21   value: FunctionalRenderContext
    22 })
    23 
    24 Vue.version = '__VERSION__'
    25 
    26 export default Vue

    3 增强_2 platforms/web/runtime/index 

    web 构建时用到,代码如下,核心加粗,主要就是对 Vue.config 和 Vue.options 增强,并在原型对象上添加了 __patch__ 和 $mount 方法,在最后启动 devtool,对生产环境使用开发模式做出检查和提示

     1 /* @flow */
     2 
     3 import Vue from 'core/index'
     4 import config from 'core/config'
     5 import { extend, noop } from 'shared/util'
     6 import { mountComponent } from 'core/instance/lifecycle'
     7 import { devtools, inBrowser } from 'core/util/index'
     8 
     9 import {
    10   query,
    11   mustUseProp,
    12   isReservedTag,
    13   isReservedAttr,
    14   getTagNamespace,
    15   isUnknownElement
    16 } from 'web/util/index'
    17 
    18 import { patch } from './patch'
    19 import platformDirectives from './directives/index'
    20 import platformComponents from './components/index'
    21 
    22 // install platform specific utils
    23 Vue.config.mustUseProp = mustUseProp // 标签和属性一定要搭配使用的检查,如 video-muted,input-checked,option-selected
    24 Vue.config.isReservedTag = isReservedTag // 判断是否是 HTML 保留标签
    25 Vue.config.isReservedAttr = isReservedAttr // 判断是不是保留属性
    26 Vue.config.getTagNamespace = getTagNamespace // 获取标签命名空间,暂时不是特别清楚作用,后续更新
    27 Vue.config.isUnknownElement = isUnknownElement // 是否为位置元素,运行平台相关
    28 
    29 // install platform runtime directives & components
    30 extend(Vue.options.directives, platformDirectives) // 指令相关逻辑
    31 extend(Vue.options.components, platformComponents) // 组件,动画相关,transition,transition-group
    32 
    33 // install platform patch function
    34 Vue.prototype.__patch__ = inBrowser ? patch : noop
    35 
    36 // public mount method
    37 Vue.prototype.$mount = function (
    38   el?: string | Element,
    39   hydrating?: boolean
    40 ): Component {
    41   el = el && inBrowser ? query(el) : undefined
    42   return mountComponent(this, el, hydrating)
    43 }
    44 
    45 // devtools global hook
    46 /* istanbul ignore next */
    47 if (inBrowser) {
    48   setTimeout(() => {
    49     if (config.devtools) {
    50       if (devtools) {
    51         devtools.emit('init', Vue)
    52       } else if (
    53         process.env.NODE_ENV !== 'production' &&
    54         process.env.NODE_ENV !== 'test'
    55       ) {
    56         console[console.info ? 'info' : 'log'](
    57           'Download the Vue Devtools extension for a better development experience:
    ' +
    58           'https://github.com/vuejs/vue-devtools'
    59         )
    60       }
    61     }
    62     if (process.env.NODE_ENV !== 'production' &&
    63       process.env.NODE_ENV !== 'test' &&
    64       config.productionTip !== false &&
    65       typeof console !== 'undefined'
    66     ) {
    67       console[console.info ? 'info' : 'log'](
    68         `You are running Vue in development mode.
    ` +
    69         `Make sure to turn on production mode when deploying for production.
    ` +
    70         `See more tips at https://vuejs.org/guide/deployment.html`
    71       )
    72     }
    73   }, 0)
    74 }
    75 
    76 export default Vue

    4 最终导出

    如果使用的 runtime only 版,导出的就是 3 中的 Vue

    如果使用的是 runtime + compile 版,有两点不同:1. Vue.prototype.$mount 又再次增强,做了一些逻辑判断; 2. 在 Vue 上添加了 compile

    runtime only 是通过 webpack 的 vue-loader 在编译过程中,将 template 编译为 JS,只包含运行时 vue 代码,更轻量,开发时更推荐使用

    runtime + compile 是运行时编译,这个编译是有时间成本的,但是如果在 new Vue 时,用到了 template,就需要使用这个,如果在 new Vue 是用的是 render,就不需要

    5 总结

    以下内容,方法用斜体,属性用下划线

    先总结下大致流程,具体细节会在后续完善

    从 instance/index 导出的 Vue 构造函数,已经在原型上拓展了以下几大类的 方法 或 属性,_ 开头的是内部使用,$ 开头的可以在工程中使用

    被导出后,只被 core/index 导入过

    这些是 import Vue from 'vue' 时做的

    1. 初始化相关,_init, 具体内容下面有补充

    2. 状态相关,$data$props$set$delete$watch。 $data, $props 重写了描述符,set 时警告,禁止修改

    3. 生命周期相关,_update$forceUpdate$destory

    4. render 相关,一些 render 函数(_v_s_l  ),$nextTick_render

    5. 事件相关,$on$once$off$emit

    以上是 instance/index 里做的事情,接下来是 core/index,在 Vue 上添加 静态方法静态属性

    这个会被两个地方用到,weex/runtime 和 web/runtime

    下面这些是通过 initGlobalAPI 添加的

    1. config 初始化,只能读,不能写,包括用户可以配置的全局配置,_lifecycleHooks 就是实例的生命周期(mounted 等),内部要用的配置等

    2. util 初始化,包括 warn,extend, mergeOptions, defineReactive 这几个方法

    3. setdeletenextTick 赋值,上面是加在原型上 $ 开头,这是加在 Vue 上,都是同一个方法

    4. observable,传入 object, 调用 observe,然后把响应处理后的 object 返回

    5. options 初始化,赋值为 null

    6. componentfilterdirective, 挂到 Vue.options 上,都初始化为 null

    7. Vue.options._base = Vue

    8. 内置组件 keep-alive 挂到 Vue.options.component 上

    9. use,插件注册时用到的,Vue.use(xxx)

    10. mixin,合并 options 实现的混入

    11. extend,生成子类要用的

    12. componentfilterdirective, 都是暴露给用户的,用来注册或获取自定义组件、过滤器、指令

    这不是 initGlobalAPI 里的

    13. 在 Vue.prototype 上加了 $isServer$ssrContext,在 Vue 上加了 FunctionalRenderContext, version

    以上是 core/index 里做的内容,下面是 web/runtime/index 内做的事情

    1. 在 Vue.config 上加了些内部要用的方法

    2. 在 Vue.options.directives 上定义了 model 和 show 两个指令

    3. 在 Vue.options.components 上定义了动画相关的内置组件 transition , transition-group

    4. 在 Vue.prototype 上添加 __patch__ 方法,用来更新 VNode 的

    5. 在 Vue.prototype 上添加 $mount 方法,在 $mounted 里会调用 mountComponent , beforeMount、beforeUpdate、mounted 就是在这里被触发的

    6. 如果是开发环境,还会调用 devtools


    new Vue() 时,在构造函数中,调用 _init 函数进行初始化,添加了原型方法和静态方法

    1. 给一个 uid,设置标志位 _isVue,把 所有传进去的 options 合并到 $options 里,在 this.$options 里可以访问到

    2. 初始化代理 proxy

    3. 把 _self 赋值为 this

    4. 初始化 lifeCycle, 跟 vue 实例的生命周期不同,是指 $parent,  $children,  $refs,  $root,  _watcher,  _inactive,  _isMounted,  _isDestroyed 等

    5. 初始化 events, _events, _hasHookEvent, 以及父组件提供的事件。

    6. 初始化 render,_vnode, _staticTrees,$slots, $scopedSlots, _c, $createElement, 后面两个创建 element 的方法区别是对子节点的处理不同,接着给 $attrs, $listeners 定义响应式

    这时调用 beforeCreate  钩子

    7. 初始化 inject, 对 inject 里的属性定义响应式

    8. 初始化 state,state 包含多种状态, props, methods, data, computed, watch

      初始化时都做了代理或混入,如把 this._data.xx 代理到 this 上,可以用 this.xx 直接访问,把 methods 每一项都混入到 vm 实例上,vm.xx 可以直接调用

    对 props 做检查,然后响应式,代理 this._props.xx -> this.xx

    methods 做检查,然后绑定 this,混入

    data 可能有数组,所以 observe,在这里面判断分别响应式处理

    computed 对每个属性实例化 watcher, 计算每一个属性的值

    watch 根据数组和其它类型分别处理,数组就是遍历后对每一个处理.. ,调用 vm.$watch

    9. 初始化 provider,其实就是绑一下 this

    这时调用 created 钩子

    10. 调用 vm.$mount

    2021.04.12 update: 

    关于构造函数使用 function 而不是 class,还有个说法是 this 指向问题

    在很多地方都涉及到修改 this 指向,class 创建的函数不能修改,function 可以

    有个相关的 JS 知识点,在这写一下:this 指向的优先级:new > 显示绑定 > 隐式绑定 > 默认绑定

    关于这个问题,在后续阅读过程中如果解惑,会在此更新

    本文内容为笔者自身理解,在源码基础上做的笔记记录,如有疏漏或不足之处欢迎指出

    感谢您的阅读及指正,让我们一起进步。
    欢迎联系我交流:veinyin@gmail.com
    作者:VeinYin
    博客地址:https://www.cnblogs.com/veinyin/
    如需转载请注明出处。
  • 相关阅读:
    事件
    10- JMeter5.1.1 工具快速入门
    06- Linux Ubuntu下sublime下载与使用与安装包
    控件是什么意思?
    09- 性能测试关键指标
    08- Tomcat入门与环境搭建部署
    07- HTTP协议详解及Fiddler抓包
    06- web兼容性测试与web兼容性测试工具
    05- web网站链接测试与XENU工具使用
    04- cookie与缓存技术
  • 原文地址:https://www.cnblogs.com/veinyin/p/14638541.html
Copyright © 2011-2022 走看看