Vue 双向数据绑定的原理(MVVM)

假设有一个输入框和一个显示区域:

<input id="val" oninput="inputVal(this)">
<div id="app"></div>

用户在输入框中输入内容时,inputVal 函数被调用,将输入框的值赋给 model.title。这触发了 setter,更新 _data 并调用 render 函数,更新显示区域中的内容。

模型初始化:

这里定义了一个对象 model,它包含一个属性 title,初始值为 '笑开了花'

const model = {title:'笑开了花'} // M---数据

创建模型数据的浅拷贝:

使用扩展运算符(spread operator)...创建一个新的对象 _data

const _data = {...model}; // 创建 model 的浅拷贝用于存储实际数据

getter 方法中直接访问 model.title 会导致无限递归和堆栈溢出,因为 model.title 本身就是通过 getter 方法访问的。这种无限递归会导致浏览器或 JavaScript 引擎报错并抛出堆栈溢出错误。为了解决这个问题,我们需要使用一个独立的数据存储(即 _data),在 gettersetter 中操作这个独立的数据存储,而不是直接操作 model。这样可以避免递归调用和堆栈溢出。

为模型的每个属性定义 getter 和 setter

// 观察者模式可以灵活地添加更多的响应逻辑,实现更复杂的功能。
// 当用户在输入框中输入内容时,inputVal 函数被调用,model.title 的值更新,触发 setter,
// 调用 notifyObservers,然后所有观察者函数依次执行,包括 render 和控制台输出。

// 存储观察者函数的数组
// const observers = [];
// 定义 watcher 函数来添加观察者
// function watcher(fn) {  observers.push(fn); }
// 更新所有观察者函数
// function notifyObservers() { observers.forEach(fn => fn()); }

// VM: 数据的劫持与修改
// Object.defineProperty 是es5中的特性
for (let k in model) {
// Object.defineProperty 方法劫持的数据的对象并不是原来访问数据的对象
    Object.defineProperty(model, k, {
        get() {
            return _data[k]; // 通过 getter 访问 _data 中的实际数据
        },
        set(n) {
            _data[k] = n; // 通过 setter 修改 _data 中的实际数据
            render(); // 每次数据修改时调用 render 更新视图
            // notifyObservers()
        }
    });
}

// 定义一个默认的渲染观察者
// watcher(() => { render(); });

// 添加额外的观察者
// watcher(() => {
//    console.log(`Title changed to: ${model.title}`);
// });

使用 Object.defineProperty 方法为 model 的每个属性定义自定义的 getter 和 setter。具体步骤如下:

  • for(let k in model) 遍历 model 对象的所有属性。
  • Object.defineProperty(model, k ,{...}) 为每个属性定义 getter 和 setter。
  • get() 方法返回 _data 中对应属性的值。
  • set(n) 方法设置 _data 中对应属性的新值,并调用 render() 函数以更新视图。

渲染函数:

// V -- 渲染界面
function render() {
    app.innerHTML = `${model.title}`; // 更新视图显示内容
}

这是一个简单的渲染函数,它将 model.title 的值插入到 app 元素的 innerHTML 中,以更新视图。

输入值处理函数:

function inputVal(e) {
    model.title = e.value; // 修改 model.title 触发 setter 和 render
}

这是一个输入值处理函数,用于响应输入事件。当输入值改变时,它会更新 model.title 的值。
每当 model.title 被更新时,set 方法会触发 render() 函数,从而更新视图中的显示内容。

  • getter: 从 _data 中获取数据,避免直接访问 model 以防止无限递归。
  • setter: 设置 _data 中的数据,并调用 render 函数更新视图。
  • 视图更新: render 函数根据 model.title 的当前值更新视图。 
  • getter 和 setter 的作用: getter 使得通过 model.title 访问的值实际上来自 _data.title,而 setter 则确保修改 model.title 时实际更新的是 _data.title。每次设置新值时,都会调用 render 函数以更新视图。

  • 更新 _data 的值: 修改 _data 的值会通过 setter 反映在 model 上,并触发相应的操作(如 render视图更新),  在数据变化时执行额外的逻辑。

为什么vue中data里面的数据是响应式的呢?

因为将一个普通的js对象传入new vue的实例作为data选项 ,vue将遍历对象所有的属性 , 并且使用Object.defineProperty 把这些属性全部转化为getter与setter ,然后它们就成为了响应式的数据

Object.defineProperty 方法它接受三个参数:

  1. 对象(object):

    这是你要在其上定义或修改属性的目标对象。

  2. 属性名称(propertyName):

    这是要定义或修改的属性的名称。它可以是一个字符串或符号。

  3. 描述符对象(descriptor):

    描述符对象包含属性的配置,它可以有以下几个属性:

    • configurable:

      • 类型:Boolean
      • 默认值:false
      • 如果为 true,则该属性描述符可以被修改,同时该属性可以被删除。
    • enumerable:

      • 类型:Boolean
      • 默认值:false
      • 如果为 true,则该属性在枚举对象属性时会被枚举出来(例如 for...in 循环或 Object.keys 方法)。
    • value:

      • 类型:Any
      • 默认值:undefined
      • 该属性的值,可以是任意类型。
    • writable:

      • 类型:Boolean
      • 默认值:false
      • 如果为 true,则该属性的值可以被修改。
    • get:

      • 类型:Function
      • 默认值:undefined
      • 一个getter函数,当访问该属性时调用此函数,无参数传入,但返回值会作为属性值。
    • set:

      • 类型:Function
      • 默认值:undefined
      • 一个setter函数,当属性值被修改时调用此函数,会接受赋值时的新值作为参数。

示例演示:

const obj = {}; // 空对象

Object.defineProperty(obj, 'example', {
    configurable: true, // 允许修改属性描述符或删除属性
    enumerable: true, // 允许通过 for...in 或 Object.keys 枚举属性
    value: 42, // 属性的值
    writable: true // 允许修改属性的值
});

console.log(obj.example); // 输出 42

obj.example = 100; // 修改属性值
console.log(obj.example); // 输出 100

for (let key in obj) {
    console.log(key); // 输出 'example' 因为属性是可枚举的
}

  • 32
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码海扬帆:前端探索之旅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值