tech share
  • tech-share
  • Engineering
    • 登录鉴权
    • SSR 页面路由
    • npm 版本号
    • 缓存
    • 数据库容灾
    • 动态效果导出 gif
    • Chrome-devtools
    • C 端 H5 性能优化
    • Docker
    • Monorepo 最佳实践
    • 技术架构演化
    • 项目规范最佳实践
    • snowpack
    • 静态资源重试
    • 前端页面渲染分析
    • Git
    • 前端重构
    • 微前端
    • 项目依赖分析
    • 前端监控原理
    • webpack
    • BS 架构与 CS 架构
    • HTTPS
    • package-lock.json 生成逻辑
    • SVN(Subversion)
    • 数据库分类
    • gulp
    • 前端架构
    • Bundle & Bundless
    • 控制反转 IoC
  • JavaScript
    • Javascript 性能
    • JavaScript 原型(2) - 原型与原型链
    • JavaScript 原型(1) - 构造函数
    • JavaScript - Promise
    • ES6 解构赋值
    • 前端离线化
    • Proxy
    • Object.defineProperty()简介
    • TypeScript
  • MachineLearning
    • GAN生成对抗网络
    • 虚拟对抗训练
    • 深度度量学习
    • 原型网络
    • PyTorch优化器
    • 隐马尔可夫模型2
    • Shapley Value 算法
    • Embarassingly Autoencoder算法
    • AutoRec算法及其后续发展
    • 深度学习常用激活函数
    • 序列预测ConvTran算法
    • 联邦学习
    • 深度学习推荐系统算法整理
    • 隐马尔可夫模型
    • 黎曼优化方法
    • FM算法
    • 机器学习常见评价指标
    • VAE算法
    • Adam优化器详解
    • Transformer算法
    • Self-attention 推荐算法
    • CNN 卷积神经网络
    • 图嵌入
    • 集成学习算法
    • RecBole开源框架
    • NCE-PLRec
    • 深度学习初始化方法
    • RNN循环神经网络
    • PyTorch数据处理
    • PyTorch安装和基本操作
    • XGBoost算法
    • NCF算法与简单MF的对比
    • 计算最佳传输
  • CSS
    • 什么是BFC
    • 纯CSS实现可拖动布局
    • 滚动穿透解决方案
  • React
    • React 生命周期
    • React Ref
    • React Hooks
    • SWR
    • React 数据流
    • React 函数式组件和类组件的区别
  • 可视化
    • OffscreenCanvas
    • Echarts 平滑曲线端点为什么不平滑
    • 颜色空间
    • 词云布局解析
    • 3D 数学基础
    • Canvas 图片处理
    • GLGL ES
    • WebGL 中绘制直线
    • Graphics API
    • 现代计算机图形学基础
    • Canvas 灰度
  • Vue
    • Vue2.x全局挂载整理
    • Vue2.6.x源码阅读
      • Vue2.6.x源码阅读 - 2.目录结构分析
      • Vue2.6.x源码阅读 - 4.源码阅读-platform
      • Vue2.6.x源码阅读 - 1.准备工作
      • Vue2.6.x源码阅读 - 5.源码阅读-core-Vue构造函数
      • Vue2.6.x源码阅读 - 7.源码阅读-core-响应式原理
      • Vue2.6.x源码阅读 - 3.源码阅读-shared
      • Vue2.6.x源码阅读 - 6.源码阅读-core-组件挂载
    • Vue + TypeScript Web应用实践
    • Vue2.x指令
    • nextTick()的使用
    • vue-cli2.x 的使用与项目结构分析
    • Vue响应式原理及总结
    • VueX的使用
    • Electron-Vue + Python 桌面应用实践
    • Vite
    • Vue组件通信整理
    • 记录一个问题的探索过程
  • Linux
    • memcg
  • GameDev
    • 游戏中的几种投影视图
    • 从零开始写软渲染器06
    • 从零开始写软渲染器05
    • 从零开始写软渲染器04
    • 从零开始写软渲染器03
    • 从零开始写软渲染器02
    • 从零开始写软渲染器01
    • 从零开始写软渲染器00
    • 现代游戏常用的几种寻路方案(一)
  • Node
    • NPM Dependency
    • Node 优势
    • Node Stream
    • Node 模块系统
  • HTML
    • html5语义与结构元素
  • 跨端
    • Flutter 介绍
  • Golang
    • Golang 基础
  • AR
    • SceneKit
由 GitBook 提供支持
在本页
  • Vue构造函数
  • initMixin

这有帮助吗?

  1. Vue
  2. Vue2.6.x源码阅读

Vue2.6.x源码阅读 - 5.源码阅读-core-Vue构造函数

阅读学习Vue源码/src目录下的core文件夹内Vue构造函数相关的代码

