Vue implements two-way data binding through data hijacking, using Object.defineProperty() to hijack properties. However, the setter in Object.defineProperty() cannot directly monitor changes in array values. To monitor direct access to array indexes, each value needs to be hijacked. However, due to performance considerations, Vue does not adopt this approach, hence the need for special handling of array changes.
Vue achieves two-way data binding through data hijacking, with the core method being Object.defineProperty(), which allows precise addition or modification of object properties. The getter and setter descriptor in the property descriptor is used for hijacking.
var obj ={__x:1};Object.defineProperty(obj,"x",{set:function(x){ console.log("watch");this.__x = x;},get:function(){returnthis.__x;}});obj.x =11;// watchconsole.log(obj.x);// 11
When hijacking is applied to an array and values in the array are directly accessed by index, the setter in Object.defineProperty() cannot directly monitor changes in the array values. Therefore, special handling of array changes is required. While it's possible to loop through each value in the array and use Object.defineProperty() to hijack them by index, Vue's creator, Evan You, explained that due to the performance cost not being proportional to the user experience benefits, this method was not used to make index access responsive. Specific details can be found in the Vue source code on GitHub in issue #8562.
// Hijacking each value by indexvar obj ={__x:[1,2,3]};Object.defineProperty(obj,"x",{set:function(x){ console.log("watch");this.__x = x;},get:function(){returnthis.__x;}});obj.x.forEach((v, i)=>{ Object.defineProperty(obj.x, i,{set:function(x){ console.log("watch"); v = x;},get:function(){return v;}})})obj.x[0]=11;// watchconsole.log(obj.x);// [11, 2, 3]
In Vue, data undergoes special processing, and direct index access modifications also do not trigger the setter, but methods like push have been overridden for this purpose.
<!DOCTYPEhtml><html><head><title>Listening for Array Changes in Vue</title></head><body><divid="app"></div></body><scriptsrc="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script><scripttype="text/javascript">var vm =newVue({el:'#app',data:{msg:[1,2,3]},template:` <div>
<div v-for="item in msg" :key="item">{{item}}</div>
<button @click="subscript">subscript</button>
<button @click="push">push</button>
</div>
`,methods:{subscript:function(){this.msg[0]=11; console.log(this.msg);// [11, 2, 3, __ob__: Observer]},push:function(){this.msg.push(4,5,6); console.log(this.msg);// [1, 2, 3, 4, 5, 6, __ob__: Observer]}}})</script></html>
The specific override solution in Vue is completed through the prototype chain. Specifically, a new object is created using the Object.create method, using the passed object as the __proto__ of the newly created object, and then intercepting operations on the array with specific methods to achieve the monitoring of array operations.
// dev/src/core/observer/array.js/*
* not type checking this file because flow doesn't play well with
* dynamically accessing methods on Array prototype
*/import{ def }from'../util/index'const arrayProto =Array.prototype
exportconst 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 methodconst original = arrayProto[method]def(arrayMethods, method,functionmutator(...args){const result =original.apply(this, args)const ob =this.__ob__
let inserted
switch(method){case'push':case'unshift': inserted = args
breakcase'splice': inserted = args.slice(2)break}if(inserted) ob.observeArray(inserted)// notify change ob.dep.notify()return result
})})
The Object.defineProperty() method cannot intercept changes to the values accessed through array index, so it is necessary to avoid this kind of access. You can use the method of modifying the value and then assigning it, or you can use some methods in the array to form a new array. Methods that do not change the original array and return a new array include slice, concat, and the spread operator. Of course, you can also use the map method to generate a new array. In addition, in Vue, because the splice method has been rewritten, you can also use the splice method to update the view.
Vue 3.0 uses Proxy to achieve data interception. Object.defineProperty can only monitor properties, while Proxy can monitor the entire object. By calling new Proxy(), a proxy can be created to replace another object, which is called the target. This proxy virtually represents the target object, so the proxy and the target object can be treated as the same object on the surface. The proxy allows intercepting low-level operations on the target object, which is originally an internal ability of the Js engine. The interception behavior uses a function that can respond to specific operations. By using Proxy to proxy an object, we will get an object that is almost identical to the object being proxied and can completely monitor this object from a low level.