Vue源码解析-响应式原理

news/2024/7/10 2:23:26 标签: vue

Vue源码解析-响应式原理

  • 课程目标
    • 源码目录结构
    • 准备工作-调试
    • 准备工作-Vue的不同构建版本
      • 完整版Vue举例
      • 小结
  • 寻找入口文件
    • 执行构建
    • script/config.js文件执行过程
  • 从入口开始
    • 阅读源码记录
  • Vue初始化过程
      • 小结-四个导出Vue的模块
    • Vue初始化-静态成员
      • src\core\index.js中,initGlobalAPI(Vue)详解
        • initUse(Vue) 注册Vue.use() 用来注册插件
        • initMixin(Vue) 注册Vue.mixin 实现混入
        • initExtend 注册Vue.extend 基于传入的options返回一个组件的构造函数
        • initAssetRegisters 注册Vue.directive(),Vue.component(),Vue.filter()
      • Vue初始化-实例成员
      • initMixin(Vue) 注册Vue.prototype._init()方法
        • 实例成员--initState
      • stateMixin(Vue)
      • eventsMixin(Vue)
      • lifecycleMixin(Vue) 混入了生命周期相关的方法
      • renderMixin(Vue)
    • 调试Vue初始化过程
    • 首次渲染过程

课程目标

  • Vue.js的静态成员和实例成员的初始化过程
  • 初次渲染过程
  • 数据响应式原理

源码目录结构

在这里插入图片描述

准备工作-调试

调试设置

  • 打包

    • 打包工具 Rollup
      • Vue.js 源码的打包工具使用的是 Rollup,比 webpack 轻量
      • webpack 会把所有文件当做模块,Rollup 只处理 js 文件,更适合在 Vue.js 这样的库中使用(开发项目使用 webpack,开发库使用 Rollup)
      • Rollup打包不会生成冗余的代码
  • 安装依赖

npm i 
  • 设置sourcemap
    • package.json 文件的 dev 脚本中添加参数 --sourcemap
"dev": "rollup -w -c scripts/config.js  --sourcemap --environment TARGET:web-full-dev"
  • 执行dev
    • npm run dev 执行打包,用的是rollup, -w 参数是监听文件的变化,文件变化自动重新打包; -c是执行的配置文件
  • 结果
    在这里插入图片描述
  • vue源码中examples中的grids为例进行调试
    在这里插入图片描述
  • 在进行代码调试的时候,如果没有开启sourcemap,则不会生成src目录(实际上是dist中的map文件指向src),断点不会进入src(源码),而直接进入打包后(dist)目录中压缩的vue.js文件,因为是压缩,编译后的代码,不方便调试,而我们希望断点直接进入src目录下的源码中调试,所以需要设置sourcemap

准备工作-Vue的不同构建版本

  • npm run build 重新打包所有文件
  • 官方文档-对不同构建版本的解释
  • 完整版:同时包含编译器和运行时的版本。
  • 编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。=>将template装换成render函数
  • 运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切。

完整版Vue举例

<div id="app"></div>
    <!-- 完整版 -->
    <script src="../../dist/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        template: "<h1>{{msg}}</h1>",
        data: {
          msg: "Hello Vue",
        },
      });
    </script>
  • 结果:可以正常显示
    在这里插入图片描述
<div id="app"></div>
    <!-- 运行时版本 -->
    <script src="../../dist/vue.runtime.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        template: "<h1>{{msg}}</h1>",
        data: {
          msg: "Hello Vue",
        },
      });
    </script>
  • 结果:报错
    在这里插入图片描述
  • 使用的是Vue的仅运行时版本,其中模板编译器不可用。方法一:将模板手动预编译为render函数,方法二:使用带编译器的vue版本
  • 方法二就是上面的引用完整版本的vue,下面介绍方法一
  • 方法一,将template模板字符串转换成render函数
