观察者模式
一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统
// 主题对象
function Dep(){
this.subs = []; //订阅者列表
}
// 主题对象通知所有的订阅者
Dep.prototype.notify=function(){
this.subs.forEach(function(sub){
sub.update();
})
}
// 订阅者
function Sub(x){
this.x=x;
}
Sub.prototype.update=function(){
this.x=this.x*this.x;
console.log(this.x)
}
//发布者
var pub={
publish:function(){
dep.notify();
}
}
var dep=new Dep();
// 使用的数组的原型上的方法
Array.prototype.push.call(dep.subs, new Sub(1), new Sub(2), new Sub(3))
//发布者发布消息
pub.publish();
对于Vue,我们这里可以认为Vue实例中的data中的每一项属性是一个Dep,而所有用到这个属性的地方都是这个Dep的一个订阅者(sub),当这个属性值变化时,观察者通过监听捕获变化,告诉这个dep去notify每一个订阅者。 以下代码实现党data.a.b.c属性发生改变是时,触发第二个参数中的函数,
class Watcher{
// expOrFn为data.a.b.c cb为回调函数
constructor(vm,expOrFn,cb){
this.vm=vm;
this.getter=parsePath(expOrFn);
this.cb=cb;
this.value=
this.get();
}
get(){
window.target=this;
let value=this.getter.caller(this.vm,this.vm);
window.target=undefined;
return value;
}
update(){
const oldValue=this.value;
this.value=this.get();
this.cb.call(this.vm,this.value,this.oldValue)
}
}
但是上面的代码只是实现了一个属性的变化侦测,我们需要做到的是侦测一个对象中所有的属性,所以需要封装一个Objserver类,也就是给一个数据里边的每一个属性都增加getter和setter属性。
依赖收集
class Dep{
constructor(){
this.subs=[];
}
addSub(sub){
this.subs.push(sub)
}
removeSub(sub){
remove(this.subs,sub)
}
depend(){
if(window.target){
this.addSub(window.target);
}
}
notify(){
let subs=this.subs.slice();
for(var i=0;i<subs.length;i++){
subs[i].update();
}
}
}
function remove(arr,sub){
if(arr.length){
const index=arr.indexOf(sub);
if(index>-1){
return arr.splice(index,1)
}
}
}
function defineReative(data,key,val){
let dep=new Dep();
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get:function(){
dep.depend();
return val;
},
set:function(newVal){
if(val==newVal) {
return
}
val=newVal;
dep.notify();
}
})
}
依赖是谁
上面想要收集的是谁?,换句话说,属性变化后应该通知谁?
<!--
* @Author: your name
* @Date: 2020-09-24 08:15:44
* @LastEditTime: 2021-01-28 18:21:44
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \day01c:\vue\objectStudy\index.html
-->
<!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></body>
<script>
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
remove(this.subs, sub);
}
depend() {
if (window.target) {
this.addSub(window.target);
}
}
notify() {
let subs = this.subs.slice();
for (var i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
function remove(arr, sub) {
if (arr.length) {
const index = arr.indexOf(sub);
if (index > -1) {
return arr.splice(index, 1);
}
}
}
let bailRe=/[^\w.$]/;
function parsePath(path){
if(bailRe.test(path)){
return
}
const segments=path.split('.');
return function(obj){
for(var i=0;i<segments.length;i++){
if(!obj) return
obj=obj[segments[i]];
}
return obj;
}
}
console.log(parsePath('a.b.c'))
class Watcher{
// expOrFn为data.a.b.c cb为回调函数
constructor(vm,expOrFn,cb){
this.vm=vm;
this.getter=parsePath(expOrFn);
this.cb=cb;
this.value=
this.get();
}
get(){
window.target=this;
let value=this.getter.caller(this.vm,this.vm);
window.target=undefined;
return value;
}
update(){
const oldValue=this.value;
this.value=this.get();
this.cb.call(this.vm,this.value,this.oldValue)
}
}
class Observer{
constructor(value){
this.value=value;
if(!Array.isArray(value)){
this.walk();
}
}
walk(){
const keys=Object.keys(obj);
for(var i=0;i<keys.length;i++){
defineReactive(obj,keys[i],obj[keys[i]])
}
}
}
function defineReactive(data,key,value){
if(typeof val==='object'){
new Observer(val)
}
let dep=new Dep();
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get:function(){
// 订阅
dep.depend();
return val
},
set:function(newVal){
if(val==newVal){
return
}
val=newVal;
dep.notify();
}
})
}
</script>
</html>