Object.defineProperty of Vue-MVVM data two-way binding responsive principle

It is better to eat in combination with items

1. Vue data two-way binding principle:

Vue internally converts the reading and writing of each data in the data object into a getter/setter by intercepting the Object.defineProperty method property, and adds a corresponding watcher. When the data changes, the watcher notifies the view to update

2. MVVM data--v-model two-way binding:

Data changes update the view, and view changes update the data.

1. When the content of the input box changes, the data in data changes synchronously. That is, the change of view => model.
2. When the data in data changes, the content of the text node changes synchronously. That is, the change of model => view.

Question: How does the data change update the view?
1. How to know that the data has changed
2. When the data has changed
3. For the rest, we only need to notify the view to update when the data changes

3. Observability of data--getter, setter:

It is the value and value of the underlying data of Vue

The Object.defineProperty() method directly defines a new property on an object, or modifies an existing property of an object, and returns this object.

1.一般情况下我们定义个对象,但是这种定义方式,并不知道什么时候修改或者读取属性,换句话说,不具备观测性。
 let car = {
    'color':'blue',
    'price':3000
}
 console.log(car.color) //blue

2.我们试着用Object.defineProperty去改写一下,car已经可以主动告诉我们它的属性的读写情况了,也意味着,这个car的数据对象已经是“可观测”的了。
    let car = {}
    let val = 3000
    Object.defineProperty(car, 'price', {
      get() {
          console.log('price属性被读取了')
          return val
      },
      set(newVal) {
          console.log('price属性被修改了')
          val = newVal
      }
  })
  car.price=5000  
  console.log(car.price)// price属性被修改了  price属性被读取了 3000

3. 现在把对象所有属性都变得可观测,我们可以编写如下两个函数:
function observable (obj) {
   if (!obj || typeof obj !== 'object') {
       return;
   }
   let keys = Object.keys(obj);
   keys.forEach((key) =>{
      defineReactive(obj,key,obj[key])
   })
   return obj;
 }
 /**
  * 使一个对象转化成可观测对象
  * @param { Object } obj 对象
  * @param { String } key 对象的key
  * @param { Any } val 对象的某个key的值
*/
 function defineReactive (obj,key,val) {
    Object.defineProperty(obj, key, {
         get(){
                console.log(`${key}属性被读取了`);
                return val;
              },
     set(newVal){
                console.log(`${key}属性被修改了`);
                val = newVal;
            }
        })
}              

现在,我们就可以这样定义car,这个car对象里的所有对象都是可观测的。
let car = observable({
        'brand':'BMW',
        'price':3000
})

4. Dependency collection (Observer):

Let us know when the data is read or written, that is, we can proceed to the next step

  1. The data objects that need to be observed are recursively traversed, including the properties of the sub-property objects. If you add setters and getters, if you assign a value to this object, the setter will be triggered, and then you can monitor the data changes.
  2. compile parses the template instructions, replaces the variables in the template with data, then initializes the rendering page view, binds the node corresponding to each instruction with an update function, and adds subscribers who listen to the data. Once the data changes, receive a notification and update view

Once the data changes,

the "publish-subscriber" mode is refreshed in real time:
the data change is "publisher", and the dependent object is "subscriber".
 

We designed a subscriber Dep class, which defines some properties and methods. What needs special attention here is that it has a static property target. new.target must be written in the constructor, which points to the class itself . Which class specifically points to this is a global unique Watcher. This is a very clever design, because only one global Watcher can be calculated at the same time. In addition, its own attribute subs is also an array of Watchers. We will Dep the subscriber The operation of adding a subscriber is designed in the getter, which is to trigger when the Watcher is initialized, so it is necessary to determine whether to add a subscriber. In the setter function, if the data changes, all subscribers will be notified, and the subscribers will execute the corresponding update function.

1.创建一个依赖收集容器(消息订阅器Dep),用来容纳所有的“订阅者”。订阅器Dep主要负责收集订阅者,然后当数据变化的时候后执行对应订阅者的更新函数
class Dep {
        constructor(){
            this.subs = []
        },
        //增加订阅者
        addSub(sub){
            this.subs.push(sub);
        },
        //判断是否增加订阅者
        depend () {
            if (Dep.target) {
                this.addSub(Dep.target)
            }
        },

        //通知订阅者更新
        notify(){
            this.subs.forEach((sub) =>{
                sub.update()
            })
        }
    }
    Dep.target = null;

2.有了订阅器,再将defineReactive函数进行改造一下,向其植入订阅器:
function defineReactive (obj,key,val) {
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            get(){
                dep.depend();
                console.log(`${key}属性被读取了`);
                return val;
            },
            set(newVal){
                val = newVal;
                console.log(`${key}属性被修改了`);
                dep.notify()   //数据变化通知所有订阅者
            }
        })
    }

5. Subscriber watcher:

Watcher Subscriber is the communication bridge between Observer and Compile

The main things to do are:

① Go to the attribute subscriber when instantiating itself, and assign this to point to when the function fetches the value (dep.target)

②It must have an update() method

③When the attribute change is notified by dep.notice(), it can call its own update() method and trigger the callback bound in Compile. We only need to trigger the corresponding get function to call when the Watcher is initialized.


