Vue响应式原理--初始化过程

目标

Vue.js静态成员和实例成员的初始化过程(vue.set、vue.get、vue.extend等)
首次渲染的过程
数据响应式的原理

准备
源码地址: https://github.com/vuejs/vue
结构

 dist:存放打包后文件
 examples:存放示例文件,例如表格的使用等
 src:compile/模板编译
      core:vue核心 
           components:存放组件,例如keep-live
           global-api:存放use、mixin、extends等
           instance:存放vue实例,vue生命周期,初始化等
           observer:实现响应式机制
           util:存放公共成员位置
           vdom:vue虚拟DOM,vue增强了,可以存放组件相关
  platforms:存放平台相关内容,例如web,weex
  sfc:单文件组件,把组件转换成js对象

了解Flow
vue源码使用了flow声明类型,并且每个文件开头都有flow标记

官网:https://flow.org/
JS的静态类型检查器
Flow的静态类型检查错误是通过静态类型腿短实现的
· 文件开头通过 // @flow 或者 /@flow/ 声明

调试

打包工具Rollup
Vue.js源码打包使用的是Rollup,比webpack轻量
webpack将所有文件当成模块,rollup只处理js文件,更适合vue这种类库的使用
Rollup打包不会生成冗余代码

安装

npm install

设置sourcemap
 package.json中的dev添加参数 --sourcemap
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",

执行dev
npm run dev  执行打包,使用rollup,-w 参数是监听文件的变化,文件变化自动重新打包

Vue的不同构建版本

使用 npm run build  重新打包所有文件,可以在dist下查看
完整版:同时包含编译器和运行时的版本
编译器:用来将模板字符串编译成JS渲染函数的代码体积大,效率低
运行时:创建Vue实例,渲染Vnode等代码,体积小,效率高,基本就是编译的代码
UMD: 通用的模块版本,支持多种模块方式。Vue默认文件是运行时 + 编译器的UMD版本
CommonJS:用来配合老的打包工具Browserify 或 webpack1.0
ES Module:2.6之后提供两个ES Module构建文件,提供现代打包提供版本
      ESM格式设计为可以被静态分析,所以攻击可以利用这点来进行“tree-shaking”,排除无用代码
Vue脚手架对webpack进行深度封装,可以通过命令行工具查看vue的配置
vue inspect
vue inspect > output.js  将vue配置输出到output.js中 
查看resolve可以看到vue运行时使用的是vue.runtaime.esm.js
是运行时版本,且使用esm的方式
开发项目时会有很多单文件组件,浏览器是不支持这种方式,
vue会将单文件转换为JS对象,转换过程中会将模板转换成render函数,所以运行时不需要编译器

入口文件

查看dist/vue.js的构建过程
先执行构建 npm run dev

"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",

script/config.js的执行过程
作用:生成rollup构建的配置文件
使用环境变量TARGET = web-full-dev
找到

'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 // 生成每一个文件头的注释内容
  },

src/platform/web/entry-runtime-with-compiler.js

 // 如果同时设置template 和 render此时会渲染什么?
 // 如果有render。不会执行template,如果有render,直接调用组件的mount渲染render函数
 const vm = new Vue({
     
    el: "#app",
    template:"

hello tempalte

"
, render(h){ return h("h1","hello Render") } })

vue 执行过程 vue-》init -》mount
Vue.prototype.$mount 执行到mount

el不能是body 或者 html
如果没有render,将template转换成render函数
如果有render函数,直接调用mount挂载DOM

const idToTemplate = cached(id => {
     
  const el = query(id)
  return el && el.innerHTML
})

