Vue 双向绑定

Vue 双向绑定

Vue 响应式流程图

在这里插入图片描述

数据处理

//  数组、数据类型处理
if (Array.isArray(value)) {
   // 重写数组 push pop shift unshift splice sort reverse 
   if (hasProto) {
      protoAugment(value, arrayMethods)
   } else {
     copyAugment(value, arrayMethods, arrayKeys)
   }
      this.observeArray(value)
} else {
   this.observe(value)
}

// 数组重写
const result = original.apply(this, args)
 // notify change
 ob.dep.notify()

// 判断是否为对象
function isObject (obj: mixed): boolean %checks {
  return obj !== null && typeof obj === 'object'
}

// 遍历数据, 简化
function observe (value) {
  if(!isObject(value))  { return }
  const keys = value.keys(value)
  
  // 深度遍历
  for (let i = 0; i < keys.length; i++) {
    observe(value, keys[i])
    // 代理
    defineReactive(value, keys[i])  
  }
}

数组重写

// 源码
function observe(value) {
  ob = new Observer(value)  
}

// 数组处理
class Observer {
  constructor (value) {
    if (Array.isArray(value)) {
      // 给数组对象设置新的原型对象,屏蔽原生数组变异方法  
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    }  
  }  
}

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

function protoAugment (target, src: Object) {
  target.__proto__ = src
}

function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

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


methodsToPatch.forEach(function (method) {
  // cache original 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)
    // 数组更新  
    // notify change
    ob.dep.notify()
    return result
  })
})

Object.defineProperty 代理

class Vue {
  // Vue 构造类
  constructor(options) {
    this._data = options.data;
    observer(this._data);

    // 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
    // 源代码中, ./instance/index.js->stateMixin->Vue.prototype.$watch->new Watcher
    new Watcher();

    // 触发test属性的get函数
    console.log(this._data.test);
  }
}

function observer(value) {
  if(!value || (typeof value !== 'object')) {
    return;
  }

  Object.keys(value).forEach(key => {
    defineReactive(value, key, value[key]);
  });
}
// 基于 Object.defineProperty 实现
function defineReactive(obj, key, val) {
  const dep = new Dep();

  Object.defineProperty(obj, key, {
    enumerable: true, // 属性可枚举
    configurable: true, // 属性可被修改或删除
    get: function reactiveGetter() {
      // 依赖收集, object.key 触发
      // 将当前的 watcher 对象存入 dep subs 中
      dep.addSub(Dep.target);
      return val;
    },
    set: function reactiveSetter(newVal) {
      if(newVal === val) return;
      val = newVal; 
      // 通知 watcher 对象更新视图
      dep.notify();
    }
  });
}
// 订阅者 Dep, 存放 Watcher 观察者对象
class Dep {
  constructor() {
    // 存放 watcher 对象数组
    this.wats = [];
  }

  // subs 添加 Watcher 对象
  addSub(wat) {
    this.wats.push(wat);
  }

  // 通知所有 Watcher 对象更新视图
  notify() {
    this.wats.forEach(wat => {
      wat.update();
    })
  }
}


// 观察者 Watcher
class Watcher {
  constructor() {
    // 在创建 Watcher 时将该对象复制给 Dep.target 在 get 中会用到
    Dep.target = this;
  }

  // 更新视图
  update() {

  }
}

Dep.target = null;

缺点

无法检测对象属性的新增或删除

this.productData = Object.assign({}, this.productData, res)

更新 data[key] 的属性,引起数据更新

无法监听数组
  • Object.defineProperty 可以检测数组变动;
  • 出于 vue 对性能的取舍;
  • 栗子,arr[5] = xxx, 可能是新增属性;
  • 栗子,arr[1000] 有效值也许仅有几个,另外遍历属性添加监听,消耗大;

proxy 代理

// es6 proxy 实现
// proxy 不需要深度遍历监听,性能高于 Object.defineProperty
// 可监听数组对象变化
function defineReactive(target) {
  const dep = new Dep();
  return new Proxy(target, {
    set(target, key, value, receiver) {
      trigger();  
    },
    get(target, key, receiver) {
      // target[key]
      return Reflect.get(target, key receive);  
    }  
  });
}

参考

剖析 Vue.js 内部运行机制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue双向原理是通过数据劫持和发布订阅模式相结合的方式来实现的。在Vue中,当用户操作View时,ViewModel会感知到变化并通知Model进行相应的改变;反之,当Model发生改变时,ViewModel也能感知到变化并使View作出相应的更新。双向的核心是使用了Object.defineProperty()方法来实现。 在Vue的初始化过程中,会对data数据进行劫持监听,这个过程由监听器Observe来完成。监听器会监听所有属性,当属性发生变化时,会通知订阅者Watcher来判断是否需要更新。由于订阅者Watcher可能有多个,所以需要一个消息订阅器Dep来统一管理这些订阅者。同时,还需要一个指令解析器Compile,用来扫描和解析每个节点的相关指令,将其初始化为一个订阅者Watcher,并替换模板数据或相应的函数。 当订阅者Watcher接收到属性的变化通知时,会执行对应的更新函数,从而更新视图。整个过程中,监听器Observer负责劫持并监听所有属性,订阅者Watcher负责接收属性的变化通知并执行相应的函数,消息订阅器Dep负责收集订阅者并通知Watcher触发更新,指令解析器Compile负责扫描和解析节点的指令并初始化相应的订阅者。 综上所述,Vue双向原理是通过数据劫持+发布订阅模式相结合的方式来实现的,通过监听器、订阅者、消息订阅器和指令解析器等组件的协作,实现了数据和视图之间的双向。 #### 引用[.reference_title] - *1* *2* [vue双向原理](https://blog.csdn.net/qq_41645323/article/details/123324680)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Vue双向原理](https://blog.csdn.net/weixin_52092151/article/details/119810514)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值