<div id="app"></div>
    <!-- 运行时版本 -->
    <script src="../../dist/vue.runtime.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        render(h) {
          return h("h1", this.msg);
        },
        data: {
          msg: "Hello Vue",
        },
      });
  • 结果:能正常显示

小结

  • vue-cli默认引用的是运行时版本(不带编译器),并且是ESModule=>vue.runtime.esm.js
  • 因为vue-cli对webpack做了一个深度的封装,我们在vue-cli创建的项目中,看不到引入vue的版本,但vue-cli提供的一个命令行工具,通过这个工具可以查看webpack的配置
  • 命令行输入vue inspect
    在这里插入图片描述
  • webpack配置项在命令行中显示查看起来不太友好,直接输出到文件中(>代表把前面命令生成的结果输入到指定文件中)
vue inspect > output.js

在这里插入图片描述

  • 我们在开发项目的时候,会有很多单文件组件(.vue文件),这些单文件组件浏览器是不支持的,所以在打包的时候我们会将这些单文件组件转换成js对象,在转换js对象的过程中,还会将template模板转换成render函数(vue-loader来实现),所以单文件组件的运行是不需要编译器的

寻找入口文件

  • 查看dist/vue.js的构建过程

执行构建

npm run build
"build": "node scripts/build.js",
  • 通过node 生成所有版本的vue
npm run dev
"dev": "rollup -w -c scripts/config.js  --sourcemap --environment TARGET:web-full-dev"
  • rollup:通过rollup构建工具生成单一的vue版本
  • environment:设置环境变量TARGET:web-full-dev
    • web:生成web平台的vue版本
    • full:生成完整版的vue(含编译器+运行时)
    • dev:开发环境下的vue版本(不压缩)
  • script/config.js 的执行过程
    • 作用:生成rollup构建的配置文件
    • 使用环境变量TARGET:web-full-dev

script/config.js文件执行过程

  • 以npm run dev 为 例
"dev": "rollup -w -c scripts/config.js  --sourcemap --environment TARGET:web-full-dev",
const path = require('path')
const buble = require('rollup-plugin-buble')
const alias = require('rollup-plugin-alias')
const cjs = require('rollup-plugin-commonjs')
const replace = require('rollup-plugin-replace')
const node = require('rollup-plugin-node-resolve')
const flow = require('rollup-plugin-flow-no-whitespace')
const version = process.env.VERSION || require('../package.json').version
const weexVersion = process.env.WEEX_VERSION || require('../packages/weex-vue-framework/package.json').version
const featureFlags = require('./feature-flags')

const banner =
  '/*!\n' +
  ` * Vue.js v${version}\n` +
  ` * (c) 2014-${new Date().getFullYear()} Evan You\n` +
  ' * Released under the MIT License.\n' +
  ' */'

const weexFactoryPlugin = {
  intro () {
    return 'module.exports = function weexFactory (exports, document) {'
  },
  outro () {
    return '}'
  }
}

const aliases = require('./alias')
const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

