• javascript中的双向绑定


    阅读目录

    前言:

       双向数据绑定的含义:可以将对象的属性绑定到UI,具体的说,我们有一个对象,该对象有一个name属性,当我们给这个对象name属性赋新值的时候,新值在UI上也会得到更新。同样的道理,当我们有一个输入框或者textarea的时候,我们输入一个新值的时候,也会在该对象的name属性得到更新。

    双向数据绑定的思想是:

      1. 我们需要一个方法来识别那个UI元素被绑定了相对应的属性。

      2. 我们需要监听属性和UI元素的变化。

      3. 我们需要将所有的变化传播到绑定的对象和元素上。

    实现数据绑定的做法有如下几种:

       1. 发布者--订阅模式(backbone.js)

       2. 脏值检查(angular.js)

       3. 数据劫持 (vue.js)

    一:发布订阅模式实现数据双向绑定

       我们现在使用 发布者-订阅模式来实现一个简单的 双向绑定数据;
    发布-订阅模式的原理:它是一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。

    先来理解下 现实生活中的发布-订阅模式

    比如小红最近在淘宝网上看上一双鞋子,但是呢 联系到卖家后,才发现这双鞋卖光了,但是小红对这双鞋又非常喜欢,所以呢联系卖家,问卖家什么时候有货,卖家告诉她,要等一个星期后才有货,卖家告诉小红,要是你喜欢的话,你可以收藏我们的店铺,等有货的时候再通知你,所以小红收藏了此店铺,但与此同时,小明,小花等也喜欢这双鞋,也收藏了该店铺;等来货的时候就依次会通知他们;

    在上面的故事中,可以看出是一个典型的发布订阅模式,卖家是属于发布者,小红,小明等属于订阅者,订阅该店铺,卖家作为发布者,当鞋子到了的时候,会依次通知小明,小红等。

    发布订阅模式的优点:
      1. 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象。
      2. 发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变;

    发布订阅模式的缺点:
    1. 创建订阅者需要消耗一定的时间和内存。
    2. 虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护等等。

    实现发布-订阅模式的步骤:
    1. 谁是发布者?(比如上面的卖家)
    2. 给发布者添加一个缓存列表,用于存放回调函数来通知订阅者。
    3. 发布消息,发布者遍历这个缓存列表,依次触发存放的订阅者回调函数。

    下面我们先来实现一个简单的发布-订阅模式(用户订阅鞋子的简单demo如下:)

    // 定义发布者
    var pubSub = {
      // 缓存列表,存放订阅者的回调函数
      callbacks: [],
      // 增加订阅者
      on: function(msg, callback) {
        if (!this.callbacks[msg]) {
          // 如果没有订阅过此消息,给这个消息创建一个缓存列表
          this.callbacks[msg] = [];
        } 
        this.callbacks[msg].push(callback);
      },
      // 发布消息
      public: function() {
        // 取出该消息对应的回调函数集合
        var key = Array.prototype.shift.call(arguments);
        var fns = this.callbacks[key];
    
        // 如果没有订阅过该消息的话,直接返回
        if(!fns || fns.length === 0) {
          return;
        }
        for(var i = 0, fn; fn = fns[i++]; ) {
          fn.apply(this, arguments);
        }
      }
    };
    // 现在空智 订阅如下消息
    pubSub.on('red', function(size) {
      console.log("你的尺码是:"+size);  // 控制台会打印出: 你的尺码是:40
    });
    
    // 空智2 订阅如下消息
    pubSub.on('blue', function(size) {
      console.log("你的尺码是:"+size); // 控制台会打印出: 你的尺码是:42
    });
    
    // 发布消息
    pubSub.public('red', 40);
    pubSub.public('blue', 42);

    通过上面了解了简单的发布-订阅模式,现在我们来看看使用发布-订阅模式来实现双向绑定。

    思路如下:
    在HTML代码中使用一个自定义属性进行绑定,所有进行绑定的javascript对象以及DOM元素都将订阅一个发布者对象。不管什么时候如果一个javascript对象或一个输入框被监听到发生变化时候,所有依赖发布者对象都将会得到通知。

    代码如下:

    <!DOCTYPE html>
     <html>
      <head>
        <meta charset="utf-8">
        <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
        <meta content="yes" name="apple-mobile-web-app-capable">
        <meta content="black" name="apple-mobile-web-app-status-bar-style">
        <meta content="telephone=no" name="format-detection">
        <meta content="email=no" name="format-detection">
        <title>标题</title>
        <link rel="shortcut icon" href="/favicon.ico">
      </head>
      <body>
        <h3>发布订阅模式实现数据双向绑定demo</h3>
        <input type="text" id="inputId" data-bind-objId="name" style="border: 1px solid #ccc; 200px; height: 24px; "/>
        <div id="modelView" style="border: 1px solid red;  200px; height: 24px; margin-top:20px; margin-bottom:20px;"></div>
        <button id="btn">model的变化导致view的变化</button>
        <script>
          function DataBinder(objId, changeId) {
            // 发布订阅原型
            var pubSub = {
              allCallbacks: [],
              // 增加订阅者
              on: function(eventName, callback) {
                // 如果没有订阅过该消息,给这个消息创建一个缓存列表
                if(!this.allCallbacks[eventName]) {
                  this.allCallbacks[eventName] = [];
                }
                this.allCallbacks[eventName].push(callback);
              },
              // 发布消息
              public: function() {
                
                var eventName = Array.prototype.shift.call(arguments);
                // 取出该消息对应的回调函数集合
                var callbacks = this.allCallbacks[eventName];
                if (!callbacks || callbacks.length === 0) {
                  return false;
                }
                for (var i = 0; i < callbacks.length; i++) {
                  var callback = callbacks[i];
                  callback.apply(this, arguments);
                }
              }
            };
            var dataAttr = "data-bind-" + objId;
            var message = objId + ":change";
    
            var changeHandler = function(e) {
              var target = e.target || e.srcElement;
              var attrName = target.getAttribute(dataAttr);
              if (attrName && attrName !== "") {
                // 发布消息
                pubSub.public(message, attrName, target.value);
              }
            };
            // 监听视图层的事件变化
            if (document.addEventListener) {
              document.addEventListener('input', changeHandler, false);
            } else {
              document.attachEvent("oninput", changeHandler);
            }
    
            // 监听模型上的变化,并把变化传播到所有绑定的元素上
            pubSub.on(message, function(attrName, newVal) {
              var elements = document.querySelectorAll("[" + dataAttr + "=" + attrName + "]");
              var tagName;
              for (var i = 0, ilen = elements.length; i < ilen; i++) {
                tagName = elements[i].tagName.toLowerCase();
                if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
                  elements[i].value = newVal;
                  changeId.innerHTML = newVal;
                } else {
                  elements[i].innerHTML = newVal;
                  changeId.innerHTML = newVal;
                }
              }
            });
            return pubSub;
          }
    
          // 定义一个User模型
          function User(uid, changeId) {
            var binder = new DataBinder(uid, changeId);
            var user = {
              attrs: {},
              set: function(key, value){
                this.attrs[key] = value;
                // model变化通知更新view
                binder.public(uid + ":change", key, value);
              },
              get: function(key) {
                return this.attrs[key];
              }
            };
            return user;
          }
    
          // 绑定model到view
          var modelView = document.getElementById("modelView");
          var inputId = document.getElementById("inputId");
    
          // 测试demo
          var user = new User("objId", modelView);
          user.set("name", 1);
    
          modelView.innerHTML = user.get("name");
    
          // 测试模型的变化到 视图层的变化 
          var btn = document.getElementById("btn");
          btn.onclick = function() {
            var value = inputId.value;
            user.set("name", parseInt(value) + 1);
            modelView.innerHTML = user.get("name");
          };
        </script>
      </body>
    </html>

    查看效果

    二:使用Object.defineProperty 来实现简单的双向绑定。

     想了解 Object.defineProperty 时的话 请看这篇文章   

    实现的效果简单如下:页面上有一个input输入框和div显示框,当在input输入框输入值的时候,div也会显示对应的值,当我打开控制台改变 obj.name="输入任意值"的时候,按回车键运行下,input输入框的值也会跟着变,可以简单的理解为 模型-> 视图的 改变,以及 视图 -> 模型的改变。如下代码:

    <!DOCTYPE html>
     <html>
      <head>
        <meta charset="utf-8">
        <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
        <meta content="yes" name="apple-mobile-web-app-capable">
        <meta content="black" name="apple-mobile-web-app-status-bar-style">
        <meta content="telephone=no" name="format-detection">
        <meta content="email=no" name="format-detection">
        <title>标题</title>
        <link rel="shortcut icon" href="/favicon.ico">
      </head>
      <body>
        <h3>使用Object.defineProperty实现简单的双向数据绑定</h3>
        <input type="text" id="input" />
        <div id="div"></div>
        <script>
            var obj = {};
            var inputVal = document.getElementById("input");
            var div = document.getElementById("div");
    
            Object.defineProperty(obj, "name", {
              set: function(newVal) {
                inputVal.value = newVal;
                div.innerHTML = newVal;
              }
            });
            inputVal.addEventListener('input', function(e){
              obj.name = e.target.value;
            });
        </script>
      </body>
    </html>

    查看效果

  • 相关阅读:
    Android-MediaProvider数据库模式
    java String.getBytes()编码问题——String.getBytes(charset)
    设置Eclipse中的tab键为4个空格的完整方法
    linux下的zip命令
    关于facebook infer 静态代码审查工具
    关于软件测试人员能力模型的建立(from知乎)
    [ 转] 漫谈iOS Crash收集框架
    iOS开发如何提高(from 唐巧的博客)
    c++ web服务器
    小米开源监控系统的说明文档
  • 原文地址:https://www.cnblogs.com/tugenhua0707/p/7471381.html
Copyright © 2020-2023  润新知