Initialize the

When new Vue() creates the Vue sample, the _init() method is called, and within this method, the initState method is called for initialization. This is where the initData method that initializes data is called.

function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? 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 const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length 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)) { proxy(vm, `_data`, key) } } // observe data observe(data, true /* asRootData */) }Copy the code

First, obtain the value of data in the parameter. In the previous parameter combination, data has been merged as function, so here we make another judgment. If function type is used, call getData() method to obtain data.

export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}
Copy the code

Here we call the data method with call and return the value. The reason why we try catch is because it’s user-written code to prevent errors. Then verify the data:

  1. If data is not an object type, an exception is raised in non-production mode.
  2. All data attributes are iterated, and exceptions are reported if they exist in methods or Prop, respectively. Their definition priority is Prop > Data > Methods.

If nothing is wrong and the key is not reserved, the proxy function is called to assign a copy of the data attribute to vm._data.

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
Copy the code

So, we usually use this.a from this._data.a. (Reasons to be answered)

Finally call the observe(data, true /* asRootData */) method.

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 }Copy the code

The first argument is the data object to observe, and the second argument is whether it is root or not, passing true. Check:

  1. If the data to be observed is not an object or VNode instance, return it directly.
  2. If data has an __ob__ and is an instance of an Observer, the data has been observed and the observed value is returned.
  3. A. ShouldobServe Is true and.
export let shouldObserve: boolean = true

export function toggleObserving (value: boolean) {
  shouldObserve = value
}
Copy the code

B. If not SSR, and. ! IsServerRendering () C. If is an array or object, and. (Array.isArray(value) || isPlainObject(value)) D. Object is extensible, and. A normal object is extensible by default. There are three ways to make an object unextensible:

Object.preventextensions (), Object.freeze() and Object.seal(). Object.isExtensible(value)Copy the code

F. The current object is not a VUE instance. ! value._isVue

If the above conditions are met, create an Observer instance

ob = new Observer(value)
Copy the code
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() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } } /** * Walk through each property 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]) } } }Copy the code

The constructor is used to assign the value to the Obsever instance, and then a new Dep() is used to create a Dep instance of data. This Dep instance is not for a single data attribute, but for data.

Set vmCount of the instantiated object to zero.

The def(value, ‘ob’, this) method is then executed.

export function def (obj: Object, key: string, val: any, enumerable? : boolean) { Object.defineProperty(obj, key, { value: val, enumerable: !! enumerable, writable: true, configurable: true }) }Copy the code

The first argument is data, and the three arguments are ObserveInstance. That is, add an unenumerable “BO” property to the data object via the accessor property method, and the value of the property is the created ObserVeinstance. After execution, the data object looks like this.

Const data = {a: 1, // __ob__ is an unenumerable attribute __ob__: {value: data, // The value attribute points to the data object itself, which is a circular reference to dep: // new dep () vmCount: 0}}Copy the code

Finally, we can determine whether the data object is an array or a pure object. In this case, we are looking at the root object data, so it is a pure group, but if we are looking at an array object inside the object, then we are going to branch off the array.

Let’s look at processing pure objects first.

 if (Array.isArray(value)) {
   if (hasProto) {
     protoAugment(value, arrayMethods)
   } else {
     copyAugment(value, arrayMethods, arrayKeys)
   }
   this.observeArray(value)
 } else {
   this.walk(value)
 }
Copy the code
 walk (obj: Object) {
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i])
  }
}
Copy the code

For pure objects, the walk function is called. Each attribute in the data object is iterated over, and the defineReactive method is called, passing in the data object and attribute name as parameters.

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() } }) }Copy the code

First, create a Dep instance, which is the container that collects observers for each data attribute. It then returns the property descriptor of the property via the getOwnPropertyDescriptor method. If the 64x is set to false, it disables the configured information and returns false.

If there is a get method, the property will not be examined in depth, because this is a user defined, can define any logic, to avoid conflicts, do not monitor. But if there is a set method, it is monitored with or without a GET method. Because the defineReactive method redefines the property’s GET and set methods, the new value is observed again in the SET method, which is contradictory. So if you have set, you’re going to do depth observations.

Code for deep observation:

let childOb = ! shallow && observe(val)Copy the code

What does the data look like after calling the observe method? Such as:

const data = {
  a: {
    b: 1
  }
}

observe(data)
Copy the code
Const data = {// property A refers to dep and childOb through the closure via setter/getter a: {// Property B refers to dep and childOb through the closure via setter/getter b: 1 __ob__: {a, dep, vmCount} } __ob__: {data, dep, vmCount} }Copy the code

The next step is to set the get and set methods of the accessor property on the property.

 Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    },
    set: function reactiveSetter (newVal) {
  })
Copy the code

The implementation is documented in an example.

perform

Examples are as follows:

<div>{{ a }}</div>

data () {
  return {
    a: 1
  }
}
Copy the code

When the template is compiled, the value of A is taken, and a’s GET method is called.

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] }Copy the code
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
 }
Copy the code

If A has a getter method, it calls the getter method; if not, it returns the argument val passed to defineReactive at initialization. Here is a typical closure application. Getters and val are both declared and assigned at function definition time. They are then retrieved from the current execution environment when the GET method is called.

Then check whether dep.target has a value. What about dep. Target?

We know that after the data is initialized, that is, after the get and set methods of data are initialized, the compiled rendering function is then evaluated and mounted by creating a Watcher instance. The function that evaluates it is watcher.get()

PushTarget (this) let value const vm = this.vm try {// Call the getter method to get the value of a, This will trigger the get method of a, so the dep. target is the render function of a. value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value }Copy the code

The watcher. Get () method assigns the current watcher instance to dep.target, so that when the watcher. Getter method is invoked to get the value of A, the value of dep.target is the current watcher.

Then, execute

dep.depend()
Copy the code

Again, the DEP is acquired as a closure, which is essentially the collector of the observer object for the current property. Here’s how.

// dep.js
depend () {
  if (Dep.target) {
    Dep.target.addDep(this)
  }
}
Copy the code

First check if there are any watchers to collect. If there are, call dep.target.adddep (this), which is watcher.adddep ().

// watcher.js constructor ( vm: Component, expOrFn: string | Function, cb: Function, options? :? Object, isRenderWatcher? : Boolean) {// omit... This.deps = [] this.newdeps = [] this.depids = new Set() this.newDepids = new Set() }Copy the code
// watcher.js addDep (dep: Dep) { const id = dep.id if (! this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (! this.depIds.has(id)) { dep.addSub(this) } } }Copy the code

Instead of putting watcher directly into the observer collector, addDep() does some pre-placement optimization.

We knew this was going to happen,

<div>{{ a }}--{{ a }}</div>

data () {
  return {
    a: 1
  }
}
Copy the code

The template gets the value of A twice, calls the get method of A twice, and triggers dep.depend() twice

if (! this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) }Copy the code

Determines if the property has already collected the current observer object. If so, the newDepIds will contain the ID of the DEP and will not be collected again. If there is no collection, the following logic continues:

const id = dep.id if (! this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (! this.depIds.has(id)) { dep.addSub(this) } }Copy the code

Determine if the deP already exists in depIds. If it does not, the current Watcher will eventually be collected into the attribution-dependent container DEP. So what are depIds?

As mentioned above, newDepIds is used to avoid repeated collection when the watcher.get() method is called. DepIds are used to avoid double collection when watcher.get() is called at different times. For example, if the data has changed and the page is rendered again, the get method is triggered, and dep.depend() is triggered. Since watcher was added to the deP of the variable attribute when the page was initially rendered, it is available at depIds, so there is no need to call dep.addSub(this) to collect it again.

When did you put the DEP id into the depIds? You need to continue with the watcher.get() method.

 get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    value = this.getter.call(vm, vm)
  } catch (e) {
    if (this.user) {
      handleError(e, vm, `getter for watcher "${this.expression}"`)
    } else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
  }
  return value
}
Copy the code

Value = this.getter.call(vm, vm) finally

this.cleanupDeps()
Copy the code
cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (! this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 }Copy the code

Get () puts the deP in deps, the ID of the DEP in depIds, and emptying newDeps and newDepIds. [To be added]

At this point, the current property A has collected the watcher that depends on its data changes. When we modify the value of A, we fire the set method of A.

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() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = ! shallow && observe(newVal) dep.notify() }Copy the code

First of all, again using the closure, we get the value before the attribute A, and then compare the value to be assigned to a with the original value. There is a judgment about consistency:

newVal ! == newVal && value ! == valueCopy the code

We know NaN! == NaN, so this is saying that if the new value is NaN and the old value is NaN, it proves that there is no change, so we don’t need to update it, we just return it.

Further down, observe is called again on the new value to ensure that the new object is also detected.

And then execute

dep.notify()
Copy the code
notify () {
  // stabilize the subscriber list first
  const subs = this.subs.slice()
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}
Copy the code

This iterates through the collected Watcher, updating each one with the Update method.

// watcher.js
update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)
  }
}
Copy the code

The second section is the watcher that updates the page synchronously. The third section is the watcher that updates the page asynchronously.

export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (! flushing) { queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush if (! waiting) { waiting = true nextTick(flushSchedulerQueue) } } }Copy the code

Observations of arrays

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() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : CopyAugment augment(value, arrayMethods, arrayKeys) this.observearray (value)} else {this.walk(value)}} // omit... }Copy the code

The constructor has logic to determine if the value is an array. If so, execute the following method:

const augment = hasProto
      ? protoAugment
      : copyAugment
    augment(value, arrayMethods, arrayKeys)
Copy the code
// can we use __proto__?
export const hasProto = '__proto__' in {}
Copy the code

HasProto determines whether the browser supports __proto__ and calls the protoAugment method if it does, and the copyAugment method if it does not.

function protoAugment (target, src: Object, keys: any) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/* 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])
  }
}
Copy the code

You put the second parameter on the value prototype. What about the second parameter, arrayMethods?

// observer/index.js
import { arrayMethods } from './array'
Copy the code

In the index.js file in the Observer folder, the following is the complete code for the file:

import { def } from '.. /util/index' const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) 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, 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 })})Copy the code

ArrayProto is an array prototype. ArrayMethods is an array prototype that creates a new object. All of the method names in the methodsToPatch array are method names that change the values of the original array.

The next logical step is to rework the methods in methodsToPatch and add a reactive expression in addition to implementing the original method.

// cache original method
const original = arrayProto[method]
Copy the code

Cache the raw method of the array,

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 })Copy the code

Intercepts each element in the array using def, assigns the corresponding function to the element, and then adds it to arrayMethods. What does the corresponding function do?

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 }Copy the code

The original array method origin.apply (this, args) is executed and the __ob__ object defined in the array is fetched. Because these methods involve modifying the original array, they are simply adding, deleting, and changing. Eventually we just need to observe the new properties and update the page.

switch (method) {
  case 'push':
  case 'unshift':
    inserted = args
    break
  case 'splice':
    inserted = args.slice(2)
    break
}
if (inserted) ob.observeArray(inserted)
Copy the code

The push and unshift methods both add elements, and the arguments are the elements to be added. Splice is the replacement method, and the third parameter is the final element to be added. The ob.observeArray method is then called to monitor the new element if it does exist.

// observer/index.js
observeArray (items: Array<any>) {
  for (let i = 0, l = items.length; i < l; i++) {
    observe(items[i])
  }
}
Copy the code
// observer.js export function observe (value: any, asRootData: ? boolean): Observer | void { if (! IsObject (value) | | value instanceof VNode) {return} / / ellipsis... }Copy the code
// shared/util.js export function isObject (obj: mixed): boolean %checks { return obj ! == null && typeof obj === 'object' }Copy the code

Observe observe observe observe observe observe observe observe observe observe observe observe observe only if the new element typeof is object (array and object typeof are object).

Firing a method in the methodsToPatch array will definitely change the array, so manually call notify to update the page

ob.dep.notify()
Copy the code

Return the result at last

return result	
Copy the code

The whole process is to add logic to the old array methods to listen for new elements and update the page.

And then back again:

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() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) { const augment = hasProto ? protoAugment : CopyAugment augment(value, arrayMethods, arrayKeys) this.observearray (value)} else {this.walk(value)}} // omit... }Copy the code

After augment method is executed, call this.observeArray(value) method and call observer method for each element of array to listen. The aim is to prevent:

Const ins = new Vue({data: {arr: [1, 2]}}) ins.arr.push(3) // Can trigger a responseCopy the code
Const ins = new Vue({data: {arr: [[1, 2]]}}) ins.arr. Push (1) // Can trigger response ins.arr[0]. Push (3) // can't trigger responseCopy the code

At this point, in initialization, the additional processing logic for the array is complete. But there is also some logic in the get method that triggers the property:

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
},
Copy the code

Childob.dep.depend (); childob.dep.depend ();

 if (Array.isArray(value)) {
    dependArray(value)
  }
Copy the code
// observer/index.js
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}
Copy the code
// dep.js
depend () {
  if (Dep.target) {
    Dep.target.addDep(this)
  }
}
Copy the code

In the case of an array, each element in the array is iterated over, followed by a manual collection of dependencies. Because there are no get and set methods for array elements, collection must be done manually.

Vue.$set(target, key, val)

$set(target, key, val)

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 }Copy the code

This is the full view of the vue.$set() method.

if (process.env.NODE_ENV ! == 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) }Copy the code
// shared/util.js
export function isUndef (v: any): boolean %checks {
  return v === undefined || v === null
}
Copy the code
// shared/util.js
export function isPrimitive (value: any): boolean %checks {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    // $flow-disable-line
    typeof value === 'symbol' ||
    typeof value === 'boolean'
  )
}
Copy the code

If the target object is of a basic type, it will be prompted.

if (Array.isArray(target) && isValidArrayIndex(key)) {
  target.length = Math.max(target.length, key)
  target.splice(key, 1, val)
  return val
}
Copy the code
export function isValidArrayIndex (val: any): boolean {
  const n = parseFloat(String(val))
  return n >= 0 && Math.floor(n) === n && isFinite(val)
}
Copy the code

Check that the target object is an array and the key to be set is a significant digit, which is 1 and an integer greater than or equal to 0.

2. On condition one, the integer cannot be infinite. Math.floor(n) === n guarantees that the index is an integer greater than or equal to 0, while isFinite(val) guarantees that the value isFinite.

If you want to change the length of the target array, take the larger value of the current length and key. The element is then replaced by the splice method. The splice method is already handled, so it will be listened on. Just return the result.

if (key in target && ! (key in Object.prototype)) { target[key] = val return val }Copy the code

If the value you want to set is in the target object, you can just assign it, because the property you want to set is already being listened for. The result is then returned directly.

If you follow the logic below, prove that the property is newly added to the target object.

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 valCopy the code

Get the __ob__ of the target object, and if not, prove that the object is responsive. The defineReactive method is called if it exists to add get and set methods for the newly added attributes. Ob.dep.notify () is then called to update the collected dependencies. Note that the dependencies are collected in __ob__, so when are they collected?

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
},
Copy the code
childOb.dep.depend()
Copy the code

Since poxy does not support it, the new properties cannot set the set and get methods. However, since the target object is modified, the dependency and its data should be updated. Use __ob__.

Ob.dep.notify () is executed after calling defineReactive, which updates the dependencies collected in OB.dep. But when were dependencies collected in OB.DEP? Get childob.dep.depend () manually adds the dependency to ob.dep.

Vue.$del(target, key)

export function del (target: Array<any> | Object, key: any) { if (process.env.NODE_ENV ! == 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) } if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = (target: any).__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 } if (! hasOwn(target, key)) { return } delete target[key] if (! ob) { return } ob.dep.notify() }Copy the code

The logic for deleting is much simpler, so if it’s an array, we call splice to delete it, and splice, because it’s overwritten, will trigger the page update and then return. If it is an object, call the delete method to delete it. If it is a responsive object, call ob.dep.notify() manually to update the dependency.

This is how the data hijacking plus observer mode of the data attribute is handled.