const builds = {
  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  'web-runtime-cjs-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.dev.js'),
    format: 'cjs',
    env: 'development',
    banner
  },
  'web-runtime-cjs-prod': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.common.prod.js'),
    format: 'cjs',
    env: 'production',
    banner
  },
  // Runtime+compiler CommonJS build (CommonJS)
  'web-full-cjs-dev': {
    // resolve函数将相对路径转换成绝对路径(web是别名)
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.common.dev.js'),
    format: 'cjs',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  'web-full-cjs-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.common.prod.js'),
    format: 'cjs',
    env: 'production',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime only ES modules build (for bundlers)
  'web-runtime-esm': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.esm.js'),
    format: 'es',
    banner
  },
  // Runtime+compiler ES modules build (for bundlers)
  'web-full-esm': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.js'),
    format: 'es',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime+compiler ES modules build (for direct import in browser)
  'web-full-esm-browser-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.browser.js'),
    format: 'es',
    transpile: false,
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  // Runtime+compiler ES modules build (for direct import in browser)
  'web-full-esm-browser-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.esm.browser.min.js'),
    format: 'es',
    transpile: false,
    env: 'production',
    alias: { he: './entity-decoder' },
    banner
  },
  // runtime-only build (Browser)
  'web-runtime-dev': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.js'),
    format: 'umd',
    env: 'development',
    banner
  },
  // runtime-only production build (Browser)
  'web-runtime-prod': {
    entry: resolve('web/entry-runtime.js'),
    dest: resolve('dist/vue.runtime.min.js'),
    format: 'umd',
    env: 'production',
    banner
  },
  // Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'), // 入口文件
    dest: resolve('dist/vue.js'), // 输出文件
    format: 'umd', // 模块化的方式
    env: 'development', // 模式
    alias: { he: './entity-decoder' }, // 别名
    banner // 文件头
  },
  // Runtime+compiler production build  (Browser)
  'web-full-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.min.js'),
    format: 'umd',
    env: 'production',
    alias: { he: './entity-decoder' },
    banner
  },
  // Web compiler (CommonJS).
  'web-compiler': {
    entry: resolve('web/entry-compiler.js'),
    dest: resolve('packages/vue-template-compiler/build.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies)
  },
  // Web compiler (UMD for in-browser use).
  'web-compiler-browser': {
    entry: resolve('web/entry-compiler.js'),
    dest: resolve('packages/vue-template-compiler/browser.js'),
    format: 'umd',
    env: 'development',
    moduleName: 'VueTemplateCompiler',
    plugins: [node(), cjs()]
  },
  // Web server renderer (CommonJS).
  'web-server-renderer-dev': {
    entry: resolve('web/entry-server-renderer.js'),
    dest: resolve('packages/vue-server-renderer/build.dev.js'),
    format: 'cjs',
    env: 'development',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-prod': {
    entry: resolve('web/entry-server-renderer.js'),
    dest: resolve('packages/vue-server-renderer/build.prod.js'),
    format: 'cjs',
    env: 'production',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-basic': {
    entry: resolve('web/entry-server-basic-renderer.js'),
    dest: resolve('packages/vue-server-renderer/basic.js'),
    format: 'umd',
    env: 'development',
    moduleName: 'renderVueComponentToString',
    plugins: [node(), cjs()]
  },
  'web-server-renderer-webpack-server-plugin': {
    entry: resolve('server/webpack-plugin/server.js'),
    dest: resolve('packages/vue-server-renderer/server-plugin.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  'web-server-renderer-webpack-client-plugin': {
    entry: resolve('server/webpack-plugin/client.js'),
    dest: resolve('packages/vue-server-renderer/client-plugin.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
  },
  // Weex runtime factory
  'weex-factory': {
    weex: true,
    entry: resolve('weex/entry-runtime-factory.js'),
    dest: resolve('packages/weex-vue-framework/factory.js'),
    format: 'cjs',
    plugins: [weexFactoryPlugin]
  },
  // Weex runtime framework (CommonJS).
  'weex-framework': {
    weex: true,
    entry: resolve('weex/entry-framework.js'),
    dest: resolve('packages/weex-vue-framework/index.js'),
    format: 'cjs'
  },
  // Weex compiler (CommonJS). Used by Weex's Webpack loader.
  'weex-compiler': {
    weex: true,
    entry: resolve('weex/entry-compiler.js'),
    dest: resolve('packages/weex-template-compiler/build.js'),
    format: 'cjs',
    external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies)
  }
}

function genConfig (name) {
  // builds是一个对象,包含各种环境变量对应的配置项,name是环境变量的值
  /* 
  builds:{
  // Runtime+compiler development build (Browser)
    'web-full-dev': {
      entry: resolve('web/entry-runtime-with-compiler.js'), // 入口文件
      dest: resolve('dist/vue.js'), // 输出文件
      format: 'umd', // 模块化的方式
      env: 'development', // 模式
      alias: { he: './entity-decoder' }, // 别名
      banner // 文件头
    }
  }
  */
 // options是基础信息
  const opts = builds[name]
  // config是rollup的完整配置信息
  const config = {
    input: opts.entry, // 入口文件 src\platforms\web\entry-runtime-with-compiler.js
    external: opts.external,
    plugins: [
      flow(),
      alias(Object.assign({}, aliases, opts.alias))
    ].concat(opts.plugins || []),
    output: {
      file: opts.dest,
      format: opts.format,
      banner: opts.banner,
      name: opts.moduleName || 'Vue'
    },
    onwarn: (msg, warn) => {
      if (!/Circular/.test(msg)) {
        warn(msg)
      }
    }
  }

  // built-in vars
  const vars = {
    __WEEX__: !!opts.weex,
    __WEEX_VERSION__: weexVersion,
    __VERSION__: version
  }
  // feature flags
  Object.keys(featureFlags).forEach(key => {
    vars[`process.env.${key}`] = featureFlags[key]
  })
  // build-specific env
  if (opts.env) {
    vars['process.env.NODE_ENV'] = JSON.stringify(opts.env)
  }
  config.plugins.push(replace(vars))

  if (opts.transpile !== false) {
    config.plugins.push(buble())
  }

  Object.defineProperty(config, '_name', {
    enumerable: false,
    value: name
  })
  // 返回配置项
  return config
}
// 判断是否有TARGET环境变量
if (process.env.TARGET) {
  // 有环境变量TARGET:web-full-dev
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}

  • 入口文件是:src\platforms\web\entry-runtime-with-compiler.js

从入口开始

  • src\platforms\web\entry-runtime-with-compiler.js
  • 通过查看源码解决下面问题=>我们知道编译器会将template模板编译成render函数,当同时存在render函数和template模板的时候,源码中怎么运行?
    在这里插入图片描述
  • 此处的$mount是谁调用的呢?什么位置调用的呢?

阅读源码记录

  • el不能是body或者html标签
  • 如果没有render,把template转换成render函数
  • 如果有render函数,直接调用mount挂载dom
// 1:el不能是body或者html标签
if (el === document.body || el === document.documentElement) {
   process.env.NODE_ENV !== 'production' && warn(
     `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
   )
   return this
 }
const options = this.$options
if(!options.render){
  // 2:把template/el转换成render函数
  ...
}
// 3:调用mount方法,挂载DOM

  • 调试代码
    <div id="app"></div>
    <!-- 完整版本 -->
    <script src="../../dist/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        template: "<h3>Hello template</h3>",
        render(h) {
          return h("h3", "Hello render");
        },
        data: {
          msg: "Hello Vue",
        },
      });
    </script>

在这里插入图片描述

  • $mount <= Vue._init(Vue初始化) <= Vue (Vue构造函数) <= Vue实例化

Vue初始化过程

在这里插入图片描述
在这里插入图片描述

  • 完整版vue在入口文件src\platforms\web\entry-runtime-with-compiler.js中实际上相对于运行时vue版本针对Vue构造函数做了两件事

    • 增强Vue构造函数的原型对象$mount方法的功能,如果没有render函数,将模板(template)转换成render函数
    • Vue构造函数绑定了compile静态方法=>在$mount方法中将模板template字符串转换成render函数时调用
  • 在src\platforms\web\runtime\index.js中的Vu我们做了三件事件

    • 给Vue构造函数注册了平台相关的通用方法=>Vue.config.isReservedTag = isReservedTag
    • 注册了与平台相关的全局指令和组件(Vue全局的执行和组件分别保存在Vue.options.directives和Vue.optiosns.componnets中)
    • Vue构造函数的与原型对象上挂载_patch和$mount方法
      • _patch方法:将Vnode转换成对应的真实DOM
      • $mount 将差异渲染到页面上
  • src\core\index.js中给Vue构造函数添加静态属性和方法

  • 静态属性 Vue.config/Vue.options

  • 讲台方法 Vue.delete/Vue.set/Vue.nextTick/Vue.observable

小结-四个导出Vue的模块

  • src\platforms\web\entry-runtime-with-compiler.js

    • web平台相关入口
    • 重写了平台相关的$mount方法
    • 注册了Vue.compile()方法,传递一个HTML字符串,返回render函数
  • src\platforms\web\runtime\index.js

    • web平台相关
    • 注册和平台相关的全局指令:v-model,v-show
    • 注册和平台相关的全局组件:v-transition,v-transition-group
    • 全局方法
      • patch:把虚拟DOM转换成真实DOM
      • $mount:挂载方法
  • src\core\index.js

    • 与平台无关
    • 定义了Vue的静态属性和方法,initGlobalAPI(Vue)
  • src\core\instance\index.js

    • 与平台无关
    • 定义了构造函数,调用了this._init(options)方法
    • 定义了Vue实例的属性和方法

Vue初始化-静态成员

src\core\index.js中,initGlobalAPI(Vue)详解

/* @flow */

import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'

export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  // Vue的Config静态属性
  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.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  // 这些工具方法不视作全局API的一部分,除非你已经意识到某些风险,否则不要去依赖他们(只在Vue内部使用它们,我们可以忽略该代码)
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }
  // 静态方法set/delete/nextTick
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick
  // observe:让一个对象可响应
  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }
  // 初始化Vue.options对象,并扩展该对象(添加components,directives,filters等属性)
  Vue.options = Object.create(null)
  // ASSET_TYPES=['component','directive','filter'],
  // 分别存储全局的组件/指令/过滤器,即通过Vue.component(),Vue.directive(),Vue.filter()注册的全局组件,全局指令和全局过滤器,都会存储到对应的Vue.options属性上来
  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
/*
export function extend (to: Object, _from: ?Object): Object {
  for (const key in _from) {
      to[key] = _from[key]
    }
  return to
}
*/
 //将一个对象的成员拷贝给另一个对象(浅拷贝)
 // 注册全局的keep-alive组件
  extend(Vue.options.components, builtInComponents)
  // 注册Vue.use()   用来注册插件
  initUse(Vue)
  // 注册Vue.mixin   实现混入
  initMixin(Vue)
  // 注册Vue.extend  基于传入的options返回一个组件的构造函数
  initExtend(Vue)
  // 注册Vue.directive(),Vue.component(),Vue.filter()
  // 因为三个方法的参数是一样的,所以一起注册的
  initAssetRegisters(Vue)
}

  • 注册全局的组件和指令,就是将组件和指令模块放到对应的Vue.options选项中
 // 注册全局的keep-alive组件
  extend(Vue.options.components, builtInComponents)

initUse(Vue) 注册Vue.use() 用来注册插件

/* @flow */

import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    // installedPlugins:我们所安装的插件,this指向Vue构造函数
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // 如果传入的插件已经存在,直接返回
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    // 注册插件
    // 当Vue.use()传递多个参数的时候(第一个参数为插件)
    const args = toArray(arguments, 1)
    // 将Vue构造函数塞到args的第一个元素中,所以插件调用install方法时第一个参数就是Vue构造函数
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      //如果plugin是对象,调用对象的install方法
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    // 将注册的组件插入到installedPlugins数组中,防止重复注册
    installedPlugins.push(plugin)
    return this
  }
}

initMixin(Vue) 注册Vue.mixin 实现混入

/* @flow */

import { mergeOptions } from '../util/index'

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    // 将用户传入的options和Vue构造函数自带的options合并(合并策略比较复杂,之后再看)
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

initExtend 注册Vue.extend 基于传入的options返回一个组件的构造函数

/* @flow */

export function initExtend (Vue: GlobalAPI) {
  Vue.extend = function (extendOptions: Object): Function {
    const Super = this
    const Sub = function VueComponent (options) {
      // 子构造器有VueComponent静态方法
      this._init(options)
    }
    // 子构造器集成自Vue构造函数
    Sub.prototype = Object.create(Super.prototype)
    return Sub
  }
}

initAssetRegisters 注册Vue.directive(),Vue.component(),Vue.filter()

/* @flow */

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    // ASSET_TYPES=['component','directive','filter'],
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        // 如果没有传递第二个参数,代表获取响应的组件,指令和过滤器
        return this.options[type + 's'][id]
      } else {
        if (type === 'component') {
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive') {
          // 如果传入的是函数
          definition = { bind: definition, update: definition }
        }
        // 将全局注册的组件/指令/过滤器存储到Vue.options对应的属性中
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

Vue初始化-实例成员

  • src\core\instance\index.js
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)
    // Vue必须是构造函数,不能作为普通函数调用
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}
// 下面的方法是给Vue实例上混入成员
// 设置Vue实例的成员(定于Vue原型的对象的属性和方法)
// 注册vm的_init()方法,初始化vm
initMixin(Vue)
// 注册vm的$data/$props/$set/$delete/$watch
stateMixin(Vue)
// 初始化事件相关方法 $on/$emit/$once/$off
eventsMixin(Vue)
// _update/$forceUpdate/$destory
lifecycleMixin(Vue)
// _render/$nextTick
renderMixin(Vue)

export default Vue

  • 定义了Vue的构造函数,在构造函数下面定义了几个以Minix结尾的方法,参数都是Vue的构造函数,目的是给Vue原型上混入相应的成员,也就是在Vue实例上增加了响应的成员

initMixin(Vue) 注册Vue.prototype._init()方法

  • 给Vue原型上添加了_init()方法,该方法在Vue构造函数中调用,相当于整个Vue的入口,所有的事情都是从它内部开始的
  • 定义了几个私有成员
    • vm = this;
    • vm._uid = uid++ vm的唯一标识
    • vm.isVue = true 如果当前的实例是Vue实例,不需要被observe处理(响应式)
    • vm._renderProxy = vm 设置渲染时的代理对象,当我们在渲染的时候会看到该对象的使用
    • vm._self = vm
  • 定义了实例的$options=>将Vue构造函数的options和用户new Vue时传入的options及vm自身的options进行合并
vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
  • vm实例属性和方法的初始化 (模块化)
    在这里插入图片描述
  • 其中initInjections(vm)和initProvide(vm)两个函数共同实现依赖注入

实例成员–initState

  • nitState帮我们初始化了vm.$options中的props,methods,data,computed和watch,并且将它们的成员都注入到vm实例中
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  // 判断vm.$options中=是否有data,props,computed,watch,methods选项,如果有响应的选项,init(初始化)该选项
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    // 如果有data选项
    initData(vm)
  } else {
    // 没有data选项,observe把某个对象转换成响应式的对象
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}
  • initProps(vm, opts.props) 将vm._props中的成员转换成响应式,并且注入到vm实例中
  • initMethods(vm, opts.methods) 把选项中的methods成员注入到vm实例,在注入之前,先判断命名是否在props中有重名的属性,并且检测了下函数命名的规范,不能以_或者$开头
  • initData(vm) 把data中的成员转换成响应式,并且注入到vm实例中

stateMixin(Vue)

  • Vue原型上添加 d a t a / data/ data/props属性
 const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    // 在开发环境下,不允许给$data/$props赋值
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  // 访问$data和$props就像访问_data与_props一样,可访问,不能重新赋值
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)
  • Vue原型上添加$set/$delete/$watch方法
  // 给原型上挂载$watcher方法,监控数据变化
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }

eventsMixin(Vue)

  • 在原型上挂载$on/$emit/$off/$once方法
 Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    // 当第一个参数是数组的时候,遍历调用$on,可以给多个事件注册同一个事件处理函数
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      // 判断该事件是否在_events数组中存在,没有的话就赋值空数组,然后将事件处理函数添加到该数组中
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

lifecycleMixin(Vue) 混入了生命周期相关的方法

  • 在原型上挂载 _update/ f o r c e U p d a t e / forceUpdate/ forceUpdate/destory方法
    在这里插入图片描述
  • update方法中最重要的是_patch 方法;将虚拟dom转换成真实dom挂载到vm.$el上
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      // 首次渲染
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      // 数据变化
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

renderMixin(Vue)

  • _render/$nextTick
    在这里插入图片描述

调试Vue初始化过程

  • 在引入Vue的四个文件中依次打上断点
  • 与平台相关(src\platforms\web\entry-runtime-with-compiler.js)
    在这里插入图片描述
  • 与平台相关(src\platforms\web\runtime\index.js)
    在这里插入图片描述
  • 与平台无关(src\core\index.js)
    在这里插入图片描述
  • 与平台无关(src\core\instance\index.js)
    在这里插入图片描述
  • 因为我们始终想观察Vue的变化,监听Vue
    在这里插入图片描述
  • F5刷新,定位到initMixin在这里插入图片描述
  • F10执行initMixin(Vue)函数,发现在Vue.pototype上混入了_init方法
    在这里插入图片描述
  • F10执行stateMixin(Vue),发现在Vue.pototype上混入了$data,$props,$set,$delete,$watch成员
    在这里插入图片描述
  • F10执行evenetMixin(Vue),发现在Vue.pototype上混入了$on,$emit,$off,$once成员
    在这里插入图片描述
  • F10执行lifecycleMixin(Vue),发现在Vue.pototype上混入了_update,$forceUpdate,$destory成员
    在这里插入图片描述
  • F10执行renderMixin(Vue),发现在Vue.pototype上混入了_render,$nextTick成员
    在这里插入图片描述
  • F8跳到下一断点initGlobalAPI(Vue)=>给Vue构造函数挂载静态成员(属性和方法)
    • 属性
      • Vue.config属性

      • Vue.options属性[components",“directives”,“filters”,"_base"],其中components中含有keep-alive全局组件,_base指向Vue构造函数本身

      • Vue.set

      • Vue.delete

      • Vue.nextTick

      • Vue.observable
        在这里插入图片描述

      • 方法

        • Vue.use()
        • Vue.mixin()
        • Vue.extend()
        • Vue.component()
        • Vue.directive()
        • Vue.filter()
          在这里插入图片描述
  • 当我们调用Vue.component(),Vue.directive(),Vue.fileter()注册全局的组件,指令和过滤器的时候,会将该模块注入带Vue.options相应的属性中

首次渲染过程

  • 在引入Vue四个文件夹定义了Vue属性和方法,以及Vue原型对象的属性和方法,在项目中const Vue from "vue"完成了上述功能
  • new Vue创建Vue实例(vm)的时候调用了this._init()方法
  • 打断点
    在这里插入图片描述
  • 重点关注_init()函数中的 vm. m o u n t ( v m . mount(vm. mount(vm.options.el),当是编译版本的话
    在这里插入图片描述
  • 在src\platforms\web\runtime\index.js中
    在这里插入图片描述
  • 关注mountComponent做了什么
import { mountComponent } from 'core/instance/lifecycle'

参数一:vm实例 参数二:el

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  //判断当前选项是否有render函数
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      // 如果是运行时版本,且传入的tempalte选项(没有render选项),则抛出一个警告
      // 当前使用的是运行时版本,编译器是无效的,你应该传入render函数或者使用带编译器的vue版本
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  // 触发beforeMount生命周期钩子(挂载之前)
  callHook(vm, 'beforeMount')
  // 定义了更新组件的函数(实际上就是挂载)
  let updateComponent
  // 定义函数的内容
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    // 开启了性能监测
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    // 未开启性能监测
    updateComponent = () => {
      // 实际上调用的lifecycleMixin(Vue)生成的Vue.prototype._update()方法及renderMixin(Vue)生成的Vue.prototype._render方法
      // vm._render()内部调用的options选项中的render函数生成虚拟dom=>vnode = render.call(vm._renderProxy, vm.$createElement)
      // vm._update会将vnode转换成真实dom挂载到vm.$el属性上  vm.$el = vm.__patch__(prevVnode, vnode),视图此时还没更新哦
      vm._update(vm._render(), hydrating)
    }
    // 此时的updateComponent只是定义了,还没执行(执行的结果是真实dom对象)
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  // 创建Watcher对象,传入updateComponent函数,所以updateComponent是在Watcher中调用的
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    // 页面挂载完毕
    callHook(vm, 'mounted')
  }
  return vm
}
  • 再来重点关注Watcher中调用的updateComponent方法=>observer中的代码都是和响应式相关的
src\core\observer\watcher.js

在Vue中Watcer有三种
第一种:渲染watcher,也就是当前我们创建的Watcher,
第二种:计算属性的watcher
第三种:侦听器的watcher,也就是说计算属性和侦听器都是通过watcher来实现的
Watcher
参数一:vm实例
参数二:expOrFn(expression或者function))可以是函数也可以是字符串
参数三:回调函数cb
参数四:选项options
参数五:isRenderWatcher是否是渲染watcher
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


http://www.niftyadmin.cn/n/1499637.html

相关文章

Jfinal initOreillyCos()的作用

2019独角兽企业重金招聘Python工程师标准>>> 待 转载于:https://my.oschina.net/u/2330181/blog/804590

Delphi JCL JEDI使用 jclDebug

开源 https://github.com/project-jedi/jcl jclDebug 下载jcl&#xff0c;还要下载https://github.com/project-jedi/jedi里的2个inc文件 放到jcl-master\jcl\source\include\jedi目录里。 运行jcl\install.bat 安装。没有dpk工程文件。 运行bat文件&#xff0c;弹出下面的界面…

数据响应式原理

数据响应式原理响应式处理入口ObserveObserver构造函数defineReactive函数依赖收集 Dep实例依赖收集调试数据响应式原理-数组数据响应式处理-数组练习数组的依赖收集是在哪实现的呢?数据响应式原理-Watcher上(首次渲染)数据响应式原理-Watcher上(数据更新)数据响应式原理-调试…

如何在 Linux 系统查询机器最近重启时间

如何在 Linux 系统查询机器最近重启时间 在你的 Linux 或类 UNIX 系统中&#xff0c;你是如何查询系统上次重新启动的日期和时间&#xff1f;怎样显示系统关机的日期和时间&#xff1f; last 命令不仅可以按照时间从近到远的顺序列出该会话的特定用户、终端和主机名&#xff0c…

Day1-HTML初识

4.1 认识什么是纯文本文件txt windows中自带一个软件&#xff0c;叫做记事本。记事本保存的文档格式就是txt格式&#xff0c;就是英语text的缩写。术语上&#xff0c;称呼这个文件叫做“纯文本文件”。 我们现在要花15分钟&#xff0c;好好研究凭什么这个txt文件叫做“纯文本文…

Vue源码剖析-虚拟DOM

Vue源码剖析-虚拟DOM课程回顾虚拟DOM概念回顾代码演示h函数用法h函数的返回结果-vnode整体分析过程VNode的创建过程-createElement-上VNode的创建过程-createElement-下VNode的处理过程-updatepatch函数初始化patch函数的执行过程cbs对象patch函数内部的实现createElm函数patch…

jstat gcutil

QQA: jstat gcutil 的输出是什么意思 当 Java 程序有性能问题时&#xff0c;尤其是响应时间有突然变化时&#xff0c;最好第一时间查看 GC 的状态。一般用 jstat -gcutil <pid> 1s 来查看&#xff0c;那么它的输出又是什么含义呢&#xff1f; 输出样例 一般会用两种方式调…

网络设备驱动第二课---收发

概述 一些接口函数 框架流程 1.预留空间 准备一个skbuf2.往sybuf中扔数据转载于:https://www.cnblogs.com/xxg1992/p/6636404.html