前言
自己学习Vue也有不少时间了,特别是喜欢研究一些原理,一直以来都是看别人的博客学习,这次想分享学习的心得,而且这也是我第一次发博客,写的不好请见谅,文章随缘更新(最近有时间),感谢观看
注:一定要搭配源码看,版本为:2.6.10
在new Vue之前的初始化
我们先来到`node_modules\vue\src\core\instance\index.js`, 这里是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'// 这里是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') } this._init(options)}// 首先会进行一系列的初始化initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)export default Vue
接下来会一步步分析, initMixin(Vue)
, stateMixin(Vue)
, eventsMixin(Vue)
, lifecycleMixin(Vue)
, renderMixin(Vue)
initMixin(Vue)
文件位置:
node_modules\vue\src\core\instance\init.js
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { // ...具体逻辑后续讲解 }}
很明显,在
Vue.prototype
上初始化了一个_init
方法, 它是在我们new Vue后进行了调用, 至于具体实现后面详解。stateMixin(Vue)
文件位置:
node_modules\vue\src\core\instance\state.js
export function stateMixin (Vue: Class<Component>) { // flow somehow has problems with directly declared definition object // when using Object.defineProperty, so we have to procedurally build up // the object here. const dataDef = {} // 定义一个data空对象, 后面就会知道,这是对$data属性的特性设置 dataDef.get = function () { return this._data } // 定义data的getter方法 const propsDef = {} propsDef.get = function () { return this._props }// 定义props的getter方法 if (process.env.NODE_ENV !== 'production') { dataDef.set = function () { warn( 'Avoid replacing instance root $data. ' + 'Use nested data properties instead.', this ) } propsDef.set = function () { warn(`$props is readonly.`, this) } } Object.defineProperty(Vue.prototype, '$data', dataDef) Object.defineProperty(Vue.prototype, '$props', propsDef) Vue.prototype.$set = set Vue.prototype.$delete = del 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() } }}
接下来我们一句句分析这个函数做了什么
首先创建了两个
dataDef
,propsDef
对象,并且设置了get
属性访问器,返回了_data
,_props
,至于具体作用在下面会有说明const dataDef = {} dataDef.get = function () { return this._data } const propsDef = {} propsDef.get = function () { return this._props }
接下来是这段代码,如果不是生产环境,也就是开发环境,我们是不能直接赋值给
$data
和$props
属性值的if (process.env.NODE_ENV !== 'production') { dataDef.set = function () { warn( 'Avoid replacing instance root $data. ' + 'Use nested data properties instead.', this ) } propsDef.set = function () { warn(`$props is readonly.`, this) } } // this.$data = ... ,this.$props 这样都是会报警告的
在原型上添加属性访问器,也就是我们可以在组件内直接使用
this.$data
访问// 在原型中添加$data属性,并设置访问器,返回的是_data Object.defineProperty(Vue.prototype, '$data', dataDef) // 在原型中添加$props属性,并设置访问器,返回的是_props Object.defineProperty(Vue.prototype, '$props', propsDef)
我们来看下这个
$data
到底是什么<template> <div class="about"> <h1>{{ appName }}</h1> <h1>{{ username }}</h1> </div></template><script>export default { data() { return { count: 1 } }, mounted() { // 打印了this console.log(this.$data); }};</script>
打印结果很明显,就是vue实例的
data
方法返回的对象,并且包含__ob__属性,表示它被观测了,这些我们以后会在数据响应会详细讲解{__ob__: Observer}count: 1__ob__: Observer {value: {…}, dep: Dep, vmCount: 1}get count: ƒ reactiveGetter()set count: ƒ reactiveSetter(newVal)__proto__:
接下来我们继续进行,它在Vue原型上添加了两个方法,set和del,那么他们到底是用来做什么的呢,我们直接定位到这两个方法的实现,并且下面两个代码实现我将以注释的形式讲解,请跳转到响应的源代码
// 原型上添加 $set和$delete方法 Vue.prototype.$set = set Vue.prototype.$delete = del
// src/core/observer/index.js // @param {*} target 目标对象// @param {*} key 要添加的键 // @param {*} val 要添加的值export function set (target: Array<any> | Object, key: any, val: any): any { // 如果目标对象是undefined或者null 或者是原始类型就警告 if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 如果目标是数组, 并且key是有效的索引,也就是说必须是整数 // 整个if所干的事情就是,删除原来的值,并替换新的值,然后直接返回新的值 if (Array.isArray(target) && isValidArrayIndex(key)) { // 就找到最大的那个长度 target.length = Math.max(target.length, key) // 如果key对应的下标有值,就删掉数组下标对应的值,然后添加新的值,否则不会删除,添加新的值 target.splice(key, 1, val) // 注意这里的splice是变异后的方法,会触发响应 return val // 直接返回属性的值 } // 如果key是对象的key,并且不是系统原生的属性,比如说一些toString,之类的存在于Object.prototype原型的属性 if (key in target && !(key in Object.prototype)) { // 直接改变目标对象的属性值,并返回,注意改变值会触发响应,如果页面有使用到这个属性,就会自动改变页面的这个值 target[key] = val return val } // 获取__ob__对象 const ob = (target: any).__ob__ // 这句话的意思就是,避免给Vue实例添加响应式的属性或者给他的$data if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } // 如果key属性不在目标对象上,也就是说本来就不存在于目标对象上,也就意味着,不是响应式的 // 当我们赋值后,会自动添加响应式,并触发页面更新 if (!ob) { target[key] = val return val } // ob.value设置为响应式 defineReactive(ob.value, key, val) // 通知页面更新 ob.dep.notify() return val}
总结下
Vue.prototype.$set
所做的事情,其实就是向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性(官方文档原话)。接下来我们再来看看
$del
实现export function del (target: Array<any> | Object, key: any) { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) } if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ) return } if (!hasOwn(target, key)) { return } delete target[key] if (!ob) { return } ob.dep.notify()}
我想不用多说,根据上面的下面不难看出,就是删除响应式数据中的一个属性,并且更新视图。
官方文档介绍:删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到属性被删除的限制,但是你应该很少会使用它。
然后我们回到初始化时候的代码
Vue.prototype.$set = set Vue.prototype.$delete = del // 添加$watch方法 Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { // ...后续 }
以上代码又是在原型上添加了一个
$watch
方法,简单说一下,其实就是监听一个数据的变化,在以后的数据响应会详细讲解回到最开始我们看到的四个初始化方法,我们继续分析
initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)
eventsMixin(Vue)
文件位置:
node_modules\vue\src\core\instance\events.js
export function eventsMixin (Vue: Class<Component>) { Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { // ... return vm } Vue.prototype.$once = function (event: string, fn: Function): Component { // ... return vm } Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { // ... return vm } Vue.prototype.$emit = function (event: string): Component { // ... return vm }}
分别在
Vue.prototype
, 初始化了四个方法$on
,$once
,$off
,$emit
,这些代码的实现我们暂且不看,以后都会有讲解,不过我们还是讲一下具体作用- vm.$on: 监听当前实例上的自定义事件。事件可以由
vm.$emit
触发。回调函数会接收所有传入事件触发函数的额外参数。 - vm.$emit: 监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。
- vm.$off: 移除自定义事件监听器。
- vm.$once: 监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。
以上方法,有个常见的使用场景:eventBus,我们可以再次
new Vue()
一个实例,专门用来父子组件直接的传值
- vm.$on: 监听当前实例上的自定义事件。事件可以由
lifecycleMixin(Vue)
文件位置: node_modules\vue\src\core\instance\lifecycle.js
export function lifecycleMixin (Vue: Class<Component>) { Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { // .. } Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } } Vue.prototype.$destroy = function () { // .. } }
在Vue.prototype
上初始化_update
, $forceUpdate
, $destroy
三个方法
- _update:用于渲染dom或者更新dom到视图
- $forceUpdate:迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
- $destroy:完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。
renderMixin(Vue)
文件位置:node_modules\vue\src\core\instance\render.js
export function renderMixin (Vue: Class<Component>) { // install runtime convenience helpers installRenderHelpers(Vue.prototype) Vue.prototype.$nextTick = function (fn: Function) { return nextTick(fn, this) } Vue.prototype.$nextTick = function (): VNode { // ... return vnode }}
在Vue.prototype
上初始化$nextTick
- $nextTick: 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法
Vue.nextTick
一样,不同的是回调的this
自动绑定到调用它的实例上。
initGlobalAPI
我们定位到src\core\global-api\index.js
文件,Vue除了初始化上面的一些实例方法,也添加了下面这些静态方法
export function initGlobalAPI (Vue: GlobalAPI) { // 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. Vue.util = { warn, extend, mergeOptions, defineReactive } Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 2.6 explicit observable API Vue.observable = <T>(obj: T): T => { observe(obj) return obj } Vue.options = Object.create(null) 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 // 这里把keep-alive组件注册到全局 extend(Vue.options.components, builtInComponents) initUse(Vue) initMixin(Vue) initExtend(Vue) initAssetRegisters(Vue)}
我们来看下具体做了哪些事情
const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { // 无法更改config,否则警告 configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } }// 添加配置对象 Object.defineProperty(Vue, 'config', configDef) // 添加了`util`对象,里面有一些方法,`warn:用于警告`,`extend:合并两个对象`,`mergeOptions:合并选项`,`defineReactive:设置响应式数据` Vue.util = { warn, extend, mergeOptions, defineReactive } Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 这三个不必多说和之前的初始化一样
Vue.observable = <T>(obj: T): T => { observe(obj) return obj}// 深度观察对象,并设置为响应式
// ASSET_TYPES // export const ASSET_TYPES = [ // 'component', // 'directive', // 'filter' // ] Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // 在Vue的options属性上添加一些属性,初始为空对象,包括我们常见的components,directives,filter // 是不是很眼熟
Vue.options._base = Vue // Vue.options添加了_base属性指向Vue,此时的options如下 /* Vue.options = { components:{}, directives:{}, filter:{}, _base: Vue } */
extend(Vue.options.components, builtInComponents)
builtInComponents
这个又是什么鬼?我们来看看是什么
// src/core/components/index.jsimport KeepAlive from './keep-alive'export default { KeepAlive}
原来是把KeepAlive
组件添加到了components
中
接下来又是各种初始化,注意千万别把之前的初始化混淆
initUse(Vue) 添加了use方法,一般用来注册插件 initMixin(Vue) 添加了mixin方法,混入全局的钩子函数 initExtend(Vue) 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象 initAssetRegisters(Vue) 注册全局组件,指令或过滤器
- initUse: 位置
src/core/global-api/use.js
,添加了use方法,一般用来注册插件 - initMixin:位置
src/core/global-api/mixin.js
,添加了mixin方法,混入全局的钩子函数 initExtend:位置
src/core/global-api/extend.js
,使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象// 样例var Profile = Vue.extend({ template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', data: function () { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } }})
initAssetRegisters:位置
src/core/global-api/assets.js
,注册全局组件,指令或过滤器Vue.component('my-component', { /* ... */ })Vue.directive('my-directive', { bind: function () {}, inserted: function () {}, update: function () {}, componentUpdated: function () {}, unbind: function () {}})Vue.filter('my-filter', function (value) { // 返回处理后的值})
经过了initGlobalAPI(Vue)
最终返回了Vue,其他代码暂且不看,主要用来处理服务端渲染
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)Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering})Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext }})Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext})Vue.version = '__VERSION__'export default Vue
总结
经过了initMixin(Vue)
, stateMixin(Vue)
, eventsMixin(Vue)
, lifecycleMixin(Vue)
, renderMixin(Vue)
, Vue.prototype有哪些方法呢
Vue.prototype = { _init: f, // new Vue初始化时调用 $data: get => _data $props: get => _props, $set: f, $delete: f, $watch: f, // 监听data数据变化的方法 $on: f, // 事件方法 $once: f, // 事件方法 $emit: f, // 手动触发事件方法 _update: f, // 初始化或者更新,把VNode渲染为真正的DOM节点 $forceUpdate: f, // $destroy: f}
经过initGlobalAPI
添加了许多静态方法和对象
1.Vue.util = { warn, extend, mergeOptions, defineReactive } 2.Vue.set = set 3.Vue.delete = del 4.Vue.nextTick = nextTick 5.Vue.observable = <T>(obj: T): T => { observe(obj) return obj } 6.Vue.options = Object.create(null) 7.Vue.options[type + 's'] = Object.create(null) 8.创建全局的keep-alive组件 9.initUse(Vue) 添加了use方法,一般用来注册插件 10.initMixin(Vue) 添加了mixin方法,混入全局的钩子函数 11.initExtend(Vue) 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象 12.initAssetRegisters(Vue) 注册全局组件,指令或过滤器
下一篇:new Vue()
后发生了什么