监听者模式
TIP
什么是监听者模式?简单来说监听者模式就是监听一个对象、数组等发生变化立刻做出回调。vue watch
是一个非常值得借鉴的一个功能点, 下面咱们就基于vue watch
实现一个监听者模式。
对对象和字符串的监听实现
- 设置数据绑定的值,和对应的派发函数 $data/$watch
- 利用 Object.defineProperty 的get和set 实现对数据的拦截和派发
TIP
先定义函数Observe $data 是咱们的数据 $watch 是咱们做数据监听触发对用的函数(相当于vue watch的handler())
数据定义和定义对应的派发函数
- 定义了一个字符串类型myString
- 定义一个boolean 类型的myBoolean
- 定义一个object类型的myObject
//相关代码入下
class Observe {
constructor() {
this.$data = {
myString: '三原',
myBoolean: true,
myObject: {name: '三原'}
}
this.$watch = {
myString: this.myStringFn,
myBoolean: this.myBooleanFn,
myObject: this.myObjectFn
}
}
myStringFn() {}
myBooleanFn() {}
myObjectFn() {}
}
利用 Object.defineProperty 的get和set 实现对数据的拦截和派发
先介绍下Object.defineProperty MDN
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象语法
Object.defineProperty(obj, prop, descriptor)
configurable
: 当且仅当该属性的configurable
键值为true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除enumerable
: 当且仅当该属性的enumerable
键值为true
时,该属性才会出现在对象的枚举属性中value
: 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)writable
: 当且仅当该属性的writable
键值为true
时,属性的值,也就是上面的value
,才能被改变get
: 属性的 getter 函数,如果没有 getter,则为undefined
。当访问该属性时,会调用此函数。set
: 属性的 setter 函数,如果没有 setter,则为undefined
。当属性值被修改时,会调用此函数。
- 了解过属性该开始咱们的代码了如下
- 增加
observer
和defineProperty
函数
class Observe {
constructor() {
this.$data = {
myString: '三原',
myBoolean: true,
myObject: {name: '三原'}
}
this.$watch = {
myString: this.myStringFn,
myBoolean: this.myBooleanFn,
myObject: this.myObjectFn
}
//绑定
this.observer(this.$data);
}
myStringFn(d,old) {
console.log('我是三原watch监听的string类型', d)
}
myBooleanFn(d,old) {
console.log('我是三原watch监听的boolean类型', d)
}
myObjectFn(d, old) {
console.log('我是三原watch监听的object类型', d)
}
observer(data) {
if (typeof data !== 'object' || data == null) {
return;
}
//循环对象绑定
for (let key in data) {
this.defineProperty(key);
}
}
defineProperty(_key) {
Object.defineProperty(this, _key, {
get: function () {
return this.$data[_key];
},
set: function (val) {
const oldVal = cloneDeep(this.$data[_key]);
if (oldVal === val) return val;
this.$data[_key] = val;
if (!!this.$watch[_key] && (typeof (this.$watch[_key]) === 'function')) {
this.$watch[_key].call(this, val, oldVal);
}
return val;
},
enumerable: true,
configurable: true
});
}
}
验证对象、字符串是否监听到并派发对应得函数
let observer = new Observer();
//更改string
observer.myString = '三原-test';
//输出: 我是三原watch监听的string类型 三原-test
//更改boolean
observer.myBoolean = false;
//输出:我是三原watch监听的boolean类型 false
//更改对象
observer.myObject = {
name: '三原文本'
};
//输出:我是三原watch监听的object类型 {name: "三原文本"}
验证通过
- 输出正确
对数组监听实现
TIP
Object.defineProperty
对数组监听不到得, 那在vue
中是那么怎么实现呢?vue
重写了数组几个原型得方法然后来实现数组得派发机制。
- 代码如下
- 更改observer 函数
- 改写数组原型得方法
- 添加一些属性辅助数组得派发机制
- 新增
$data
/$watch
对数组得绑定
class Observe {
constructor() {
this.$data = {
myString: '三原',
myBoolean: true,
myObject: {name: '三原'},
myArray: ['三原', '三原']
}
this.$watch = {
myString: this.myStringFn,
myBoolean: this.myBooleanFn,
myObject: this.myObjectFn,
myArray: this.myArrayFn,
}
//绑定
this.observer(this.$data);
}
myStringFn(d,old) {
console.log('我是三原watch监听的string类型', d)
}
myBooleanFn(d,old) {
console.log('我是三原watch监听的boolean类型', d)
}
myObjectFn(d, old) {
console.log('我是三原watch监听的object类型', d)
}
myArrayFn(d,old) {
console.log('我是三原watch监听的Array类型', d)
}
observer(data) {
if (typeof data !== 'object' || data == null) {
return;
}
let _this = this;
let originalProto = Array.prototype;
// 先克隆一份Array的原型出来
let arrayProto = Object.create(originalProto);
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
methodsToPatch.forEach(method => {
arrayProto[method] = function () {
console.log('数组方法被调用了')
const oldVal = _this.$data[this.notify];
originalProto[method].apply(this, arguments);
//实现派发机制
_this.$watch[this.notify].call(_this, this, oldVal);
};
});
for (let key in data) {
if (Array.isArray(data[key])) {
//给数组方法上绑定一个notify 指定需要得key
arrayProto.notify = key;
//重写array 上的__proto__
data[key].__proto__ = arrayProto;
}
this.defineProperty(key);
}
}
defineProperty(_key) {
Object.defineProperty(this, _key, {
get: function () {
return this.$data[_key];
},
set: function (val) {
const oldVal = cloneDeep(this.$data[_key]);
if (oldVal === val) return val;
this.$data[_key] = val;
if (!!this.$watch[_key] && (typeof (this.$watch[_key]) === 'function')) {
this.$watch[_key].call(this, val, oldVal);
}
return val;
},
enumerable: true,
configurable: true
});
}
}
验证数组是否监听到并派发对应得函数
//更改数组
observer.myArray.push('push进来的');
//输出:数组方法被调用了 我是三原watch监听的Array类型 (3) ["三原", "三原", "push进来的"]
验证数组方法成功
- 验证成功
源码和测试代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script>
class Observer {
constructor() {
this.$data = {
myString: '三原',
myBoolean: true,
myObject: {name: '三原'},
myArray: ['三原', '三原']
}
this.$watch = {
myString: this.myStringFn,
myBoolean: this.myBooleanFn,
myObject: this.myObjectFn,
myArray: this.myArrayFn,
}
//绑定
this.observer(this.$data);
}
myStringFn(d,old) {
console.log('我是三原watch监听的string类型', d)
}
myBooleanFn(d,old) {
console.log('我是三原watch监听的boolean类型', d)
}
myObjectFn(d, old) {
console.log('我是三原watch监听的object类型', d)
}
myArrayFn(d,old) {
console.log('我是三原watch监听的Array类型', d)
}
observer(data) {
if (typeof data !== 'object' || data == null) {
return;
}
let _this = this;
let originalProto = Array.prototype;
// 先克隆一份Array的原型出来
let arrayProto = Object.create(originalProto);
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
methodsToPatch.forEach(method => {
arrayProto[method] = function () {
console.log('数组方法被调用了')
const oldVal = _this.$data[this.notify];
originalProto[method].apply(this, arguments);
//实现派发机制
_this.$watch[this.notify].call(_this, this, oldVal);
};
});
for (let key in data) {
if (Array.isArray(data[key])) {
//给数组方法上绑定一个notify 指定需要得key
arrayProto.notify = key;
//重写array 上的__proto__
data[key].__proto__ = arrayProto;
}
this.defineProperty(key);
}
}
defineProperty(_key) {
Object.defineProperty(this, _key, {
get: function () {
return this.$data[_key];
},
set: function (val) {
const oldVal = (this.$data[_key]);
if (oldVal === val) return val;
this.$data[_key] = val;
if (!!this.$watch[_key] && (typeof (this.$watch[_key]) === 'function')) {
this.$watch[_key].call(this, val, oldVal);
}
return val;
},
enumerable: true,
configurable: true
});
}
}
let observer = new Observer();
//更改string
observer.myString = '三原-test';
//输出: 我是三原watch监听的string类型 三原-test
//更改boolean
observer.myBoolean = false;
//输出:我是三原watch监听的boolean类型 false
//更改对象
observer.myObject = {
name: '三原文本'
};
//输出:我是三原watch监听的object类型 {name: "三原文本"}
//更改数组
observer.myArray.push('push进来的');
//输出:数组方法被调用了 我是三原watch监听的Array类型 (3) ["三原", "三原", "push进来的"]
</script>