数据响应式

59 篇文章 1 订阅
10 篇文章 0 订阅

数据响应式

简单的订阅发布模式

git地址:my-mvvm

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        class Dep {
            constructor() {
                this.subscribers = new Set()
            }
			
			// 添加依赖
            depend() {
                if (activeUpdate) {
                    this.subscribers.add(activeUpdate)
                }
            }

            notify() {
                //  收集依赖 
                this.subscribers.forEach(sub => sub())
            }
        }

        function observe(obj) {
            Object.keys(obj).forEach(key => {
                let internalValue = obj[key]

                const dep = new Dep()
                Object.defineProperty(obj, key, {
                    // 在getter收集依赖项,当触发notify时重新运行
                    get() {
						console.log("执行get")
                        dep.depend()
                        return internalValue
                    },

                    // setter用于调用notify
                    set(newVal) {
                        const changed = internalValue !== newVal
                        internalValue = newVal
                        if (changed) {

                            dep.notify()
                        }
                    }
                })
            })
            return obj
        }

        let activeUpdate = null

        function autorun(update) {
            const wrappedUpdate = () => {
                activeUpdate = wrappedUpdate
                update()
                activeUpdate = null
            }
            wrappedUpdate()
        }



        const state = {
            count: 0
        }

        observe(state)

        autorun(() => {
                console.log(state.count)
			})
            // 打印"count is: 0"

        state.count = state.count*2; // 打印"count is: 1"
        //state.count = 10 //打印10
    </script>
</body>

</html>

在这里插入图片描述

defineReactive函数监听变化

import observe from './observe'
export default function defineReactive(data, key, val) {

    console.log("我是defineReactive", data, key)
    if (arguments.length === 2) {
        val = data[key]
    }
    // 子元素要进行observe ,至此形成 递归, 这个递归不是函数自己调用自己,而是多个函数循环、类循环调用
    let childOb = observe(val)
    Object.defineProperty(data, key, {
        // 可枚举
        enumerable: true,
        // 可被配置,比如delete
        get() {
            console.log("你试图访问" + key)
            return val
        },
        set(newVal) {
            console.log("你试图设置:", data, "的" + key + ",-----newVal", newVal)
            if (val === newVal) {
                return
            }
            val = newVal
                // 当这个值设置了新值,这个值也要被observe
            console.log('childOb', childOb)
            childOb = observe(newVal)
        }

    })
}

工具(添加属性)

