我的vue版本 -> Vue.js v2.6.11
概述:vue響應(yīng)式系統(tǒng)構(gòu)建過程主要是在Init階段烁设,在
initState()
方法中會對props
檬某,methods
撬腾,data
螟蝙,computed
恢恼,watcher
等內(nèi)容進行初始化,在初始化data階段會對傳入的options
中的data
進行一些校驗胰默,接下來就是使用new Observer()
對data
中的數(shù)據(jù)進行響應(yīng)化處理
1.src\core\instance\index.js -> vue源碼的入口文件
//入口
function Vue(options) {
//初始化vue
this._init(options);
}
2.src\core\instance\init.js -> 初始化文件场斑,主要關(guān)注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) {
//判斷是不是一個組件牵署,是的話執(zhí)行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); //對父組件傳入事件添加監(jiān)聽,初始化事件
initRender(vm); //生命$slots,$createElemnet,渲染相關(guān)的
callHook(vm, "beforeCreate");
initInjections(vm); // resolve injections before data/props,注入數(shù)據(jù)
//數(shù)據(jù)初始化
initState(vm); //初始化props奴迅、data青责、watch、methods取具、computed等屬性脖隶,因此在beforeCreate的鉤子函數(shù)中獲取不到前面的這些定義的屬性和方法
initProvide(vm); // resolve provide after data/props,提供數(shù)據(jù)
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 {
//沒有創(chuàng)建則創(chuàng)建一個空對象暇检,并設(shè)置位響應(yīng)式
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
重名的屬性
- 是否有和
- 校驗通過調(diào)用proxy方法产阱,將所有的屬性都掛載到
_data
屬性上,vue
中提供了使用$xxx
直接訪問vue
實例屬性的方法 - 數(shù)據(jù)響應(yīng)化處理
function initData(vm: Component) {
let data = vm.$options.data;
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
//校驗data必須是一個對象块仆,其他的類似Array构蹬,F(xiàn)unction等也算是對象類型王暗,但是有著更精確的數(shù)據(jù)類型
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代理有什么區(qū)別铐姚?
//proxy代理的是將自定義的data中的所有屬性定義到_data上策肝,而walk中的Object.defineProperty則是針對data的值
}
}
// observe data
observe(data, true /* asRootData */);
}
4.src\core\observer\index.js
關(guān)鍵一步:ob = new Observer(value)
,開始構(gòu)建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中不能直接處理數(shù)組的方法隐绵,需要重寫數(shù)組的方法之众,怎樣重寫數(shù)組的方法?看這里->監(jiān)聽數(shù)組變化的方法 原理是將常用的操作數(shù)組的方法列下來依许,替換原來數(shù)組的方法棺禾,同時也方便擴展。
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
//創(chuàng)建依賴收集的容器
this.dep = new Dep()
this.vmCount = 0
//設(shè)置一個__ob__屬性引用當(dāng)前observer實例
def(value, '__ob__', this)
//判斷類型
if (Array.isArray(value)) {
//如果是數(shù)組峭跳,替換數(shù)組對象的原型
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
//如果數(shù)組里面元素是對象還需要做響應(yīng)化處理
this.observeArray(value)
} else {
//walk方法膘婶,將每個值進行響應(yīng)化處理
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
了,至此響應(yīng)化data的部分就已經(jīng)完成了蛀醉。
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter ? : ? Function,
shallow ? : boolean
) {
//和Key一一對應(yīng)
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
的理解割捅,有不對之處歡迎指證,共同學(xué)習(xí)帚桩!