双向绑定基础原理——Object.defineProperty()的使用

news/2024/7/10 0:23:32 标签: 设计模式, js, vue

文章目录

  • 1、极简双向绑定示例
    • 1.1 Object.defineProperty()
    • 1.2 实现过程
      • 1.2.1 单个DOM与单个Obj
      • 1.2.2 两个DOM与单个Obj
  • 2、思考
  • 3.参考资料


1、极简双向绑定示例

前端有关VUE的笔试和面试中,观察订阅者模式和VUE中双向绑定的原理这两道题几乎是必考的了。

Vue双向绑定vm视图模型简单来说就是利用了Object.defineProperty(),通过劫持setter,实现model到view,view到model则是一堆事件监听 输入框的input,选择组件的 change等等。具体复杂些的实现就要看观察订阅设计模式了。

核心是通过Object.defineProperty() + addEventListener,对data的每个属性进行了get、set的拦截。其实只用Object.defineProperty()已经可以实现双向绑定,只是这样做效率很低。

1.1 Object.defineProperty()

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。该方法有三个参数,(属性所在的对象,你要操作的属性,被操作的属性的特性),示例如下。

js">// 示例
let _xxObj = {}

Object.defineProperty(_xxObj, 'xx_val', {
    get: function (_n) {
          console.log('读取时get被触发')
        	// 可以在这里操作
          return _n;
        }
})

1.2 实现过程

1.2.1 单个DOM与单个Obj

这里定义了一个input框,一个用来显示值的span块,并定义了一个名为_xxObj的Object,现在利用Object.defineProperty() + addEventListener 来实现input中的value与该xxObj中的一个名为xx_val的属性的绑定。原理大致如下图。

img

代码如下。

 <body>
    <h1>极简双向绑定</h1>
    <input id="id_input_01" type="text"/>
    <h3>当前xx_value的值:<span id="id_text_01"></span></h3>

    <script>
      // -------------------获取元素--------------------------
      let eInput01 = document.getElementById("id_input_01");
      let eText01 = document.getElementById("id_text_01");
      
      // 定义一个obj
      let _xxObj = {
        xx_val: null,  // 这里的定义xx_val将会被之后的defineProperty()修改
      };

      // ---------------往Obj里写修改Dom的操作-------------------
      /* 
      Object.defineProperty() 方法会直接在一个对象上定义一个新属性,
      或者修改一个对象的现有属性,并返回此对象。
      */
      Object.defineProperty(_xxObj, "xx_val", {
        get: function () {
          console.log('读取时,get被触发')
        },
        set:function(_n){
          console.log("写入时,set被触发")

          // 直接在这里操作DOM 相当于在这里单向绑定了一次 Obj->DOM
          // _xxObj -> eInput01 eText01
          eInput01.value = _n;
        
          // 这里是单向绑定,用于查看当前value的值
          eText01.innerHTML = _n;
        }
      });
            
      // -----------------给DOM绑定监听事件----------------------------  
      eInput01.addEventListener("keyup", function(e){
        console.log(e.target.value);

        // 在这里操作Obj,相当于反向绑定了一次 eInput01->_xxObj
        _xxObj.xx_val = e.target.value;
        
      })
      

    </script>
  </body>

效果图如下。

img

1.2.2 两个DOM与单个Obj

现在新增加一个id为"id_input_02"的input框,并与_xxObj.xx_val双向绑定,示意图如下。

img

代码如下。

 <body>
    <h1>极简双向绑定</h1>
    <input id="id_input_01" type="text"/>
    <h3>当前xx_value的值:<span id="id_text_01"></span></h3>
    <input id="id_input_02" type="text"/>

    <script>
      // -------------------获取元素--------------------------
      let eInput01 = document.getElementById("id_input_01");
      let eInput02 = document.getElementById("id_input_02");
      let eText01 = document.getElementById("id_text_01");
      
      // 定义一个obj
      let _xxObj = {
        xx_val: null,  // 这里的定义xx_val将会被之后的defineProperty()修改
      };

      // ---------------往Obj里写修改Dom的操作-------------------
      /* 
      Object.defineProperty() 方法会直接在一个对象上定义一个新属性,
      或者修改一个对象的现有属性,并返回此对象。
      */
      Object.defineProperty(_xxObj, "xx_val", {
        get: function (_n) {
          console.log('读取时get被触发')
          return _n;
        },
        set:function(_n){
          console.log("写入时set被触发")
          
          // 直接在这里操作DOM 相当于在这里单向绑定了一次 Obj->DOM
          // _xxObj -> eInput01 eInput02 eText01
          eInput01.value = _n;
          eInput02.value = _n;

          // 这里是单向绑定,用于查看当前value的值
          eText01.innerHTML = _n;
        }
      });
            
      // -----------------给DOM绑定监听事件----------------------------  
      eInput01.addEventListener("keyup", function(e){
        console.log(e.target.value);

        // 在这里操作Obj,相当于反向绑定了一次 eInput01->_xxObj
        _xxObj.xx_val = e.target.value;
      })

      eInput02.addEventListener("keyup", function(e){
        console.log(e.target.value);

        // 在这里操作Obj,相当于反向绑定了一次 eInput02->_xxObj
        _xxObj.xx_val = e.target.value;
      })

    </script>
  </body>

