[Vue2 source code] responsive principle

[Vue2 source code] responsive principle


Vue.js basically follows the MVVM (Model–View–ViewModel) architectural pattern, and the data model is just an ordinary JavaScript object. And when you modify them, the view will update. This article explains the underlying details of the Vue responsive system.

Vue响应式The core design idea of

When creating Vuean instance, vueit traverses datathe properties of the option, hijacks the Object.definePropertyaddition of properties getterand reads the data ( used for dependency collection, used to dispatch updates), and tracks dependencies internally, notifying changes when properties are accessed and modified .settergettersetter

Each component instance will have a corresponding watcherinstance, which will record all the data attributes of the dependency during the rendering of the component (dependency collection, and computed watcherinstance user watcher), and then when the dependency is changed, setterthe method will notify the instance datathat depends on it to re- watcherComputes (dispatches updates), causing its associated components to re-render.

overall process

As a front-end MVVMframework, Vuethe basic idea is the same as Angular, and its core is: when the data changes, the page is automatically refreshed , which frees us from cumbersome operations and concentrates on processing business logic .ReactDOMDOM

This is Vuethe data two-way binding (also known as the responsive principle). Data two-way binding is Vueone of the most unique features. Here we use an official flowchart to briefly explain Vuethe entire process of the responsive system:

insert image description here

In Vue, each component instance has a corresponding watcherinstance object, which will record the property as a dependency during the rendering of the component, and then when the dependency setteris called, it will notify watcherthe recalculation, thereby causing its associated component to be updated .

This is a typical observer pattern.

A key role in responsiveness

In the implementation logic of Vue data two-way binding, there are three key roles:

  • Observer: Its function is to add getterand to the properties of the object setterfor dependency collection and dispatch update
  • Dep: It is used to collect the dependencies of the current responsive object. Each responsive object, including sub-objects, has an Depinstance ( substhere is Watcheran instance array inside). When the data changes, it will dep.notify()notify each watcher.
  • Watcher: Observer object, the instance is divided into three types 渲染 watcher (render watcher), 计算属性 watcher (computed watcher),侦听器 watcher(user watcher)

Detecting Change Considerations

In Vue 2.0, it is Object.definePropertya responsive system based on implementation (this method is a feature that cannot be shimmed in ES5, which is why Vue does not support IE8 and lower versions of browsers). In vue3, it is implemented based Proxy/Reflecton

1. Due to the limitations of JavaScript, the Object.defineProperty() api cannot monitor changes in the length of arrays, nor can it detect new changes in arrays and objects.

2. Vue cannot detect the operation of directly changing the array item through the array index. This is not the reason for the Object.defineProperty() api, but Yuda believes that the performance consumption is not directly proportional to the user experience it brings. Responsive detection of the array will bring a lot of performance consumption, because the array items may be large, such as 1000 or 10000 items.

Responsive principle

The basic principle of responsiveness is to process the data of options in the constructor of Vue. That is, when initializing the vue instance, each attribute of data, props and other objects is defined once through Object.defineProperty, and when the data is set, some operations are performed to change the corresponding view.

data observation

Based on Object.defineProperty to realize the hijacking of arrays and objects.

\src\observe\index.js

import {
    
     newArrayProto } from "./array"

class Observe {
    
    
    constructor (data) {
    
    
        //Object.defineReactive只能劫持已经存在的属性(vue俩民回为此单独写一些api)
        //data.__ob__ = this  //这里的this指的是Observe,把这个实例附到了ob上,还可以用于检测是否被劫持过
        Object.defineProperty(data,'__ob__',{
    
    
            value:this,
            enumerable:false //将__ob__编程不可枚举,这样循环的时候就无法获取到,不会进入死循环
        })
        if(Array.isArray(data)) {
    
    
            data.__proto__ = newArrayProto
            this.observeArray(data)  //如果数组中放置的是对象,也可以被监控到
            //这里我们可以重写数组中的方法,7个变异方法,是可以修改到数组本身的
        }else {
    
    
            this.walk(data)
        }
        
    }
    walk (data) {
    
     //循环对象,对属性依次劫持
        //“重新定义”属性  性能差
        Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
    }
    observeArray(data) {
    
    
        data.forEach(item=> observe(item))
    }
}

export function defineReactive (target, key, value) {
    
      //闭包
    observe(value) //对所有的对象都进行属性劫持  深度劫持
    Object.defineProperty(target, key, {
    
    
        get () {
    
     //取值的时候会执行get
            console.log(key,"key");
            return value
        },
        set (newValue) {
    
      //修改的时候会执行set
            if (newValue === value) return
            observe(newValue)
            value = newValue
        }
    })
}