watch allows to perform asynchronous operations 


How to trigger?
Get the corresponding attribute value

We only need to add subscribers when the subscriber Watcher is initialized, so we need to do a judgment operation, so we can do something on the subscriber: cache the subscriber on Dep.target, and then remove it after adding it successfully OK. Subscriber Watcher is implemented as follows

class Watcher {
        constructor(vm,exp,cb){
            this.vm = vm;
            this.exp = exp;
            this.cb = cb;
            this.value = this.get();  // 将自己添加到订阅器的操作
        },

        update(){
            let value = this.vm.data[this.exp];
            let oldVal = this.value;
            if (value !== oldVal) {
                this.value = value;
                this.cb.call(this.vm, value, oldVal);
            },
        get(){
            Dep.target = this;  // 缓存自己
            let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
            Dep.target = null;  // 释放自己
            return value;
        }
    }

    订阅者Watcher 是一个 类,在它的构造函数中,定义了一些属性:
    vm:一个Vue的实例对象;
    exp:是node节点的v-model或v-on:click等指令的属性值。如v-model="name",exp就是name;
    cb:是Watcher绑定的更新函数;

MVVM is used as the entrance of data binding, integrates Observer, Compile and Watcher, monitors its own model data changes through Observer, parses and compiles template instructions through Compile, and finally uses Watcher to build a communication bridge between Observer and Compile to achieve :

Data change -> view update; view interaction change (input) -> two-way binding effect of data model change.

When we instantiate a rendering watcher, we first enter the constructor logic of the watcher, and then execute its this.get() method, enter the get function, and first execute:

Dep.target = this;  // 缓存自己

In fact, assign Dep.target to the current rendering watcher, and then execute:

let value = this.vm.data[this.exp]  // 强制执行监听器里的get函数

In this process, the data access on the vm is actually to trigger the getter of the data object.

The getter of each object value holds a dep. When the getter is triggered, the dep.depend() method will be called, and this.addSub(Dep.target) will be executed, that is, the current watcher is subscribed to this data holder. In the subs of the dep, this purpose is to prepare for which subs can be notified when subsequent data changes. This has actually completed a process of dependency collection. So is it over here? In fact, no, after completing the dependency collection, you need to restore Dep.target to the previous state, namely:

Dep.target = null;  // 释放自己

Because the data dependency collection of the current vm has been completed, the corresponding rendering Dep.target also needs to be changed. The update() function is used to call Watcher's own update function to update when the data changes. First get the latest data through let value = this.vm.data[this.exp];, then compare it with the old data obtained by get() before, if not the same, call the update function cb to update.

6. What are the disadvantages of using this thing for data hijacking (Object.defineProperty())

Some cannot be intercepted: [array: most operations cannot be intercepted]

When the property is operated, such as modifying the array data by subscripting or adding properties to the object, this cannot trigger the re-rendering of the component. Object.defineProperty cannot intercept these operations. Vue solves this problem by rewriting the function internally

In Vue3.0, Object.defineProperty is no longer used, and Proxy is used to proxy objects to realize data hijacking.

Proxy can listen to data changes in any way. The only disadvantage is compatibility issues, because Proxy is an ES6 syntax

Seven, the difference between MVVM, MVC, MVP

Common ground: separate concerns to organize code structure and optimize development efficiency

(1)MVC

   Organize code structure by separating Model, View and Controller


   View: UI view, responsible for data display
   Model: responsible for storing business data on the page, and logical operations on the corresponding data

   

Observer mode    is applied to View and Model , when the Model layer changes, the View layer will be updated in real time

   Take a look in detail:  Specific explanation of the observer mode

   Controller: The Controller layer is the link between the View layer and the Model layer.
                       It is mainly responsible for the response operation between the user and the application, and the interaction between the user and the page.

   Disadvantages: The View layer and the Model layer are coupled together, and the Controller only knows the interface of the Model, so it has no way to control the update of the View layer. When the project logic becomes complicated, it may cause code confusion and may affect the code Reusability causes some problems

(2) MVVM
   



    MVVM is divided into Model, View, and ViewModel:

    View: UI view, responsible for data display
    Model: data model, responsible for storing business data on the page, and logical operations on corresponding data
    ViewModel: responsible for monitoring data changes in the Model and controlling the view Update and handle user interaction operations;

    Model and View are not directly related, but are connected through ViewModel.
    When the data in the Model changes, it will trigger the refresh of the View layer, and the data changed in the View due to user interaction operations will also be displayed in the Model. Central synchronization, data is automatically synchronized, developers only need to focus on data maintenance operations, and do not need to operate DOM by themselves

(3)
     
MVP The only difference between MVP mode and MVC is Presenter and Controller

     MVC can cause code confusion, and the MVP pattern solves this.
     The MVP mode uses Presenter to realize the decoupling of the View layer and the Model layer. The interface of the View layer is exposed to the Presenter. Therefore, the changes of the Model and the changes of the View can be bound together in the Presenter, so as to realize the integration of View and Model. The synchronous update of the Model
realizes the decoupling of the View and the Model, and the Presenter also includes other response logic.

Guess you like

Origin blog.csdn.net/weixin_48927323/article/details/126833485