provide vue 响应式_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) {

// 给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

}

}

首次渲染过程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值