响应式Vue实现原理
Object.defineProperty
语法
Object.defineProperty(obj, prop, descriptor)
参数
obj
需要定义其属性的对象。
prop
要定义或更改的属性的名称或 Symbol
。
descriptor
要定义或修改的属性描述符。
描述符参数详细信息
-
configurable
configurable
当且仅当属性configurable
的键值configurable
时,才可以修改属性描述符,也可以从对应的对象中删除该属性。 默认为false
。 -
当且仅当enumerable
enumerable
属性的键值为true
时,属性才会出现在对象的枚举属性中。 默认为
false
。 -
value
该属性对应的值。它可以是任何有效的 JavaScript 值(数字、对象、函数等)。 默认为
false
。 -
writable
当且仅当该属性的键
writable
的值为true
,则该属性的值,即上面的value
,可以更改为赋值运算符
。 默认为false
。 -
get
属性的 getter 函数,或者
undefined
(如果没有 getter)。当访问属性时会调用该函数。运行时不传递任何参数,但会传递对象this
(因为继承关系this
不一定是这里定义该属性的对象)。该函数的返回值将用作属性值。 默认为undefined
。 -
set
属性设置功能或
undefined
如果没有设置。当属性值改变时调用该函数。此方法接受一个参数(即要分配的新值),该参数将在分配时传入this
对象。 默认为undefined
。
getter 和 setter
getter
ES5 Object.defineProperty
提供跟踪属性更改的功能。下面将介绍如何使用covert
函数修改输入对象的getter
和setter
,以实现更改对象属性时打印日志的功能。
const obj = { foo: 123 }
convert(obj)
obj.foo // 需要打印: 'getting key "foo": 123'
obj.foo = 234 // 需要打印: 'setting key "foo" to 234'
obj.foo // 需要打印: 'getting key "foo": 234'
隐藏功能实现如下:
function convert (obj) {
// Object.keys获取对象的所有key值,通过forEach对每个属性进行修改
Object.keys(obj).forEach(key => {
// 保存属性初始值
let internalValue = obj[key]
Object.defineProperty(obj, key, {
get () {
console.log(`getting key "${key}": ${internalValue}`)
return internalValue
},
set (newValue) {
console.log(`setting key "${key}" to: ${newValue}`)
internalValue = newValue
}
})
})
}
依赖性跟踪(订单发布模式)
必须实现依赖跟踪类Dep
,类中有一个方法叫做depend
,用于收集依赖关系;还有一个notify
方法,用于触发依赖的执行,即只要调用notfiy
方法时,之前通过dep
方法收集的依赖都会被触发执行。
以下是Dep
类的预期效果。调用方法dep.depend
收集并收集依赖关系。当调用方法dep.notify
时,控制台会发出语句❙
const dep = new Dep()
autorun(() => {
dep.depend()
console.log('updated')
})
// 打印: "updated"
dep.notify()
// 打印: "updated"
函数 autorun
是接收函数。此功能帮助我们创建响应区域。一旦代码被放置在这个响应区域中,就可以使用 dep.depend
Dep类最终实现代码如下:
window.Dep = class Dep {
constructor () {
// 订阅任务队列,方式有相同的任务,用Set数据结构简单处理
this.subscribers = new Set()
}
// 用于注册依赖项
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
// 用于发布消息,触发依赖项重新执行
notify () {
this.subscribers.forEach(sub => sub())
}
}
let activeUpdate = null
function autorun (update) {
//把wrappedUpdate赋值给activeUpdate,这会使得当依赖关系发生改变update函数会重新执行
//实际上是调用wrappedUpdate,如果以后有改动,这个依赖跟踪器依然会不断的收集依赖项
//因为update函数有可能包含条件,如果一个变量是true就收集这个依赖,如果是false就收集另外的依赖
//所以,依赖收集系统需要动态更新这些依赖,保证依赖项一直是最新的
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
迷你观察员的实现
将以上两个练习结合在一起,制作一个小观察者。在getter和setter中调用depend
方法和notfiy
方法可以达到自动更新数据的目的,这也是Vue自动更新的核心原理。
预期通话效果:
const state = {
count: 0
}
//监听state
observe(state)
//依赖注入
autorun(() => {
console.log(state.count)
})
// 打印"count is: 0"
//每次重新赋值的时候执行notfiy函数 重走一遍所有的依赖函数
state.count++
// 打印"count is: 1"
最终集成代码如下:
class Dep {
constructor () {
this.subscribers = new Set()
}
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
notify () {
this.subscribers.forEach(sub => sub())
}
}
function observe (obj) {
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
const dep = new Dep()
Object.defineProperty(obj, key, {
// 在getter收集依赖项,当触发notify时重新运行
get () {
dep.depend()
return internalValue
},
// setter用于调用notify
set (newVal) {
const changed = internalValue !== newVal
internalValue = newVal
if (changed) {
dep.notify()
}
}
})
})
return obj
}
let activeUpdate = null
function autorun (update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。