vue手写双向绑定实现

news/2024/7/10 3:01:58 标签: vue, 设计模式, js

前言

  1. 认识Object.defineProperty()
js">    // Object.defineProperty 三个参数
    // 第一个参数,属性所在的对象
    // 第二个参数,要操作的属性
    // 第三个参数,被操作属性的特性
      // 格式是 {} 
      //configurable,enumerable,value,writable
      // set 写入属性时触发
      // get 读取属性
let obj = {value:'kk'}
    Object.defineProperty(obj, "value", {
      get: function() {
        console.log('get');
        return 1;//这里不可以写obj.value会死循环,疯狂get,因为obj.value就是给一个读取操作
      },
      set: function(newValue) {
        console.log('set');
        //obj.value = newValue  同理这里也会死循环
        console.log(newValue)
      }
    });

Object.defineProperty()在MDN 详细介绍看这里
2. 简单了解发布订阅模式
本人也不是很懂设计模式,后期补充。
简单描述:这个模式就像是你订阅关注了科比,以后有科比的消息会主动推送给你。需要有一个订阅器,一个发布器。

js">订阅器:就像是存放当前订阅者信息的容器。
	举个简单的例子:
	我的手机,里面有我的身份讯息,比如我喜欢某岛国知名动作女星--深田某美,咳咳不对,是科比,我订阅了科比,
	那么我的手机像是监听器一样,帮我盯着科比的动向,科比不单单是一个人的青春。别人也可以通过订阅器订阅科比
	当然也可以订阅别人,但是手机作为订阅器记录了我的信息我的喜好,并且手机收到了我订阅的新闻之后会告诉我。
发布器:是存放所有订阅器的容器和发布装置。
	里面存放了所有的订阅信息,有某美的,有科比的,有周星星的,这里他负责帮我监听科比的动向。
	一旦有消息,便会给订阅器的订阅者发送消息

原理核心

1-5准备工作
6 代码部分
1.html部分

 <div id="app">
   <span>改变我:</span>
   <input type="text" id="input" kk-model="name"  />
   <br />
   <span>我也变:</span>
   <input type="text" id="input1" kk-model="name" />
 </div>

2.实例生成

js"> // 生成vue实例
 const vm = new MyVue({
   el: "#app",
   data: {
     value: "123",
     name: "kk",
   },
 });

3.MyVue类

js">1.创建MyVue声明类:接收el,data等参数
2.数据初始化initData:
 将vm实例化的data数据挂载在实例对象下面,通过Object.defineProperty()
 并添加set() get()方法
3.视图层初始化initModel:
 a.获取所有[kk-model]绑定的节点,遍历循环所有节点给他们添加订阅器
 b.第一次数据驱动dom更新=>给node节点value赋值
 c.给订阅节点添加input事件=> 用户输入操作更新数据变化

4.发布器Dep

js">1.存储所有订阅器的容器
2.发布更新通知===>通过触发订阅器的更新方法

5.订阅器Watcher

js">1.存放订阅者的信息
2.更新视图

6.code

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="" id="app">
      <span>改变我:</span>
      <input type="text" id="input" kk-model="name"  />
      <br />
      <span>我也变:</span>
      <input type="text" id="input1" kk-model="name" />
    </div>
    <script>
      // 发布订阅部分
      // 发布器
      class Dep {
        constructor() {
          // 存储所有
          this.watchers = [];
        }
        addWatcher(watcher) {
          this.watchers.push(watcher);
        }
        // 发布通知
        notify() {
          this.watchers.forEach((watcher) => {
            //这里所有的订阅者都会遍历
            watcher.update(); //更新通知所有观察者
          });
        }
      }
      // 订阅器
      class Watcher {
        constructor(vm,key,callback) {
          this.callback = callback;
          this.vm = vm
          this.key = key
          
          Dep.target = this
          this.value = this.vm[this.key]
          Dep.target = null
        }
        update() {
          console.log("update");
          this.callback();
        }
      }

      class MyVue {
        constructor({ el, data }) {
          // 挂载容器
          this.container = document.querySelector(el);
          // 数据挂载
          this.data = data;
          this.ininData();
          this.initModel()
        }
        // 初始化数据
        ininData() {
          // 将data里面的数据 挂载到vue实例下
          Object.entries(this.data).forEach(([key, value]) => {
            let reactiveValue = value;
            const dep = new Dep()
            Object.defineProperty(this, key, {
              configurable: true,
              enumerable: true,
              get() {
                // dep.addWatcher(this[key])  这里不能这样写会造成死循环
                // 为了解决这个问题 需要给Dep添加一个 属性target
                // 那么Dep.target 添加在哪里
                Dep.target&&dep.addWatcher(Dep.target)
                return reactiveValue;
              },
              set(newValue) {
              //如果数据发生变化才会更新数据
                if (reactiveValue !== newValue) {
                  reactiveValue = newValue;
                  dep.notify()
                }
              },
            });
          });
        }
        // 初始化model 视图
        initModel() {
          // 获取所有双向绑定的dom 这里属性命名可以自定义
          const nodes = document.querySelectorAll("[kk-model]");
          nodes.forEach((node) => {
            const key = node.getAttribute('kk-model')
            // 这里给data里每一条数据加上订阅器 添加回调(发布时赋值)可以在Dep里触发Watcher.update 
            new Watcher(this,key,()=>{
              node.value = this[key]
            })
            // 这里第一次初始化 将data数据赋值给node(双向绑定的节点)
            node.value = this[key]
            // 监听触发set方法
            node.addEventListener("input",(ev)=>{
              this[key] = ev.target.value
            },false)
          });
        }
      }
      // 生成vue实例
      const vm = new MyVue({
        el: "#app",
        data: {
          value: "123",
          name: "kk",
        },
      });
    </script>
  </body>