Vue构造函数

  • core目录下的index.js对Vue构造函数依旧进行了一定程度的封装,先除开封装的内容,直接进入到Vue真正的构造函数所在的位置src/core/instance/index.js。

    /* Vue 构造函数 */
    function Vue (options) {
      // 入参 options 即为开发者new一个Vue实例时,Vue中传入的带有data、method、watch等属性的对象,在日常代码中,直接使用 this 指代。
      if (process.env.NODE_ENV !== 'production' &&
        !(this instanceof Vue) // 判断是否通过 new 调用 Vue 构造函数
      ) {
        warn('Vue is a constructor and should be called with the `new` keyword')
      }
      this._init(options) // 初始化 Vue 实例,方法定义于 Vue 的 prototype 中
    }
    
    initMixin(Vue) // 混入初始化方法,构造函数中的 Vue.prototype._init()方法来自于其中
    stateMixin(Vue) // 混入 状态处理 的方法
    eventsMixin(Vue) // 混入 事件 的方法
    lifecycleMixin(Vue) // 混入 生命周期 的方法
    renderMixin(Vue) // 混入 渲染相关 的方法
    
    export default Vue
  • ⭐️ 构造函数所在的文件(src/core/instance/index.js)内容较少,但需要注意各个方法的执行顺序。构造函数在实际样例代码调用new Vue(options)才会执行,各类Mixin方法都先于构造函数执行,这些方法将包括prototype方法、状态处理、事件、生命周期、渲染相关的方法都预先写入至构造函数内,所以在实际调用构造方法时,Vue实例内容已经具有一定的复杂程度。

  • 当然实例所挂载的内容也不仅限于src/core/instance/index.js文件,其外层的src/core/index.js以及再外层的src/platforms/web/runtime/index.js也同样进行了一定程度的挂载。从入口文件开始到构造函数,一个Vue实例经过了三层的封装。最外层的封装其实能够理解,是基于平台入口的不同而会存在挂载内容的区别。中层与内层的挂载内容区分依旧比较费解,初步认为是处于代码结构的考虑,需要更加深入的学习。

initMixin

  • 回到构造函数,进入到含有初始化方法的initMixin(),由于方法较长,所以按顺序分别进行分析。

    1. 初始化定义

      // src/core/instance/init.js
      let uid = 0 // 每个类型的实例(组件)都含有的唯一标识,在vue-cli脚手架下,每个Vue文件就是一个单独的uid,可以通过this._uid获取。
      export function initMixin (Vue: Class<Component>) {
       /* 挂载初始化方法至prototype,用于构造函数调用 */
       Vue.prototype._init = function (options?: Object) {
         /* 定义Vue实例 */
         const vm: Component = this
         // a uid
         vm._uid = uid++
      
         // continued...
       }
      }
    2. 性能测试,development模式下,可开启core/config.js中的性能分析配置performance: true以测试各个Vue组件在初始化阶段的性能表现。

      • 在这里也可以看出core/config.js这份文件用于更改Vue内部的一些配置,这些配置不开放给实际开发者,但支持开发者更改在调试时使用。

      • 这段代码中有一处非代码逻辑的细节,可以看到有一些istanbul开头的注释,如/* istanbul ignore if */。istanbul为一个用于检查代码覆盖率的JavaScript库。在不开启性能分析配置的情况下,性能测试相关的两个if-else代码块无法被覆盖到,故加了这一段注释用于忽略覆盖率检测。扩展:如果需要忽略整个文件,可在文件开头增加/* istanbul ignore next */实现。

      export function initMixin (Vue: Class<Component>) {
       Vue.prototype._init = function (options?: Object) {
         // ...
      
         // 性能测试时使用
         let startTag, endTag
         /* istanbul ignore if */
         if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
           // 非生产模式、且开启性能分析配置config.performance后进入到此
           // 根据_uid定义开始与结束两个唯一标识
           startTag = `vue-perf-start:${vm._uid}`
           endTag = `vue-perf-end:${vm._uid}`
           mark(startTag) // 通过window.performance.mark()方法记录组件初始化的开始时点
         }
      
         // ...
      
         /* 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) // 计算组件初始化消耗的时间,并清空mark的时点
         }
      
         // continued...
       }
      }
      • 另一个关于代码顺序的细节,我们可以发现片段一的定义的代码内容是很少的,除了需要在性能分析中使用的变量,均未在片段一内进行处理。可以合理猜测这里的处理是为了让初始化阶段的性能更加精确。结合下面片段三的内容,也可以得出当前初始化方法内的性能,是用于测试实例创建,至生命周期created为止所消耗的时间。

      • 通过performance API可以获取到Vue源码所记录的性能记录。

    3. options合并。else部分的合并逻辑中,可以发现vm.constructor.options内部是有内容的,甚至已经挂载了KeepAlive等组件,以及一些命令。构造函数自带的options就来自于上文所述的入口文件的多层封装。

      export function initMixin (Vue: Class<Component>) {
       Vue.prototype._init = function (options?: Object) {
         // ...
      
         // a flag to avoid this being observed
         vm._isVue = true
         // merge options
         if (options && options._isComponent) {
           // 内部组件 internal component,由VNode构成的组件,使用于render方法内,用createElement或jsx语法构建
           // 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 {
           // 一般组件合并当前组件options与构造函数自带的options
           vm.$options = mergeOptions(
             resolveConstructorOptions(vm.constructor),
             options || {},
             vm
           )
         }
         // continued...
       }
      }
    4. export function initMixin (Vue: Class<Component>) {
       Vue.prototype._init = function (options?: Object) {
         // ...
      
         // 开发环境下赋值 _renderProxy属性
         // 在生产环境或不支持Proxy的环境下,直接将实例本身赋值给该属性
         // 在 $option.render._withStripped 属性为true时,将启用Proxy,使用其中的 getHandler(自定义对象get方法) 以及 hasHandler(自定义对象in方法)
         // Proxy在Vue3中才大范围开始使用,在这里主要用于开发环境下,起到警告提示作用
         /* istanbul ignore else */
         if (process.env.NODE_ENV !== 'production') {
           initProxy(vm)
         } else {
           vm._renderProxy = vm
         }
         // expose real self
         // 实例本身赋值至 _self
         vm._self = vm
      
         // 初始化一些重要属性,包括 $refs | $children | $parent | $root
         // 初始化生命周期的一些状态变量
         initLifecycle(vm)
         initEvents(vm) //初始化事件的容器 events | listeners
         initRender(vm) //初始化渲染标记用到的变量以及创建元素的方法
         callHook(vm, 'beforeCreate') // 调用beforeCreate生命周期函数
         initInjections(vm) // resolve injections before data/props 初始化注入器
         initState(vm) // ⭐️初始化状态数据,处理 data | props | watch | computed | method
         initProvide(vm) // resolve provide after data/props
         callHook(vm, 'created') // 调用created生命周期函数
      
         // continued...
       }
      }
      • 通过观察初始化各类属性的方法以及beforeCreate、created两个生命周期方法的顺序,可以知道data、props、injects、provides等数据属性在created时候已经被创建,且存在内容。而此时与dom相关的属性依旧为空状态。

      • 关于实例的属性,带$符号以及下划线的属性一般都为实例的私有属性,而带$符号的属性一般认为为只读属性。

      • 关于initRender方法注释中所说的“创建元素的方法”即createElement,将VNode转换为真实的的dom,也是自定义render方法中的第一个参数。经常能够在那些可自定义dom的组件中使用到。

    5. ⭐️调用mount方法挂载组件。

      export function initMixin (Vue: Class<Component>) {
       Vue.prototype._init = function (options?: Object) {
         // ...
         // vm.$options.el 存在的情况下调用 mount 方法
         if (vm.$options.el) {
           vm.$mount(vm.$options.el)
         }
         // 无el情况下可手动调用$mount方法
         // let vm = new Vue(...)
         // vm.$mount(...)
         // 先调用platform下的扩展的 $mount 方法,生成render
         // 再调用原始的 $mount 方法,获取元素
         // 再调用 mountComponent 方法
       }
      }
    6. export function initState (vm: Component) {
       vm._watchers = []
       const opts = vm.$options
       // 初始化 options.props,通过defineReactive方法响应式化props
       if (opts.props) initProps(vm, opts.props)
       // 初始化 options.methods,method不需要响应式化,仅遍历method,进行属性判重,不与prop重复,并通过bind方法将method绑定至Vue实例(this)上。
       if (opts.methods) initMethods(vm, opts.methods)
       if (opts.data) {
         // 初始化 options.data,
         // 在Vue实例中,data为Object,而在组件中,data为Function
         // 判重处理,不与prop、method重复
         initData(vm)
       } else {
         // 空data时同样建立observer监听数据
         observe(vm._data = {}, true /* asRootData */)
       }
       // 初始化 options.computed,通过watcher监听computed数据,prop、data判重处理
       if (opts.computed) initComputed(vm, opts.computed)
       // 初始化 options.watch
       if (opts.watch && opts.watch !== nativeWatch) {
         initWatch(vm, opts.watch)
       }
      }
上一页Vue2.6.x源码阅读 - 1.准备工作下一页Vue2.6.x源码阅读 - 7.源码阅读-core-响应式原理

最后更新于4年前

这有帮助吗?

丰富Vue实例内容,赋值各类属性,以及生命周期方法beforeCreate、created的调用。部分可参考学习。

可以发现构造函数所在的文件并没有该方法的定义,但$mount方法又充满了既视感。该方法会根据platform不同会有所区别,结合web模式下入口文件中的内容(可见上一篇)可知,该方法已经挂载在Vue.prototype上。而代码内容由运行模式决定(该方法于进行解析)。

关于4中代码的initState方法,这里进行一定的展开。以下代码中提及的observe与defineReactive方法于中解析

initProxy
4.源码阅读-platform
6.源码阅读-core-组件挂载
7.源码阅读-core-响应式原理