美文网首页Web前端之路饥人谷技术博客让前端飞
2017百度前端技术学院——vue源码分析之——动态数据绑定二(

2017百度前端技术学院——vue源码分析之——动态数据绑定二(

作者: 起这么长的名字根本没有用 | 来源:发表于2017-02-28 21:27 被阅读991次

    源码地址

    一、发布/订阅者模式

    订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码;如下图

    二、程序详解(实现$watcher方法)

    一共三个js文件:

    • index.js:用来遍历数据,并在数据对象的每个属性上添加gettersetter,当有数据变动的时候给通道发送一个notify
    • dep.js:通道,用来连接发布者和订阅者,有一个数组变量通过addSub方法来存放watcher,当通道收到notify之后遍历数组触发每个watcherupdate
    • watcher.js:充当订阅者,获取更新后的数据并且执行回调函数
    watcher.js:
    // Watcher的实例就是订阅者
    function Watcher(vm,exp,cb){
        console.log("watcher执行了");
        this.cb = cb;
        this.vm = vm;
        this.exp = exp
        this.value = this.get()//更新前的值
    }
    var w = Watcher.prototype;
    
    // 订阅者的更新方法
    w.update = function (){
        var value = this.get() //这里是更新后的值
        if(value!==this.value){
            this.value = value //用新值覆盖旧值
            this.cb.call(this.vm,value)
        }
    }
    // 通过Watcher的实例调用了getter
    w.get = function (){
        Dep.target = this//表明是watcher调用了getter
        return this.vm.data[this.exp] //这里会调用get方法
    }
    
    这里有两个小问题:
    1. 如何把watcher加入到数组里;
    2. 如何判断是watcher触发的getter还是普通触发getter
    解决办法:

    在dep.js中设置一个全局变量,如果是watcher触发的getter就把这个watcher实例赋值给这个变量,然后在index.js中的getter里进行判断,如果全局变量不为null,则把watcher实例添加到数组;

    dep.js代码如下:

    // 定义一个Dep(调度中心),用来维护一系列观察者,方便添加观察者
    function Dep(){
        this.subs = [] //存放订阅者的数组
    }
    var s = Dep.prototype;
    // 把订阅者都存到数组里面
    s.addSub = function (sub){
        this.subs.push(sub)
    }
    // 订阅者想订阅的事件,注册到事件中心
    s.notify = function (){
        // 一旦调用了set就触发notify,然后遍历每个观察者,并触发他们相应的update方法
        this.subs.forEach(function (sub){
            sub.update();
            /* 
                sub.update():
                调度中心统一调度订阅者注册到调度中心的处理代码
             */
        })
    }
    Dep.target = null;//定义一个全局变量,用来判断是否是watcher调用了getter
    

    index.js代码如下:

    // 发布者,对data做监听,提供了某个数据项变化的能力
    function Observer(data){
        this.data = data;
        this.walk(data)
    }
    // 将原型赋值给一个变量
    var p = Observer.prototype;
    
    // 遍历对象所有属性,包括子属性
    p.walk = function (obj){
        var _this = this;
        Object.keys(obj).forEach(function (key){
            _this.observer(obj[key])
            _this.convert(key,obj[key])
        })
    }
    
    // 绑定getter 和setter
    p.convert = function (key,val){
        //每次set函数调用的时候,触发notify
        var dep = new Dep()  //发布给订阅者
        var _this = this;
        Object.defineProperty(this.data,key,{
            configurable:true,
            enumarable:true,
            get:function (){
                console.log("你访问了"+key);
    
                // Watcher的实例调用了getter,将watcher加入到调度中心的数组里面
                if(Dep.target){
                    dep.addSub(Dep.target)
                }
    
                return val
            },
            set:function (newVal){
                // 如果新设置的值和原来相等则不重新赋值
                if(newVal==val){
                    return 
                }
                console.log("你设置了"+key);
                console.log("新的"+key+"="+newVal);
                val = newVal
                // 如果设置的新值是一个对象,则递归它,加上set/get
                _this.observer(newVal)
                dep.notify()//发布者发布到订阅中心
            }
        })
    }
    // 判断属性值是否是一个对象,如果是再深度监听
    p.observer = function (val){
        if(typeof val ==="object"){
             new Observer(val)
        }
    }
    // 定义一个watcher
    p.$watcher = function (exp,cb){
        new Watcher(this,exp,cb)
    }
    
    let data = {
        name:'dailu',
        age:25
    };
    var app = new Observer(data);
     app.$watcher('age', function(age) {
             console.log(`我的年纪变了,现在已经是:${age}岁了`)
     });
    app.data.age = 100;
    // console.log(app.data.name);
    

    相关文章

      网友评论

        本文标题:2017百度前端技术学院——vue源码分析之——动态数据绑定二(

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