• vue 双向数据绑定的实现学习(一)


    前言:本系列学习笔记从以下几个点展开

    • 什么是双向数据绑定
    • 双向数据绑定的好处
    • 怎么实现双向数据绑定
      • 实现双向数据数据绑定需要哪些知识点
        • 数据劫持
        • 发布订阅模式

     先看看我们要实现的目标是什么,如下动图:

    0、什么是双向数据绑定

      单向数据绑定:把Model 绑定到View上,当我们用js修改模型 Model 时候,视图View上对应的内容也会改动,这就是 数据动,页面动 。

      双向数据绑定:简言之 数据动 页面动,页面动,数据动, 典型的应用就是在做表单时候,输入框的内容改动后,跟该输入框的value 的值改动。

      

    看vue 官网上的这个V-model  的演示案例:

    1、双向数据绑定的好处

      要说出这个好处的时候,也只有在实际场景中才能对应的显示出来。比如我们需要实时显示数据,我们一边说话,一边实时显示我们说的话的文字内容,等等。这让我想起了去年参加云栖大会,台上的大佬一边说话,下面的字幕实时更新。(当然实现这个技术有很多技术点,我们不讨论这个内容,小编也才疏学浅,搞不懂)

    以上的都是废话,我们直接看看怎么实现这个双向数据绑定。

    一、实现原理

      Vue实现双向数据绑定的原理:数据劫持 + 发布订阅模式(有的也称为观察者模式)

      数据劫持的核心技术: Object.defineProperty()

      **vue 3.0 已经用的不是这个技术了,采用是 原生的 Proxy,据说速度能够提升100%,截张尤大的ppt,** 2018-11-21 修改本篇笔记

    (香菇,刚研究会一点,就立马变了,这就是前端世界),Proxy 的方式将会在本系列笔记结束后,再记录这个技术点的使用

    二、数据劫持的方法 Object.defineProperty()

      先上一个参照代码,它长这个样子:

    var book = {
     _year: 2004,
     edition: 1
    };
    Object.defineProperty(book, "year", {
     get: function(){
       console.log('访问year了,返回_year')   
    return this._year; }, set: function(newValue){   if (newValue > 2004) {     this._year = newValue;
         console.log('重新设置_year了,并返回edition')     
    this.edition += newValue - 2004;   } } }); book.year = 2005; alert(book.edition); //2

     ---摘自 JavaScript高级程序设计

      Object.defineProperty() 的具体介绍,我们本文不做具体展开,查看我这里的一篇文章,Object.defineProperty。 我们这里先要知道这么一个事情。这个方法要传入三个参数,传入的数据对象data,属性key,描述符对象。其中,描述符(descriptor)对象的属 性必须是:configurable、enumerable、writable 和 value。设置其中的一或多个值,可以修改 对应的特性值。我们需要用的是这访问器属性。当我们在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。上文代码上的 get 方法,在读取属性时调用的函数,set方法,在写入属性时调用的函数。

    三、发布订阅者模式

      我画了一个图,来理解这个模式,如下图:

      

    代码解释:

    //下面封装一个单例模式,内容是发布订阅模式
    let event = {
        eventList: [],
        listener: function (key, fn) {
            if (!this.eventList[key]) { //没有订阅过此类消息,创建一个缓存列表
                this.eventList[key] = [];
            }
            this.eventList[key].push(fn)
        },
        trigger: function () {
            let key = Array.prototype.shift.call(arguments); // marry
            let fns = this.eventList[key];
            if (!fns || fns.length == 0) { //没有订阅 则返回
                return false;
            }
            for (let i = 0, fn; fn = fns[i++];) {
                fn.apply(this, arguments)
                // 调用 event.listen 里面的 fn 方法,通过apply将当前执行的对象指向当前的this,arguments 传进 fn 函数
            }
        },
        remove: function (key, fn) { // 取消订阅
            let fns = this.eventList[key];
            if (!fns) {
                return false;
            }
            if(!fn) {
                fns && (fns.length = 0)
            } else {
                for (let l = fns.length-1; l>=0; l--) {
                    let _fn = fns[l];
                    if( _fn === fn) {
                        fns.splice(l, 1)
                    }
                }
            }
        },
        install: function (obj) {
            for (let i in this) {
                if (i === 'install') {
                    return false
                }
                obj[i] = this[i];
            }
        }
    }
    
    
    let testMsg = {}
    event.install(testMsg)
    
    // 上面方法 就会将event的方法 浅拷贝给 testMsg, 这样testMsg就有 event的方法和属性
    testMsg.listener('rich', fn1 = (name) => {
        console.log(`${name}知道你有钱了`)
    })
    testMsg.listener('borrowMoney', fn2 =  (name) => {
        console.log(`${name}想问你借钱`)
    })
    
    // listen方法将事件 放进队列
    // testMsg.remove('rich', fn1) // 取消订阅
    
    // trigger方法,处理事件队列的方法,调用listen的函数的里面的回调函数 fn
    testMsg.trigger('rich', '张三')
    testMsg.trigger('rich', '张三2')
    testMsg.trigger('borrowMoney', '李四')
    testMsg.trigger('borrowMoney', '李四2')
    
    
    
    // 代码总结:
    // 订阅的事件具有对应的key
        // 通过listener方法,将具体的事件队列保存到 eventList ,可以理解为缓存列表也可以是事件队列;
        // 执行trigger 方法,将事件队列拿出来执行调用
        // remove 方法根据对应的key值,删除对应的订阅事件
    // 模式总结:封装一个单例event, 执行installEvent方法,将想要event对象拷贝到某个对象中去,
    // 发布者trigger方法,监听者listen方法 ,trigger 发布一个东西,listen立马知道你要发布的东西
    View Code

     

    四、实现分解

    • 主函数入口
      •   
        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)
        }
    • 主函数调用
      •   
        <script>
            const vm = new Myvue({
                el: "#app",
                data: {
                    name: "vue 双向数据绑定test1"
                }
            });
        </script>
    • 监听器
    • 订阅者
    • 模板编译器

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

    下一篇 vue 双向数据绑定的实现学习(二)-监听器的实现

    文章参考:

      https://www.cnblogs.com/beevesnoodles/p/9844854.html

      https://github.com/youngwind/blog/issues/87

      https://juejin.im/post/5a9108b6f265da4e7527b1a4

    后记:代码只做基本实现,不做代码健壮性处理,一些错误处理已经忽略

  • 相关阅读:
    CodeForces
    CodeForces
    CodeForces
    CodeForces
    CodeForces
    CodeForces
    CodeForces
    CodeForces
    Code froces 831 A. Unimodal Array
    无聊随便敲敲
  • 原文地址:https://www.cnblogs.com/adouwt/p/9928278.html
Copyright © 2020-2023  润新知