手写一个数据响应式函数

  • 目的:当数据改变时,要能监听到数组或对象上值的改变,并且进行自己的操作。
  • demo:index.js文件的简单使用为示例:
// 引入observe函数 与 Watcher 工具类(类似于vue.$Watch())
import observe from './my-reactive/observe'
import Watcher from './my-reactive/Watcher'
let obj = {
    a: {
        m: {
            n: 999
        }
    },
    b: '666',
    c:[1,2,3,4,5]
}
// 监听与调用
observe(obj)
new Watcher(obj, 'a.m.n', (val)=>{
    console.log("😔", val);
})
// 改变对象和数组
obj.c.splice(2,1,88,99)
obj.a.m.n = 10000

创建oberve侦察函数

import Observer from "./Observer"

// 创建oberve侦察函数
export default function observe(val) {
    if (typeof val !== 'object') return
    let ob
    if (typeof val.__ob__ !== 'undefined') {
        ob = val.__ob__
    } else {
        ob = new Observer(val)
    }
    return ob
}

Observer类

循环递归对象的属性 将一个正常的obj转换成为每一个层级的属性都是响应式的(都可以被侦测的)obj


import defineReactive from "./defineReactive"
import { arrayMethods } from './array'
import observe from "./observe";
import Dep from "./Dep";

function def(obj, key, value, enumerable) {
    Object.defineProperty(obj, key, {
        value,
        enumerable,
        writable: true,
        configurable: true,
    })
}

export default class Observer {
    constructor(value) {
        // 每一个Observer的实例上 都会有一个dep
        this.dep = new Dep()
        // this是Oberver类的实例,不是类本身
        def(value, '__ob__', this, false)
        // 检查是数组还是对象
        if (Array.isArray(value)) {
            Object.setPrototypeOf(value, arrayMethods)
            // 让这个数组进行observe
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }
    walk(value) {
        for (const k in value) {
            defineReactive(value, k)
        }
    }
    observeArray(arr){
        for (let i = 0; i < arr.length; i++) {
            // 逐项去observe
            observe(arr[i])
        }
    }
}

defineReactive

创建一个可以使用外部变量的闭包函数,包含了Object.defineProperties的核心功能

import observe from "./observe";
import Dep from "./Dep";
export default function defineReactive(data, key, val) {
    // 每个层级的__ob__属性上都有一个dep: Dep {}
    const dep = new Dep()
    if (arguments.length == 2) {
        val = data[key]
    }
    // 子元素要进行observe,至此形成了循环递归
    let childOb = observe(val)
    Object.defineProperty(data, key, {
        // 可被枚举
        enumerable: false,
        // 一些可配置的属性
        configurable: true,
        // getter 收集依赖
        get() {
            if (Dep.target) {
                dep.depend()
                if (childOb && childOb.length) {
                    childOb.dep.depend()
                }
            }
            return val
        },
        // setter 数据劫持
        set(newVal) {
            if (val === newVal) return
            val = newVal
            // 当设置了新值,这个值也要被observe
            childOb = observe(newVal)
            // 观察者模式 
            dep.notify()
        }
    })
}

Dep类

使用发布订阅的广播模式,用来管理依赖
dep.depend()
dep.notify()

let uid = 0
export default class Dep{
    constructor(){
        // 这个数组里放的是watcher的实例
        this.id = uid++
        this.subs = []
    }
    // 添加订阅
    addSub(sub){
        this.subs.push(sub)
    }
    // 添加依赖
    depend(){
        if (Dep.target) {
            this.addSub(Dep.target)
        }
    }
    // 通知更新
    notify(){
        console.log('dep类 notify方法');
        // 浅拷贝一份subs
        const subs = this.subs.slice()
        // 遍历
        for (let i = 0; i < subs.length; i++) {
            subs[i].update()
        }

    }
} 

watcher类

是一个中介,数据发生变化的时候通过watcher中转,通知组件更新

import Dep from "./Dep"
let uid = 0
// 闭包 函数柯里化 高阶函数
function parsePath(str) {
    let strArr = str.split('.')
    return (obj) => {
        for (let i = 0; i < strArr.length; i++) {
            if (!obj) return
            obj = obj[strArr[i]]
        }
        return obj
    }
}
export default class Watcher {
    constructor(target, expression, callback) {
        console.log('Watcher类的依赖收集器');
        this.id = uid++
        this.target = target
        this.getter = parsePath(expression)
        this.callback = callback
        this.value = this.get()
    }
    update() {
        this.run()
    }
    get() {
        // 进入依赖收集
        Dep.target = this
        const obj = this.target
        let value
        try {
            value = this.getter(obj)
        } catch (error) {
            console.log('Watcher 报错:', error);
        } 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)
        }
    }
} 

重点:ArrayMethod

  • 改写7个方法,原来的方法在Array.prototype上
  • vue当中数组的响应侦测是如何实现的?以arrayPrototype为原型,创建了arrayMethords的对象,使用了Object.setPrototypeOf()改写了七个数组的方法
function def(obj, key, value, enumerable) {
    Object.defineProperty(obj, key, {
        value,
        enumerable,
        writable: true,
        configurable: true,
    })
}
// 得到Array的原型对象
const arrayPrototype = Array.prototype

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

// 要改写的7个方法
const methodNeedChange = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'revert'
]

// 遍历 并把改写的方法添加到自己的原型上
methodNeedChange.forEach(item => {
    // 备份原来的方法
    const OriginalMethod = arrayPrototype[item]
    // 定义新的方法
    def(arrayMethods, item, function()  { // 这里不能用箭头函数,当前this指向的是传入的这个数组本身 [2, 3, 4, 5]
        // 恢复原来的功能
        const result =  OriginalMethod.apply(this, arguments)
        // arguments是类数组对象 需要转变为数组 才能使用数组的slice等方法
        const args = [...arguments]
        let insertArr = []
        switch (item) {
            case 'push':
            case 'unshift':
                insertArr = args
                break;
            case 'splice':
                insertArr = args.slice(2)
        }
        // 让新项也变为可响应的
        const ob = this['__ob__']
        if (insertArr.length>0) {
            ob.observeArray(insertArr)
        }
        // 让数组也有notify方法
        ob.dep.notify()
        return result

    }, false)
})
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容