一、理论知识
Vue双向数据绑定原理:
二、基本流程
实现一个类似于vue双向数据绑定的响应式系统,以下是简易流程图
由上面的图,关于监听器(Observer)、订阅器(Dep)、订阅者(Watcher)可能对没有学过设计模式的人有一点点抽象,那换个简单的例子说下他们的关系
在以上信息中,我们抽离出小明这个人他就是一个订阅者实例,小红手上的户型A信息就是数据,小红就是监听器实例,而这个记录这些A户型意向购买者信息的小本本就是Dep实例。当户型A有开始售卖了,小红就赶紧通知小本本上的人。
这里是举的一个小小的例子,可能不太恰当,详细具体的可以去看发布-订阅模式
接下来,我们一点一点的实现一个响应式系统
1.实现监听器Observer
在这里我们将通过Object.defineProperty方法实现数据劫持,也就是说我们能感知数据对象的读写,使得数据对象变得可观测。
- 关于ObjectdefineProperty
这里就不展开细说了 详细的使用方法查看MDN。需要注意的就是属性描述符和存取描述符
的使用
接下来是coding时间:
基本实现
- defineReactive()方法实现属性的数据拦截
function defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
get: function getter() {
console.log("获取")
return value;
},
set: function setter(newValue) {
if (newValue === value) return;
value = newValue
console.log("设置")
}
})
}
- Observer类实现对象属性的遍历拦截
class Observer {
constructor(obj) {
this.value = obj
this.trave()
}
trave() {
Object.keys(this.value).forEach((key) => defineReactive(this.value, key,this.value[key]))
}
}
优化提升
通过以上代码我们基本可以实现对一个对象obj所有属性的数据劫持,但是有一个小小的问题,如果对象属性是一个对象呢,也就是说obj内有嵌套属性,因此我们可以进行遍历,因此将代码优化为
class Observer {
constructor(obj) {
this.value = obj
this.trave()
}
trave() {
Object.keys(this.value).forEach((key) => defineReactive(this.value, key,this.value[key]))
}
}
function observe(obj){
if(!obj||typeof obj!=='object'){
return;
}
//监听器实例化
new Observer(obj);
}
function defineReactive(obj, key, value) {
//对每个属性值进行判断
observe(value)
Object.defineProperty(obj, key, {
get: function getter() {
console.log("获取")
return value;
},
set: function setter(newValue) {
if (newValue === value) return;
value = newValue
//对新设置的属性也需要进行判断
observe(newValue)
console.log("设置")
}
})
}
2.实现订阅者Watcher
先来了解Watcher需要实现的功能是什么?
- 当实例化一个订阅者的时候,意味着需要获取它订阅的属性
- 当订阅者收到数据变化后,需要去调用相应的回调函数
接下来coding时间:
基本实现
class Watcher{
constructor(obj,exp,cb){
this.obj = obj;
//exp 为string类型 类似于'person.name'这样的string
this.exp = exp;
this.cb = cb;
//实例化Watcher实例时获取它订阅的属性
this.value = this.get()
}
//订阅属性
get(){
var value = getValue(this.obj,this.exp);
return value;
}
//数据变化时,执行回调函数
update(){
var newVal = getValue(this.obj,this.exp);
var oldVal = this.value;
if(newVal!==oldVal){
//数据更新
this.value = newVal;
//执行回调函数
this.cb();
}
}
}
function getValue(obj,exp) {
var keys= exp.split(".");
var value;
for(let i of keys){
if(!obj) return;
value = obj[i];
}
return value;
}
3.实现订阅器Dep
我们先来了解一下它需要实现的功能是什么?
- 每一个属性对应一个dep实例,dep实例存储每一个订阅该属性的Watcher
- 当数据发生变化时,对应的dep实例要去通知存储的每一个watcher
基本实现
class Dep{
constructor(){
this.subs = [];
}
//将Watcher实例添加进subs数组
addSub(sub){
this.subs.push(sub);
}
//派发消息 使得每个watcher实例调用update方法
notify(){
this.subs.forEach(function (sub) {
sub.update();
})
}
}
优化提升
经过上述的coding,我们只是进行了基本类的实现,而他们之间的联系还没有进行实现,具体是以下功能:
- 属性什么时候添加订阅器,属性改变时怎么通知对应的dep实例
function defineReactive(obj, key, value) {
//实例化dep
var dep = new Dep();
observe(value)
Object.defineProperty(obj, key, {
get: function getter() {
return value;
},
set: function setter(newValue) {
if (newValue === value) return;
value = newValue;
observe(newValue);
//派发消息
dep.notify();
}
})
}
- 怎么将实例化watcher实例时添加到对应属性的dep实例中?
- 怎么getter方法中获取watcher实例?
- 修改Watcher
get(){
//将watcher实例挂靠在getter方法能访问到地方
Dep.target = this
var value = getValue(this.obj,this.exp);
//需要重置Dep.target
Dep.target = null;
return value;
}
- 修改defineReactive函数
get: function getter() {
//判断Dep.target是否有值,如果有的话 添加watcher实例
if(Dep.target){
dep.addSub(Dep.target);
return value;
}
}
三、完整代码
class Observer {
constructor(obj) {
this.value = obj
this.trave()
}
trave() {
Object.keys(this.value).forEach((key) => defineReactive(this.value, key,this.value[key]))
}
}
function observe(obj){
if(!obj||typeof obj!=='object'){
return;
}
new Observer(obj);
}
function defineReactive(obj, key, value) {
observe(value)
//实例化dep
var dep = new Dep();
Object.defineProperty(obj, key, {
get: function getter() {
if(Dep.target){
dep.addSub(Dep.target);
}
return value;
},
set: function setter(newValue) {
if (newValue === value) return;
value = newValue
observe(newValue);
dep.notify()
}
})
}
//watcher
class Watcher{
constructor(obj,exp,cb){
this.obj = obj;
this.exp = exp;
this.cb = cb;
//实例化Watcher实例时获取它订阅的属性
this.value = this.get()
}
//订阅属性
get(){
Dep.target = this;
var value = getValue(this.obj,this.exp);
Dep.target = null
return value;
}
//数据变化时
update(){
var newVal = getValue(this.obj,this.exp);
var oldVal = this.value;
if(newVal!==oldVal){
//数据更新
this.value = newVal;
this.cb();
}
}
}
function getValue(obj,exp) {
var keys= exp.split(".");
console.log(keys)
var value;
for(let i of keys){
if(!obj) return;
value = obj[i];
}
return value;
}
class Dep{
constructor(){
this.subs = [];
}
//将Watcher实例添加进subs数组
addSub(sub){
this.subs.push(sub);
}
//派发消息 使得每个watcher实例调用update方法
notify(){
this.subs.forEach(function (sub) {
sub.update();
})
}
}
四、实现渲染
通过以上步骤 我们就是实现了数据变化,相应的订阅者收到消息,类似于Vue.$watch。对于真正的view和data的双向绑定需要有进一步的具体的实现。4.8号之前找时间实现了
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!