一、双向绑定
- 双向绑定是指视图层和数据层的双项绑定
- 视图改变,数据同步,view→model:事件监听
- 数据改变,视图同步,model→view:数据劫持+发布订阅者模式
二、原理
1. Vue2实现双向数据绑定
- 数据劫持+发布订阅者模式
- 对象
- 对象存储是将一个指向对象的指针(即引用)存储在栈中,指针指向真正的存储在堆中的位置
- 对象的属性描述符分为数据描述符和存取描述符
- 数据描述符:writable(可修改)、enumerable(可枚举)、configurable(可配置),属性如果是可配置的,就可以使用Object.defineProperty(...)来修改属性描述符
- 操作描述符:function get() → getter(获取属性值时调用)、function set → setter(设置属性值时调用)
- Object.defineProperty
- 此方法可用来创建、修改、添加多个属性值
- 数据劫持
- 通过定义Object.defineProperty的set函数,当数据发生改变就触发此函数,将处理数据更新的一些方法存在set函数中来实现实时更新。
- 发布订阅模式
- 定义对象之间一对多的依赖关系,当一个对象的状态改变(发布者)时,所有依赖于他的对象(订阅者)都将得到通知
- 数据变化为发布者,依赖对象为订阅者
2. Vue2中的实现
- 数据改变,视图同步
- 主要通过Observer(监听器)、Dep(消息订阅器)、Watcher(订阅者)、Compiler(解析器)
- 主要通过Observer(监听器)、Dep(消息订阅器)、Watcher(订阅者)、Compiler(解析器)
- 具体过程
- 监听器劫持并监听所有属性,如果属性变化,就通知订阅者
- 订阅器收集订阅者,对监听器和订阅者进行统一管理
- 订阅者收到属性的变化通知并执行相应的方法,从而更新视图
- 解析器解析每个节点的相关指令,对模板数据和订阅器进行初始化
- 流程图:编译器首先根据元数据初始化视图,并且将更新函数绑定到订阅者上,一个属性对应一个订阅者,消息订阅器负责把一个个订阅者收集起来,一旦监听器发现属性中有发生变化的,就将变化通知给对应的消息订阅器,订阅器找到对应的订阅者调用他的更新函数,实现视图的更新。
三、实现
1. Vue 2-:Object.defineProperty
- 参数
- obj:操作对象
- prop:操作对象的某个具体属性
- descriptor:被定义或修改的属性描述符
- 返回值
- 被传递给函数的对象
- 实现
- (1)Observer监听器实现数据可监测
// 循环遍历数据对象的每个属性 function observable(obj) { if (!obj || typeof obj !== 'object') { return } let keys = Object.keys(obj) keys.forEach((key) =? { defineReactive(obj, key, obj[key]) }) return obj } // 将对象的属性用Object.defineProperty进行设置,使数据可观测 function defineReactive(obj, key, val) { var dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, // 取值操作 get() { // 植入订阅器 if (Dep.target) { dep.addsub(Dep.target) } return val }, // 赋值操作 set(newVal) { if (newVal === val) return val = newVal dep.notify() } }) }
- (2)Dep订阅器实现依赖收集
function Dep() { this.subs = [] } Dep.prototype = { addSub: function(sub) { this.subs.push(sub) }, notify: function() { this.subs.forEach(function(sub) { sub.update() }) } } Dep.target = null // 全局唯一的Watcher
- (3)Watcher订阅者实现
function Watcher(vm, exp, cb) { this.vm = vm // Vue的实例对象 this.exp = exp // node节点的v-model等指令的属性值或插值符号中的属性 this.cb = cb // Watcher绑定的更新函数 this.value = this.get() } Watcher.prototype = { update: function() { this.run() }, run: function() { var value = this.vm.data[this.exp] var oldVal = this.value if (value !== oldVal) { this.value = value this.cb.call(this.vm, value, oldVal) } }, get: function() { Dep.target = this // 将自己赋值成全局的订阅者 var value = this.vm.data[this.exp] // 强制执行监听器里的get函数 Dep.target = null return value } }
- (4)Compile解析器实现解析DDM节点
function Compile(el, vm) { this.vm = vm this.el = document.querySelector(el) } Compile.prototype = { compileText: function(node, exp) { var self = this var initText = this.vm[exp] this.updateText(node, initText) new Watcher(this.vm, exp, function(value) { self.updateText(node, value) }) }, updateText: function (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }, }
2. Vue 3+: proxy && value setter
- proxy:ES6新增方法,用来修改某些操作的默认行为
- 参数:支持两个参数
- 参数1:target(目标对象)
- 参数2:prop(配置对象,对每一个需要代理的操作,提供一个处理函数,共13种,包括get、set、has、deleteProperty、ownKeys、getOwnPropertyDescriptor、defineProperty、preventExtensions、getprototypeOf、isExtensible、setPrototypeOf、apply、construct)