我的vue版本 -> Vue.js v2.6.11
概述:vue响应式系统构建过程主要是在Init阶段,在
initState()
方法中会对props
,methods
,data
,computed
,watcher
等内容进行初始化,在初始化data阶段会对传入的options
中的data
进行一些校验,接下来就是使用new Observer()
对data
中的数据进行响应化处理
1.src\core\instance\index.js -> vue源码的入口文件
//入口
function Vue(options) {
//初始化vue
this._init(options);
}
2.src\core\instance\init.js -> 初始化文件,主要关注initState()
这个方法,其他的后面再做研究
export function initMixin(Vue: Class<Component>) {
//在initMinxin里面定义Vue的原型方法_init
Vue.prototype._init = function (options?: Object) {
const vm: Component = this;
// a uid
vm._uid = uid++;
let startTag, endTag;
// a flag to avoid this being observed
vm._isVue = true;
//合并选项
// merge options
if (options && options._isComponent) {
//判断是不是一个组件,是的话执行initInternalComponent,否则合并options
// 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放在_self属性上暴露出去
initLifecycle(vm); //初始化生命周期:$parent,$root,$children,$refs
initEvents(vm); //对父组件传入事件添加监听,初始化事件
initRender(vm); //生命$slots,$createElemnet,渲染相关的
callHook(vm, "beforeCreate");
initInjections(vm); // resolve injections before data/props,注入数据
//数据初始化
initState(vm); //初始化props、data、watch、methods、computed等属性,因此在beforeCreate的钩子函数中获取不到前面的这些定义的属性和方法
initProvide(vm); // resolve provide after data/props,提供数据
callHook(vm, "created");
//最终挂载方法
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
3.src\core\instance\state.js
export function initState(vm: Component) {
vm._watchers = [];
const opts = vm.$options;
//初始化props
if (opts.props) initProps(vm, opts.props);
//初始化methods
if (opts.methods) initMethods(vm, opts.methods);
//初始化data
if (opts.data) {
initData(vm);
} else {
//没有创建则创建一个空对象,并设置位响应式
observe((vm._data = {}), true /* asRootData */);
}
//初始化computed
if (opts.computed) initComputed(vm, opts.computed);
//初始化watch
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch);
}
}
initData
方法
可以看到我们平时常见的一些报错提示信息
- 校验传入的data是否是一个object类型
- 遍历所有的
keys
(校验keys的合法性)- 是否有和
methods
重名的属性 - 是否有和
props
重名的属性
- 是否有和
- 校验通过调用proxy方法,将所有的属性都挂载到
_data
属性上,vue
中提供了使用$xxx
直接访问vue
实例属性的方法 - 数据响应化处理
function initData(vm: Component) {
let data = vm.$options.data;
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
//校验data必须是一个对象,其他的类似Array,Function等也算是对象类型,但是有着更精确的数据类型
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 instance
//获取所有的data中的Key
const keys = Object.keys(data);
//获取配置对象上的props属性
const props = vm.$options.props;
//获取配置对象上的methods属性,所有的方法都在Methods对象中
const methods = vm.$options.methods;
let i = keys.length;
while (i--) {
const key = keys[i];
if (process.env.NODE_ENV !== "production") {
//校验methods->Key的唯一性
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
);
}
}
//校验props->Key的唯一性
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)) {
//校验key是否是使用$或者下划线进行定义的,因为vue中定义了很多的$data,$el等来直接操作vue对象。把所有的key都代理到vm的_data属性上面
proxy(vm, `_data`, key);
//此处的proxy代理和observe中的walk代理有什么区别?
//proxy代理的是将自定义的data中的所有属性定义到_data上,而walk中的Object.defineProperty则是针对data的值
}
}
// observe data
observe(data, true /* asRootData */);
}
4.src\core\observer\index.js
关键一步:ob = new Observer(value)
,开始构建observe
对象
export function observe(value: any, asRootData: ? boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
src\core\observer\index.js
在vue2中不能直接处理数组的方法,需要重写数组的方法,怎样重写数组的方法?看这里->监听数组变化的方法 原理是将常用的操作数组的方法列下来,替换原来数组的方法,同时也方便扩展。
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor(value: any) {
this.value = value
//创建依赖收集的容器
this.dep = new Dep()
this.vmCount = 0
//设置一个__ob__属性引用当前observer实例
def(value, '__ob__', this)
//判断类型
if (Array.isArray(value)) {
//如果是数组,替换数组对象的原型
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
//如果数组里面元素是对象还需要做响应化处理
this.observeArray(value)
} else {
//walk方法,将每个值进行响应化处理
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk(obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray(items: Array < any > ) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
defineReactive()
可以看到我们常说的Object.defineProperty
了,至此响应化data的部分就已经完成了。
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter ? : ? Function,
shallow ? : boolean
) {
//和Key一一对应
const dep = new Dep()
//childOb,属性拦截,只要是对象类型都会返回childobj
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
//如果存在依赖
if (Dep.target) {
//依赖收集
dep.depend()
if (childOb) {
//如果存在子obj,子obj也收集这个依赖?为什么要这么做?作用:Obj和父变了和子改变了都会通知进行更新。例:在访问时{obj.foo}无论是Obj变了还是obj.foo的值变了都会通知进行更新。
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
以上是我对vue
初始化data
的理解,有不对之处欢迎指证,共同学习!
网友评论