前言
- 认识Object.defineProperty()
// Object.defineProperty 三个参数
// 第一个参数,属性所在的对象
// 第二个参数,要操作的属性
// 第三个参数,被操作属性的特性
// 格式是 {}
//configurable,enumerable,value,writable
// set 写入属性时触发
// get 读取属性
let obj = {value:'kk'}
Object.defineProperty(obj, "value", {
get: function() {
console.log('get');
return 1;//这里不可以写obj.value会死循环,疯狂get,因为obj.value就是给一个读取操作
},
set: function(newValue) {
console.log('set');
//obj.value = newValue 同理这里也会死循环
console.log(newValue)
}
});
Object.defineProperty()在MDN 详细介绍看这里
2. 简单了解发布订阅模式
本人也不是很懂设计模式,后期补充。
简单描述:这个模式就像是你订阅关注了科比,以后有科比的消息会主动推送给你。需要有一个订阅器,一个发布器。
订阅器:就像是存放当前订阅者信息的容器。
举个简单的例子:
我的手机,里面有我的身份讯息,比如我喜欢某岛国知名动作女星--深田某美,咳咳不对,是科比,我订阅了科比,
那么我的手机像是监听器一样,帮我盯着科比的动向,科比不单单是一个人的青春。别人也可以通过订阅器订阅科比
当然也可以订阅别人,但是手机作为订阅器记录了我的信息我的喜好,并且手机收到了我订阅的新闻之后会告诉我。
发布器:是存放所有订阅器的容器和发布装置。
里面存放了所有的订阅信息,有某美的,有科比的,有周星星的,这里他负责帮我监听科比的动向。
一旦有消息,便会给订阅器的订阅者发送消息
原理核心
1-5准备工作
6 代码部分
1.html部分
<div id="app">
<span>改变我:</span>
<input type="text" id="input" kk-model="name" />
<br />
<span>我也变:</span>
<input type="text" id="input1" kk-model="name" />
</div>
2.实例生成
// 生成vue实例
const vm = new MyVue({
el: "#app",
data: {
value: "123",
name: "kk",
},
});
3.MyVue类
1.创建MyVue声明类:接收el,data等参数
2.数据初始化initData:
将vm实例化的data数据挂载在实例对象下面,通过Object.defineProperty()
并添加set() get()方法
3.视图层初始化initModel:
a.获取所有[kk-model]绑定的节点,遍历循环所有节点给他们添加订阅器
b.第一次数据驱动dom更新=>给node节点value赋值
c.给订阅节点添加input事件=> 用户输入操作更新数据变化
4.发布器Dep
1.存储所有订阅器的容器
2.发布更新通知===>通过触发订阅器的更新方法
5.订阅器Watcher
1.存放订阅者的信息
2.更新视图
6.code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div class="" id="app">
<span>改变我:</span>
<input type="text" id="input" kk-model="name" />
<br />
<span>我也变:</span>
<input type="text" id="input1" kk-model="name" />
</div>
<script>
// 发布订阅部分
// 发布器
class Dep {
constructor() {
// 存储所有
this.watchers = [];
}
addWatcher(watcher) {
this.watchers.push(watcher);
}
// 发布通知
notify() {
this.watchers.forEach((watcher) => {
//这里所有的订阅者都会遍历
watcher.update(); //更新通知所有观察者
});
}
}
// 订阅器
class Watcher {
constructor(vm,key,callback) {
this.callback = callback;
this.vm = vm
this.key = key
Dep.target = this
this.value = this.vm[this.key]
Dep.target = null
}
update() {
console.log("update");
this.callback();
}
}
class MyVue {
constructor({ el, data }) {
// 挂载容器
this.container = document.querySelector(el);
// 数据挂载
this.data = data;
this.ininData();
this.initModel()
}
// 初始化数据
ininData() {
// 将data里面的数据 挂载到vue实例下
Object.entries(this.data).forEach(([key, value]) => {
let reactiveValue = value;
const dep = new Dep()
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get() {
// dep.addWatcher(this[key]) 这里不能这样写会造成死循环
// 为了解决这个问题 需要给Dep添加一个 属性target
// 那么Dep.target 添加在哪里
Dep.target&&dep.addWatcher(Dep.target)
return reactiveValue;
},
set(newValue) {
//如果数据发生变化才会更新数据
if (reactiveValue !== newValue) {
reactiveValue = newValue;
dep.notify()
}
},
});
});
}
// 初始化model 视图
initModel() {
// 获取所有双向绑定的dom 这里属性命名可以自定义
const nodes = document.querySelectorAll("[kk-model]");
nodes.forEach((node) => {
const key = node.getAttribute('kk-model')
// 这里给data里每一条数据加上订阅器 添加回调(发布时赋值)可以在Dep里触发Watcher.update
new Watcher(this,key,()=>{
node.value = this[key]
})
// 这里第一次初始化 将data数据赋值给node(双向绑定的节点)
node.value = this[key]
// 监听触发set方法
node.addEventListener("input",(ev)=>{
this[key] = ev.target.value
},false)
});
}
}
// 生成vue实例
const vm = new MyVue({
el: "#app",
data: {
value: "123",
name: "kk",
},
});
</script>
</body>
</html>
7.总结
难点:get方法里面的Dep.target 需要好好理解
总结:通过Object.defineProperty定义data的所有属性,在get中收集观察者,在set中触发观察者更新DOM
缺点:这里只是简单模拟了vue的双向绑定实现,有很多待优化的地方
郑重声明
本人代码搬运工,非常感谢这些资料让我受益良多,也分享给看到这篇文章的人。
另外,如有错误欢迎指出
学习资源来自于:
手写双向绑定
小破站视频:手动实现MVVM双向绑定