实现效果如下,这两个输入框都和_xxObj.xx_value完成了双向绑定。

img

2、思考

当有多个DOM和多个Obj需要双向绑定的时候呢?

img

上面这种模式有什么问题?

答案显而易见,每当要新增加一个Obj和DOM的双向绑定时,都要到对应的Obj定义和DOM定义的地方新加代码。涉及到的DOM和Obj多了,维护起来就变得非常麻烦,而且代码也变得很不易读,是不符合“高内聚,低耦合”的原则的。于是,需要引入观察-订阅者模式来简化代码。

阅者模式在双向绑定中扮演了什么角色呢?它让双向绑定更有效率!

秉持着文章“简洁”的原则,这篇文章我就只介绍到这里,之后我会专门写一篇有关观察订阅者模式和一篇详解vue双向绑定原理的文章…敬请期待。

另外,值得注意的是,观察者模式和订阅发布模式是有一些差别的…
规律大致为:Publishers + Subscribers = Observer Pattern
在这里插入图片描述

3.参考资料

剖析Vue原理&实现双向绑定MVVM


http://www.niftyadmin.cn/n/555401.html

相关文章

搭建一个web服务下载HDFS的文件

需求描述 为了能方便快速的获取HDFS中的文件&#xff0c;简单的搭建一个web服务提供下载很方便快速&#xff0c;而且在web服务器端不留临时文件&#xff0c;只做stream中转&#xff0c;效率相当高&#xff01; 使用的框架是SpringMVCHDFS API 关键代码 Controller RequestMappi…

html 正则表达式密码判断,密码的正则表达式

当用户登录网站时&#xff0c;一般要求用户提供登录该网站的用户名称和密码。从某种意义上说&#xff0c;密码的复杂程度越高&#xff0c;用户的信息安全性越高。因此&#xff0c;很多网站在注册用户信息或修改用户密码时&#xff0c;往往会提供一个密码强度的说明&#xff0c;…

Spring的InitializingBean与DisposableBean方法

在bean初始化的时候&#xff0c;将所有显示提供的属性设置完毕后调用这个方法 org.springframework.beans.factory.InitializingBean#afterPropertiesSet单例对象销毁时候该方法被调用&#xff0c;可以用于释放资源 org.springframework.beans.factory.DisposableBean#destroy …

HeadFirst设计模式之“策略”——以动作冒险游戏为例

文章目录一、前言二、动作冒险游戏的实现UML图Weapon.javaCharacter.javaActionGame.java运行结果一、前言 策略模式定义了算法族&#xff0c;分别封装起来&#xff0c;让他们之间可以相互替代&#xff0c;此模式让算法的变化独立于使用算法的客户。 在Head First设计模式第一章…

将vbcrlf转换成html的换行符lt;brgt;,velocity 替换\n 换行符 为html

用velocity 将字符串中的"\n"换行符转变成现在非常常用下面提供两种解决方案如果用如下方法经过测试不能够正常工作#set($comments $stringUtils.replace($comments, "\n", ""))但是如果采用jdk1.4中的 String.replaceAll(new, old) 可以解决解…

设计模式之观察者模式——Js实现

文章目录前言参考资料1、版本1 你发布我就收到2、版本2 我只想收到我关注类型的消息3、版本3 不仅能发布文章&#xff0c;还能发布手机&#xff01;4、版本4 什么玩意&#xff1f;退订退订&#xff01;5、版本5 以类之名&#xff0c;用ES6语法重构之&#xff01;结语前言 《He…

Python概念(八)字符串格式化:%和.format

https://www.cnblogs.com/nulige/p/6115793.html转载于:https://www.cnblogs.com/Jomini/p/8763153.html

html flex几个属性,CSS Flex布局属性整理

Flex布局display: flex; 将对象作为弹性伸缩盒展示&#xff0c;用于块级元素display: inline-flex; 将对象作为弹性伸缩盒展示&#xff0c;用于行内元素注意兼容问题&#xff1a;webkit内核浏览器应使用前缀-webkitIE浏览器&#xff0c;可以很好的兼容IE11版本&#xff0c;对于…