1.1 Getters and Setters
目标
实现一个有如下功能的 convert
函数:
- takes an Object as the argument
以对象作为参数
-
converts the Object's properties in-place into getter/setters using
Object.defineProperty
使用
Object.defineProperty
方法将对象的属性转换为 getter/setters
- The converted object should retain original behavior, but at the same time
log all the get/set operations.
转换后的对象应该保留原来的行为,但与此同时记录所有get/set操作。
预期效果:
const obj = { foo: 123 }
convert(obj)
obj.foo // should log: 'getting key "foo": 123'
obj.foo = 234 // should log: 'setting key "foo" to: 234'
obj.foo // should log: 'getting key "foo": 234'
具体实现:
function isObject (obj) {
return typeof obj === 'object'
&& !Array.isArray(obj)
&& obj !== null
&& obj !== undefined
}
function convert(obj) {
if(!isObject(obj)) {
throw new TypeError()
}
for(let key in obj) {
let currentVal = obj[key];
Object.defineProperty(obj, key,
{
get() {
console.log(`getting key "${key}": ${currentVal}`);
return currentVal;
},
set(newValue) {
console.log(`setting key "${key}" to: ${newValue}`);
currentVal = newValue;
}
}
)
}
}
1.2 Dependency Tracking (依赖跟踪)
目标
- Create a
Dep
class with two methods:depend
andnotify
.
创建一个
Dep
依赖关系类,这个类有两个方法depend
和notify
。depend
表示当前执行的代码正在收集依赖项;notify
表示依赖发生改变,之前任何被定义为依赖的如表达式,函数等都会被通知
- Create an
autorun
function that takes an updater function.
创建一个
autorun
函数,去接收一个updater
函数
- Inside the updater function, you can explicitly depend on an instance of
Dep
by callingdep.depend()
在updater函数内部,通过调用dep.depend()显式地依赖于' Dep '的一个实例,目的就是为了收集依赖或者说订阅这个 updater 函数
- Later, you can trigger the updater function to run again by calling
dep.notify()
.
稍后,通过调用' dep.notify() '再次触发updater函数运行。
预期效果:
// 构建一个 依赖类,然后在 updater 函数中调用依赖对象dep的depend()方法去订阅这个函数,然后能够通过dep对象的 notify() 再次调用这个updater函数
const dep = new Dep()
autorun(() => {
dep.depend()
console.log('updated')
})
// should log: "updated"
dep.notify()
// should log: "updated"
具体实现:
// a class representing a dependency
// exposing it on window is necessary for testing
window.Dep = class Dep {
constructor() {
this.subscribers = new Set()
}
depend() {
if(activeUpdate) {
// 注册这个 activeUpdate 作为订阅者
this.subscribers.add(activeUpdate)
}
}
notify() {
// 通知所有订阅者
this.subscribers.forEach(sub => sub())
}
}
let activeUpdate;
function autorun (update) {
function wrappedUpdate() {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
体验一下这个函数的效果
const dep = new window.Dep()
function say() {
dep.depend();
console.log("say");
}
function laugh() {
dep.depend();
console.log("laugh");
}
autorun(say); //===> say
autorun(laugh); //===> laugh
dep.notify();
//===> say
//===> laugh
1.3 Mini Observer(迷你观察者)
目标
结合前面两个函数,将 convert()
重命名为 observe()
并保留 autorun()
:
-
observe()
converts the properties in the received object and make them
reactive. For each converted property, it gets assigned aDep
instance which keeps track of a list of subscribing update functions, and triggers them to re-run when its setter is invoked.
observe()
接收对象中的属性进行转换,让它们具有响应式的效果。对于每个转换后的属性,它会被分配一个' Dep '实例,该实例跟踪订阅更新函数的列表,并在其setter被调用时触发它们重新运行。
-
autorun()
takes an update function and re-runs it when properties that the
update function subscribes to have been mutated. An update function is said
to be "subscribing" to a property if it relies on that property during its
evaluation.
autorun()
接收一个更新函数并且当这个更新函数订阅的属性改变时重新运行它。如果更新函数在求值期间依赖于某个属性,则称为“订阅”该属性。
预期效果:
const state = {
count: 0
}
observe(state)
autorun(() => {
console.log(state.count)
})
// should immediately log "count is: 0"
state.count++
// should log "count is: 1"
具体实现:
function isObject(obj) {
return typeof obj === 'object'
&& !Array.isArray(obj)
&& obj !== null
&& obj !== undefined
}
window.Dep = class Dep {
constructor() {
this.subscribers = new Set()
}
depend() {
if (activeUpdate) {
// 注册这个 activeUpdate 作为订阅者
this.subscribers.add(activeUpdate)
}
}
notify() {
// 通知所有订阅者
this.subscribers.forEach(sub => sub())
}
}
const dep = new window.Dep();
function observe(obj) {
if (!isObject(obj)) {
throw new TypeError()
}
for (let key in obj) {
let currentVal = obj[key];
Object.defineProperty(obj, key,
{
get() {
dep.depend()
return currentVal
},
set(newValue) {
if (newValue != currentVal) {
currentVal = newValue
dep.notify()
}
}
}
)
}
}
let activeUpdate;
function autorun(update) {
function wrappedUpdate() {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
体验一下效果
let state = {
count: 0,
message: "stateMesage"
}
observe(state);
autorun(()=> {
console.log("state.count:", state.count);
console.log("state.message:", state.message);
})
state.count++;
state.message = "changeMessage";
每次状态改变,都会触发autorun里的update函数
总结
- 构建一个依赖跟踪类
Dep
, 类里有一个叫depend
方法,该方法用于收集依赖项;另外还有一个notify
方法,该方法用于触发依赖项的执行,也就是说只要在之前使用dep
方法收集的依赖项,当调用notfiy
方法时会被触发执行。 - ES5的
Object.defineProperty
提供监听属性变更的功能,封装成一个obseve()
方法。在对象属性的get
方法下通过dep.depend()
去收集需要监听更改的属性,然后在set
方法下调用dep.notify()
去触发set新数据后执行需要的方法。 - 通过封装好的
obseve()
,去观察一个对象。然后在autorun
函数内写需要的更新操作,可以是简单的打印一句话,也可以是渲染DOM。然后在更改被观察对象的属性时,就会自动执行autorun
内的更新操作
网友评论