As we all know, vue’s data listening is implemented through Object.defineProperty, collecting dependencies in getters, triggering changes in setters, When we assign a key defined in data as this.xx = xyz, vUE can detect this behavior and respond. For arrays, vue explicitly states in the document that the array can only be updated by providing some variation methods. It does not support this.xx[n] = xyz, nor does it support this.xx.length = n.

Variation method

Vue provides some array variation methods:

Push () POP () Shift () Unshift () splice() sort() reverse() Updates to the array from these method calls can be detected by the VUE. So how do these variations work? Let’s take a look at the source code.

// src/observer/array.js
import { def } from '.. /util/index'
const arrayProto = Array.prototype
exportconst arrayMethods = Object.create(arrayProto) /** * Intercept mutating methods and emit events */ ; ['push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
.forEach(function(method) { // cache original method const original = arrayProto[method] // def: Def (arrayMethods, method, object.defineProperty)functionmutator (... 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} // observe the added elementif (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})Copy the code

This file exports an arrayMethods file, which inherits Array.prototype and defines its own variant methods to intercept method calls from the original Array, then triggers an update via ob.dep.notify().

// src/observer/index.js
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)) {// Check whether the operating environment supports __proto__ const augment = hasProto? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observeArray(value) }else {
      this.walk(value)
    }
  }
  observeArray (items: Array<any>) {
    for (leti = 0, l = items.length; i < l; i++) { observe(items[i]) } } // ... Hidden some non-article code}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])
  }
}Copy the code

The main purpose of the Observer class is to generate an OB for a value of type Object in data that holds the DEP required to subscribe to Watcher. After creating OB, determine that value is an array, and then check whether the current operating environment supports proTO. If so, The call protoAugment assigns the array’s proto to ArrayMethods, otherwise the call copyAugment defines those variant methods on the array.

Tell me the proto

[[prototype]] is a built-in property for all objects in JS. It points to the constructor’s Prototype property. Most browsers support proto to access it, and this property is as writable as the constructor property. Like the prototype chain we talked about in the interview is actually the performance of this PROTO. When we access an attribute on the object, if the object itself does not have this attribute, we will continue to search for it in its ProTO. If we cannot find it, we will continue. Therefore, in protoAugment above, only target.proto = SRC is required to point the proto of Array to Vue’s own ArrayMethods, which realizes intercepting some attributes and inheriting other prototype methods of the original Array. Very clever.

An update to an element of an array

For this. Xx [n] = xyz, vue provides vue. Set and this.$set. So is there really no way to monitor direct assignment directly?

// No. END

The answer is: Proxy

Proxy implements data binding

Proxy is a new feature in ES6 that, as the name implies, intercepts the default behavior of Proxy objects. Object.defineproperty gives us the ability to modify Object getters, setters, etc. Proxy gives us more options to manipulate (13 interceptions supported). Here’s a simple example:

<ul></ul>
<input type="text">
<button id="add">Add</button>Copy the code
const $ul = document.querySelector('ul')
const $add = document.querySelector('#add')
const $input = document.querySelector('input')
const todos = new Proxy([], {
  set (target, prop, value, receiver) {
    target[prop] = value
    render()
    return true}})function render () {
  $ul.innerHTML = todos.map((todo) => {
    return `<li>
            ${todo}
            <button class="del">x</button>
            <button class="edit">i</button>
            </li>`
  }).join(' ')}$add.addEventListener('click', () => {
  todos.push($input.value)
}, false)
$ul.addEventListener('click', (e) => {
  const el = e.target
  const li = el.parentElement
  const idx = Array.from($ul.children).indexOf(li)
  if (el.classList.contains('del')) {
    todos.splice(idx, 1)
  } else if (el.classList.contains('edit')) {
    todos[idx] = 'I am edited'}},false)Copy the code

This is a simple todo List implementation that you can play with on JsFiddle. Clicking add will push the current input value to Todos, clicking X in Li will delete the current LI value in Todos, and clicking I will modify the current LI text. These functions only need to be realized simply through the set behavior of Proxy array. You can use breakpoint debugging to see what the Proxy does when you execute splice or push on an array, which is fun. So, we can use Proxy to intercept the default behavior of the data, that is to say, we can monitor any changes in the data, and there is no need to use the mutation method.

Vue 3.0 is also said to use Proxy to reconfigure the current data binding implementation. Since Proxy browser support is currently limited to modern browsers, Vue 3.0 will also support only modern browsers. 👻