前言
在上一节我们实现了Compile
类来进行视图的初始化 时光机 这一节来实现一下对所有数据的劫持监听Observer
类,相对来说较为简单。
Observer语法
实现数据劫持必不可少的就是使用Object.defineProperty
方法(V2),所以在实现Observer
之前先来了解一下它。
定义
Object.defineProperty()
静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
参数
该方法有三个参数分别为对象本身,要监听的属性,属性描述符,其中属性描述符又分为数据描述符和存取描述符,存取描述符顾名思义就是get
set
。数据描述符中有value
(默认值)enumberable
(是否可枚举)writable
(是否可以被修改)configurable
(是否可删除)需要注意的是描述符对象中不能同时存在value
和get
或者writable
和set
,原因也很简单,如果设置了value
和get
将会一直调用get
方法。
形式
const obj = {};
Object.defineProperty(obj, "name", {
enumerable: false,
configurable: false,
writable: false,
value: "skyler",
});
缺点
该方法也存在一些弊端,也是vue中存在的并且改用proxy
来进行响应式的原因之一。
1、不能监听添加的额外属性或者删除属性;
const obj = { foo: 'hello' };
Object.defineProperty(obj, 'foo', {
get() {
console.log('get foo');
return this._foo;
},
set(value) {
console.log('set foo');
this._foo = value;
}
});
obj.bar = 'world'; // 无法被监听到
delete obj.foo; // 无法被监听到
2、不能监听数组下标的变化
const arr = [1, 2, 3];
Object.defineProperty(arr, '0', {
get() {
console.log('get 0');
return this._0;
},
set(value) {
console.log('set 0');
this._0 = value;
}
});
arr[0] = 0; // 可以被监听到
arr.push(4); // 无法被监听到
3、性能问题
使用Object.defineProperty
监听数据变化,需要为每个属性都创建一个 setter
和 getter
,这会带来一定的性能问题,特别是当数据对象较大时。
实现Observer数据劫持
作用
- 劫持监听所有属性
实现
新建Observer.js
文件并在index.html
文件中进行引入
<script src="./Observer.js"></script>
<script src="./Mvue.js"></script>
然后在Mvue.js
文件中给Observer
类传递要监听的数据源
class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 1、实现一个数据观察者
new Observer(this.$data);
// 2、实现一个指令解析器
new Compile(this.$el, this)
}
}
}
创建Observer
类,判断传递过来的数据是否为对象,这里只考虑data
为对象的情况,如果是对象就获取到对象中的每一个属性并传入defineReactive
中进行监听,该方法就是用来定义响应式数据的。
class Observer {
constructor(data) {
this.observer(data)
}
observer(data) {
if (data && typeof data == 'object') {
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
}
}
考虑到person:{name:'Joker'}
的这种情况也就是对象中还有对象的情况时,需要对其进行递归遍历,目的就是要为每个属性添加上响应式。
defineReactive(obj, key, value) {
// 为每个属性添加响应式
this.observer(value)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
return value
},
set:(newVal)=> {
if (newVal != value) {
value = newVal
}
}
})
}
这时候我们在控制台中测试一下可以发现已经为每个属性添加上了get
和set
,但是如果为其重新赋值一个对象的话并没有被监听到,解决方法也很简单,就是在做set
操作的时候重新调用一下observer
方法并传入新的值即可。
set: (newVal) => {
this.observer(newVal)
if (newVal != value) {
value = newVal
}
}
到此为止数据劫持这部分就做完了,下一步就是要为每个属性创建观察者,当数据发生变化时要去通知观察者更新视图,观察者会有很多所以我们会使用订阅器Dep
来对它进行收集并且还要与Observer
进行关联,当前的首要问题就是在哪里以及如何创建观察者呢?这部分我们放到下一章讲解。
原文链接:https://juejin.cn/post/7341282461202530338 作者:孤独的根号_