Vue--双向绑定的原理--手写双向绑定(模仿vue)

逃离我推掉我的手 2023-10-02 17:37 34阅读 0赞

原文网址:Vue—双向绑定的原理—手写双向绑定(模仿vue)_IT利刃出鞘的博客-CSDN博客

简介

说明

本文用示例介绍Vue的双向绑定的原理(实现方式)。

什么是双向绑定

双向绑定:数据和视图任意一个变化,则另一个也跟着变化。也就是:数据变化更新视图,视图变化更新数据。

Vue是MVVM模型。数据属于Model,视图属于View。本文要介绍的双向绑定就是:VM(ViewModel)。

MVVM表示的是 Model-View-ViewModel

  • Model:模型层。负责处理业务逻辑以及和服务器端进行交互
  • View:视图层:负责将数据模型转化为UI展示出来,可以简单理解为HTML页面
  • ViewModel:视图模型层。用来连接Model和View,是Model和View之间的通信桥梁

Vue双向绑定的实现方案

官网

深入响应式原理 — Vue.js

简介

Vue是通过数据劫持和观察者模式(订阅者模式)来实现双向绑定的。



















数据劫持

观察者模式

目的

监视数据的变化。

用于数据变化时更新视图。

监视视图的变化。

用于视图变化时更新数据。

实现方案

Vue2: Object.defineProperty()

Vue3: Proxy(ES6的新特性)

对每个元素节点进行扫描和解析并绑定更新函数。

Object.defineProperty

优点

  1. 兼容性好。(ES5就已支持)

缺点

  1. 不能监听数组;

    1. 因为数组没有getter和setter。(数组长度不确定,如果太长性能负担太大)
  2. 只能监听属性,而不是整个对象;需要遍历属性;
  3. 只能监听属性变化(set、get),不能监听属性的删减;

Proxy

优点

  1. 可以直接监听数组的变化
  2. 可以直接监听整个对象,,而非是对象的某个属性
  3. 拦截方法丰富: 多达13种, 不限于 get、set、deleteProperty、has等。

缺点

  1. 兼容性差。(ES6的新特性)

详述

双向绑定需要三个部分:监听器(Observer),解析器(Compile),订阅者(Watcher)。

  • 监听器(Observer)

    • 监听数据的变化。
    • 对数据的属性进行递归遍历,都加上setter和getter。给数据赋值时,会触发setter;读数据时,会触发getter。
  • 解析器(Compile)

    • 作用1:初始化页面。

      • 对每个元素节点进行扫描和解析,利用正则将页面中的”{ {xxx}}“替换成data中对应的数据
    • 作用2:监听视图的变动。

      • 将每个指令对应的节点绑定更新函数,添加订阅者。一旦订阅者发出通知,就执行相应的更新函数。
  • 订阅者(Watcher)

    • 连接Observer和Compile 之间的桥梁。
    • 通过Observer监听数据变化,更新视图。
    • 通过Compile监听视图变化,更新数据。

手写双向绑定(简单实现)

需求:将输入的内容(“视图”内容)保存到对象(“数据”内容)中,对象保存时,同时更新

的内容(“视图”内容)。

代码

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>This is title</title>
  6. </head>
  7. <body>
  8. <input type="text" id="id-input"><br/>
  9. <div id="id-div"></div>
  10. <script>
  11. let user = {};
  12. let input = document.querySelector("#id-input");
  13. let text = document.querySelector("#id-div");
  14. // 数据到视图 model => view
  15. Object.defineProperty(user,"name",{
  16. get:function(){
  17. console.log('获取user的name');
  18. return this._name;
  19. },
  20. set:function(val){
  21. console.log('修改user的name为:' + val);
  22. this._name = val;
  23. text.textContent = this._name;
  24. }
  25. })
  26. // 视图到数据 view => model
  27. input.addEventListener('input',function(e){
  28. user.name = e.target.value;
  29. console.log("-----------------------")
  30. console.log("监听器中将user的name设置为:" + user.name);
  31. })
  32. </script>
  33. </body>
  34. </html>

测试

a9747cc9f7cd424894f78f590e67a9c2.gif

手写双向绑定(模仿vue2)

