跟着coderwhy学习
1.对象的依赖管理
- 我们目前是创建了一个Depend对象,用来管理对于name变化需要监听的响应函数:
- 但是实际开发中我们会有不同的对象,另外会有不同的属性需要管理;
- 我们如何可以使用一种数据结构来管理不同对象的不同依赖关系呢?
- 在前面刚刚学习过WeakMap,并且在学习WeakMap的时候我讲到了后面通过WeakMap如何管理这种响应式的数据依赖:
2.对象依赖管理的实现
- 可以写一个getDepend函数专门来管理这种依赖关系:
// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据target对象获取map的过程
let map = targetMap.get(target)
if (!map) {
map = new Map()
targetMap.set(target, map)
}
// 根据key获取depend对象
let depend = map.get(key)
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
const objProxy = new Proxy(obj, {
get: function(target, key, receiver) {
// 根据target.key获取对应的depend
const depend = getDepend(target, key)
// 给depend对象中添加响应函数
depend.addDepend(activeReactiveFn)
return Reflect.get(target, key, receiver)
},
set: function(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// depend.notify()
const depend = getDepend(target, key)
depend.notify()
}
})
3.正确的依赖收集
- 之前收集依赖的地方是在 watchFn 中:
- 但是这种收集依赖的方式我们根本不知道是哪一个key的哪一个depend需要收集依赖;
- 你只能针对一个单独的depend对象来添加你的依赖对象;
- 那么正确的应该是在哪里收集呢?应该在我们调用了Proxy的get捕获器时
- 因为如果一个函数中使用了某个对象的key,那么它应该被收集依赖;
// 封装一个响应式的函数
let activeReactiveFn = null
function watchFn(fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
const objProxy = new Proxy(obj, {
get: function(target, key, receiver) {
// 根据target.key获取对应的depend
const depend = getDepend(target, key)
// 给depend对象中添加响应函数
depend.addDepend(activeReactiveFn)
return Reflect.get(target, key, receiver)
},
set: function(target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
const depend = getDepend(target, key)
depend.notify()
}
})
4.对Depend重构
- 但是这里有两个问题:
- 问题一:如果函数中有用到两次key,比如name,那么这个函数会被收集两次;
- 问题二:我们并不希望将添加reactiveFn放到get中,以为它是属于Dep的行为;
- 所以我们需要对Depend类进行重构:
- 解决问题一的方法:不使用数组,而是使用Set;
- 解决问题二的方法:添加一个新的方法,用于收集依赖;
5.Vue2响应式原理
- 前面所实现的响应式的代码,其实就是Vue3中的响应式原理:
- Vue3主要是通过Proxy来监听数据的变化以及收集相关的依赖的;
- Vue2中通过我们前面学习过的Object.defineProerty的方式来实现对象属性的监听;
- 我们可以将reactive函数进行如下的重构:
- 在传入对象时,我们可以遍历所有的key,并且通过属性存储描述符来监听属性的获取和修改;
- 在setter和getter方法中的逻辑和前面的Proxy是一致 的;
function reactive2(obj) {
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
get: function () {
const depend = getDepend(obj, key)
depend.depend()
return value
},
set: function(newValue) {
value = newValue
const depend = getDepend(obj, key)
depend.notify()
}
})
})
return obj
}
6.异步任务的处理
-
在ES6出来之后,有很多关于Promise的讲解、文章,也有很多经典的书籍讲解Promise
- 虽然等你学会Promise之后,会觉得Promise不过如此,但是在初次接触的时候都会觉得这个东西不好理解;
-
那么这里我从一个实际的例子来作为切入点:
- 我们调用一个函数,这个函数中发送网络请求(我们可以用定时器来模拟);
- 如果发送网络请求成功了,那么告知调用者发送成功,并且将相关数据返回过去;
- 如果发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息;
function requestData(url) { // 模拟请求网络请求 setTimeout(() => { if (url === 'mint') { // 请求成功 let names = ['abc','cba','nba'] successCallback(names) } else { // 请求失败 } }, 3000) }function requestData(url, successCallback, failureCallback) { // 模拟请求网络请求 setTimeout(() => { // 拿到请求结果 // url 传入的是mint,请求成 if (url === 'mint') { //成功 let names = ['abc','cba','nba'] successCallback(names) } else { // 否则请求失败 // 失败 let errMessage = '请求失败, url错误' failureCallback(errMessage) } }, 3000) }
7.什么是Promise呢?
-
在上面的解决方案中,我们确确实实可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:
- 第一,我们需要自己来设计回调函数、回调函数的名称、回调函数的使用等;
- 第二,对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它这个函数到底怎么用;
-
来看一下Promise的API是怎么样的:
- Promise是一个类,可以翻译成 承诺、许诺 、期约;
- 当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;
- 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
- 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
- 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
- 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;
8.Promise的代码结构
- 来看一下Promise代码结构
const promise = new Promise((resolve, reject) => {
// 调用resolve, 那么then传入的回调会被执行
resolve("哈哈哈哈")
// 调用reject,那么catch传入的回调会被执行
reject("错误信息")
})
promise.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
- 上面Promise使用过程,我们可以将它划分成三个状态:
- (pending): 初始状态,既没有被兑现,也没有被拒绝;
- 当执行executor中的代码时,处于该状态;
- 现(fulfilled): 意味着操作成功完成;
- 执行了resolve时,处于该状态;
- 绝(rejected): 意味着操作失败;
- 执行了reject时,处于该状态;
- (pending): 初始状态,既没有被兑现,也没有被拒绝;
9.Promise重构请求
- 那么有了Promise,我们就可以将之前的代码进行重构了:
function requestData(url) {
// 异步请求的代码会被放入到executor中
return new Promise((resolve, reject) => {
// 模拟网络请求
setTimeout(() => {
// 拿到请求的结果
// url传入的是mint, 请求成功
if (url === "mint") {
// 成功
let names = ["abc", "cba", "nba"]
resolve(names)
} else { // 否则请求失败
// 失败
let errMessage = "请求失败, url错误"
reject(errMessage)
}
}, 3000);
})
}
10.Executor
- Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数:
new Promise((resolve, reject) => {
console.log('executor代码
}
- 我们会在Executor中确定我们的Promise状态:
- resolve,可以兑现(fulfilled)Promise的状态,我们也可以称之为已决议(resolved);
- reject,可以拒绝(reject)Promise的状态;
- 这里需要注意:一旦状态被确定下来,Promise的状态会被 锁死,该Promise的状态是不可更改的
- 调用resolve的时候,如果resolve传入的值本身不是一个Promise,那么会将该Promise的状态变成 兑现(fulfilled);
- 后我们去调用reject时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise状态);
11.resolve不同值的区别
- 一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
- 情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态:
- 情况三:如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态: