vue手写双向绑定实现

本文详细解析了如何使用Object.defineProperty()实现数据响应式,并介绍了发布订阅模式,通过创建Dep、Watcher类实现数据变化监听和视图更新。同时,通过一个简单的Vue实例展示了数据初始化和视图层初始化的过程,模拟了Vue的双向绑定机制,探讨了其工作原理和可能的优化方向。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

  1. 认识Object.defineProperty()
    // 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. 简单了解发布订阅模式
本人也不是很懂设计模式,后期补充。
简单描述:这个模式就像是你订阅关注了科比,以后有科比的消息会主动推送给你。需要有一个订阅器,一个发布器。

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

原理核心

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.实例生成

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

3.MyVue类

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

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

5.订阅器Watcher

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.总结

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

郑重声明

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值