原理
vue2.x是基于Object.defineProperty
实现双向数据绑定的;该函数可以在获取属性值或者设置属性值的时候监听属性的get
和set
事件,并进行相关的操作;当然,这些具体的操作就需要通过发布订阅者模式作为补充;
Vue 3.0是基于ES6 Proxy
重写了其核心逻辑,代替了原来的Object.defineProperty
;优势是:
- 劫持整个对象并返回一个新对象,而不是像
Object.defineProperty
需要循环遍历整个对象属性; - 支持监听数组变化;
- 支持更加丰富的数据劫持操作;
如模板解析时每遇到一个属性,就为该属性添加一个发布订阅,从而能够进行双向数据绑定;
-
乞丐版
function observe(obj) { if (!obj || typeof obj !== 'object') { // 处理类型为null/undefined/非对象 return; } Object.keys(obj).forEach(key => { // 遍历子属性并进行属性监听; defineReactive(obj, key, obj[key]); }) } function defineReactive(obj, key, value){ observe(value); // 递归子属性 Object.defineProperty(obj, key, { configurable: true, enumerable: true, get() { console.log('get value: ', value); return value; }, set(newVal) { console.log('set value: ', newVal); value = newVal } }) } var data = {name: 'vue'} observe(data); data.name; // get value: fn data.name = 'fn'; // set value: fn data.name; // get value: fn
-
为属性添加发布订阅模式
class Dep { constructor(){ this.subs = []; } // 添加订阅者 add(sub) { // sub 是watcher的实例, 每次解析到模板中的属性时,就通过watcher为属性添加订阅并更新视图; this.subs.push(sub); } // 通知变化 notify() { this.subs.forEach(sub => { sub.update(); }) } } // 通过静态属性设置Dep的目标对象 Dep.target = null; class Watcher { constructor(obj, key, cb) { Dep.target = this; // 设置Dep通知变化的目标对象 this.cb = cb; this.obj = obj; // 待监听对象 this.key = key; this.value = obj[key]; // 手动触发属性的get事件 Dep.target = null; // 每次为属性添加一个watcher之后解除Dep与Watcher的关联; } // 接受变化并更新视图 update() { // get new value this.value = this.obj[this.key]; // update view this.cb(this.value); } }
测试
// Watcher的cb函数 function update(value) { document.querySelector('div').innerText = value } var data= {name: 'vue'}; observe(data); new Watcher(data, 'name', update); // 模拟解析属性name的操作
-
升级版
上边一小节中, 我们已经通过watcher模拟解析到属性时的操作;但是这个行为是我们手动操作的,并没有与observe建立关系让其自动订阅与通知;本节实现此功能;function observe(obj) { if (!obj || typeof obj !== 'object') { // 处理类型为null/undefined/非对象 return; } Object.keys(obj).forEach(key => { // 遍历子属性并进行属性监听; defineReactive(obj, key, obj[key]); }) } function defineReactive(obj, key, value) { observe(value); // 递归子属性 let dep = new Dep(); Object.defineProperty(obj, key, { configurable: true, enumerable: true, get() { console.log('get value: ', value); if (Dep.target) { dep.addSub(Dep.target); // Dep.target是watcher实例,即订阅者sub } return value; }, set(newVal) { console.log('set value: ', newVal); value = newVal; // 调用watcher的update方法更新视图 dep.notify(); } }) } class Dep { constructor() { this.subs = []; } // 添加订阅者 addSub(sub) { // 订阅者sub 是watcher的实例, 每次解析到模板中的属性时,就通过watcher为属性添加订阅并更新视图; this.subs.push(sub); } // 通知变化 notify() { this.subs.forEach(sub => { sub.update(); }) } } // 通过静态属性设置Dep的目标对象, 建立Dep与Watcher的关系 Dep.target = null; class Watcher { constructor(obj, key, cb) { Dep.target = this; // 设置Dep通知变化的目标对象 this.cb = cb; this.obj = obj; // 待监听对象 this.key = key; this.value = obj[key]; // 手动触发属性的get事件 Dep.target = null; // 每次为属性添加一个watcher之后解除Dep与Watcher的关联; } // 接受变化并更新视图 update() { // get new value this.value = this.obj[this.key]; // update view this.cb(this.value); } }
-
完整demo
参见github<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue数据劫持</title> <style> .collapse { color: lightskyblue; } #show { font-size: 30px; color: greenyellow; } </style> </head> <body> <div class="collapse"> <label for="input">Your name: </label> <input type="text" id="input"> <div id="show"</div> </div> <script> function observe(obj) { if (!obj || typeof obj !== 'object') { // 处理类型为null/undefined/非对象 return; } Object.keys(obj).forEach(key => { // 遍历子属性并进行属性监听; defineReactive(obj, key, obj[key]); }) } function defineReactive(obj, key, value) { observe(value); // 递归子属性 let dep = new Dep(); Object.defineProperty(obj, key, { configurable: true, enumerable: true, get() { console.log('get value: ', value); if (Dep.target) { dep.addSub(Dep.target); // Dep.target是watcher实例 } return value; }, set(newVal) { console.log('set value: ', newVal); value = newVal; // 调用watcher的update方法更新视图 dep.notify(); } }) } class Dep { constructor() { this.subs = []; } // 添加订阅者 addSub(sub) { // sub 是watcher的实例, 每次解析到模板中的属性时,就通过watcher为属性添加订阅并更新视图; this.subs.push(sub); } // 通知变化 notify() { this.subs.forEach(sub => { sub.update(); }) } } // 通过静态属性设置Dep的目标对象, 建立Dep与Watcher的关系 Dep.target = null; class Watcher { constructor(obj, key, cb) { Dep.target = this; // 设置Dep通知变化的目标对象 this.cb = cb; this.obj = obj; // 待监听对象 this.key = key; this.value = obj[key]; // 手动触发属性的get事件 Dep.target = null; // 每次为属性添加一个watcher之后解除Dep与Watcher的关联; } // 接受变化并更新视图 update() { // get new value this.value = this.obj[this.key]; // update view this.cb(this.value); } } function update(value) { document.querySelector('#show').innerText = value } var data = { name: 'vue' }; observe(data); new Watcher(data, 'name', update); function handleChange(e) { data.name = e.target.value; } window.onload = function () { let input = document.querySelector('#input'); let show = document.querySelector('#show').innerText = data.name; input.addEventListener('change', e => { handleChange(e); }) } </script> </body> </html>