[Análisis de código fuente front-end] Principio de respuesta de datos

Referencia: Cursos de la serie de análisis de código fuente de Vue

Notas de la serie:

Código fuente de este capítulo: https://gitee.com/szluyu99/vue-source-learn/tree/master/Data_Reactive_Study

Propósito del curso: Comprender a fondo el principio de actualización de datos de Vue2

sensible a los datos

Patrón MVVM:

Intrusivos y no intrusivos:

Objeto.defineProperty()

Documentación de referencia: Object.defineProperty() - JavaScript | MDN (mozilla.org)

Object.defineProperty()Se utiliza para el secuestro de datos/proxy de datos, utilizando las funciones dotadas por el motor de JavaScript para detectar cambios en las propiedades de los objetos.

Este método define una nueva propiedad directamente en un objeto o modifica una propiedad existente de un objeto y devuelve este objeto.

var obj = {
    
    }

Object.defineProperty(obj, 'a', {
    
    
    value: 3, // 值
    writable: false, // 只读
    enumerable: true, // 可枚举
})

console.log(obj.a) // 3
obj.a++ // 只读的,不能修改
console.log(obj.a); // 3

El getter/setter del método requiere una rotación variable para funcionar:

var obj = {
    
    }
var temp // 临时变量

Object.defineProperty(obj, 'a', {
    
    
    get() {
    
    
        console.log('get a');
        return temp
    }, 
    set(newVal) {
    
    
        console.log('set a',  newVal);
        temp = newVal
    }
})

obj.a = 1
obj.a++ 
console.log(obj.a) // 2

Personaliza una defineReactivefunción y usa closures , para que no necesites establecer variables temporales:

export default function defineReactive(obj, key, val) {
    
    
    if (arguments.length == 2) {
    
    
        val = obj[key]
    }
    Object.defineProperty(obj, key, {
    
    
        enumerable: true, // 可枚举
        configurable: true, // 可配置
        get() {
    
    
            console.log('get', key);
            return val
        },
        set(newVal) {
    
    
            console.log('set', key, newVal);
            if (val === newVal) return
            val = newVal
        }
    })
}
let obj = {
    
    }

// 实现了数据响应式
defineReactive(obj, 'a', 1)
console.log(obj.a); // 1
obj.a = 10
console.log(obj.a); // 10

Detectar recursivamente todas las propiedades de un objeto

Para los siguientes objetos, use defineReactivelas obj.a.m.npropiedades anteriores que no se pueden monitorear (solo se puede monitorear una capa y no se pueden monitorear múltiples capas)

let obj = {
    
    
    a: {
    
    
        m: {
    
    
            n: 1
        }
    },
    b: 1
}

El efecto deseado: observehacer que todas las propiedades de obj respondan

observe(obj)
obj.b++ // 响应式
obj.a.m.n++ // 响应式

Diagrama de flujo del programa: (El efecto de la recursión se realiza a través de las llamadas entre funciones en todos los niveles)

observe.js

/**
 * 将 obj 所有属性变为响应式
 */
export default function observe(obj) {
    
    
    if (!obj || typeof obj !== 'object') return
    var ob;
	// 判断是否已经是响应式
    if (typeof obj.__ob__ != 'undefined') {
    
    
        ob = obj.__ob__
    } else {
    
    
        ob = new Observer(obj)
    }
    return ob
}

Observer.js

/**
 * 将一个正常的 object 转换成每个层级的属性都是响应式的 object
 */
export default class Observer {
    
    
    constructor(obj) {
    
    
        console.log('Observer constructor', obj);
        // 构造函数中的 this 不是类本身,而是表示实例
        def(obj, '__ob__', this, false)
        // 将 object 中的属性转换成响应式的属性
        this.walk(obj)
    }
    // 遍历 object 的属性,将其转换成响应式的属性
    walk(obj) {
    
    
        console.log('walk', obj);
        for (let k in obj) {
    
    
            defineReactive(obj, k)
        }
    }
}

/**
 * 对 Object.defineProperty 的封装
 */
