1. getter / setter
Object.defineProperty
var obj = {};
obj.defineProperty(obj, prop, descriptor);
// descriptor 可选属性包括:
// enumerable: bool (数据/存储描述符)定义该属性能否被for..in,Object.keys 等遍历出来
// configurable: bool (数据/存储描述符)定义该属性是否可以改变,能否从对象上被删除
// value: any (数据描述符)该属性对应的值
// writable: bool (数据描述符)仅当writable 为 true 时,value 才能被改变
// get: func (存取描述符) 访问该属性时,getter 方法会被执行,默认为 undefined
// set: func (存取描述符) 当属性发生修改时会触发该方法,接受新的值作为唯一参数
需要注意的是:
-
数据描述符
跟存取描述符
不能同时出现,只能是两种形式中的一个,即:value
writable
跟get
set
不能一起用 -
configurable
一旦定义便不可改变,否则报错 -
configurable
与writable
作用相似,当同时设置时以writable
为主,也就是说:{ configurable: false, writable: true
时,属性值仍可操作
2. dependency tracking
当把一个普通的js对象传入Vue 实例作为
data
选项,Vue 将遍历此对象的所有属性,并利用Object.defineProperty
将这些属性全部变为getter/setter
,从而追踪属性的访问和修改,每个组件都有一个watcher
,他会把组件渲染过程中"接触"到的数据记录为依赖,当依赖项的setter
触发时,通知watcher
,从而使它关联的组件重新渲染。
function convert(obj) {
Object.keys.forEach(ele => {
let initialValue = Object[key];
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get(){
console.log(`getting ${key} value: ${initialValue}`);
return initialValue
},
set(newVal) {
console.log(`assign ${newVal} to ${key}`);
initialValue = newVal
}
})
})
}
上面是一个简单的转换功能。
实际上,需要一个完整的依赖追踪需要的做的事比较多:
- 创建一个
Dep
类, 包含两个方法:depend
和notify
- 创建一个
autorun
方法,接受一个update
函数作为参数 -
update
方法内部,可以通过实例化Dep
的实例dep
,并调用dep.depend
来生成依赖 - 然后,通过调用
dep.notify
来出发触发update
代码如下:
const dep = new Dep();
autorun(() => {
dep.depend();
console.log('updated')
});
// 任何时候调用,都会触发 autorun 接受的函数参数
dep.notify();
现在我们需要实现的是 Dep
和 autorun
window.Dep = class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if(activeUpdate) {
// register the current active update as a subscriber
this.subscribers.add(activeUpdate);
}
}
notify() {
// run all subscriber functions
this.subscribers.forEach(sub = sub());
}
}
// 声明一个 `activeUpdate`, 方便在外部可以访问
let activeUpdate;
function autorun(update) {
function wrappedUpdate() {
activeUpdate = wrappedUpdate;
update();
activeUpdate = null;
}
}
autorun(() => {
dep.depend()
})
3. observer
一个简单的observer 其实就是把上述两者合并。
window.Dep = class Dep(){
constructor(){
this.subscribers = new Set();
}
// 添加依赖
depend() {
if(activeUpdate) {
this.subscribers.add(actievUpdate);
}
}
// 添加订阅
notify() {
this.subscribers.forEach(sub => sub())
}
}
// dealare a variable to expose inner update function
let activeUpdate;
function antorun(update) {
function wrappedUpdate() {
activeUpdate = wrappedUpdate;
update();
activeUpdate = null;
}
}
function convert(state){
const dep = new Dep();
Object.keys(state).forEach(ele => {
let initialValue = state[ele];
Object.defineProperty(state, ele, {
enumberable: true,
configurable: true,
get(){
dep.depend();
return initialValue;
},
set(newValue) {
dep.notify();
initialValue = newValue;
}
})
})
}
关于 Vue 响应原理,也可以参考这篇文章:
Build a Reactivity System
更多资源:
尤雨溪教你写vue 高级vue教程 源码分析
网友评论