> 文章列表 > Vue2源码-初始化

Vue2源码-初始化

Vue2源码-初始化

源码能理解的问题

Q1. 如何让构造函数和class 一样,不能直接调用只能通过new 实例化?
Q2. 为什么在beforeCreate钩子中无法读取data,props等属性
Q3. 为什么data | props 中的数据,我们可以直接通过this.xx访问?
Q4. computed 和watch的区别?

Vue构造函数

Vue其实就是构造函数,主要为构造函数的原型上添加方法,源码:vue/src/core/instance/index.js
Q1的答案就在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'function Vue (options) {	
/**
* Vue是一个构造函数,必须使用new关键字进行实例化才能正常使用, 防止构造函数被当成普通函数调用,
* 因为只有通过实例化this才指向实例,this instanceof Vue 才会为真,否则this将指向window
*/ 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)
}
// 为Vue 构造函数的原型绑定初始化函数_init
initMixin(Vue)
/* 为Vue构造函数的原型绑定 $data,$props,$set,$delete,$watch */
stateMixin(Vue)
/* 为Vue构造函数的原型绑定$on,$once,$off,$off,$emit */
eventsMixin(Vue)
/* 为Vue构造函数的原型上绑定_update,$forceUpdate,$destroy */
lifecycleMixin(Vue)
/* 为Vue构造函数的原型绑定$nextTick, _render */
renderMixin(Vue)export default Vue

初始化_init()

源码:vue/src/core/instance/init.js
Q:为什么在beforeCreate钩子中无法读取data,props等属性?
A:我们不难发现callHook(vm, ‘beforeCreate’)->initState-> callHook(vm, ‘created’) 执行顺序,这就是为什么我们在beforeCreate钩子中无法获取到data的原因,但是我们能获取到this 对象。

export function initMixin (Vue: Class<Component>) {Vue.prototype._init = function (options?: Object) {const vm: Component = this// a uidvm._uid = uid++// ...// a flag to avoid this being observedvm._isVue = true// merge optionsif (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 selfvm._self = vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate')initInjections(vm) // resolve injections before data/propsinitState(vm) // 初始化props,methods,data,computed,watchinitProvide(vm) // resolve provide after data/propscallHook(vm, 'created')// 挂载if (vm.$options.el) {vm.$mount(vm.$options.el)}}
}

initState

初始化props,methods,data,computed,watch
源码:vue/src/core/instance/state.js

export function initState (vm: Component) {vm._watchers = []const opts = vm.$optionsif (opts.props) initProps(vm, opts.props)if (opts.methods) initMethods(vm, opts.methods)if (opts.data) {initData(vm)} else {/*该组件没有data的时候绑定一个空对象*/observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed)if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}
}

initProps

props 上的属性调用defineReactive 进行劫持监听。

function initProps (vm: Component, propsOptions: Object) {const propsData = vm.$options.propsData || {}// 引用类型的应用const props = vm._props = {}// cache prop keys so that future props updates can iterate using Array// instead of dynamic object key enumeration.const keys = vm.$options._propKeys = []const isRoot = !vm.$parent// root instance props should be convertedif (!isRoot) {toggleObserving(false)}for (const key in propsOptions) {keys.push(key)const value = validateProp(key, propsOptions, propsData, vm)/* istanbul ignore else */if (process.env.NODE_ENV !== 'production') {const hyphenatedKey = hyphenate(key)if (isReservedAttribute(hyphenatedKey) ||config.isReservedAttr(hyphenatedKey)) {warn(`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,vm)}// 进行数据劫持改写,回调检测props的异常overwrittendefineReactive(props, key, value, () => {if (!isRoot && !isUpdatingChildComponent) {warn(`Avoid mutating a prop directly since the value will be ` +`overwritten whenever the parent component re-renders. ` +`Instead, use a data or computed property based on the prop's ` +`value. Prop being mutated: "${key}"`,vm)}})} else {defineReactive(props, key, value)}// static props are already proxied on the component's prototype// during Vue.extend(). We only need to proxy props defined at// instantiation here.if (!(key in vm)) {proxy(vm, `_props`, key)}}// 是否进行劫持改写的变量控制toggleObserving(true)
}

initMethods

  • 验证methods的名字是否重名
  • 通过bind函数将事件绑定到vm;