export const def = function (obj, key, value, enumerable) {
    
    
    Object.defineProperty(obj, key, {
    
    
        value,
        enumerable,
        writable: true,
        configurable: true
    })
}

defineReactive.js

/**
 * 将数据变为响应式
 */
export default function defineReactive(obj, key, val) {
    
    
    console.log('defineReactive', key);
    if (arguments.length == 2) {
    
    
        val = obj[key]
    }

    // 子元素要进行 observe,至此形成递归
    // 这个递归是多个函数、类循环调用
    let childOb = observe(val)

    Object.defineProperty(obj, key, {
    
    
        enumerable: true, // 可枚举
        configurable: true, // 可配置
        get() {
    
    
            // console.log('get', key);
            return val
        },
        set(newVal) {
    
    
            console.log('set', key, newVal);
            if (val === newVal) return
            val = newVal
            // 当设置了新值,这个新值也要被 observe
            childOb = observe(newVal)
        }
    })
}

Efecto:

let obj = {
    
    
    a: {
    
    
        m: {
    
    
            n: 1
        }
    },
    b: 1
}

observe(obj)
obj.b++ // 响应式
obj.a.m.n++ // 响应式

El principio de respuesta de las matrices

Para los objetos de matriz, la implementación anterior no puede monitorear sus métodos de modificación de elementos push, popy otros:

let obj = {
    
    
	c: [1, 2, 3, 4]
}

Anular 7 métodos: push, pop, shift, unshift, splice, sort,reverse

array.js

// 数组的原型
const arrayPrototype = Array.prototype

// 以 Array.prototype 为原型创建 arrayMethods 对象,并暴露
export const arrayMethods = Object.create(arrayPrototype)

// 要被改写的 7 个数组方法
const methodsNeedChange = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]

// 数组方法实际是自己写的,自己写的再调用真实的数组方法,中间可以拦截数据
methodsNeedChange.forEach(methodName => {
    
    
    // 备份原来的方法
    const originMethod = arrayPrototype[methodName]
    // 给原型定义新的方法
    def(arrayMethods, methodName, function () {
    
    
        console.log('arrayMethods', methodName);

        // 执行原来的函数
        const result = originMethod.apply(this, arguments)
        // arguments 是伪数组对象,转成数组对象
        const args = [...arguments]

        // 把数组身上的 __ob__ 取出来
        const ob = this.__ob__

        // 有三种方法 push / unshift / splice 可以插入新项
        // 要将插入的新项也变为 observe 的
        let inserted = []

        switch (methodName) {
    
    
            case 'push':
            case 'unshift':
                inserted = args
                break;
            case 'splice':
                // splice(下标, 数量, 插入的新项)
                inserted = args.slice(2)
                break;
        }

        // 判断有没有要插入的新项,让新项也变为响应的
        if (inserted) ob.observeArray(inserted)

        return result
    }, false)
})

Modifique el código para construir Observer Observer.jsen :

/**
 * 将一个正常的 object 转换成每个层级的属性都是响应式的 object
 */
export default class Observer {
    
    
    constructor(obj) {
    
    
        console.log('Observer constructor', obj);
        // 构造函数中的 this 不是类本身,而是表示实例
        def(obj, '__ob__', this, false)
        // 将 object 中的属性转换成响应式的属性
        if (Array.isArray(obj)) {
    
    
            // 将数组的原型指向 arrayMethods
            Object.setPrototypeOf(obj, arrayMethods)
            // 让数组变的 observe
            this.observeArray(obj)
        } else {
    
    
            this.walk(obj)
        }
    }
    // 遍历 object 的属性,将其转换成响应式的属性
    walk(obj) {
    
    
        console.log('walk');
        for (let k in obj) {
    
    
            defineReactive(obj, k)
        }
    }
    // 数组的特殊遍历
    observeArray(arr) {
    
    
        for (let i = 0, l = arr.length; i < l; i++) {
    
    
            // 逐项进行 observe
            observe(arr[i])
        }
    }
}

Efecto:

