这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
vue watch 是一个非常值得借鉴的一个功能点,在你写插件或者写小demo的时候用上就非常漂亮了, 能提升你的编码速度
watch
- 咱们就实现一个自己的 watch
- 实现数据监听
对对象和字符串的监听实现
- 设置数据绑定的值,和对应的派发函数 watch
- 利用 Object.defineProperty 的get和set 实现对数据的拦截和派发
先定义函数Observe watch 是咱们做数据监听触发对用的函数(相当于vue watch的handler())
数据定义和定义对应的派发函数
- 定义了一个字符串类型myString
- 定义一个boolean 类型的myBoolean
- 定义一个object类型的myObject
//相关代码入下
class Observe {
constructor() {
this.$data = {
myString: 'juejin',
myBoolean: true,
myObject: {name: 'juejin'}
}
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: 'juejin',
myBoolean: true,
myObject: {name: 'juejin'}
}
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 = 'jun-jin-test';
//输出: 我是掘金watch监听的string类型 jun-jin-test
//更改boolean
observer.myBoolean = false;
//输出:我是掘金watch监听的boolean类型 false
//更改对象
observer.myObject = {
name: '掘金文本'
};
//输出:我是掘金watch监听的object类型 {name: "掘金文本"}
验证通过
- 输出正确
对数组监听实现
大家都是知道Object.defineProperty对数组监听不到得是那么怎么实现呢?
- vue就是重写数组几个原型得方法然后来实现数组得派发机制得
重写数组得原型上得方法,然后这些方法被调用了 就能监听到了 然而实现派发机制
- 代码如下
- 更改observer 函数
- 改写数组原型得方法
- 添加一些属性辅助数组得派发机制
- 新增
$data
/$watch
对数组得绑定
class Observe {
constructor() {
this.$data = {
myString: 'juejin',
myBoolean: true,
myObject: {name: 'juejin'},
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: 'juejin',
myBoolean: true,
myObject: {name: 'juejin'},
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 = 'jun-jin-test';
//输出: 我是掘金watch监听的string类型 jun-jin-test
//更改boolean
observer.myBoolean = false;
//输出:我是掘金watch监听的boolean类型 false
//更改对象
observer.myObject = {
name: '掘金文本'
};
//输出:我是掘金watch监听的object类型 {name: "掘金文本"}
//更改数组
observer.myArray.push('push进来的');
//输出:数组方法被调用了 我是掘金watch监听的Array类型 (3) ["掘金", "三原", "push进来的"]
</script>