likes
comments
collection
share

请这样回答双向数据绑定原理

作者站长头像
站长
· 阅读数 22

前言

双向数据绑定是Vue的重要原理,也是面试过程中几乎必问的一道题目,搞懂这个原理无论是对我们的技术提高还是对面试都是有百利而无一害的,下面就让我们一起来解决这个问题吧~

Vue是如何实现双向数据绑定的?

Vue双向数据绑定是通过数据劫持+发布订阅者模式来实现的。Vue采用的是MVVM架构,实现MVVM主要包含两个方面,一是数据变化更新视图,二是试图变化更新数据。 在实现过程上来说,主要有四个模块:

  1. 监听器Observer:执行劫持监听的所有属性,如果属性发生变化了,就通知订阅者Watcher看是否需要更新。
  2. 订阅者Watcher:可以受到属性的变化通知并执行相应的函数,从而更新视图。
  3. 消息订阅器Dep:因为订阅者有很多个,所以需要一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理。
  4. 解析器Compile:可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

Vue是如何实现响应式的?

Vue实现响应式的核心就是通过Object.defineProperty来劫持对象的get和set,在get中收集依赖,在set中分发依赖,其中Vue2是借助Object.defineProperty实现的,而Vue3则是借助Proxy实现的。

双向数据绑定每一个模块都负责什么?

new Vue

new Vue这个操作主要是调用了数据劫持Observe方法和模板编译的函数Compile。

Observe函数

Observer函数将递归遍历传入对象的所有property,并使用Object.defineProperty把这些property全部转化为getter/setter。一旦监听的属性发生变化的时候变通知Dep类。

Dep类

Dep类负责收集每个watcher,并当Observe监听到属性变化时通知收集到的每一个watcher。

Watcher类

Watcher是Observe和Compile之间的桥梁,watcher会在自身实例化的时候向dep类上添加自身,并提供一个update方法,一旦属性发生变化时,dep类会通过这个方法来触发Compile中的回调函数进行渲染数据。

Compile函数

Compile函数的主要工作是将模板中的变量替换成数据,然后渲染页面视图,并给每个节点绑定更新函数,创建订阅者,一旦数据有变化,收到通知就更新视图,因为遍历的时候会多次操作DOM,为了提高效率会将根节点转换成文档碎片fragment进行离线DOM操作,解析完成之后再将fragment添加到真实的DOM中。

双向数据绑定原理流程图

请这样回答双向数据绑定原理

Vue2和Vue3响应式原理的不同之处

  1. Vue2使用的是Object.defineProperty()实现响应式原理,而Vue3使用的是Proxy()实现。
  2. 在面对对象嵌套的时候,Vue2和Vue3都需要进行递归,但是Vue2是对所有属性进行递归,而Vue3则是按需递归,如果没有使用到内部对象的属性,就不要递归,性能更好。
  3. Vue2中对象不存在的属性是不能被拦截的,但是Vue3可以。

Vue3为什么弃用了ObjectdefineProperty选择了Proxy

  1. Object.defineProperty无法监控到数组下标的变化,导致通过数组数组下标添加元素不能实时响应。
  2. Proxy不仅可以代理对象,还可以代理数组,还可以代理动态增加的属性。
  3. Object.defineProperty不能对ES6新产生的Map、Set这样的数据结构进行监听。

手写双向数据绑定

<div>
    展示:<h1></h1>
    输入: <input type="text">
</div>

<script>
    // 创建definePropertyFn来挟持数据
    function definePropertyFn() {
        let obj = {}
        let val = null

        Object.defineProperties(obj, {
            val: {
                get() {
                    return val
                },
                set(newV) {
                    val = newV
                    // 数据控制视图 将更改的数据赋值给h1
                    document.querySelector('h1').innerText = newV
                    console.log('调用了set,获取:' + newV, val);
                }
            }
        })

        return obj
    }

    let newObj = definePropertyFn()
    document.querySelector('h1').innerText = newObj.val // 调用了get,执行数据渲染视图
    document.querySelector('input').value = newObj.val // 调用了get,执行数据渲染视图

    // 下面监听视图 input 标签,标签一变动,将最新数据获取调用set,赋值给val,并且赋值给h1
    document.querySelector('input').addEventListener('input', function () {
        newObj.val = document.querySelector('input').value
    })
</script>

参考文档

总结

在回答关于双向数据绑定的问题的时候,可以结合上面的流程图,对每一个节点主要的工作进行介绍,并把流程讲清楚即可。