let obj = {
    
    
    c: [1, 2, 3, 4]
}

observe(obj)
obj.c.push(55, 66, 77)
obj.c.splice(1, 1, [1, 2])
console.log(obj.c);

colección de dependencia

¿Qué es la dependencia? Cuando se necesitan datos, se llama dependencia.

  • En Vue 1.x, las dependencias son detalladas y el DOM utilizado para los datos depende de
  • En Vue 2.x, las dependencias son de grano medio y los componentes son dependencias.
  • Recopile dependencias en getters y active dependencias en setters

Clase Dep y clase Watcher:

  • La clase Dep encapsula el código de la colección de dependencias y se dedica a administrar las dependencias. Cada instancia de Observer tiene una instancia de Dep en sus miembros.
  • Watcher es un intermediario, cuando los datos cambien, se transferirán a través de Watcher para notificar al componente

  • La dependencia es el observador. Solo el captador activado por el vigilante recopilará las dependencias. El vigilante que desencadena el captador recopilará el vigilante que se despide.
  • Dep usa el modo de publicación-suscripción Cuando los datos cambian, cicla la lista de dependencias y notifica a todos los Vigilantes una vez.
  • El ingenio de la implementación del código: Watcher se establece en una ubicación específica globalmente y luego lee los datos. Debido a que se leen los datos, se activará el captador de estos datos. En el getter, puede obtener el Watcher que actualmente está leyendo datos y recopilar este Watcher en el Dep.

Artículo de referencia: Principio de respuesta en profundidad de Vue

Efecto:

let obj = {
    
    
    a: 1,
    b: {
    
    
        m: {
    
    
            n: 1
        }
    },
    c: [1, 2, 3, 4]
}

observe(obj)

// 监控依赖
new Watcher(obj, 'b.m.n', val => {
    
    
    console.log('Watcher 在监控 b.m.n', val)
}) 

obj.b.m.n++ 
console.log(obj)

Dep.js

/**
 * 全局唯一的 依赖收集器
 */
export default class Dep {
    
    
    constructor() {
    
    
        // console.log('Dep constructor'); 
        this.id = uid++

        // 用数组存储自己的订阅者,这个数组中存放 Watcher 实例
        this.subs = [] // subscribers
    }
    // 添加订阅
    addSub(sub) {
    
    
        this.subs.push(sub)
    }
    // 添加依赖
    depend() {
    
    
        // Dep.target 就是自己指定的全局的位置(window.targte 也可以,全局唯一即可)
        if (Dep.target) {
    
    
            this.addSub(Dep.target)
        }
    }
    // 通知更新
    notify() {
    
    
        console.log('Dep notify'); 
        // 浅克隆一份
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
    
    
            subs[i].update()
        }
    }
}

Watcher.js

var uid = 0

export default class Watcher {
    
    
    // 监听 target 对象的 expression 属性,执行 callback 回调
    constructor(target, expression, callback) {
    
    
        // console.log('Watcher constructor');
        this.id = uid++
        this.target = target
        this.getter = parsePath(expression) // 解析 expression 为一个函数
        this.callback = callback
        this.value = this.get()
    }
    update() {
    
    
        // console.log('Watcher update');
        this.run()
    }
    get() {
    
    
        // 进入依赖收集阶段,让全局 Dep.tartget 设置为 Watcher 本身
        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)
        }
    }
}

// 返回一个可以解析 "a.b.c" 格式的函数
// let fn = parsePath('a.b.c')
// fn({ a: { b: { c: 1 } } })
function parsePath(str) {
    
    
    var segments = str.split('.');
    return obj => {
    
    
        for (let i = 0; i < segments.length; i++) {
    
    
            if (!obj) return
            obj = obj[segments[i]];
        }
        return obj
    }
}

El resto del código del archivo se omite...

Referencia completa del código fuente: https://gitee.com/szluyu99/vue-source-learn/tree/master/Data_Reactive_Study

Supongo que te gusta

Origin blog.csdn.net/weixin_43734095/article/details/125488301
Recomendado
Clasificación