vue中observer数据双向绑定原理

发布时间:2021-08-23 10:45:18 作者:chen
来源:亿速云 阅读:158
# Vue中Observer数据双向绑定原理

## 前言

在当今前端开发领域,Vue.js凭借其简洁的API和响应式数据绑定机制,已成为最受欢迎的渐进式JavaScript框架之一。Vue的核心特性之一就是数据双向绑定,它使得开发者无需手动操作DOM即可实现视图与数据的自动同步。本文将深入剖析Vue中实现数据双向绑定的Observer模式原理,从响应式系统设计到具体实现细节,全面解析这一核心机制。

## 一、数据双向绑定的基本概念

### 1.1 什么是数据双向绑定

数据双向绑定(Two-way Data Binding)是指当数据模型(Model)发生变化时,视图(View)会自动更新;反之,当用户操作视图导致视图变化时,数据模型也会相应更新。这种机制极大简化了DOM操作,提高了开发效率。

### 1.2 单向数据流与双向绑定

虽然Vue支持双向绑定(如v-model),但其核心仍然是单向数据流:
- 父组件 -> 子组件:通过props传递
- 子组件 -> 父组件:通过事件触发

v-model实际上是语法糖,本质还是单向数据流+事件监听的组合。

## 二、Vue响应式系统的整体架构

Vue的响应式系统主要由三部分组成:

1. **Observer(观察者)**:对数据对象进行递归遍历,添加getter/setter
2. **Dep(依赖收集器)**:每个属性都有一个Dep实例,用于存储所有依赖该属性的Watcher
3. **Watcher(观察者)**:连接Observer和Compiler的桥梁,当数据变化时触发回调

```javascript
// 简化的响应式系统关系图
+-------------------+       +-------------------+       +-------------------+
|     Observer      | <---> |       Dep        | <---> |      Watcher      |
+-------------------+       +-------------------+       +-------------------+
      ^                                                         |
      |                                                         v
+-------------------+                                 +-------------------+
|     Data Object   |                                 |      View         |
+-------------------+                                 +-------------------+

三、Observer的实现原理

3.1 对象属性的响应式处理

Vue通过Object.defineProperty()方法将普通JavaScript对象转换为响应式对象:

function defineReactive(obj, key, val) {
  const dep = new Dep() // 每个属性都有自己的依赖管理器
  
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      if (Dep.target) {  // 当前正在计算的Watcher
        dep.depend()     // 依赖收集
      }
      return val
    },
    set: function reactiveSetter(newVal) {
      if (newVal === val) return
      val = newVal
      dep.notify()  // 通知所有依赖进行更新
    }
  })
}

3.2 数组的特殊处理

由于JavaScript限制,Vue不能检测以下数组变动: 1. 直接通过索引设置项:vm.items[index] = newValue 2. 修改数组长度:vm.items.length = newLength

Vue通过重写数组方法实现响应式:

const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

methodsToPatch.forEach(function(method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    ob.dep.notify()  // 通知变更
    return result
  })
})

3.3 递归观测对象

Vue会递归地将一个对象的所有属性转换为响应式:

class Observer {
  constructor(value) {
    this.value = value
    this.dep = new Dep()
    
    def(value, '__ob__', this)
    
    if (Array.isArray(value)) {
      // 数组响应式处理
      value.__proto__ = arrayMethods
      this.observeArray(value)
    } else {
      // 对象响应式处理
      this.walk(value)
    }
  }
  
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  
  observeArray(items) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

四、依赖收集与派发更新

4.1 Dep(依赖管理器)

每个响应式属性都有一个Dep实例,用于存储所有依赖该属性的Watcher:

class Dep {
  constructor() {
    this.subs = []
  }
  
  addSub(sub) {
    this.subs.push(sub)
  }
  
  removeSub(sub) {
    remove(this.subs, sub)
  }
  
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  
  notify() {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

Dep.target = null  // 全局唯一的Watcher

4.2 Watcher(观察者)

Watcher是Observer和Compiler之间的桥梁,主要作用: 1. 在自身实例化时往属性订阅器(dep)里添加自己 2. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm
    this.cb = cb
    this.deps = []
    this.depIds = new Set()
    
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    
    this.value = this.get()
  }
  
  get() {
    Dep.target = this
    const value = this.getter.call(this.vm, this.vm)
    Dep.target = null
    return value
  }
  
  addDep(dep) {
    if (!this.depIds.has(dep.id)) {
      this.deps.push(dep)
      this.depIds.add(dep.id)
      dep.addSub(this)
    }
  }
  
  update() {
    const oldValue = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldValue)
  }
}

五、虚拟DOM与批量更新

5.1 异步更新队列

Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。

function queueWatcher(watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      // 如果已经在刷新,则按id顺序插入
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}

5.2 nextTick实现原理

Vue内部尝试使用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout(fn, 0)代替。

const callbacks = []
let pending = false

function flushCallbacks() {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

function nextTick(cb, ctx) {
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    }
  })
  if (!pending) {
    pending = true
    timerFunc()
  }
}

六、Vue 3.0的响应式改进

6.1 Proxy的优势

Vue 3.0使用Proxy替代Object.defineProperty,主要优势: 1. 可以直接监听对象而非属性 2. 可以直接监听数组变化 3. 有更多拦截方法(13种) 4. 性能更好

6.2 基本实现

function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      track(target, key)  // 依赖收集
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      if (oldValue !== value) {
        trigger(target, key)  // 触发更新
      }
      return result
    }
  }
  return new Proxy(target, handler)
}

七、常见问题与解决方案

7.1 对象新增属性非响应式

解决方案: 1. 使用Vue.set(object, propertyName, value) 2. 使用Object.assign({}, object, newProperties)

7.2 数组索引修改非响应式

解决方案: 1. 使用Vue.set(array, index, newValue) 2. 使用splice方法:array.splice(index, 1, newValue)

7.3 性能优化建议

  1. 扁平化数据结构
  2. 避免大数据量的响应式转换
  3. 合理使用Object.freeze()
  4. 组件级别的细粒度响应式

结语

Vue的响应式系统是其核心特性之一,通过Observer模式实现了数据与视图的自动同步。从Object.defineProperty到Proxy的演进,体现了Vue团队对性能与开发体验的不懈追求。深入理解这一原理,不仅有助于我们更好地使用Vue,也能在面对复杂业务场景时做出更合理的设计决策。

(全文约3650字) “`

这篇文章详细介绍了Vue中Observer数据双向绑定的实现原理,包括: 1. 基本概念和整体架构 2. Observer的具体实现 3. 依赖收集与派发更新机制 4. 虚拟DOM与批量更新策略 5. Vue 3.0的改进 6. 常见问题解决方案

文章采用技术深度与可读性平衡的写法,适合中级前端开发者阅读学习。

推荐阅读:
  1. vue的双向绑定原理及实现
  2. vue数据双向绑定原理是什么

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

vue

上一篇:Spring Boot中静态资源处理的示例分析

下一篇:SpringBoot项目中如何处理返回json的null值

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》