基本语法:
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):
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):
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
输出:
setter触发了,值为 李四 getter触发了 李四
主要分为一个类两个方法,先来个最小demo实现后面讲他们怎么合作
class Dep{ constructor() { this.subs = [] } addSub (sub) { this.subs.push(sub) } notify() { this.subs.forEach(item => { item.update() }) } }
class Watch { constructor() { Dep.target = this } update() { //更新视图 } } Dep.target = null
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内部大概是这样的
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监测
假设有两个组件视图的 Watcher:
Watcher A (组件A)
Watcher B (组件B)
它们依次初始化时:
// 组件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
已经被重置了。