this.$set method = = Vue Set vue.set is defined on the constructor, and the former is defined on the prototype object
/** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */ export function set (target: Array<any> | Object, key: any, val: any): any { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ) return val } if (!ob) { target[key] = val return val } defineReactive(ob.value, key, val) ob.dep.notify() return val }
if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) }
First, the set method will judge whether the target passed in is null, undefined or original type (string, number, boolean, symbol). If so, throw a warning.
if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val }
Determine whether the target is an array and whether the key value is a legal key. The following is how to check the Index:
function isValidArrayIndex(val) { var n = parseFloat(String(val)); return n >= 0 && Math.floor(n) === n && isFinite(val); }
Then, go back to the source code above. The second line sets target.length to target The maximum value of length and key. This is to prevent errors from being reported in some cases. For example, the set key value is greater than the length of the array.
new Vue({
el: '#root',
data: {
list: [1, 2]
}
})
Vue.set(vm.list, 10, 'error');
The third line of the copied code is a splice method, replacing the value of the key position with val. Note: the new attempt will be re rendered when splice is called. Because this is a special splice method, Vue rewrites it. See the following source code:
var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); var methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', ]; methodsToPatch.forEach(function(method) { // Method to cache the original array var original = arrayProto[method]; def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args); ...Omit some source code // Send update notification ob.dep.notify(); return result; }); });
if (key in target && !(key in Object.prototype)) { target[key] = val; return val; }
Copy the code above means that if the key already exists on the target Object and the key is not an attribute on the Object prototype Object. This indicates that the key attribute is already responsive, so val is directly assigned to this attribute.
var ob = target.__ob__; if (target._isVue || (ob && ob.vmCount)) { warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ); return val; }
Copy code__ ob__ Refers to the Observer object. vmCount is used to indicate whether the current object is a root level attribute_ isVue is used to indicate whether it is a Vue instance. As mentioned above, target cannot be a root level attribute or Vue instance.
if (!ob) { target[key] = val; return val; } defineReactive$$1(ob.value, key, val); ob.dep.notify(); return val;
Copying lines 1 to 4 of the code means that if the Observer object does not exist on the target (this means that the target is just a normal object, not a responsive data), it is directly assigned to the key attribute.
Line 5, ob Value is actually the target, but it is the tracked dependent object in $data on the Vue instance. Then add the key attribute to the observed object. Let the key attribute also be changed to getter/setter.
In line 6, let dep notify all watcher s to re render the components.
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 def(value, '__ob__', this) if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { 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]) } } } // helpers /** * Augment a target Object or Array by intercepting * the prototype chain using __proto__ */ function protoAugment (target, src: Object) { /* eslint-disable no-proto */ target.__proto__ = src /* eslint-enable no-proto */ } /** * Augment a target Object or Array by defining * hidden properties. */ /* istanbul ignore next */ 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]) } }
export 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] } 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() 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() } }) }