Pay attention to the public number “kite”, reply to “information” to obtain 500G information (all “arms”), there are professional communication groups waiting for you to come together. (ha ha)

In Vue, one of the most core knowledge points is the principle of data responsiveness. The principle of data responsiveness can be summed up into two parts: detecting data changes and relying on collection. If you understand these two points, you can understand the essence of the principle of data responsiveness.

1. Detect data changes

Being able to hear data changes is a prerequisite for the data responsiveness principle because it is based on listening for data changes and then triggering a series of update operations. This introduction to the principle of data responsiveness will be based on vue2.x, which mainly uses Object.defineProperty() to turn data into detectable data.

1.1 Non-array objects

Let’s start with an example of a non-array object

const obj = {
    a: {
        m: {
            n: 5
        }
    },
    b: 10
};
Copy the code

Observing the above objects, it can be found that there is inclusion relation (that is, one object may contain another object), so it is natural to realize it by recursion. In Vue, three modules are introduced to realize this logic in order to ensure high readability of the code: Observe, Observer, defineReactive, and their invocation relationships are as follows:

1.1.1 observe

This function is the entry file of frame listening data changes. On the one hand, it triggers the ability of frame listening object data changes by calling this function. On the other hand, it defines the termination condition for when it recurses to the innermost layer.

