【Vue2和Vue3的双向绑定区别】

Vue2和Vue3的双向绑定区别

vue2 双向绑定原理

Vue2 双向绑定的实现主要依赖于 Object.defineProperty() 方法和观察者模式,其中 Object.defineProperty() 方法用于定义属性的 getter 和 setter 方法,观察者模式用于监听数据变化并更新视图。

具体实现步骤如下:

  1. 首先,Vue 将 data 对象中的每个属性都转换为 getter 和 setter 方法,以便在属性值发生变化时能够触发视图的更新,这里使用了 Object.defineProperty() 方法。
javascript 复制代码
function defineReactive(data, key, val) {
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            // ...
        },
        set: function(newVal) {
            // ...
        }
    });
}
  1. 在 getter 方法中,Vue 将当前的观察者对象添加到该属性的订阅器中,以便在属性值发生变化时能够得到通知并触发视图的更新,这里使用了 Dep.target 属性和观察者模式。
javascript 复制代码
function defineReactive(data, key, val) {
    const dep = new Dep();
    Object.defineProperty(data, key, {
        // ...
        get: function() {
            if (Dep.target) {
                dep.addSub(Dep.target);
            }
            // ...
        },
        // ...
    });
}
  1. 在 setter 方法中,Vue 首先更新属性的值,然后遍历该属性的订阅器,并调用每个观察者对象的 update() 方法,以便通知它们属性发生了变化,这里同样使用了观察者模式。
javascript 复制代码
function defineReactive(data, key, val) {
    const dep = new Dep();
    Object.defineProperty(data, key, {
        // ...
        set: function(newVal) {
            if (val === newVal) {
                return;
            }
            val = newVal;
            dep.notify();
        }
    });
}
  1. 在组件初始化时,Vue 实例化一个 Watcher 对象,该对象会调用 data 中的属性 getter 方法,并将自身添加到该属性的订阅器中,以便在属性值发生变化时能够得到通知并触发视图的更新,这里使用了观察者模式。
javascript 复制代码
function Watcher(vm, expOrFn, cb) {
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn);
    this.value = this.get();
}

Watcher.prototype.get = function() {
    Dep.target = this;
    const value = this.getter.call(this.vm, this.vm);
    Dep.target = null;
    return value;
};
  1. 当某个属性的值发生变化时,该属性的订阅器会遍历其中的所有观察者对象,并调用它们的 update() 方法,以便通知它们属性发生了变化,这里同样使用了观察者模式。
javascript 复制代码
function Dep() {
    this.subs = [];
}

Dep.prototype.addSub = function(sub) {
    this.subs.push(sub);
};

Dep.prototype.notify = function() {
    this.subs.forEach(function(sub) {
        sub.update();
    });
};

这样,当数据发生变化时,观察者模式会实时地通知所有依赖该数据的组件,在组件中更新相应的视图。

以上是 Vue2 双向绑定的大致实现原理,具体可以参考 Vue 源码。

vue3 双向绑定原理

Vue3 的双向绑定原理与 Vue2 类似,都是基于 Object.defineProperty 实现的。不过,Vue3 对此做了一些改进,通过 Proxy 实现了更高效的双向绑定。

Proxy 的基本使用方法是通过将对象包装在一个句柄中来拦截对该对象的访问。当访问对象时,句柄会调用相关的拦截方法。

下面我们通过一个简单的示例来了解 Vue3 双向绑定的实现原理。

首先,我们初始化一个 Vue3 实例:

javascript 复制代码
const app = Vue.createApp({
  data() {
    return {
      count: 0
    }
  }
})

const vm = app.mount("#app")

然后,我们为 count 属性添加一个双向绑定:

javascript 复制代码
<input type="text" v-model="count">

此时,我们需要在数据对象上添加一个 getter 和一个 setter 方法,使得在修改输入框的值时,数据对象也会同步更新。这可以通过 Proxy 来实现。