// 保留 Vue 实例的$mount 方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  // 非ssr情况下为false,ssr时候为true
  hydrating?: boolean
): Component {
     
  // 获取 el 对象
  el = el && query(el)

  /* istanbul ignore if */
  // el 不能是 body 或者html
  if (el === document.body || el === document.documentElement) {
     
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to  or  - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  // 把 template/el 转换成render函数
  if (!options.render) {
     
    let template = options.template
    // 如果模板存在
    if (template) {
     
      if (typeof template === 'string') {
     
        // 如果模板是 id 选择器
        if (template.charAt(0) === '#') {
     
          // 获取对应节点的 innerHTML
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
     
            warn(
              `Template element not found or is empty: ${
       options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
     
        template = template.innerHTML
      } else {
     
        if (process.env.NODE_ENV !== 'production') {
     
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
     
      template = getOuterHTML(el)
    }
    if (template) {
     
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     
        mark('compile')
      }

      const {
      render, staticRenderFns } = compileToFunctions(template, {
     
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     
        mark('compile end')
        measure(`vue ${
       this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  // 调用 mount方法,渲染DOM
  return mount.call(this, el, hydrating)
}

初始化

过程

platfroms/web  存放平台相关代码
entry-runtime-with-compiler.js  打包完整版vue入口文件
entry-runtime.js  运行时版本文件
platfroms/web/runtime/index.js  存放平台相关,运行时相关指令,注册patch以及mount方法
core/index.js  initGlobalAPI挂载静态方法
core/instance/index.js 存放vue的静态函数,判断是否是vue的实例
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'
// 此处不使用 class 原因是方便后续给 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')
  }
  // 调用init 方法
  this._init(options)
}
// 注册vm 的init方法,初始化vm
initMixin(Vue)
// 注册cm的¥data/$props/$set/$delete/$watch
stateMixin(Vue)
//初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入render
// $nextTick/_render
renderMixin(Vue)

export default Vue

四个导出Vue 的模块
src/platforms/web/entry-runtime-with-compiler.js

web平台相关的入口
重写了平台相关的$mount
注册了Vue.compile()方法,传递一个HTML字符串返回 render函数
如果同时传入template,render,会执行render函数,没有render会将template编译成render

/* @flow */

import config from 'core/config'
import {
      warn, cached } from 'core/util/index'
import {
      mark, measure } from 'core/util/perf'

import Vue from './runtime/index'
import {
      query } from './util/index'
import {
      compileToFunctions } from './compiler/index'
import {
      shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'

const idToTemplate = cached(id => {
     
  const el = query(id)
  return el && el.innerHTML
})

// 保留 Vue 实例的$mount 方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  // 非ssr情况下为false,ssr时候为true
  hydrating?: boolean
): Component {
     
  // 获取 el 对象
  el = el && query(el)

  /* istanbul ignore if */
  // el 不能是 body 或者html
  if (el === document.body || el === document.documentElement) {
     
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to  or  - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  // 把 template/el 转换成render函数
  if (!options.render) {
     
    let template = options.template
    // 如果模板存在
    if (template) {
     
      if (typeof template === 'string') {
     
        // 如果模板是 id 选择器
        if (template.charAt(0) === '#') {
     
          // 获取对应节点的 innerHTML
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
     
            warn(
              `Template element not found or is empty: ${
       options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
     
        template = template.innerHTML
      } else {
     
        if (process.env.NODE_ENV !== 'production') {
     
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
     
      template = getOuterHTML(el)
    }
    if (template) {
     
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     
        mark('compile')
      }

      const {
      render, staticRenderFns } = compileToFunctions(template, {
     
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     
        mark('compile end')
        measure(`vue ${
       this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  // 调用 mount方法,渲染DOM
  return mount.call(this, el, hydrating)
}

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML (el: Element): string {
     
  if (el.outerHTML) {
     
    return el.outerHTML
  } else {
     
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

Vue.compile = compileToFunctions

export default Vue

src/platforms/web/runtime/index.js

web平台相关
注册和平台相关的全局指令:v-model、v-show
注册和平台相关的全局组件:v-transtion、v-transition-group
全局方法:patch:把虚拟DOM转换成真实DOM、 $mount:挂载方法
/* @flow */

import Vue from 'core/index'
import config from 'core/config'
import {
      extend, noop } from 'shared/util'
import {
      mountComponent } from 'core/instance/lifecycle'
import {
      devtools, inBrowser } from 'core/util/index'

import {
     
  query,
  mustUseProp,
  isReservedTag,
  isReservedAttr,
  getTagNamespace,
  isUnknownElement
} from 'web/util/index'

import {
      patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// extend是负责对象成员的功能
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
     
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
     
  setTimeout(() => {
     
    if (config.devtools) {
     
      if (devtools) {
     
        devtools.emit('init', Vue)
      } else if (
        process.env.NODE_ENV !== 'production' &&
        process.env.NODE_ENV !== 'test'
      ) {
     
        console[console.info ? 'info' : 'log'](
          'Download the Vue Devtools extension for a better development experience:\n' +
          'https://github.com/vuejs/vue-devtools'
        )
      }
    }
    if (process.env.NODE_ENV !== 'production' &&
      process.env.NODE_ENV !== 'test' &&
      config.productionTip !== false &&
      typeof console !== 'undefined'
    ) {
     
      console[console.info ? 'info' : 'log'](
        `You are running Vue in development mode.\n` +
        `Make sure to turn on production mode when deploying for production.\n` +
        `See more tips at https://vuejs.org/guide/deployment.html`
      )
    }
  }, 0)
}

export default Vue

src/core/index.js

与平台无关
设置了Vue的静态方法,initGlobalAPI(Vue),设置了vue的一些set方法,delete,nextTick

import Vue from './instance/index'
import {
      initGlobalAPI } from './global-api/index'
import {
      isServerRendering } from 'core/util/env'
import {
      FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue) //给vue挂载静态函数

Object.defineProperty(Vue.prototype, '$isServer', {
     
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
     
  get () {
     
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
     
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue

src/core/instance/index.js

与平台无关
定义了构造函数Vue,调用了this._init(options)方法
给Vue 中混入了常用的实例成员

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'
// 此处不使用 class 原因是方便后续给 Vue实例混入实例成员
function Vue (options) {
     
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue) //判断是否是Vue的实例,如果不是,不是通过new调用,抛出警告
  ) {
     
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 调用init 方法
  this._init(options)
}
// 注册vm 的init方法,初始化vm
initMixin(Vue)
// 注册cm的$data/$props/$set/$delete/$watch
stateMixin(Vue)
//初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入render
// $nextTick/_render
renderMixin(Vue)

export default Vue

全局的vue组件都存放在Vue.options.components中
全局环境存放在inBrowser中,在until/env中

Vue 初始化静态成员
vue官网的 全局API 设置的就是此处
vue-dev\src\core\global-api\index.js

初始化设置vue.config
设置Vue.util
设置静态方法 set,del,nextTick
设置Vue.options
注册component,directive,fiffifter
设置keep-live 注册use 注册Vue.mixin()

vue源码实现 directive 、component 、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数组 ,为Vue 定义相应方法
   // ASSET_TYPES包含 directive 、 filter 、 component
  ASSET_TYPES.forEach(type => {
     
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
     
      if (!definition) {
      // 如果未定义 就在Vue.options上查找
        return this.options[type + 's'][id]
      } else {
     
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
     
          validateComponentName(id)
        }
        // Vue.component('textCom':"")
        if (type === 'component' && isPlainObject(definition)) {
     
          definition.name = definition.name || id
          // 把组件配置转换为组件的构造函数
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
     
          definition = {
      bind: definition, update: definition }
        }
        // 全局注册,存储并赋值 
        // this.options['components']['textCom'] = definition
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

Vue 初始化实例成员
src/core/instance/index.js

调用了initMixin, 注册vm 的init方法,初始化vm
调用stateMixin,设置了 $data, $props , $ watch, d e l e t e / delete/ delete/watch
eventsMixin,通过订阅发布 模式,Vue挂载了on,once,off,emit
lifecycleMixin,初始化生命周期的混入_update/$ forceUpdate/$ destroy
renderMixin ,混入了render 和 nextTick
整体是在Vue或原型上挂载对应的方法

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'
// 此处不使用 class 原因是方便后续给 Vue实例混入实例成员
function Vue (options) {
     
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue) //判断是否是Vue的实例,如果不是,不是通过new调用,抛出警告
  ) {
     
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 调用init 方法
  this._init(options)
}
// 注册vm 的init方法,初始化vm
initMixin(Vue)
// 注册cm的¥data/$props/$set/$delete/$watch
stateMixin(Vue)
//初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入render
// $nextTick/_render
renderMixin(Vue)

export default Vue

init的实现

src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
     
  // 给Vue 实例增加了 _init() 方法
  // 合并 options / 初始化操作
  Vue.prototype._init = function (options?: Object) {
     
    const vm: Component = this
    // a uid
    vm._uid = 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
    // 如果是vue实例不需要observe
    vm._isVue = true
    // merge options
    // 合并 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
    // vm 的生命周期相关变量初始化
    // $children / $parent / $root / $refs
    initLifecycle(vm)
    // vm 的事件监听变化,父组件绑定在当前组件上的事情
    initEvents(vm)
    // vm的编译 render初始化
    // $slots /$scopedSlots / _c /$createElemt / $attrs / $listeners
    initRender(vm)
    // beforeCreate 的回调
    callHook(vm, 'beforeCreate')
    // 将inject 的成员注入到vm上
    initInjections(vm) // resolve injections before data/props
    // 初始化 vm 的_props/methods/_data/computed/watch
    initState(vm)
    // 初始化 provide
    initProvide(vm) // resolve provide after data/props
    // create 生命钩子的回调
    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) // 调用mount 挂载
    }
  }
}

initState 的实现
src\core\instance\state.js

export function initState (vm: Component) {
     
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props) // 初始化props,并且通过defineReactive函数将值转换为set,get
  if (opts.methods) initMethods(vm, opts.methods) // 将选项中的methods注入到vue实例,
  if (opts.data) {
     
    initData(vm) // 
  } else {
     
    observe(vm._data = {
     }, true /* asRootData */) //转换成响应式数据
  }  
  if (opts.computed) initComputed(vm, opts.computed)  // 初始化computed
  if (opts.watch && opts.watch !== nativeWatch) {
     
    initWatch(vm, opts.watch)  // 初始化watch
  }
}

首次渲染过程

Vue响应式原理--初始化过程_第1张图片
Vue响应式原理--初始化过程_第2张图片

你可能感兴趣的:(#,vue源码解析,vue,vue.js)