1. How Vue2 implements data responsiveness
Vue
The way to achieve responsive changes is through data hijacking and publish-subscribe patterns.
-
Data hijacking: Hijacking the properties of a data object
Vue
by using methods so that it can trigger corresponding operations when the property value changes.Object.defineProperty()
-
Publish-Subscribe Pattern:
Vue
Use the Publish-Subscribe pattern to listen for data changes and notify relevant subscribers to update the view when the changes occur. When the data changes, the corresponding setter method will be triggered, and then all subscribers will be notified for updates.
The specific implementation steps are as follows:
-
When initializing
Vue
the instance, by traversing the data object, useObject.defineProperty()
methods to convert each attribute intogetter
a sumsetter
. -
In
getter
the method, add the SubscriberWatcher
object to the dependency list of the current property. -
In
setter
the method, when the data changes, the update operation of all subscribers of the property will be triggered, that is,Watcher
the object'supdate
method is added to the asynchronous update queue. -
When all synchronization tasks are completed, the asynchronous update queue will execute the methods
Watcher
of each object in sequenceupdate
and update the view.
Through the combination of data hijacking and publish-subscribe mode, Vue
the view can be updated in time when the data changes, achieving responsive changes.
export function defineReactive ( // 定义响应式数据
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// 如果不可以配置直接return
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]
}
// 对数据进行观测
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) {
// 让对象本身进行依赖收集
childOb.dep.depend() // {a:1} => {} 外层对象
if (Array.isArray(value)) {
// 如果是数组 {arr:[[],[]]}
vm.arr取值只会让arr属性和外层数组进行收集
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()
}
})
}
2. How to detect changes in arrays in vue2
For performance reasons, the array does not use defineProperty to intercept each item of the array. Instead, it chooses to rewrite
the array (push, shift, pop, splice, unshift, sort, reverse) methods.
If the array is an object data type, it will also be hijacked recursively.
The index and length changes of the array cannot be monitored
Detecting changes in the array is achieved by overriding the relevant methods on the array prototype. Specific steps are as follows:
-
First, Vue will determine whether the current browser supports native array methods. If supported, directly override the method on the array prototype and add the corresponding mutation method to the overridden method. If it is not supported, create a new object and use Object.defineProperty to intercept the array mutation method.
-
When overriding array methods, Vue will first cache the native array methods, such as
Array.prototype.push
,Array.prototype.pop
etc. Then, in the overridden method, the cached native method is first called, and then different operations are performed according to different mutation method types, such as adding responsive elements, triggering dependency updates, etc. -
Vue will also determine whether
__proto__
the attribute is supported. If it is supported, it will directly point the array's prototype to the overridden prototype object so that the array's prototype chain can normally find the overridden method. If it is not supported, traverse the overridden prototype object and copy the methods to the array instance.
The sample code is as follows:
// 是否支持原生数组方法
const hasProto = '__proto__' in {
};
// 缓存原生数组方法
const arrayProto = Array.prototype;
// 创建重写的原型对象
const arrayMethods = Object.create(arrayProto);
// 定义重写的原型方法
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(function (method) {
// 缓存原生数组方法
const original = arrayProto[method];
// 重写原型方法
def(arrayMethods, method, function mutator(...args) {
// 调用原生数组方法
const result = original.apply(this, args);
// 获取当前数组的__ob__属性,即Observer实例
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);
// 触发依赖更新
ob.dep.notify();
return result;
});
});
// 将重写的原型对象设置到数组的原型上
const arrayKeys = Object.getOwnPropertyNames(arrayMethods);
if (hasProto) {
protoAugment(target, arrayMethods);
} else {
copyAugment(target, arrayMethods, arrayKeys);
}
Through the above code, Vue realizes the detection of array changes and can automatically track the operations of the array to achieve responsive updates.