vue|你不会还觉得响应式原理和双向绑定是一个东西吧❓❓

136 阅读4分钟

今天发生一个很有意思的事情,我的好朋友(贴贴)今天问了我一个问题,当时我在忙,就简单回复了一下,下午我们发起了学习的热潮!!!

Snipaste_2022-07-20_20-06-57.png

那我接下来就好好唠唠响应式和双向绑定吧

一、响应式原理

思考💡:vue最大的特点呢就是数据驱动视图,什么是数据驱动视图呢?

数据 state、视图UI

render(state) = UI

数据变化,视图就会发生变化,数据是单向的,vue就扮演了这个render的角色

思考💡:data变化,为什么视图就会变化?

Snipaste_2022-07-20_20-14-25.png

数据驱动视图只干2件事

1、 监听数据变化

用Object.defineProperty监听数据变化

说明:vue2 用的Object.defineProperty实现数据劫持,vue3用的Proxy实现数据代理(这个有时间我再写)

2、更新视图

用发布订阅者模式更新视图

1、vue开始展示

<body>
    <div id="app">
        <h1>{{message}}</h1>
    </div>
    <script src="./vue.js"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                message: '哈哈哈!',
            }
        })

    </script>
</body>

思考💡:通过app.message修改数据,vue内部是如何监听数据的变化的?

通过Object.defineProperty()

思考💡:vue监听到数据的变化,是如何通知页面更新的?

通过发布订阅者模式

一道面试题拿下!!

2、看看Object.defineProperty()

    <script>
        const obj = {
            message: '哈哈哈',
            name: 'why'
        }
        //遍历对象的所有key值
        Object.keys(obj).forEach(key => {
            //获取值
            let value = obj[key];
            // 通过 Object.defineProperty完成监听
            Object.defineProperty(obj, key, {
                set(newValue) {
                    console.log('监听' + key + '的改变');
                    value = newValue;
                },
                get() {
                    console.log('获取' + key + '的值');
                    return value;
                }
            })
        })
    </script>

obj定义的数据就相当于实例化Vue中data定义的数据,遍历对象的所有key值,通过Object.defineProperty监听对象的所有属性

数据变化触发set方法,访问数据触发get方法

思考💡:set中干点什么?get中干点什么?

既然要说到发布订阅模式了,就先理理思路,首先要通知视图更新,我得知道谁用我了,那访问数据即调用get方法的时候,我就收集起来,收集起来的我们优雅的称为订阅者,收集的过程我们成为依赖收集,其次就是视图更新了,数据变化的时候会触发set方法,所以在set中去通知更新是最合适的了。

以上的分析就概括为一句话在set中去通知更新,在get中去添加订阅

3、学学发布订阅者模式

1、前言

在set中去通知更新,也就是会调用notify()

在get中去添加订阅,因为只有调用get方法,才知道谁调用了,谁调用了就通知谁

2、代码实现发布订阅

  //发布者
        class Dep {
            constructor() {
                //存储订阅者
                this.subs = [];
            }
            //添加订阅者
            addSub(watcher) {
                this.subs.push(watcher);
            }
            //通知更新
            notify() {
                this.subs.forEach((item) => {
                    item.update();
                })
            }
        }
        //订阅者
        class Watcher {
            constructor(name) {
                this.name = name;
            }
            update() {
                console.log(this.name + '发生了更新');
            }
        }
        //new的时候会实现constructor
        let dep = new Dep();
        // 定义订阅者,并且添加订阅者
        let w1 = new Watcher('张三');
        dep.addSub(w1);
        let w2 = new Watcher('李四');
        dep.addSub(w2);
        let w3 = new Watcher('王五');
        dep.addSub(w3);
        dep.notify();

发布者能够
用this.subs = [] 实现存储订阅者
用addSub()实现新增订阅者
用notify()实现通知更新

订阅者能够

自己的属性,通过constructor实现
通过update实现能够更新视图

3、发布订阅结合Object.defineProperty

        defineReactive(data, key, val) {
          const dep = new Dep();
          Object.defineProperty(data, key, {
            enumerable: true,
            configurable: true,
            get() {
              if (Dep.target) {
              // get收集订阅
                dep.addSub(Dep.target);
              }
              return val;
            },
            set(nv) {
              if (nv === val) {
                return;
              }
              console.log(nv, val);
              val = nv;
              // set通知更新
              dep.notify();
            },
          });
        }
      }

监听之前,定义一个发布者
在get方法中添加订阅者 dep.addSub(Dep.target)
在set中通知更新: dep.notify();

二、响应式原理(图文并茂版)

1.new一个vue的过程

image (2).png

new一个vue的时候创建了一个vue实例,主要有2部分,el和data

2.Observer的工作

image (3).png

Observer通过Object.defineProperty将data对象的属性都监听起来,每个属性都对应一个Dep对象(依赖),Dep中定义了subs(依赖收集器),subs存储的是所有watcher(订阅者)

3.Compile的工作

image (4).png

Compile解析html的代码,在解析时,会触发get方法,也就会触发添加订阅者的方法,将watcher添加到subs中

4.数据更新

image (5).png

数据更新时,会触发set方法,set会调用notify(),notify会通知所有订阅者,调用自身的update()更新视图

三、数据劫持存在的问题

vue是通过数据劫持实现响应式的,那么也存在一些问题

1.问题

无法监听数组长度的变化
通过下标操作数组的方式无法监听到
给对象新增属性、删除属性无法监听到

2.解决方案

vue.set()
vue3的proxy

四、双向绑定

双向绑定数据是双向的

数据改变视图改变,也就是响应式

视图改变,数据改变,这是原生的方式👇

从视图到数据,这个本来就很好理解,你想,你的input是不是一个框,input天生就有value属性,也就是说你输入一个数,input.value就拿到那个数了,然后你保存下来,发给后台,存到数据库,一条线,完成

也就是可以总结一下,响应式是双向绑定中的一环

1.vue方式实现

    <input type="text" v-model="message">
  1. 原生实现
    <input type="text" :value="message" @input="message = $event.target.value">

3.分析

在value中 数据的流向 message=>value ,也就是说data中定义的属性通过value绑定给input

在@input中 数据流向 value =>message ,也就是说通过监听input事件,值发生改变时,改变data中的属性的值

思考💡:oninput和@input和onchange的区别?

oninput事件是输入框在输入时就会触发

onchange是输入框输入内容完毕,失去焦点的时候触发

@input是vue中的输入框事件,输入值的发生变化会触发

参考👀

www.jianshu.com/p/41e53e6e1…

www.bilibili.com/video/BV157…