我们可以在 Vue3 组件的 setup 函数中使用 reactive 函数来创建响应式数据对象。reactive 函数采用的就是 Proxy 来实现数据的双向绑定。

javascript 复制代码
const { reactive } = Vue

const state = reactive({ count: 0 })

watch(() => {
  console.log(state.count)
})

当修改数据对象中的 count 属性时,会触发 watch 中的监听函数,输出新的 count 值。

在原理上,Vue3 会为数据对象中的每个属性创建一个 Proxy 对象,并通过该对象的 get 和 set 方法来实现数据对象的双向绑定。

下面是 Vue3 的源码分析:

  1. reactive 函数
javascript 复制代码
function reactive(obj) {
  if (!isObject(obj)) {
    return obj
  }

  // 对象已经被代理过了,直接返回它的代理对象
  if (obj.__v_proxy) {
    return obj.__v_proxy
  }

  // 创建 Proxy 对象
  const observed = new Proxy(obj, baseHandlers)

  // 缓存代理对象并返回
  obj.__v_proxy = observed
  return observed
}

在 reactive 函数中,我们首先对传入的 obj 进行判断,如果不是对象或者已经被代理过了,直接返回该对象。

如果 obj 尚未被代理,则使用 Proxy 对象创建一个新的代理对象 observed,并缓存该代理对象到原始对象 obj 的 __v_proxy 属性中,并返回 observed。

  1. baseHandlers

创建 Proxy 对象的关键在于使用 Proxy 的句柄(handler)。该句柄对象包含了一系列的拦截方法,例如 get 和 set 方法,用于拦截对对象属性的访问和修改。

Vue3 中的 baseHandlers 是在 createReactiveObject 函数中定义的。它是一个包含了处理属性的 getter 和 setter 的对象。其中,getter 方法会返回原始值,setter 方法则会通过 emit 调用来触发更新。

javascript 复制代码
const mutableHandlers = {
  get: createGetter(),
  set: createSetter()
}

function createGetter() {
  return function get(target, key, receiver) {
    const res = Reflect.get(target, key, receiver)
    return isObject(res) ? reactive(res) : res
  }
}

function createSetter() {
  return function set(target, key, value, receiver) {
    const oldValue = target[key]
    const result = Reflect.set(target, key, value, receiver)

    if (result && oldValue !== value) {
      trigger(target, key)
    }

    return result
  }
}

在上述代码中,我们使用了 Reflect 的 get 和 set 方法来代替直接操作原始对象(obj)的方式。这么做是因为使用 Reflect 方法可以处理更多的情况,并且保证了代码的健壮性。

简要解释一下这两个拦截器函数,createGetter 方法用于拦截对象属性的读取操作。当我们读取对象属性时,如果该属性是对象,则递归调用 reactive 函数来创建对该对象的代理。

createSetter 方法用于拦截对象属性的赋值操作。当我们为对象的属性赋值时,会触发该拦截器的 setter 方法。我们需要在该方法中判断新的值是否与旧值相同,如果不同,则调用 trigger 函数以触发更新。

  1. trigger 函数

trigger 函数的作用是触发数据更新,通知视图进行重新渲染。

javascript 复制代码
const effectStack = []