代码

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>This is title</title>
  6. </head>
  7. <body>
  8. <!-- 实现vue -->
  9. <div id="app">
  10. <input type="text" v-model="text">
  11. {
  12. { text }}
  13. </div>
  14. <script type="text/javascript">
  15. function defineReactive(obj, key, value) {
  16. let dep = new Dep();
  17. Object.defineProperty(obj, key, {
  18. get: function () {
  19. if (Dep.target) {
  20. dep.addSub(Dep.target);
  21. }
  22. return value
  23. },
  24. set: function (newVal) {
  25. // 数据没有改变则不需要通知
  26. if (newVal === value) {
  27. return
  28. }
  29. value = newVal;
  30. console.log('新值:' + value);
  31. // 数据改变,通知订阅者
  32. dep.notify();
  33. }
  34. })
  35. }
  36. // 观察者函数
  37. function observe(obj, vm) {
  38. for (let key of Object.keys(obj)) {
  39. defineReactive(vm, key, obj[key]);
  40. }
  41. }
  42. /*编译函数*/
  43. function compile(node, vm) {
  44. let reg = /\{\{(.*)\}\}/; // 来匹配 {
  45. { xxx }} 中的xxx
  46. // 如果是元素节点
  47. if (node.nodeType === 1) {
  48. let attr = node.attributes;
  49. // 解析元素节点的所有属性
  50. for (let i = 0; i < attr.length; i++) {
  51. if (attr[i].nodeName === 'v-model') {
  52. let name = attr[i].nodeValue; // 看看是与哪一个数据相关
  53. node.addEventListener('input', function (e) {
  54. vm[name] = e.target.value; // 将实例的text 修改为最新值
  55. });
  56. node.value = vm[name]; // 将data的值赋给该node
  57. node.removeAttribute('v-model');
  58. }
  59. }
  60. }
  61. // 如果是文本节点
  62. if (node.nodeType === 3) {
  63. if (reg.test(node.nodeValue)) {
  64. let name = RegExp.$1; // 获取到匹配的字符串
  65. name = name.trim();
  66. // node.nodeValue = vm[name]; // 将data的值赋给该node
  67. new Watcher(vm, node, name); // 不直接通过赋值的操作,而是通过绑定一个订阅者
  68. }
  69. }
  70. }
  71. // Watcher构造函数
  72. function Watcher(vm, node, name) {
  73. Dep.target = this; // Dep.target 是一个全局变量
  74. this.vm = vm;
  75. this.node = node;
  76. this.name = name;
  77. this.update();
  78. Dep.target = null;
  79. }
  80. Watcher.prototype = {
  81. update() {
  82. this.get();
  83. this.node.nodeValue = this.value; // 注意,这是更改节点内容的关键
  84. },
  85. get() {
  86. this.value = this.vm[this.name]; // 触发相应的get
  87. }
  88. }
  89. // dep构造函数
  90. function Dep() {
  91. this.subs = [];
  92. }
  93. Dep.prototype = {
  94. addSub(sub) {
  95. this.subs.push(sub);
  96. },
  97. notify() {
  98. this.subs.forEach(function (sub) {
  99. sub.update();
  100. })
  101. }
  102. }
  103. function nodeToFragment(node, vm) {
  104. let fragment = document.createDocumentFragment();
  105. let child;
  106. while (child = node.firstChild) {
  107. compile(child, vm);
  108. fragment.appendChild(child);
  109. }
  110. return fragment
  111. }
  112. // Vue构造函数
  113. function MyVue(options) {
  114. this.data = options.data;
  115. let data = this.data;
  116. observe(data, this);
  117. let id = options.el;
  118. let dom = nodeToFragment(document.getElementById(id), this);
  119. // 处理完所有dom节点后,重新将内容添加回去
  120. document.getElementById(id).appendChild(dom);
  121. }
  122. let vm = new MyVue({
  123. el: 'app',
  124. data: {
  125. text: 'hello world'
  126. }
  127. });
  128. </script>
  129. </body>
  130. </html>

测试

cd20d13463f04f949ec996d97b0607f0.gif

其他网址

通俗易懂了解Vue双向绑定原理及实现 - osc_2x36yftz的个人空间 - OSCHINA - 中文开源技术交流社区

手写JS(七)—实现VUE的双向绑定 - 掘金

手写VUE mvvm双向数据绑定_Alinachanchan的博客-CSDN博客_手写vue双向绑定

发表评论

表情:
评论列表 (有 0 条评论,34人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Vue双向

    实现mvvm的双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发

    相关 VUE双向

    1、什么是setter、getter ?   答:首先,别误以为他们就是一会要说的get、set,我们先看一句定义: >        对象有两种属性:(1)数据属性,