export function observe (data) {
    
    
    if (typeof data !== 'object' || data == null) {
    
    
        //只对对象进行劫持
        return
    }
    if(data.__ob__ instanceof Observe) {
    
     //如果存在data.__ob__就说明这个被代理过了
        return data.__ob__
    }
    //如果一个对象被劫持过了,那就不需要再被劫持了
    //要判断一个对象是否被劫持过,可以增添一个实例,用实例来判断是否被劫持过
    return new Observe(data)

}

Rewrite array 7 mutation methods

The 7 methods refer to: push, pop, shift, unshift, sort, reverse, splice. (These seven will change the original array) Implementation idea: slice-oriented programming! ! !

Instead of directly overriding the methods on Array.prototype, it is a transition through prototype chain inheritance and function hijacking.

Use Object.create(Array.prototype) to generate a new object newArrayProto, the object's proto points to Array.prototype, and then points our array's proto to the new object newArrayProto with an overridden method, thus ensuring newArrayProto and Array.prototype are all on the prototype chain of the array.

arr.proto === newArrayProto;newArrayProto.proto === Array.prototype

Then use Array.prototype.push.call to call the original method inside the overridden method, and hijack the newly added data.
\src\observe\array.js


let oldArrayProto = Array.prototype  //获取数组的原型

export let newArrayProto = Object.create(oldArrayProto)

let methods = [ //通过遍历寻找到所有变异方法
    'push',
    'pop',
    'shift',
    'reverse',
    'sout',
    'splice'
] //concat slice不会改变原数组

methods.forEach(method => {
    
    
    newArrayProto[method] = function (...args) {
    
     //这里重写了数组的方法
        const result = oldArrayProto[method].call(this,...args)  //再内部调用原来的方法,函数的劫持,切片编程
        //我们需要对新增的数据进行劫持
        let inserted
        let ob = this.__ob__
        switch (method) {
    
    
            case 'push':
            case 'unshift':
                inserted = args
                break;
            case 'splice' : //arr.splice(0,1,{a:1},{b:2})
                inserted = args
                break

        }
        console.log("xinzeng ");
        if(inserted) {
    
    
            //对新增的内容再次进行观测
            ob.observeArray(inserted)

        }
        
        
        return result
    }
})

Add __ob__ attribute

This is a nasty and clever property, we add this instance to the reactive data inside the Observer class. It is equivalent to adding an identifier to all responsive data, and the method on the Observer instance can be obtained on the responsive data

class Observe {
    
    
    constructor (data) {
    
    
        //Object.defineReactive只能劫持已经存在的属性(vue俩民回为此单独写一些api)
        //data.__ob__ = this  //这里的this指的是Observe,把这个实例附到了ob上,还可以用于检测是否被劫持过
        Object.defineProperty(data,'__ob__',{
    
    
            value:this,
            enumerable:false //将__ob__编程不可枚举,这样循环的时候就无法获取到,不会进入死循环
            
        })

        if(Array.isArray(data)) {
    
    
            data.__proto__ = newArrayProto

            this.observeArray(data)  //如果数组中放置的是对象,也可以被监控到
            //这里我们可以重写数组中的方法,7个变异方法,是可以修改到数组本身的
        }else {
    
    
            this.walk(data)
        }
        
    }
    walk (data) {
    
     //循环对象,对属性依次劫持
        //“重新定义”属性  性能差
        Object.keys(data).forEach(key => defineReactive(data, key, data[key]))
    }
    observeArray(data) {
    
    
        data.forEach(item=> observe(item))
    }
}
__ob__ has two major uses:

1. If an object has been hijacked, it does not need to be hijacked again. To judge whether an object has been hijacked, you can use ob to judge


export function observe (data) {
    
    
    if (typeof data !== 'object' || data == null) {
    
    
        //只对对象进行劫持
        return
    }
    if (data.__ob__ instanceof Observe) {
    
     //如果存在data.__ob__就说明这个被代理过了
        return data.__ob__
    }
    //如果一个对象被劫持过了,那就不需要再被劫持了
    //要判断一个对象是否被劫持过,可以增添一个实例,用实例来判断是否被劫持过
    return new Observe(data)

}

2. We have rewritten the 7 mutation methods of the array, among which the push, unshift, and splice methods will add new members to the array. At this time, the newly added members need to be observed again, and the observeArray method on the Observer instance can be called through ob

Guess you like

Origin blog.csdn.net/m0_63779088/article/details/130273962