前言
前置知识
Object.defineProperty(obj, prop, descriptor)
基本语法:
css
Object.defineProperty(obj, prop, descriptor);
参数 | 说明js |
---|---|
obj | 需要定义属性的目标对象 |
prop | 属性名(字符串) |
descriptor | 属性描述符(定义属性行为) |
descriptor 具体说明:
选项 | 类型 | 含义描述 | 默认值 |
---|---|---|---|
value |
任意类型 | 属性的值(静态定义值时用) | undefined |
writable |
Boolean | 属性的值是否可修改(必须搭配 value 使用) | false |
enumerable |
Boolean | 属性是否可以被遍历(for...in ) |
false |
configurable |
Boolean | 属性能否删除、重新定义 | false |
get |
Function | 属性被访问时调用函数 | undefined |
set |
Function | 属性被修改时调用函数 | undefined |
特别注意:
- value/writable 与 get/set 不能同时存在。
- 如果定义了 get 或 set,则不能定义 value 或 writable。
🌰 三、简单易懂的例子(快速理解):
实例1:定义普通属性(value / writable):
javascript
const person = {};
Object.defineProperty(person, 'name', {
value: '张三',
writable: true, // 能否修改
enumerable: true, // 可否被遍历
configurable: true // 可否删除
});
console.log(person.name); // 输出:"张三"
person.name = "李四";
console.log(person.name); // 输出:"李四"
实例2(getter/setter):
javascript
const user = {
_name: '张三'
};
Object.defineProperty(user, 'name', {
enumerable: true,
configurable: true,
get() {
console.log('getter触发了');
return this._name;
},
set(val) {
console.log('setter触发了,值为', val);
this._name = val;
}
});
user.name = '李四'; // 触发setter
console.log(user.name); // 触发getter
输出:
objectivec
setter触发了,值为 李四
getter触发了
李四
主要三大模块
主要分为一个类两个方法,先来个最小demo实现后面讲他们怎么合作
Dep:
perl
class Dep{
constructor() {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(item => {
item.update()
})
}
}
Watcher:
javascript
class Watch {
constructor() {
Dep.target = this
}
update() {
//更新视图
}
}
Dep.target = null
Observer
javascript
function defineReactive(obj, key, val){
const dep = new Dep()
Object.defineProperty(obj, key, {
get: function reactiveGetter() {
dep.addSub(Dep.target)
return val
},
set: function reactiveGetter(newVal) {
if(newVal === val) return
dep.notify() //更新所有和这个数据有关的视图
}
})
}
分工
vue内部大概是这样的
kotlin
class Vue {
constructor(options) {
this.data = options.data
observer(this.data) //为数据构造一个Dep,存储Watcher
new Watcher() //新建一个Watcher用来更新视图
}
}
简述:
当dep.target为watcher时,watcher如果get了obj,那么就会被dep记录,反之则不会,每次创建vue实例都会有一个watcher被数据对象的dep.target监测
扩展与疑问:全局的Dep.target不会被多个watcher抢占吗?
假设有两个组件视图的 Watcher:
- Watcher A (组件A)
- Watcher B (组件B)
它们依次初始化时:
ini
// 组件A初始化:
Dep.target = WatcherA;
renderA(); // 此刻只有 WatcherA 在进行依赖收集(调用getter)
Dep.target = null;
// 然后才会到组件B初始化:
Dep.target = WatcherB;
renderB(); // 此刻只有 WatcherB 在进行依赖收集(调用getter)
Dep.target = null;
可以看出,每次依赖收集过程是串行而非并发的:
- WatcherA 收集依赖时,不可能被 WatcherB 抢占。
- WatcherB 收集时,WatcherA 的依赖收集早已完成,
Dep.target
已经被重置了。