美文网首页
(依赖收集vue) 联想学习

(依赖收集vue) 联想学习

作者: 不知名的啦 | 来源:发表于2019-04-25 17:18 被阅读0次

    现在假设我们已经理解了vue响应式的原理,然或者希望这篇文章能有点作用
    https://juejin.im/post/5cc14c87f265da035c6bc9bb
    ,现在我们创建一个girl,她的所有属性读写已经变得可观察了:

    function defineReactive(obj, key, val, cb){
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: ()=>{
                console.log(`${key}属性被获取了`);
                return val
            },
            set:newVal=> {
                console.log(`${key}属性被修改了`);
                val = newVal;
                // cb();
            }
        })
    }
    let girl = { height: '1.7', weight: '48', whole: '90分'}
    function observe(value) {
        Object.keys(value).forEach((key) => defineReactive(value, key, value[key]));
    }
    observe(girl)
    girl.height
    height属性被获取了
    "1.7"
    girl.weight
    weight属性被获取了
    "48"
    girl.height = '1.9'
    height属性被修改了
    "1.9"
    

    但是这就可以了吗,我们如果想知道这个女孩是否漂亮怎么办呢,假设啊,girl身高大于1.65且体重小于48kg就是漂亮啊,原谅我这么肤浅,那我们改该怎么做呢?

    计算属性

    假设有这么一个方法叫watcher(观察者),我们在girl对象上面创建了一个属性isBeautiful,现在我们定义了她的值取决于该对象的另外两个属性,当满足条件时就是true了,现在我们来实现一下这个方法:

    // 检测监听属性的值更新时调用
    function updateNotice(val) {
        console.log(`girl的isBeautiful属性为${val}`)
    }
    // obj 监听对象
    // key 监听的属性
    // cb 监听的回调函数
    function watcher(obj, key, cb) {
        Object.defineProperty(obj, key, {
            get: function() {
                var val = cb();
                <!--通知监听属性的值-->
                updateNotice(val);
                return val;
            },
            set: function() {
                console.log('watcher属性不可以被修改');
            }
        })
    }
    watcher(girl, 'isBeautiful', () => {
      return (girl.height > 1.65 && girl.weight < 48) ? '漂亮' : '差点漂亮'
    })
    girl.isBeautiful
    height属性被获取了
    girl的isBeautiful属性为差点漂亮
    "差点漂亮"
    girl.height = '1.7'
    height属性被修改了
    "1.7"
    girl.weight = '47'
    weight属性被修改了
    "47"
    girl.isBeautiful
    height属性被获取了
    weight属性被获取了
    girl的isBeautiful属性为漂亮
    "漂亮"
    

    perfect现在,要是只要我们获取下girl的isBeautiful属性就能知道她的类型了,就是有些麻烦,我们还是想观察者要是能检测到对应的依赖属性发生变化时,能主动通知我们她是否漂亮的属性的值就更完美了

    依赖收集

    现在我们怎么实现呢,不妨想想,上面说了(对应的依赖属性发生变化时)对应的就是每个可观察对象的set方法执行的时候,如果我们在set方法里面能够调用监听属性变化的方法updateNotice,不就可以实现主动通知了吗,道理是这个道理, 但是updateNotice方法需要接受回调值作为参数,而set方法的上下作用域是没有的,我们需要一个工具将监听器可观测对象连接起来,这个工具的作用就是收集监听器里面的回调参数cb及监听属性更新的方法updateNotice用来在可观测对象里面来使用。
    下面我们给这个工具起个好听的名字依赖收集器

    let Dep = {
      target: null // 用来接收监听器里面的回调参数和更新通知方法
    }
    

    target等于我们监听器里面的回调参数cb及监听属性更新的方法updateNotice,下面我们来优化下对应的updateNotice方法:

    // obj 监听对象
    // key 监听的属性
    // cb 监听的回调函数
    function watcher(obj, key, cb) {    
        // 所依赖的属性发生变化时调用
        function onDepUpdated () {
            var val = cb();
            updateNotice(val)
        }
        Object.defineProperty(obj, key, {
            get: function() {
                Dep.target = onDepUpdated;
                var val = cb();
                Dep.target = null;
                return val;
            },
            set: function() {
                console.log('watcher属性不可以被修改');
            }
        })
    }
    

    重点来了,大家可能会好奇Dep.target先是等于onDepUpdated后又为null的作用,解释下,当我们首次获取监听对象的监听属性时,举个列子girl.isBeautiful这是会执行对应的get方法,这个时候我们先是将对应的Dep.target = onDepUpdated(收集监听器里面的回调参数cb及监听属性更新的方法updateNotice),这一步非常关键,通过这样的操作,依赖收集器就获得了监听器的回调值以及updateNotice()方法,然后执行对应的cb方法,监听属性所依赖的属性这个时候被获取,将会触发对应的defineReactive的get方法,这个时候我们对应的依赖收集下,cb执行之后再将Dep.target = null,是不是豁然开朗,Dep.target作为全局变量,理所当然的能够被可观测对象的get/set所使用。下面我们将代码补充下:

    function defineReactive(obj, key, val, cb){
        let deps = [];
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: ()=>{
                if (Dep.target && deps.indexOf(Dep.target) === -1) {
                    deps.push(Dep.target)
                }
                console.log(`${key}属性被获取了`);
                return val
            },
            set:newVal=> {
                console.log(`${key}属性被修改了`);
                val = newVal;
                deps.forEach((dep) => {
                    dep();
                })
            }
        })
    }
    

    正如上面写的,当被依赖的属性获取时,对应的deps便会收集依赖属性(计算属性)的onDepUpdated方法,当被依赖属性发生变化的时候,便会主动通知所有依赖者(计算属性),也就是执行对应的onDepUpdated方法。
    下面贴上完整代码:

    function defineReactive(obj, key, val, cb){
        let deps = [];
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: ()=>{
                if (Dep.target && deps.indexOf(Dep.target) === -1) {
                    deps.push(Dep.target)
                }
                console.log(`${key}属性被获取了`);
                return val
            },
            set:newVal=> {
                console.log(`${key}属性被修改了`);
                val = newVal;
                deps.forEach((dep) => {
                    dep();
                })
            }
        })
    }
    function observe(value) {
        Object.keys(value).forEach((key) => defineReactive(value, key, value[key]));
    }
    let Dep = {
      target: null // 用来接收监听器里面的回调参数和更新通知方法
    }
    // 检测监听属性的值更新时调用
    function updateNotice(val) {
        console.log(`girl的isBeautiful属性为${val}`)
    }
    // obj 监听对象
    // key 监听的属性
    // cb 监听的回调函数
    function watcher(obj, key, cb) {    
        // 所依赖的属性发生变化时调用
        function onDepUpdated () {
            var val = cb();
            updateNotice(val)
        }
        Object.defineProperty(obj, key, {
            get: function() {
                Dep.target = onDepUpdated;
                var val = cb();
                Dep.target = null;
                return val;
            },
            set: function() {
                console.log('watcher属性不可以被修改');
            }
        })
    }
    
    let girl = { height: '1.7', weight: '48', whole: '90分'}
    observe(girl)
    watcher(girl, 'isBeautiful', () => {
      return (girl.height > 1.65 && girl.weight < 48) ? '漂亮' : '差点漂亮'
    })
    
    girl.isBeautiful
    height属性被获取了
    weight属性被获取了
    "差点漂亮"
    girl.weight = '46'
    weight属性被修改了
    height属性被获取了
    weight属性被获取了
    girl的isBeautiful属性为漂亮
    "46"
    

    到此,我们已经实现了我们初始时候的设想,是不是对依赖收集也有了简单的了解了,如果能对你有一点点的帮助,再好不过了,
    下面我们贴上优化后的代码,将依赖收集有关的功能、观察者、订阅者都功能集成下:

    <!--订阅者-->
    class Observer {
        constructor(data) {
            return this.walk(data);
        }
        walk(data) {
            const key = Object.keys(data);
            key.forEach( key => {
                this.defineProperty(data, key, data[key]);
            })
            return data;
        }
        defineProperty(obj, key, val) {
            const dep = new Dep();
            Object.defineProperty(obj, key, {
                get() {
                    console.log(`${key}属性被获取了`);
                    dep.addSub();
                    return val
                },
                set(newVal) {
                    console.log(`${key}属性被修改了`);
                    val = newVal;
                    dep.notify();
                }
            }) 
        }
    }
    <!--观察者-->
    class Watcher {
        constructor(obj, key, cb, onComputedUpData) {
            this.obj = obj;
            this.key = key;
            this.cb = cb;
            this.onComputedUpData = onComputedUpData;
            return this.defineComputed();
        }
        defineComputed() {
            let _this = this;
            let DepUpdated = function() {
                let val = _this.cb();
                _this.onComputedUpData(val);
            }
            Object.defineProperty(_this.obj, _this.key, {
                get() {
                    Dep.target = DepUpdated;
                    let val = _this.cb();
                    Dep.target = null;
                    return val;
                },
                set() {
                    console.log('wacther不能被修改');
                }
            })
        }
    }
    <!--依赖收集器-->
    class Dep {
        constructor(){
            this.deps = [];
        }
        addSub() {
            if (Dep.target && this.deps.indexOf(Dep.target) == -1) {
                this.deps.push(Dep.target);
            }
        }
        notify() {
            this.deps.forEach(dep => {
                dep();
            })
        }
    }
    Dep.target = null;
    
    <!--验证-->
    let girl = new Observer({ height: '1.7', weight: '48', whole: '90分'})
    watcher(girl, 'isBeautiful', () => {
      return (girl.height > 1.65 && girl.weight < 48) ? '漂亮' : '差点漂亮'
    }, (val)=> {
        console.log(`girl的isBeautiful属性为${val}`)
    })
    
    image

    扩展

    终于结束了,来稍微总结下,依赖收集仅仅是用来实现计算属性的吗,下面我们看一个列子,我们使用vue的时候:

    new Vue({
        template: 
            `<div>
                <span>text1:</span> {{text1}}
                <span>text2:</span> {{text2}}
            <div>`,
        data: {
            text1: 'text1',
            text2: 'text2',
            text3: 'text3'
        }
    });
    

    由于响应式的原因,大家都知道当我们修改对应的text1或者text2的时候,数据驱动对应的dom便会发生更新,但是当我们修改对应的text3呢,对应的set是不是也会执行对应的渲染呢,其实不然,那么vue是怎么做的呢,原理图如下

    image
    • 每个组件实例都有相应的watcher实例

    • 渲染组件的过程,会把属性记录为依赖

    • 当我们操纵一个数据时,依赖项的setter会被调用,从而通知watcher重新计算,从而致使与之相关联的组件得以更新

    联想下,

    1. 既然模板渲染需要用到某个数据,那么一定会对这个数据进行访问,所以只要拦截getter,进行依赖收集,
    2. 当依赖的数据被设置时,setter能获得这个通知,如果有依赖的话进行对应更新render
      如此的话,上面说的问题是不是就解决了,vue在渲染视图的时候便会将有用到的data进行依赖收集,也就是只有被Dep.target标记过的才会进行收集。

    在vue里面,对应的观察者和观察目标又有哪些呢?

    • 依赖的数据是观察目标
    • 视图、计算属性、侦听器这些是观察者

    差不多了,希望这篇文章对大家有帮助,如果发现有任何错漏的地方,也欢迎向我指出,谢谢大家~(鄙人才疏学浅,如有理解不到或是错误的地方还望各位看客见谅)

    学习文章

    相关文章

      网友评论

          本文标题:(依赖收集vue) 联想学习

          本文链接:https://www.haomeiwen.com/subject/ucnzgqtx.html