vue数据劫持

4 篇文章 0 订阅

原理

vue2.x是基于Object.defineProperty实现双向数据绑定的;该函数可以在获取属性值或者设置属性值的时候监听属性的getset事件,并进行相关的操作;当然,这些具体的操作就需要通过发布订阅者模式作为补充;

Vue 3.0是基于ES6 Proxy重写了其核心逻辑,代替了原来的Object.defineProperty;优势是:

  1. 劫持整个对象并返回一个新对象,而不是像Object.defineProperty需要循环遍历整个对象属性;
  2. 支持监听数组变化;
  3. 支持更加丰富的数据劫持操作;

如模板解析时每遇到一个属性,就为该属性添加一个发布订阅,从而能够进行双向数据绑定;

  1. 乞丐版

    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
    
  2. 为属性添加发布订阅模式
    在这里插入图片描述

    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的操作
    
  3. 升级版
    上边一小节中, 我们已经通过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);
        }
    }
    
    
  4. 完整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: &nbsp;</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>
    

参考文献

  1. 架构通识
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neil-

你们的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值