function initMethods (vm: Component, methods: Object) {const props = vm.$options.propsfor (const key in methods) {if (process.env.NODE_ENV !== 'production') {if (typeof methods[key] !== 'function') {warn(`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +`Did you reference the function correctly?`,vm)}if (props && hasOwn(props, key)) {warn(`Method "${key}" has already been defined as a prop.`,vm)}if ((key in vm) && isReserved(key)) {warn(`Method "${key}" conflicts with an existing Vue instance method. ` +`Avoid defining component methods that start with _ or $.`)}}vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)}
}

initData

  • 判断data是否为函数,并且赋值给vm._data上;
  • 判断data 内的属性与props,methods 是否重名;
  • 对data 的属性进行proxy代理,this.xx === this._data.xx。这就是为什么data | props 中的数据,我们可以直接通过this.xx访问;
  • data属性进行劫持监听observe;
function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}if (!isPlainObject(data)) {data = {}process.env.NODE_ENV !== 'production' && warn('data functions should return an object:\\n' +'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`,vm)}}if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && warn(`The data property "${key}" is already declared as a prop. ` +`Use prop default value instead.`,vm)} else if (!isReserved(key)) {// 非服务端渲染data内的属性同步到vm 并且同步到vm._dataproxy(vm, `_data`, key)}}// observe data 数据监听observe(data, true /* asRootData */)
}
export function proxy (target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter () {return this[sourceKey][key]}sharedPropertyDefinition.set = function proxySetter (val) {this[sourceKey][key] = val}Object.defineProperty(target, key, sharedPropertyDefinition)
}

initComputed

  • 遍历computed的key,实例化订阅器wactcher进行数据监听;
  • 通过Object.defineProperty方法进行劫持改写;

computed 会对结果进行缓存,只有依赖改变才会触发更新。watch 是函数,只要依赖项改变就会再次执行,对象形式的watch也可以设置immediate参数立即执行。其实computed 就是惰性的watch, computed 创建就是watch的实例化,只是额外添加computedWatcherOptions = { lazy: true }参数。

const computedWatcherOptions = { lazy: true }function initComputed (vm: Component, computed: Object) {// $flow-disable-lineconst watchers = vm._computedWatchers = Object.create(null)// computed properties are just getters during SSRconst isSSR = isServerRendering()for (const key in computed) {const userDef = computed[key]const getter = typeof userDef === 'function' ? userDef : userDef.getif (process.env.NODE_ENV !== 'production' && getter == null) {warn(`Getter is missing for computed property "${key}".`,vm)}if (!isSSR) {// create internal watcher for the computed property.watchers[key] = new Watcher(vm,getter || noop,noop,computedWatcherOptions)}// component-defined computed properties are already defined on the// component prototype. We only need to define computed properties defined// at instantiation here.if (!(key in vm)) {// 定义计算属性defineComputed(vm, key, userDef)} else if (process.env.NODE_ENV !== 'production') {if (key in vm.$data) {warn(`The computed property "${key}" is already defined in data.`, vm)} else if (vm.$options.props && key in vm.$options.props) {warn(`The computed property "${key}" is already defined as a prop.`, vm)}}}
}

定义计算属性有函数和对象两种方式,函数的形式只有get,没有set;对象形式可以自定义get,set。最后需要将计算属性绑定到vm

export function defineComputed (target: any,key: string,userDef: Object | Function
) {// 是否为服务端渲染判断是否需要缓存const shouldCache = !isServerRendering()// 计算属性默认没有set,但是对象类型的计算属性可以自定义if (typeof userDef === 'function') {// 判断创建监听还是使用自定义的方法sharedPropertyDefinition.get = shouldCache? createComputedGetter(key): createGetterInvoker(userDef)sharedPropertyDefinition.set = noop} else {sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): createGetterInvoker(userDef.get): noopsharedPropertyDefinition.set = userDef.set || noop}if (process.env.NODE_ENV !== 'production' &&sharedPropertyDefinition.set === noop) {sharedPropertyDefinition.set = function () {warn(`Computed property "${key}" was assigned to but it has no setter.`,this)}}// 将计算属性key挂到vm上Object.defineProperty(target, key, sharedPropertyDefinition)
}

计算属性的getter方法

function createComputedGetter (key) {return function computedGetter () {const watcher = this._computedWatchers && this._computedWatchers[key]if (watcher) {//再创建watcher是存在lazy:true,即dirty:trueif (watcher.dirty) {watcher.evaluate()// 返回计算属性的初始值,将dirty赋值false}if (Dep.target) {watcher.depend() // dep收集依赖}return watcher.value}}
}

initWatch

watch:{ [key: string]: string | Function | Object | Array }
将监听全部同步到vm.$watch

function initWatch (vm: Component, watch: Object) {for (const key in watch) {const handler = watch[key]if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i])}} else {createWatcher(vm, key, handler)}}
}function createWatcher (vm: Component,expOrFn: string | Function,handler: any,options?: Object
) {if (isPlainObject(handler)) {options = handlerhandler = handler.handler}if (typeof handler === 'string') {handler = vm[handler]}return vm.$watch(expOrFn, handler, options)
}