As many of you who have memorized the interview questions know, Array response listening in Vue is achieved by rewriting the prototype method on the Array class to achieve bidirectional binding. So how does Vue do method interception and response listening?
1. Hijack the array prototype method
Vue uses override prototype interception because set and GET cannot respond to array changes.
- Start by creating an empty object whose prototype points to Array’s prototype
let createArr = Object.create(Array.prototype);
Copy the code
- CreateArr is used to iterate over methods that will change the contents of the array and export the intercepted createArr for easy use
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'sort', 'reverse']. ForEach (function(method){const Func = createArr[method]; Object.defineProperty(createArr,method,{ value:function(... Return Func. Apply (this,args); }}); })Copy the code
- Hang the processed interceptor object on the array that requires responsive listening
export class Observer { constructor(value){ this.value = value; if(Array.isArray(value)){ value.__proto__ = createArr; }}}Copy the code
Note: the __proto__ attribute is not accessible in all browsers, so in browsers that do not support access; Vue does this by setting these interceptors directly on the array as private properties:
/ / here for the keys on the prototype array const createArrkeys = Object. GetOwnPropertyNames (createArr); for(let i = 0; i<createArrkeys.length; i++){ const key = createArrkeys[i]; value[key] = createArr[key]; }Copy the code
3. Collect array dependencies
I am inPrevious articleSo when we use arrays in different places, we still collect them in the GET function, but we store dependencies in a different way than objects
export class Observer { constructor(value){ this.value = value; this.dep = new Dep(); // Dep is an array class in vUE that collects dependencies}}Copy the code
So why add a DEP to the Observer detection class as a dependency collector? Because our action of triggering the modification of the array is encapsulated in the interceptor, and the action of collecting dependencies is encapsulated in the GET function, we need a middleman so that both can get the DEP. This gives us access to the DEP when we trigger changes and collect dependencies.
Therefore, we need to rewrite the defineData and Observer classes
Export function dep(data,key,val,enumerable){object.defineProperty (data,key,{value:val, enumerable:!! enumerable, writable:true, configurable:true }) } export class Observer { constructor(value){ this.value = value; this.dep = new Dep(); Def (value,'_ob_',this); If (array.isarray (value)){this.observearray (value)}} observeArray(value){for(let I =0; i<value.length; I ++){observe(value[I])}}Copy the code
Function observe(val){if(val){function observe(val){function observe(val){if(val); isObejct(val)){ return; } if(hasOwn(val,'_ob_') && val._ob_ instanceof Observer){return val._ob_; } return new Observer(val); } function defineData(data,key,val){ let childOb = observe(val); let dep = new Dep(); Object.defineProperty(data,key,{ get:function(){ dep.depend(); If (childOb){childob.dep.depend (); } return val; }})}Copy the code
Triggers the collected array dependencies
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'sort', 'reverse']. ForEach (function(method){const Func = createArr[method]; Object.defineProperty(createArr,method,{ value:function(... Args){// Handle intercepting code const ob = this._ob_; ob.dep.notify(); // Trigger the update function return func. apply(this,args); }}); })Copy the code
Our detection of arrays is specific to the changes in the index group, that is, not only the array element updates need to be detected, but also new elements need to be detected. It is not difficult to detect the new element. The general idea is to interpret the new element’s interceptor methods :push,unshift,splice, obtain the new element, and finally instantiate the new element in the observe method
Object.defineProperty(createArr,method,{ value:function(... Args){// Handle intercepting code const ob = this._ob_; let newElements; switch(method){ case 'push': case 'unshift': newElements = args; case 'splice': newElements = args.slice(2); break; } the if (newElements) {/ / observerArray is a simple for loop, iterate through group to observe each call to instantiate the ob. ObserveArray (newElements); } ob.dep.notify(); // Trigger the update function return func. apply(this,args); }});Copy the code
Note: Because Vue2 detects array changes by intercepting stereotypes, some special array operations cannot be intercepted. For example :this.arr. Length = 0 or index access this.arr[0] = 10
The flow chart is as follows
Graph TD hijacks the array type Data in Data -- creates a createArr interceptor that intercepts operations on arrays -- assigns the interceptor to the _proto_ property of the array -- stores dependencies in the Observer for both responsiveness and interception Dependency collection is performed in GET, and dependency trigger update notification is performed in intercept handlers
- Source: Vue.js In Plain English