# 数据初始化
数据初始化是initState
里实现,涉及数据响应相关的内容
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function' //判断data是否为function,此时data 变量已经不是函数了,而是最终的数据对象
? 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 instance
//防止props 和methods 名字和data的key相同
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
//props优先级 > data优先级 > methods优先级
while (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)) {//key 是否是保留键 $_开头
//实例对象 vm 上定义与 data 数据字段同名的访问器属性,并且这些属性代理的值是 vm._data 上对应属性的值
proxy(vm, `_data`, key)
}
}
// observe data 响应系统的开始
observe(data, true /* asRootData */)
}
//通过调用 data 选项从而获取数据对象
function getData (data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget() //防止使用 props 数据初始化 data 数据时收集冗余的依赖
try {
return data.call(vm, vm)
} catch (e) {
handleError(e, vm, `data()`)
return {}
} finally {
popTarget()
}
}
# observe 工厂函数
function observe (value: any, asRootData: ?boolean): Observer | void { //asRootData根级数据
if (!isObject(value) || value instanceof VNode) {//不是对象或者是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) &&//必须是可扩展的
//Object.preventExtensions()、Object.freeze() 以及 Object.seal()可以使对象不可扩展
!value._isVue//避免Vue 实例对象被观测
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
# Observer 构造函数
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()//收集依赖,dependence
this.vmCount = 0
//为数据对象定义了一个 __ob__ 属性
def(value, '__ob__', this)
if (Array.isArray(value)) {
//无论是 protoAugment 函数还是 copyAugment 函数,他们的目的只有一个:把数组实例与代理原型或与代理原型中定义的函数联系起来,从而拦截数组变异方法
const augment = hasProto//是否有__proto__ ie11有
? protoAugment
: copyAugment
function protoAugment (target, src: Object, keys: any) {//数组实例的原型指向代理原型
target.__proto__ = src
}
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])//使用 def 函数在数组实例上定义与数组变异方法同名的且不可枚举的函数,这样就实现了拦截操作
}
}
//value 数组实例本身
//arrayMethods 代理原型
//const arrayKeys = Object.getOwnPropertyNames(arrayMethods) arrayMethods 对象上的 key,所有我们要拦截的数组变异方法的名字
//arrayKeys = ['push','pop','shift','unshift','splice','sort', 'reverse']
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
// 省略...
}
}
# defineReactive 函数
//将数据对象的数据属性转换为访问器属性,数据对象的属性设置一对 getter/setter
function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)//属性描述对象
if (property && property.configurable === false) { //以被改变或者属性可被删除
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
//即 每一个数据字段都通过闭包引用着属于自己的 dep 常量
let childOb = !shallow && observe(val)//shallow 深度观测
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
//正确地返回属性值以及收集依赖
get: function reactiveGetter () {
//getter 常量中保存的是属性原型的 get 函数,如果 getter 存在那么直接调用该函数,
//并以该函数的返回值作为属性的值,保证属性的原有读取操作正常运作
const value = getter ? getter.call(obj) : val
if (Dep.target) {
// 这里闭包引用了上面的 dep 常量
dep.depend()//收集依赖
if (childOb) {
childOb.dep.depend()//
if (Array.isArray(value)) {
dependArray(value)//数组每个元素的依赖收集
function dependArray (value: Array<any>) {
for (let e, i = 0, l = value.length; i < l; i++) {
e = value[i]
//该元素的值拥有 __ob__ 对象和 __ob__.dep 对象,那说明该元素也是一个对象或数组,此时只需要手动执行 __ob__.dep.depend() 即可达到收集依赖的目的
e && e.__ob__ && e.__ob__.dep.depend()
//数组的元素仍然是一个数组,那么需要递归调用 dependArray 继续收集依赖
if (Array.isArray(e)) {
dependArray(e)
}
}
}
}
}
}
return value
},
//set 正确地为属性设置新值,第二是能够触发相应的依赖
set: function reactiveSetter (newVal) {
//需要拿到原有的值与新的值作比较,并且只有在原有值与新设置的值不相等的情况下才需要触发依赖和重新设置属性值
const value = getter ? getter.call(obj) : val
//NaN===NaN false
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()//用来打印辅助信息
}
if (setter) {//原有的set存在
setter.call(obj, newVal)
} else {
val = newVal
}
//在 !shallow 为真,使用新的观测对象重写 childOb 的值
childOb = !shallow && observe(newVal)
// 这里闭包引用了上面的 dep 常量
dep.notify()//触发依赖
}
})
}
# 拦截数组变异方法
import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)//arrayMethods 对象的原型是真正的数组构造函数的原型
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
//使用 def 函数在 arrayMethods 对象上定义与数组变异方法同名的函数,从而做到拦截的目的
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args) //原本变异方法的返回值
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2) //第三个参数开始到最后一个参数都是数组的新增元素
break
}
if (inserted) ob.observeArray(inserted)
// notify change 该数组的所有依赖(观察者)全部拿出来执行
ob.dep.notify()
return result //保证了拦截函数的功能与数组原本变异方法的功能是一致
})
})
function observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
# Vue.$set和Vue.$delete
//对象 key,value
function set (target: Array<any> | Object, key: any, val: any): any {
//isUndef 函数用来判断一个值是否是 undefined 或 null,如果是则返回 true
//isPrimitive 函数用来判断一个值是否是原始类型值,如果是则返回 true
//理论上只能为对象(或数组)添加属性(或元素)
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${target}`)
}
//isValidArrayIndex 检验是否是有效的数组索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
//将数组的长度修改为 target.length 和 key 中的较大者
target.length = Math.max(target.length, key)
//指定位置元素的值替换为新值,splice 方法本身是能够触发响应的
target.splice(key, 1, val)
return val
}
//key 在 target 对象上,或在 target 的原型链上,同时必须不能在 Object.prototype
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
//数据对象 __ob__ 属性的引用
const ob = target.__ob__
//vue实例 当使用 Vue.set/$set 函数为根数据对象添加属性时,是不被允许的,这样做是永远触发不了依赖的。原因就是根数据对象的 Observer 实例收集不到依赖(观察者),
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn('...')
return val
}
if (!ob) {//不存在ob,赋值
target[key] = val
return val
}
//设置属性值
defineReactive(ob.value, key, val)
//触发响应
ob.dep.notify()
return val
}
//target key
export function del (target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&
//target 是否是 undefined 或 null 或者是原始类型值
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot delete reactive property on undefined, null, or primitive value: ${target}`)
}
//去删除一个数组的索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
//同样不能使用 Vue.delete/$delete 删除 Vue 实例对象或根数据的属性
const ob = target.__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
}
//检测 key 是否是 target 对象自身拥有的属性
if (!hasOwn(target, key)) {
return
}
delete target[key]
if (!ob) {
return
}
//触发响应
ob.dep.notify()
}