vue双向绑定实现的过程

点击此处跳转查看vue双向绑定基本理论

1. 实现双向绑定的过程理论阐述
在这里插入图片描述
如何实现双向绑定,上图的流程显示,
(1)需要用Observer来监听属性的变化
(2)当属性变化时候,需要通知订阅者Watcher是否需要更新,此时订阅者可能具有多个,因此需要订阅器Dep来专门接收这些订阅者,并统一管理
(3)解析器Compile可以对每一个元素节点进行解析和扫描,将相关指令对应初始化成为一个订阅者Watcher,并替换对象中的数据或者函数
(4)订阅者Watcher接收相应的属性变化,更新函数和视图
2.代码展示
展示vue data对象中已经封装set 和get函数:
在这里插入图片描述

var vm = new Vue({
    data: {
        obj: {
            a: 1
        }
    },
    created: function () {
        console.log(this.obj);
    }
});

2.如何实现一个Observer:
Observer是一个数据监听器,其核心就是使用Object.defineProperty().

function monitorData(data, key, val) {
    observe(val); // 递归遍历所有子属性
    Object.defineProperty(data, key, {
        get: function() {
            return val;
        },
        //利用set函数监听data对象
        set: function(newVal) {
            val = newVal;
            console.log('属性' + key + '正在被监听属性值在发生改变:“' + newVal + '”');
        }
    });
}
 
function observe(data) {
	//判断data是否为object
    if (!data || typeof data !== 'object') {
        return;
    }
    //Object.keys(data) 返回一个所有元素为字符串的数组,其元素来自于从给定的object上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。
    Object.keys(data).forEach(function(key) {
    	console.log(key);
        monitorData(data, key, data[key]);
    });//遍历所有data的对象的属性状态
};
 
var data = {
    data1: {
        value: ''
    },
    data2: ''
};
observe(data);
data.data1.value = '1'; 
data.data2 = '2';  

结果展示:
在这里插入图片描述

提要:(1)Object.keys(obj);方法:点击此处查看用法

3.如何实现Dep

需要创建一个可以容纳订阅器Dep的容器,这个容器是list用一个数组表示,将上面的Observer改造一下,植入dep消息订阅器

function defineReactive(data, key, val) {
		observe(val); // 递归遍历所有子属性
		var dep = new Dep(); 
		console.log(dep);                   
		Object.defineProperty(data, key, {
			enumerable: true,
			configurable: true,
			get: function() {
				if(Dep.target) { // 判断是否需要添加订阅者
					dep.addSub(Dep.target); // 在这里添加一个订阅者
				}
				return val;
			},
			set: function(newVal) {
				if(val === newVal) {
					return;
				}
				val = newVal;
		   console.log('属性' + key + '正在被监听属性值在发生改变:“' + newVal + '”');
				dep.notify(); // 如果数据变化,通知所有订阅者
			}
		});
	}
	

	function observe(data) {
		if(!data || typeof data !== 'object') {
			return;
		}
		Object.keys(data).forEach(function(key) {
			defineReactive(data, key, data[key]);
		});
	};
	//维护订阅器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;
var data = {
    data1: {
        value: ''
    },
    data2: ''
};
observe(data);
data.data1.value = '1'; 
data.data2 = '2';  

结果展示:
在这里插入图片描述
4.如何实现订阅者Watcher
(1)订阅者Watcher在初始化阶段将自己添加入Dep中,然后同时调用对应的在监听器Observer的Object.defineProperty();方法中的get函数去执行相应的添加订阅者的操作即可。
(2)在Watcher初始化阶段才需要添加订阅者,因此需要用Dep.target缓存下订阅者,添加成功后直接去除也就是Dep.target = null;

function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    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;
    }
};

5.如何实现简易的数据双向传递实例
第一步:关联Observer 和Watcher关联起来

	//observer和Watcher关联起来
	function TestVue(data, el, exp) {
		this.data = data;
		observe(data);
		el.innerHTML = this.data[exp]; // 初始化模板数据的值
		new Watcher(this, exp, function(value) {
			el.innerHTML = value;
		});
		return this;
	}

第二步:new 一个TestVue类,来实现双向绑定

	var testVue = new TestVue({
		data1: "1"
	}, elem, 'data1');
	window.setTimeout(function() {
		console.log('name值改变了');
		testVue.data.data1 = '2';
	}, 2000);

第三步:在body中写入{{data1}}

<h1 id="name">{{data1}}</h1>

第四步:在script中写入

var elem = document.querySelector('#name');

第五步:呈现完整的代码

<!DOCTYPE html>
<html>

	<head>
		<meta charset="utf-8" />
		<title></title>
		<!--<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>-->
	</head>
	<h1 id="name">{{data1}}</h1>

	<body>

	</body>

</html>
<script type="text/javascript">
	function defineReactive(data, key, val) {
		observe(val); // 递归遍历所有子属性
		var dep = new Dep();
		console.log(dep);
		Object.defineProperty(data, key, {
			enumerable: true,
			configurable: true,
			get: function() {
				if(Dep.target) { // 判断是否需要添加订阅者
					dep.addSub(Dep.target); // 在这里添加一个订阅者
				}
				return val;
			},
			set: function(newVal) {
				if(val === newVal) {
					return;
				}
				val = newVal;
				console.log('属性' + key + '已经被监听了,现在值为:“' + newVal + '”');
				dep.notify(); // 如果数据变化,通知所有订阅者
			}
		});
	}

	function observe(data) {
		if(!data || typeof data !== 'object') {
			return;
		}
		Object.keys(data).forEach(function(key) {
			defineReactive(data, key, data[key]);
		});
	};

	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;

	function Watcher(vm, exp, cb) {
		this.cb = cb;
		this.vm = vm;
		this.exp = exp;
		this.value = this.get(); // 将自己添加到订阅器的操作
		console.log(cb);
		console.log(vm)
		console.log(exp);
		console.log(this.get());
	}

	Watcher.prototype = {
		update: function() {
			this.run();
		},
		run: function() {
			var value = this.vm.data[this.exp];
			var oldVal = this.value;
			if(value !== oldVal) {
				console.log(value);
				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;
		}
	};
	//observer和Watcher关联起来
	function TestVue(data, el, exp) {
		this.data = data;
		observe(data);
		el.innerHTML = this.data[exp]; // 初始化模板数据的值
		new Watcher(this, exp, function(value) {
			el.innerHTML = value;
		});
		return this;
	}
	var elem = document.querySelector('#name');
	var testVue = new TestVue({
		data1: "1"
	}, elem, 'data1');
	window.setTimeout(function() {
		console.log('name值改变了');
		testVue.data.data1 = '2';
	}, 2000);
</script>

最后结果展示:
在这里插入图片描述
未完待续

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值