vue数据劫持

原理

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. 为属性添加发布订阅模式
    vue数据劫持_第1张图片

    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

    
    <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> 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. 架构通识

你可能感兴趣的:(react,vue)