zoukankan      html  css  js  c++  java
  • vue 双向数据绑定的实现学习(二)- 监听器的实现

    废话:上一篇https://www.cnblogs.com/adouwt/p/9928278.html 

    提到了vue实现的基本实现原理:Object.defineProperty() -数据劫持 和 发布订阅者模式(观察者),下面讲的就是数据劫持在代码中的具体实现。

    1.先看如何调用

      

    new一个对象,传入我们的参数,这个Myvue ,做了啥?

      

    上面看到了在实例化一个Myvue 对象的时候,会执行init方法, init 方法做了两个事,调用了observer 方法,和 实例化调用了 compile 方法。 到这里我们就明白了,实例化一个Myvue后,我们要做的就是监听数据变化和编译模板 。

    上面Object.key() 方法,实例化时传入的data里面对应的变量缓存到 Myvue 对象的 $prop上,这样方便在后续处理数据。怎么个方便法呢!...

    2.observer 的实现

      observer ,模式里面的角色定位 他是一个发布者,也可以理解为是一个观察者

    function observer (data) {
        if(!data || typeof data !== 'object') {
            return;
        }
        Object.keys(data).forEach(key => {
            // 对每个属性监听处理
            defineReactive(data, key, data[key]);
        })
    }

      defineReactive

    function defineReactive (data,key,value) {
        // 每次访问/修改属性的时候 实例化一个调度中心Dep
        var dep = new Dep();
        Object.defineProperty(data,key,{
            get: function() {
               // 添加到watcher 的Dep 调度中心
                if (Dep.target) { // Dep.target 是个什么鬼? 转到watcher.js 它是某个订阅者 watcher
                    dep.addSub(Dep.target);  //这个代码段的意思: 如果有订阅者(访问/修改属性的时候) 就将这个订阅者统一放进 Dep 调度中心中
                }
                // console.log(`${key}属性被访问了`)
                return value
            },
            set: function (newValue) {
                if (value != newValue) {
                    // console.log(`${key}属性被重置了`)
                    value = newValue
                    dep.notify(); //我这里有做改动了,通知调度中心的notify方法
                }
            }
        })
        // 递归调用,observe 这个value
        observer(value)
    }

      Dep: 这里是所有订阅者的一个调度中心,它不是直接监听 发布者的信息,发布者将要发布的信息 发布到 一个中介、调度中心(Dep),由这个Dep 来调度信息给哪个订阅者(Watcher)

    // 统一管理watcher订阅者的Dep (调度中心)  Dispatch center
    function Dep () {
        // 所有的watcher 放进这里统一管理
        this.subs = []
    }
    Dep.target = null;
    // 通知视图更新dom的 notify的方法
    Dep.prototype.notify   = function () {
        // this.subs 是上面订阅器watcher 的集合
        this.subs.forEach(sub => {
            // sub 是某个Watcher 具体调用某个Watcher的update 方法
            sub.update()
        })
    }
    
    // 添加订阅者的方法
    Dep.prototype.addSub  = function (sub) {
        this.subs.push(sub)
    }

    3.订阅器Watcher

    // 具体的订阅器Watcher
    // 传入一个vue 的实例, 监听的属性, 以及处理的回调函数
    function Watcher (vm,prop,callback) {
        this.vm  =  vm;
        this.$prop = prop;
        this.value = this.get();
        this.callback = callback; // 具体watcher所具有的方法,不同的watcher 不同的回调函数,处理不同的业务逻辑
     }
    // 添加watcher 获得属性的get 方法,当有属性访问/设置 的时候,就产生订阅者 将这个订阅者放进调度中心
    Watcher.prototype.get = function () {
        Dep.target = this;
        // 获得属性值
        const value = this.vm.$data[this.$prop];
        return value
    }
    // 添加watcher的更新视图的方法
    Watcher.prototype.update = function () {
        // 当属性值有变化的时候,执行方法,更新试图
        const value = this.vm.$data[this.$prop];
        const oldValue = this.value;
        // update 执行的时候,先获取 vm 中data实时更新的属性值,this.value 是vm data中之前的老值
        if (oldValue != value) {
            // console.log('人家通知了,我要改变了')
            // 把刚刚获取的更新值赋给之前vm data 中的值
            this.value =  value 
            // 执行回调函数 具体怎么处理这个,看实际调用时候 callback 的处理 
            this.callback(this.value)
        }
    }

    4.模板编译

    (为了直接看到页面数据变化的效果,在模板编译的核心数据处理上做了dom 操作,下一篇将讲模板编译的一些细节处理)

    // dom模板编译 vm 就是我们最上面的Myvue 对象
    function Compile (vm) {
        this.vm = vm;
        this.$el = vm.el;
        // this.data = vm.data;
        this.fragment = null; // 用作后面模板引擎 创建文档片段
        this.init()
    }
    
    Compile.prototype = {
        // init 方法简单处理,直接做dom 操作,后面会用详细的模板引擎的学习
        init: function () {
            let value = this.vm.$data.name // 初始化获取到的值 放进dom节点中
            document.querySelector('.form-control').value = value;
            document.querySelector('.template').textContent  = value
            // 通知订阅者更新dom
            new Watcher(this.vm,this.vm.$prop, (value) => {
                document.querySelector('.form-control').value = value;
                document.querySelector('.template').textContent  = value
            })
            document.querySelector('.form-control').addEventListener('input',(e) => {
                let targetValue = e.target.value
                if(value !== targetValue) {
                    this.vm.$data.name = e.target.value // 将修改的值 更新到 vm的data中
                    document.querySelector('.form-control').value = targetValue; // 更新dom 节点
                    document.querySelector('.template').textContent  = targetValue
                }
                 
            },false)
        }
    }

    这样就可以看到 在表单中,数据的双向绑定了。

    未完待续,错误之处,敬请指出,共同进步!

    下一篇 vue 双向数据绑定的实现学习(三)- 模板编译

    附:演示代码:

    js:

    function Myvue (options) {
        this.$options = options
        this.$el = document.querySelector(options.el);
        this.$data = options.data;
        Object.keys(this.$data).forEach(key => {
            this.$prop = key;
        })
        this.init()
    }
    Myvue.prototype.init = function () {
        // 监听数据变化
        observer(this.$data);
                // 获得值
                // let value = this.$data[this.$prop];
                // 不经过模板编译直接 通知订阅者更新dom
                // new Watcher(this,this.$prop,value => {
                //     console.log(`watcher ${this.$prop}的改动,要有动静了`)
                //     this.$el.textContent = value
                // }) 
        //通知模板编译来执行页面上模板变量替换
        new Compile(this)
    }
    
    function observer (data) {
        if(!data || typeof data !== 'object') {
            return;
        }
        Object.keys(data).forEach(key => {
            // 对每个属性监听处理
            defineReactive(data, key, data[key]);
        })
    }
    
    function defineReactive (data,key,value) {
        // 每次访问/修改属性的时候 实例化一个调度中心Dep
        var dep = new Dep();
        Object.defineProperty(data,key,{
            get: function() {
               // 添加到watcher 的Dep 调度中心
                if (Dep.target) { // Dep.target 是个什么鬼? 转到watcher.js 它是某个订阅者 watcher
                    dep.addSub(Dep.target);  //这个代码段的意思: 如果有订阅者(访问/修改属性的时候) 就将这个订阅者统一放进 Dep 调度中心中
                }
                // console.log(`${key}属性被访问了`)
                return value
            },
            set: function (newValue) {
                if (value != newValue) {
                    // console.log(`${key}属性被重置了`)
                    value = newValue
                    dep.notify(); //我这里有做改动了,通知调度中心的notify方法
                }
            }
        })
        // 递归调用,observe 这个value
        observer(value)
    }
    
    // 统一管理watcher订阅者的Dep (调度中心)  Dispatch center
    function Dep () {
        // 所有的watcher 放进这里统一管理
        this.subs = []
    }
    Dep.target = null;
    // 通知视图更新dom的 notify的方法
    Dep.prototype.notify   = function () {
        // this.subs 是上面订阅器watcher 的集合
        this.subs.forEach(sub => {
            // sub 是某个Watcher 具体调用某个Watcher的update 方法
            sub.update()
        })
    }
    
    // 添加订阅者的方法
    Dep.prototype.addSub  = function (sub) {
        this.subs.push(sub)
    }
    
    // 具体的订阅器Watcher
    // 传入一个vue 的示例, 监听的属性, 以及处理的回调函数
    function Watcher (vm,prop,callback) {
        this.vm  =  vm;
        this.$prop = prop;
        this.value = this.get();
        this.callback = callback; // 具体watcher所具有的方法,不同的watcher 不同的回调函数,处理不同的业务逻辑
     }
    // 添加watcher 获得属性的get 方法,当有属性访问/设置 的时候,就产生订阅者 将这个订阅者放进调度中心
    Watcher.prototype.get = function () {
        Dep.target = this;
        // 获得属性值
        const value = this.vm.$data[this.$prop];
        return value
    }
    // 添加watcher的更新视图的方法
    Watcher.prototype.update = function () {
        // 当属性值有变化的时候,执行方法,更新试图
        const value = this.vm.$data[this.$prop];
        const oldValue = this.value;
        // update 执行的时候,先获取 vm 中data实时更新的属性值,this.value 是vm data中之前的老值
        if (oldValue != value) {
            // console.log('人家通知了,我要改变了')
            // 把刚刚获取的更新值赋给之前vm data 中的值
            this.value =  value 
            // 执行回调函数 具体怎么处理这个,看实际调用时候 callback 的处理 
            this.callback(this.value)
        }
    }
    
    // dom模板编译 vm 就是我们最上面的Myvue 对象
    function Compile (vm) {
        this.vm = vm;
        this.$el = vm.el;
        // this.data = vm.data;
        this.fragment = null; // 用作后面模板引擎 创建文档片段
        this.init()
    }
    
    Compile.prototype = {
        // init 方法简单处理,直接做dom 操作,后面会用详细的模板引擎的学习
        init: function () {
            let value = this.vm.$data.name // 初始化获取到的值 放进dom节点中
            document.querySelector('.form-control').value = value;
            document.querySelector('.template').textContent  = value
            // 通知订阅者更新dom
            new Watcher(this.vm,this.vm.$prop, (value) => {
                document.querySelector('.form-control').value = value;
                document.querySelector('.template').textContent  = value
            })
            document.querySelector('.form-control').addEventListener('input',(e) => {
                let targetValue = e.target.value
                if(value !== targetValue) {
                    this.vm.$data.name = e.target.value // 将修改的值 更新到 vm的data中
                    document.querySelector('.form-control').value = targetValue; // 更新dom 节点
                    document.querySelector('.template').textContent  = targetValue
                }
                 
            },false)
        }
    }
    View Code

    html:

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Vue双向绑定原理及实现</title>
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
            crossorigin="anonymous">
            <style>
                #app {
                    margin: 20px auto;
                     400px;
                    padding: 50px;
                    text-align: center;
                    border: 2px solid #ddd;
                }
            </style>
        </head>
    
        <body>
            <div id="app">
                <input class="form-control" v-model="name" type="text">
                <h1 class="template">{{name}}</h1>
            </div>
            <script src="./js/index1.js"></script>
            <script>
                const vm = new Myvue({
                    el: "#app",
                    data: {
                        name: "vue 双向数据绑定test1"
                    }
                });
            </script>
        </body>
    </html>
    View Code
  • 相关阅读:
    【转】编写高质量代码改善C#程序的157个建议——建议70:避免在调用栈较低的位置记录异常
    【转】编写高质量代码改善C#程序的157个建议——建议69:应使用finally避免资源泄漏
    【转】编写高质量代码改善C#程序的157个建议——建议68:从System.Exception或其他常见的基本异常中派生异常
    【转】编写高质量代码改善C#程序的157个建议——建议67:慎用自定义异常
    Arrays数组工具类中存在的坑!
    java.util.ArrayList
    java.util包下面的类---------01---示意图
    elasticsearch从入门到出门-08-Elasticsearch容错机制:master选举,replica容错,数据恢复
    elasticsearch从入门到出门-06-剖析Elasticsearch的基础分布式架构
    CentOS7.1安装 Vsftpd FTP 服务器
  • 原文地址:https://www.cnblogs.com/adouwt/p/10039900.html
Copyright © 2011-2022 走看看