一、Proxy 对象
-
Proxy(代理) 是 ES6中新增的一个特性,Proxy 让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式
-
Proxy 可以理解为在目标对象之前架设一层拦截,外部所有的访问或者修改都必须先通过这层拦截,因此提供了一种机制,可以对外部的操作进行过滤和修改。这个词的原理为代理,在这里可以表示由它来“代理”某些操作,译为“代理器”。
-
Proxy 构造函数第一个参数是要代理的对象,第二个参数是一个对象(可以称为处理器,或者监听器)
Proxy 出现的问题一:
问题一: set 和 deleteProperty 中需要返回布尔类型的值,在严格模式下,如果返回 false 的话会出现 Type Error 的异常
解决方法:使用 ES6 的 Reflect
Reflect 是 ES6 为操作对象而提供的新API,而这个API设计的目的只要有:
-
将 Object 对象的一些属于语言内部的方法放到
Reflect对象上,从Reflect上能拿到语言内部的方法。如:Object.defineProperty -
修改某些 object 方法返回的结果。如:Object.defineProperty(obj, name, desc) 在无法定义属性的时候会报错,而 Reflect.defineProperty(obj, name, desc) 则会返回 false ,如果定义成功会返回 true
-
让 Object 的操作都变成函数行为。如 object 的命令式:name in obj 和delete obj[name] 则与 Reflect.has(obj, name)、Reflect.deleteProperty(obj, name)相等
-
Reflect 对象的方法与 Proxy 对象的方法一一对应,只要 proxy 对象上有的方法 reflect 也能找到
// 开启严格模式
'use strict'
// 问题1: set 和 deleteProperty 中需要返回布尔类型的值
// 在严格模式下,如果返回 false 的话会出现 Type Error 的异常
const target = {
foo: 'xxx',
bar: 'yyy'
}
// Reflect.getPrototypeOf()
// Object.getPrototypeOf()
// 通过 Proxy 代理 target 对象
// Proxy 构造函数第一个参数是要代理的对象,第二个参数是一个对象(可以称为处理器,或者监听器)
const proxy = new Proxy(target, {
// 监听属性访问操作
get(target, key, receiver) { // 第三个参数 receiver 指的是当前的 proxy 对象
// Reflect 是反射的意思,是 es6 中新增的成员,用来代码运行期间获取或者设置成员
return Reflect.get(target, key, receiver)
},
// 监听属性赋值操作
set(target, key, value, receiver) {
// Reflect.set 设置成功会返回 true ,
return Reflect.set(target, key, value, receiver)
},
// 监听属性删除操作
deleteProperty(target, key) {
return Reflect.deleteProperty(target, key)
}
})
proxy.foo = 'zzz'
// // delete proxy.foo
Proxy 出现的问题二:
问题2:Proxy 和 Reflect 中使用的 receiver
- Proxy 中 receiver:Proxy 或者继承 Proxy 的对象
- Reflect 中 receiver:如果 target 对象中设置了 getter,那么 getter 中的 this 指向 receiver
// 问题2:Proxy 和 Reflect 中使用的 receiver
const obj = {
get foo() {
// 如果没有设置 receiver,此处 this 指向 obj,如果设置了 receiver,此处 this 指向向代理对象 proxy
console.log(this)
return this.bar
}
}
const proxy = new Proxy(obj, {
get(target, key, receiver) {
if (key === 'bar') {
return 'value - bar'
}
return Reflect.get(target, key, receiver) // 设置 receiver
}
})
console.log(proxy.foo)
二、实现 reactive
reactive 主要三点
- 接收一个参数,判断这个参数是不是对象
- 创建拦截器对象 handler ,设置 get/set/deleteProperty 方法
- 返回 Proxy 对象
const isObject = val => val !== null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target) : target
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key)
// reactive
export function reactive(target) {
// 1、判断是不是对象,如果不是直接返回
if (!isObject(target)) return target
// 2、创建拦截器对象 handler ,设置 get/set/deleteProperty 方法
const handler = {
get(target, key, receiver) {
// 收集依赖
track(target, key)
const result = Reflect.get(target, key, receiver)
// 如果 result 是一个对象,就将这个对象传到 reactive 做一层代理
return convert(result)
},
set(target, key, value, receiver) {
// 先获取这个 key 属性的值
const oldValue = Reflect.get(target, key, receiver)
let result = true
// 判断旧值和将要修改的新值是否相等
if (oldValue !== value) {
result = Reflect.set(target, key, value, receiver)
// 触发更新
trigger(target, key)
}
return result
},
deleteProperty(target, key) {
// 判断 target 中是否有这个 key 属性
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
// 触发更新
trigger(target, key)
}
return result
}
}
// 3、返回经过 Proxy 代理后的对象
return new Proxy(target, handler)
}
三、实现依赖收集
-
先实现 effect 方法,effect 方法的使用和 watchEffect 类似,watchEffect 内部就是调用 effect 方法的。
-
effect 方法是用来监视响应式数据的变化的,一旦变化,就执行该回调函数
// effect 实现
let activeEffect = null // 用来记录 callback,因为收集依赖函数track要用
export function effect(callback) {
activeEffect = callback
callback() // 执行 callback 函数,这样就可以访问响应式对象属性,去收集依赖
activeEffect = null
}
- 收集依赖的过程就是存储这个属性和这个回调函数,然而属性又和对象相关,所以在 track 方法中首先会存储 target 目标对象,然后是 target 对应的属性,和属性对于的箭头函数
收集依赖 track 方法实现
// 存储目标对象
let targetMap = new WeakMap()
// 收集依赖 ( track 第一个参数是目标对象,第二个参数是要收集的属性 )
export function track(target, key) {
if (!activeEffect) return // 如果 activeEffect 不存在,就不收集依赖
let depsMap = targetMap.get(target) // 判断 targetMap 中是否有这个目标对象
if (!depsMap) {
// 如果 targetMap 没有这个目标对象,targetMap 设置这个目标对象为 key,并设置 value 为一个新的 Map 对象
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key) // 存储 key 和对应的回调函数集合
if (!dep) {
// 如果 depsMap 没有这个属性,depsMap 就设置这个属性为 key,并设置value 为一个新的 Set 对象(Set集合用来收集回调函数)
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
四、实现触发更新
触发更新 trigger 函数实现
// 触发更新
export function trigger(target, key) {
// 从 targetMap 中找到触发更新的 depsMap
const depsMap = targetMap.get(target)
if (!depsMap) return
// 根据 key 找到 depsMap 中的对应的 dep 集合(集合中存储的就是 effect 回调函数)
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => {
effect()
})
}
}
五、实现 ref
export function ref(raw) {
// 1、判断 raw 是否是 ref 创建的对象,如果是的话直接返回
if (isObject(raw) && raw.__v_isRef) {
return
}
// 2、判断 raw 是否是对象,如果是对象直接调用 reactive 创建响应式对象,否者直接返回
let value = convert(raw)
// 3、创建一个带有 value 属性的响应式对象返回
const r = {
__v_isRef: true, // 标识,如果是 ref 创建的对象都有该属性
get value() {
track(r, 'value')
return value
},
set value(newValue) {
if (newValue !== value) {
raw = newValue
value = convert(raw)
trigger(r, 'value')
}
}
}
return r
}
总结:
- ref 可以将基本数据类型数据转成响应式对象
- ref 返回的对象,被重新赋值成对象也是响应式的
- reactive 返回的对象,重新赋值会丢失响应式
- reactive 返回的对象不可以解构,需要结构必须先调用 toRefs
六、实现 toRefs
// toRefs 实现
export function toRefs(proxy) {
// 1、判断传入的对象是否是 proxy 对象,如果不是就发送警告
// 2、判断传入的对象是否是数组,如果是创建一个长度为 proxy.length 的数组
const ret = proxy instanceof Array ? new Array(proxy.length) : {}
// 3、遍历 proxy 中所有属性(如果是数组,就遍历所有索引)
for (const key in proxy) {
// 4、把每一个属性都转换成 ref 返回的对象
ret[key] = toProxyRef(proxy, key)
}
return ret
}
function toProxyRef(proxy, key) {
const r = {
__v_isRef: true,
get value() {
// 这里不需要收集依赖,因为访问 proxy[key] 的时候代理对象内部已经收集了依赖
return proxy[key]
},
set value(newValue) {
// 同理,代理对象已经触发更新
proxy[key] = newValue
}
}
return r
}
七、实现 computed
computed 接收一个有返回值的函数作为参数,这个函数的返回值就是计算属性的值,并且会监听函数内部使用的响应式数据的变化,最后把函数执行结果返回
export function computed(getter) {
// 1、创建一个 ref 响应式对象 result
const result = ref() // 默认传入 undefined
// 2、把 getter 函数结果赋值给 result
effect(() => (result.value = getter()))
// 3、返回
return result
}