function trigger(target, key) {
  const depsMap = targetMap.get(target)

  if (!depsMap) {
    return
  }

  const effects = new Set()
  const add = (effectsToAdd) => {
    effectsToAdd.forEach(effect => {
      effects.add(effect)
    })
  }

  const run = (effect) => {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  if (key) {
    const dep = depsMap.get(key)

    if (dep) {
      add(dep)
    }
  } else {
    targetMap.forEach((dep, key) => {
      if (key === 'length' || isArray(target) && parseInt(key, 10) >= target.length) {
        add(dep)
      }
    })
  }

  effects.forEach(run)
}

在 trigger 函数中,我们首先获取与目标对象关联的依赖表 depsMap。然后,我们遍历依赖表,根据依赖项的数量触发数据更新操作。

  1. effect 函数

effect 函数可以用于创建一个响应式的副作用,当关联的数据发生变化时,会自动更新视图。

javascript 复制代码
function effect(fn, options = {}) {
  const effect = createReactiveEffect(fn, options)
  if (!options.lazy) {
    effect()
  }
  return effect
}

function createReactiveEffect(fn, options) {
  const effect = function reactiveEffect() {
    try {
      // 入栈
      effectStack.push(effect)
      return fn()
    } finally {
      // 出栈
      effectStack.pop()
    }
  }

  effect.id = uid++
  effect._isEffect = true
  effect.raw = fn
  effect.deps = []
  effect.options = options

  return effect
}

在 effect 函数中,我们首先使用 createReactiveEffect 函数创建一个新的响应式副作用 effect,并将其返回。createReactiveEffect 函数主要用于创建响应式副作用的内部实现,包括将副作用函数 fn 转换为响应式版本、保存响应式副作用与其相关的状态等。

在响应式副作用创建完成后,我们可以直接调用该副作用(即执行 effect 函数),也可以将其作为参数传递给其他地方使用。

总结

Vue3 双向绑定的原理与 Vue2 并没有本质区别,都是使用 Object.defineProperty 或者 Proxy 实现的双向绑定。不同之处在于,Vue3 采用了更高效的 Proxy 实现方式,并且对一些细节做了优化,提高了整个框架的性能。

此外,在 Vue3 中,由于使用了拦截器函数来对数据进行包装,因此其内部实现也更加复杂。不过,理解 Vue3 双向绑定的原理对于我们深入理解整个框架的设计思想和实现方式非常有帮助。

Vue2和Vue3的双向绑定存在以下区别:

1. 响应式系统的改进:Vue3通过Proxy替换了Vue2中使用的Object.defineProperty来实现响应式数据。这使得Vue3的响应式系统更加高效和灵活,可以更好地支持嵌套对象和数组。

2. 组件的更新策略:Vue2中的组件更新是通过递归式处理的,即每次更新时会遍历整个组件树,这样会导致效率较低。Vue3中采用了静态分析技术进行组件更新,可以更好地实现局部更新,提高渲染效率。

3. 模板语言的改进:Vue3中提供了更加灵活的模板语法,并增加了一些新特性,例如:v-model的多个绑定值、v-model修饰符的增加、el和ref的区别等。

4. 生命周期的改变:Vue3中的生命周期函数名称发生了改变。例如:created改为了setup,beforeDestroy改为了unmounted。

5. Composition API:Vue3中引入了Composition API,可以使得组件的逻辑更加清晰和组织化。它通过将相关的逻辑组合成一个逻辑组合体提高代码的可维护性和可读性。

总的来说,Vue3在双向绑定方面做了很多的改进和优化,可以更好地满足现代应用程序的需求。

相关推荐
续亮~2 小时前
6、Redis系统-数据结构-05-整数
java·前端·数据结构·redis·算法
顶顶年华正版软件官方4 小时前
剪辑抽帧技巧有哪些 剪辑抽帧怎么做视频 剪辑抽帧补帧怎么操作 剪辑抽帧有什么用 视频剪辑哪个软件好用在哪里学
前端·音视频·视频·会声会影·视频剪辑软件·视频剪辑教程·剪辑抽帧技巧
MarkHD4 小时前
javascript 常见设计模式
开发语言·javascript·设计模式
程序员云翼4 小时前
7-理财平台
java·vue.js·spring boot·后端·毕设
托尼沙滩裤5 小时前
【js面试题】js的数据结构
前端·javascript·数据结构
不熬夜的臭宝5 小时前
每天10个vue面试题(一)
前端·vue.js·面试
朝阳395 小时前
vue3【实战】来回拖拽放置图片
javascript·vue.js
不如喫茶去5 小时前
VUE自定义新增、复制、删除dom元素
前端·javascript·vue.js
长而不宰5 小时前
vue3+electron项目搭建,遇到的坑
前端·vue.js·electron
阿垚啊6 小时前
vue事件参数
前端·javascript·vue.js