import Observer from './Observer'; Export default function (value) {// If the value is not an object, do nothing. == 'object') { return; } // Observer instance let ob; // __ob__ is an attribute on value whose value is the corresponding Observer instance. If (typeof value.__ob__! == 'undefined') { ob = value.__ob__; Ob = new Observer(value);} else {ob = new Observer(value); } return ob; }Copy the code

1.1.2 the Observer

This function has two main purposes. One is to mount the instance to the __ob__ property of the object’s value (observe uses this property to determine whether it is in the frame state). The other is to iterate over all the properties on the object and then make them all frameable (by calling defineReactive).

Export default class Observer {constructor(value) {constructor(value) {def(value, '__ob__', this, false); // Check if it is an array or an object. Array.isarray (value)) {// If it is an object, it will traverse and change the property on it to this.walk(value) in response; }} // Walk (value) {for (let key in value) {defineReactive(value, key); }}}Copy the code

1.1.3 defineReactive

DefineProperty encapsulates object.defineProperty into a function. The reason for this step is that object.defineProperty needs a temporary variable to store the value before it changes when it sets the set property. This eliminates the need to set temporary variables outside the function.

export default function defineReactive(data, key, val) { if (arguments.length === 2) { val = data[key]; } // Let childOb = observe(val); DefineProperty (data, key, {// Enumerable: true, // configurable: True, // getter get() {console.log(' access ${key} property '); return val; }, // setter set(newValue) {console.log(' change ${key} property to ${newValue} '); if (val === newValue) { return; } val = newValue; Observe childOb = observe(newValue); // Observe childOb = observe(newValue); }}); }Copy the code

1.2 an array

Object.defineproperty can’t listen for changes inside an array directly. Vue takes the form of modifying array methods (push, POP, Shift, unshift, splice, sort, reverse) to make newly added items responsive while retaining their original functionality.

// array.js file // Get array prototype const arrayPrototype = array.prototype; Export const arrayMethods = object.create (arrayPrototype); // 7 array methods to be overwritten const methodsNeedChange = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']; MethodsNeedChange. ForEach (methodName = > {/ / backup of the original method is const what = arrayMethods [methodName]; Def (arrayMethods, methodName, function () {const result = original. Apply (this, arguments); // Convert an array-like object to an array const args = [...arguments]; Const ob = this.__ob__; const ob = this.__ob__; // Push /unshift/splice will insert a new item, and you need to change the inserted item to observe let inserted = []; switch (methodName) { case 'push': case 'unshift': { inserted = args; break; } case 'splice': { inserted = args.slice(2); break; Insert.length) {ob.observearray (inserted); } ob.dep.notify(); return result; }, false); });Copy the code

In addition to modifying its original array methods, the Observer functions will also add processing logic for arrays.

Export default class Observer {constructor(value) {constructor(value) {def(value, '__ob__', this, false); If (array.isarray (value)) {// Change the prototype of Array to the newly modified content object.setProtoTypeof (value, arrayMethods); // Change this array to observe this.observeArray(value); } else {// If it is an object, iterate over it and change the attribute to this.walk(value) in response; }} // Walk (value) {for (let key in value) {defineReactive(value, key); ObserveArray (arr) {for (let I = 0, l = arr.length; i < l; I ++) {// Enter arr[I]); }}}Copy the code

2. Rely on collection

At present, all the properties in the object have become framable state, and the next step is to enter the dependency collection stage, the whole process is as follows:

In fact, after seeing this god map, due to the limited ability is not very understanding, after their own separation, I think it can be divided into two steps to understand.

  1. The state after collecting dependencies in the getter (the get property in Object.defineProperty)

All Watcher’s stored in the Dep will be notified and executed when the dependency is triggered, notifies the associated component to update, for example, the location of the data update is the data associated with Dep1. Watcher1, Watcher2, and WatcherN are notified and executed.

The most important ones are the Dep class, the Watcher class, and the set and GET functions in the defineReactive function.

2.1 Dep class

The Dep class is used to manage dependencies, including adding, removing, and sending messages for dependencies, and is a typical observer pattern.

Export default class constructor {constructor() {console.log(' Dep constructor '); // Array stores its own subscribers, which is Watcher instance this.subs = []; } // Add a subscription addSub(sub) {this.subs.push(sub); If (dep.target) {this.addSub(dep.target); if (dep.target) {this.addSub(dep.target); }} // Notify () {const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); }}}Copy the code

2.2 Watcher class

The instance of Watcher class is a dependency, which will be stored in Dep as a dependency in the instantiation stage. When the corresponding data changes, the Watcher instance related to the data will be updated to perform the corresponding task and update the corresponding component.

Export default class Watcher {constructor(target, expression, callback) {console.log('Watcher constructor '); this.target = target; this.getter = parsePath(expression); this.callback = callback; this.value = this.get(); } update() { this.run(); Dep. Target = this; Dep. Target = this; const obj = this.target; let value; try { value = this.getter(obj); } finally { Dep.target = null; } return value; } run() { this.getAndInvoke(this.callback); } getAndInvoke(cb) { const value = this.get(); if (value ! == this.value || typeof value === 'object') { const oldValue = this.value; this.value = value; cb.call(this.target, value, oldValue); } } } function parsePath(str) { const segments = str.split('.'); return obj =>{ for (let i = 0; i < segments.length; i++) { if (! obj) { return; } obj = obj[segments[i]]; } return obj; }; }Copy the code

2.3 Set and get functions in defineReactive

The getter stage in Object.defineProperty collects dependencies and the setter stage fires dependencies.

export default function defineReactive(data, key, val) { const dep = new Dep(); if (arguments.length === 2) { val = data[key]; } // Let childOb = observe(val); DefineProperty (data, key, {// Enumerable: true, // configurable: True, // getter get() {console.log(' access ${key} property '); If (dep.target) {dep.depend(); If (childOb) {childob.dep.depend (); if (childOb) {childob.dep.depend (); } } return val; }, // setter set(newValue) {console.log(' change ${key} property to ${newValue} '); if (val === newValue) { return; } val = newValue; Observe childOb = observe(newValue); // Observe childOb = observe(newValue); // Publish subscribe mode, notify update dep.notify(); }}); }Copy the code

reference

This article is the author saw shao Shanhuan teacher’s video after a summary, Shao teacher speaks really good, praise.

1. If you think this article is good, share and like it so that more people can see it

2 pay attention to the public number kite, receive learning materials (front “multiple arms” information), regularly push original depth of good articles for you