• vue的双向绑定和依赖收集


    在掘金上买了一个关于解读vue源码的小册,因为是付费的,所以还比较放心

    在小册里看到了关于vue双向绑定和依赖收集的部分,总感觉有些怪怪的,然后就自己跟着敲了一遍。 敲完后,发现完全无法运行,  坑啊,  写书人完全没有测试过。

    然后自己完善代码, 越写越发现坑, 问题有些大。。。。。。

    最后自己重新实现了一遍,代码较多。 用到观察订阅者模式实现依赖收集, Object.defineProperty() 实现双向绑定

    /*
        自己写的代码, 实现vue的双向绑定和依赖收集
        场景: 多个子组件用到父组件data中的数据, 当父组件data中的此数据发生改变时, 
        所有依赖它的 子组件全部更新
        通常子组件的从父组件中拿取的数据不允许发生改变
    */
    
        //订阅者 Dep
        //一个订阅者只管理一个数据
        class Dep {
            constructor () {
                this.subs = []    //存放vue组件
            }
            addSubs (sub) {
                this.subs.push(sub)
                console.log('add watcher: ', sub._name)
            }
            notify () {
                this.subs.forEach( sub => {    //通知vue组件更新
                    sub.update()
                })
            }
        }
    
        //监听者
        //一个vue实例包含一个Watcher实例
        class Watcher {
            // 在实例化Watcher时, 将Dep的target指向此实例, 在依赖收集中使用
            // 因为依赖收集是在组件初始化时触发的, 而数据变更后视图相应变更是在初始化后
            // 所以让Dep.target指向此实例, 当此vue实例初始化完成后, 再指向下一个正在初始化的vue实例完成依赖收集
            constructor (name) {
                Dep.target = this
                this._name = name
            }
            update () {
                // 这里模拟视图更新
                // 其实还应该让子组件的props相应值与父组件更新的数据同步
                console.log("子组件视图更新了..." + this._name)
            }
        }
    
        //对data中的数据设置读写监听, 并且创建订阅者, 用于收集子组件的依赖和发布
        function defineReactive (obj, key, value) {
    
            // 对vue实例中data对象的每一个属性都 设置一个订阅者Dep
            let dep = new Dep()
    
            // 第二个vue实例的监听 覆盖了第一个vue实例的监听, 因为引用的obj是同一个
            Object.defineProperty(obj, key, {
                configurable: true,
                enumerable: true,
                get () {    
                // 在读此属性时, 将当前 watcher 对象收集到此属性的 dep 对象中
                // 在实例化vue时将Dep.target指向当前Watcher
                // get()依赖收集的时候是vue组件初始化的时候, set()是在初始化后
                    if (dep.subs.indexOf(Dep.target) === -1) {
                        dep.addSubs(Dep.target)
                    }
                    //return obj[key]     此写法报错 提示栈溢出 原因是无限调用get()
                    return value
                },
                set (newVal) {    // 此属性改变时, 通知所有视图更新
                    if (newVal !== value) {
                        value = newVal
                        dep.notify()    
                    }
                }
            })
        }
    
        //接收一个对象作为参数, 将该对象的所有属性调用defineReactive设置读写监听
        function observer (obj) {
            if (!obj || (typeof obj !== 'object')) {
                return 
            }
            Object.keys(obj).forEach( key => {
                defineReactive(obj, key, obj[key])
            }) 
        }
    
        // 构造函数, 监听 配置options中的data()方法返回的对象的所有属性 的读写
        class Vue {
            constructor (options) {
                this._name = options.name
                this._data = options.data
                // 每个vue组件都是一个vue实例, 在一个页面中有多个vue实例
                // 在初始化该vue实例时, new一个Watcher对象, 使Dep.target指向此实例
                new Watcher(options.name)
                // 给data中的数据挂载读写监听
                observer(this._data)
                //模拟vue解析template过程, 获取从父组件传递过来的props
                //在这里进行依赖收集
                this._props = options.props ? getProps() : {}
                // 实例化该组件的子组件
                this._children = options.render ? (options.render() || {}) : {}
            }
        }
    
        // 父组件数据
        let data = {
            first: "hello",
            second: 'world',
            third: ['啦啦啦']
        }
    
        let times = 0
        // 第一次调用返回的是第一个子组件的从父组件继承的数据(vue中props属性的值)
        // 第二次调用返回的是第二个子组件的从父组件继承的数据(vue中props属性的值)
        function getProps () {
            times++
            if (times == 1) {
                
                let obj = {first: "", second: ""}
                Object.keys(obj).forEach( key => {
                    // 如果是对象, 则进行深拷贝
                    // 这里使用到了父组件的数据, 触发依赖收集
                    if (data[key] instanceof Object) {
                        obj[key] = JSON.parse(JSON.stringify(data[key]))
                    } else {
                        obj[key] = data[key]
                    } 
                })
                return obj
    
            } else if (times == 2) {
    
                let obj = {first: "", third: ""}
                Object.keys(obj).forEach( key => {
                    if (data[key] instanceof Object) {
                        obj[key] = JSON.parse(JSON.stringify(data[key]))
                    } else {
                        obj[key] = data[key]
                    } 
                })
                return obj
            }    
        }
    
         let vue_root = new Vue({
             name: 'vue_root',
             data,
             //模拟编译template和实例化vue的过程 
             //在编译父组件 并且传递参数给子组件时, 将子组件的 watcher 添加进父组件的 dep
             render () {
                 let vue_1 = new Vue({
                     name: 'vue_1',
                     data: {},
                     props: true,
                     render () {}
                 }) 
                 let vue_2 = new Vue({
                     name: 'vue_2',
                     data: {},
                     props: true,
                     render () {}
                 }) 
                 return {
                     vue_1,
                     vue_2
                 }
             }
         })
        console.log(vue_root)
         vue_root._data.first = 'hello hello'    // vue_1 和 Vue_2 都依赖此数据, 都更新
         vue_root._data.third =  "aaa"            // 只有 vue_2 依赖到了此数据, 更新
  • 相关阅读:
    echarts 饼图
    vue echarts
    :style :class
    弹框弹出时禁止页面滚动
    2019-2-10 日记
    2019-1-27 日记
    2019-1-26 日记
    2019-1-3 日记
    2019-1-10 日记
    2019-1-2 日记
  • 原文地址:https://www.cnblogs.com/zhaowj/p/10034506.html
Copyright © 2020-2023  润新知