export const def = (obj, key, value, enumerable) => {

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

Observer类

  • 作用:
    将一个正常object转化成一个每个层级的属性都是响应式(可以被侦测的)object
import { def } from './utils'
import defineReactive from './defineReactive'

import { arrayMethods } from './array'
import observe from './observe';
/* 
Observer 类
    - 作用:
      将一个正常object转化成一个每个层级的属性都是响应式(可以被侦测的)object

*/

export default class Observer {
    constructor(value) {

            // 给实例(this, 一定要注意,构造函数的this不是类本身 ,是实例) 添加了__ob__属性
            def(value, '__ob__', this, false)
            console.log("我是 Object 对象", value);
            // 检查是数组还是对象
            if (Array.isArray(value)) {
                // 如果是数组,要非常强行的蛮干: 将数组的原型 ,指向arrayMethods
                Object.setPrototypeOf(value, arrayMethods);
                // 让数组变成observ
                this.observeArray(value);
            }
            this.walk(value)
        }
        // 遍历
    walk(value) {
        for (const k in value) {
            defineReactive(value, k)
        }
    }

    // 数组的特殊遍历
    observeArray(arr) {

        for (let index = 0; index < arr.length; index++) {
            console.log(arr[index])
                // console.log(observe)
            observe(arr[index]);
        }
    }

}

observe函数(绑定Observer类)

import Observer from './Observer'
export default function observe(value) {
    // 如果value不是对象则什么多不做
    if (typeof value != 'object') return

    // 定义ob
    let ob;

    if (typeof value.__ob__ != 'undefined') {
        ob = value.__ob__
    } else {
        ob = new Observer(value)
        console.log('ob', ob)
    }

    return ob
}

arrayMethods (重写数组方法)

当数组中数据发生变化时,将新增数据也变成响应式数据

import defineReactive from './defineReactive'
import { def } from './utils'
import Observer from './Observer'

/*
    改写 7个方法
    push
    pop
    shift
    unshift
    splice
    sort
    reverse
*/



const arrayPrototype = Array.prototype;

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

const methodsNeedChange = [
    "push",
    "pop",
    "shift",
    "unshift",
    "splice",
    "sort",
    "reverse",
]

methodsNeedChange.forEach(methodsName => {
    // 备份原来的方法
    const orginal = arrayPrototype[methodsName];

    // 定义新的方法
    def(arrayMethods, methodsName, function() {
        // 恢复原来的功能
        const result = orginal.apply(this, arguments)
            /* 
 
把数组身上的 __ob__ 取出来, __ob__ 已经被添加  ,为什么被添加了呢,为什么是已经被添加了呢?因为 数组肯定不是最高层
比如obj.arr 属性是数组,obj不能是数组,第一次遍历obj这个对象的第一层的时候,已经给g属性(就是这个数组) 添加了 __ob__属性
*/
        const ob = this.__ob__;
        //  有三种方法 push \ unshift \ splice  能够插入新的项, 先要把插入的新项 也要变成observe 
        let inserted = []
        const args = [...arguments]
        switch (methodsName) {
            case 'push':
            case 'unshift':
                inserted = arguments
                break
            case 'splice':
                // splice格式是splice(下标,数量,插入新项)
                inserted = args.slice(2)
                break


        }
        // 判断有没有要插入的新项
        if (inserted.length) {
        	// 重新遍历数据,将数组中新增数据变成响应式
            ob.walk(this);
        }
        return result
    }, false)

})

console.log(arrayMethods);

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Dep类

let uid = 0;
export default class Dep {
    constructor() {
            // console.log("我是Dep的构造器")
            this.id = uid++;
            // 用数组存储自己的订阅者,英语subscribes订阅者的意思
            this.subscribers = []
        }
        // 添加订阅
    addSubscriber(subscribe) {
            this.subscribers.push(subscribe)
        }
        // 添加依赖
    depend() {
            // Dep.target就是一个我们自己指定的全局的位置,用window.target 也行,只要是全局唯一,没有歧义就行
            if (Dep.target) {
                this.addSubscriber(Dep.target)
            }
        }
        // 通知更新
    notify() {
        // console.log("我是notify")

        // 浅克隆一份
        const subscribes = this.subscribers.slice()

        // 遍历
        for (let i = 0; i < subscribes.length; i++) {
            subscribes[i].update()

        }
    }
}

parsePath使用

 let str = "a.b.c.d"
        let o = {
            a: {
                b: {
                    c: {
                        d: 23
                    }
                }
            }
        }

        function parsePath(str) {
            let segment = str.split('.')

            return (obj) => {
                for (let i = 0; i < segment.length; i++) {
                    if (!obj) return
                    console.log(obj)
                    obj = obj[segment[i]]
                }
                return obj
            }
        }
        let fn = parsePath(str)
        console.log(fn(o));

在这里插入图片描述

Watcher类

import Dep from "./Dep";

let uid = 0;

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 设置为Watcher本身那么就是进入到了依赖收集阶段
        Dep.target = this;
        const obj = this.target;
        var 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) {
    let segment = str.split('.')

    return (obj) => {
        for (let i = 0; i < segment.length; i++) {
            if (!obj) return
            obj = obj[segment[i]]
        }
        return obj
    }
}

defineReactive

import observe from './observe'
import Dep from './Dep.js'
export default function defineReactive(data, key, val) {
    const dep = new Dep()
        // console.log("我是defineReactive", data, key)
    if (arguments.length === 2) {
        val = data[key]
    }
    // 子元素要进行observe ,至此形成 递归, 这个递归不是函数自己调用自己,而是多个函数循环、类循环调用
    let childOb = observe(val)
    Object.defineProperty(data, key, {
        // 可枚举
        enumerable: true,
        // 可被配置,比如delete
        get() {
            console.log("你试图访问" + key)
            dep.depend();
            if (childOb) {
                childOb.dep.depend()
            }
            return val
        },
        set(newVal) {
            console.log("你试图设置:", data, "的" + key + ",-----newVal", newVal)
            if (val === newVal) {
                return
            }
            val = newVal
                // 当这个值设置了新值,这个值也要被observe
            childOb = observe(newVal)

            // 发布的订阅模式
            dep.notify()
        }

    })
}

遇到的问题

watcher 收集依赖时多次添加重复依赖,导致监听多次执行

改进方法

使用Set数据结构 添加依赖

let uid = 0;
export default class Dep {
    constructor() {
        this.id = uid++;
        // 用数组存储自己的订阅者,英语subscribes订阅者的意思
        this.subscribers = new Set()

    }
    // 添加订阅
    addSubscriber(subscribe) {
        this.subscribers.add(subscribe)
    }
    // 添加依赖
    depend() {
        console.log(this.subscribers)
        // Dep.target就是一个我们自己指定的全局的位置,用window.target 也行,只要是全局唯一,没有歧义就行
        if (Dep.target) {
            this.addSubscriber(Dep.target)
        }
    }
    // 通知更新
    notify() {

        // 浅克隆一份
        // const subscribes = this.subscribers.slice()

        // 遍历
        // for (let i = 0; i < this.subscribers; i++) {
        //     this.subscribers[i].update()

        // }
        this.subscribers.forEach(sub => {
            sub.update();
        })
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值