先思考一个问题?
如何让变量b的值是a的10倍:
let a = 3
let b = a * 10
console.log(b) // 30
但是如果当a不断变换,b并不会随着a的变换而变化,因为它是命令式的,并不存在同步关系;
let a = 3
let b = a * 10
console.log(b) // 30
a = 4
b = a * 10
console.log(b) // 40
1.数据劫持
那如何让他们俩保持同步呢?
这里就关系到Object.defineProperty这个方法。
对obj的属性foo进行读写操作的劫持
const obj = {
foo: 123
}
let internalValue = obj.foo
Object.defineProperty(obj,'foo',{
get(){// 获取obj.foo时触发
console.log('get---触发')
return internalValue
},
set(newValue){// 更改obj.foo时触发
console.log('set---触发')
const isChanged = internalValue !== newValue
if (isChanged) {
internalValue = newValue
}
}
})
// obj.foo
// obj.foo = 456
接下来改造代码封装函数,对obj所有属性设置getter和setter(劫持属性)
function isObject (obj) {
return typeof obj === 'object'
&& !Array.isArray(obj)
&& obj !== null
&& obj !== undefined
}
function observe(obj){
if (!isObject(obj)) {
throw new TypeError()
}
Object.keys(obj).forEach((key)=>{
let internalValue = obj[key]
Object.defineProperty(obj,key,{
get(){
return internalValue
},
set(newValue){
const isChanged = internalValue !== newValue
if (isChanged) {
internalValue = newValue
}
}
})
})
}
const state = {
count:0
}
observe(state)
以上做到了对对象所有的属性进行了劫持,接下来对于这个劫持能联系到什么呢
2.依赖收集
先来理解什么是依赖?
- 先创建一个Dep的类,这个类有两个方法depend()和notify()
- depend表示当前正在执行的代码,收集这种依赖
- notify表示依赖发生改变,任何并定义为依赖的表达式、计算、函数都会被通知重新执行
找到一种让他们创建关联的方式,可能是函数、表达式或者计算,那这样的计算关系方式叫做依赖,也被认为是订阅者模式
接下来写一个autorun函数,它可以接受一个更新函数
当进入更新函数的时候,一切就变得很特别,当处于这个响应式空间时,就可以注册依赖
class Dep{
constructor(){
this.subscribers = new Set()
}
depend(){//订阅
if(activeUpadte){
this.subscribers.add(activeUpadte) //2.1 添加订阅
}
}
notify(){//通知
this.subscribers.forEach(upade=>upade())
}
}
let dep = new Dep()
let activeUpadte
function autorun(upade){
function wrappedUpade(){
activeUpadte = wrappedUpade //1.放到全局变量,以便外部访问
// 这里可以添加些当依赖发生变化的执行与否的条件
upade()
activeUpadte = null
}
wrappedUpade()
}
autorun(()=>{
// 此时 dep中可以处理activeUpadte
dep.depend()//2.加入依赖
console.log('---Upade')
})
// dep.notify()
3.迷你观察者
将以上的例子联系起来,在对数据设置getter和setter的时候,分别depend加入订阅和notify通知订阅
Vue的响应式机制完整代码就如下:
class Dep{
constructor(){
this.subscribers = new Set()
}
depend(){
if(activeUpadte){
this.subscribers.add(activeUpadte)
}
}
notify(){
this.subscribers.forEach(upade=>upade())
}
}
let activeUpadte
function isObject (obj) {
return typeof obj === 'object'
&& !Array.isArray(obj)
&& obj !== null
&& obj !== undefined
}
function observe(obj){
if (!isObject(obj)) {
throw new TypeError()
}
Object.keys(obj).forEach((key)=>{
let internalValue = obj[key]
let dep = new Dep()
Object.defineProperty(obj,key,{
get(){
dep.depend()//订阅
return internalValue
},
set(newValue){
const isChanged = internalValue !== newValue
if (isChanged) {
internalValue = newValue
dep.notify()//通知
}
}
})
})
}
function autorun(upade){
function wrappedUpade(){
activeUpadte = wrappedUpade //放入全局依赖
upade()
activeUpadte = null
}
wrappedUpade()
}
const state = {
count:0,
status:true
}
// 做成响应式
observe(state)
// 依赖跟踪
autorun(()=>{
//依赖状态变化
console.log('---',state.count)
})
state.count++