</html>

7.总结

js">难点:get方法里面的Dep.target 需要好好理解
总结:通过Object.defineProperty定义data的所有属性,在get中收集观察者,在set中触发观察者更新DOM
缺点:这里只是简单模拟了vue的双向绑定实现,有很多待优化的地方

郑重声明

本人代码搬运工,非常感谢这些资料让我受益良多,也分享给看到这篇文章的人。
另外,如有错误欢迎指出
学习资源来自于:
手写双向绑定
小破站视频:手动实现MVVM双向绑定


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

相关文章

vue 组件通讯总结

Props $emit 方式 父子传参最常用的方式就是使用props,$emit 1. 父传子 子组件从父组件接受参数通过props&#xff0c;并且可以规定类型&#xff0c;默认值等 总结 1.父组件中 v-bind:parentToOne"parentMsg" , 一般parentToOne 与 parentMsg 名称相同即可 即&am…

某网络专业人士笔记(非常值得收藏)2

4、执行路由核心复制 core dump包含一份当前系统内存中信息的精确拷贝。捕捉包含在内存中信息的方法有&#xff1a; 1&#xff09; 配置路由器在崩溃时执行Core Dump&#xff0c;存储到TFTP、FTP、RCP服务器&#xff1a; 对TFTP协议&#xff0c;只需指定TFTP服务器IP&#xff0…

JavaWeb项目-超市订单管理系统【Day02】

密码修改 1、编写接口方法和mybatis的SQL映射文件 Mybatis配置多参数SQL语句 当我们的SQL语句中有多个参数的时候&#xff0c;需要设置每个参数名对应的接口参数&#xff0c;不然会报错&#xff1a; Parameter ‘id’ not found. Available parameters are [argl, argg, par…

vue vuex模块化

简单介绍 state //store.js //存放数据state: {index:index in root store}, //组件内获取方式computed: {// 可以结合computed 使用index(){return this.$store.state.index}}mutations mutations两个参数 第一个是默认参数state 第二个是传参payload,这里有一个异步同步的…

vue WebWorker的使用

webworker 我们知道javascript是单线程&#xff0c;当主线程遇到大量计算或者复杂的业务逻辑时&#xff0c;会对我们的页面造成不好的用户体验。 webworker 很好的解决了这个问题&#xff0c;我们可以在主线程开启一个worker线程执行任务而不干扰用户界面&#xff08;主线程&a…

SE100101系统概述

目录课程说明.................................................................................................................................. 1<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />课程介绍.............…

CSS 常见的兼容性问题

1.浏览器私有前缀 五大浏览器内核 1.Gecko内核 -moz- 火狐浏览器 2.Webkit -webkit- 谷歌内核 safari 买去 3.Trident -ms- IE内核 4.Presto内核 -o- 只有opera 采用 5.blink内核 -webkit- 谷歌采用标准写法 -moz-animation: ; -o-animation: ; -webkit-animation: ;…

潘石屹能否拯救中国楼市?

潘石屹能否拯救中国楼市&#xff1f; 时寒冰在楼市成交低迷&#xff0c;大调整渐渐逼近之时&#xff0c;在一个月黑风高之夜&#xff0c;一个瘦弱的汉子壮烈地高呼&#xff1a;“我不涨价谁涨价&#xff01;” 北京震动了&#xff0c;神州大地震动了&#xff0c;整个世界…