一. Reflect拦截器
Reflect(反射) 是一个内置的对象,它提供拦截 JavaScript 操作的方法。Reflect
的所有属性和方法都是静态的(就像Math
对象)。Reflect
对象提供了的静态方法与proxy handler methods的命名相同.其中的一些方法与 Object
相同, 尽管二者之间存在 某些细微上的差别 .
二. proxy代理对象
语法
new Proxy(target, handler)
参数
-
target
Proxy
会对 target 对象进行包装。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。 -
handler
它是一个对象,它的属性提供了某些操作发生时所对应的处理函数。
proxy中使用Reflect的两个需要注意的问题
问题1:
set 和 deleteProperty 中需要返回布尔类型的值,在严格模式下,如果返回 false 的话会出现 Type Error 的异常.
const target = {
foo: 'xxx',
bar: 'yyy'
}
const proxy = new Proxy(target, {
get (target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set (target, key, value, receiver) {
// 如果返回 false 的话会出现 Type Error 的异常
return Reflect.set(target, key, value, receiver)
},
deleteProperty (target, key) {
// 如果返回 false 的话会出现 Type Error 的异常
return Reflect.deleteProperty(target, key)
}
})
proxy.foo = 'zzz'
delete proxy.foo
问题2:
Proxy 中的 receiver:Proxy 或者继承 Proxy 的对象.
Reflect 中的 receiver:如果目标对象中设置了 getter,getter 中的 this 指向 receiver
const obj = {
get foo() {
console.log(this) // getter中的this指向receiver(也就是Proxy对象)
return this.bar
}
}
const proxy = new Proxy(obj, {
get (target, key, receiver) { // receiver指向Proxy对象
if (key === 'bar') {
return 'value - bar'
}
return Reflect.get(target, key, receiver)
}
})
console.log(proxy.foo) // 'value - bar'
三. 为数据设置响应式-reactive
使用
<script type="module">
import {reactive} from './reactivity/index.js';
const obj = reactive({
name:'zhangsan',
age:'25',
instrasting:{
play:{
bor : { name : 'footbor'}
}
}
});
const obj2 = reactive([1,2,3]);
obj.name = 'zhangsan';
delete obj.age;
obj.sex = '男'
console.log(obj.name);
</script>
原理实现
- reactive接受一个目标参数,判断这个参数是否是对象
- 是对象,创建拦截器对象handler,设置get/set/deleteProperty
- 在get中判断取出的值是否是对象,是则递归调用reactive
- 返回Proxy对象
const isObject = (target)=> target !== null && typeof target === 'object';
const convert = (target) => isObject(target) ? reactive(target) : target;
const isOwnProperty = (target,key) => Reflect.getOwnPropertyDescriptor(target,key);
export function reactive (target) {
if(!isObject(target)) return target
const handler = {
get(target,key,receiver){
console.log('get',key);
// 递归 返回一个target 或 一个proxy对象
return convert(Reflect.get(target,key,receiver))
},
set(target,key,value,receiver){
const oldValue = Reflect.get(target,key,receiver);
// set需要返回true,返回false的时候回报错
if(oldValue !== value) {
console.log('set',key,value);
return Reflect.set(target,key,value,receiver)
}
return true
},
deleteProperty(target,key){
// deleteProperty需要返回true,返回false的时候回报错
// 判断target是否有key属性
const hasKey = isOwnProperty(target,key);
const result = Reflect.deleteProperty(target,key);
if(hasKey && result){
console.log('delete',key);
}
return result
}
}
return new Proxy(target,handler);
}
四. 依赖收集和触发-effect && track && trigger
使用
<script type="module">
import { reactive, effect } from './reactivity/index.js'
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
let total = 0
effect(() => {
total = product.price * product.count
})
console.log(total)
product.price = 4000
console.log(total)
product.count = 1
console.log(total)
</script>
实现原理
effect
函数首先会调用传入的执行函数.- 在执行函数中如果访问了响应式对象的属性,此时在get方法中会调用
track
函数去收集依赖.- 收集依赖的过程就是
存储响应式对象对应的属性和属性对应的箭头函数
.- 在track当中首先可以使用
new WeakMap()
以目标对象为key存储所有相关联的属性,而这些属性值又对应了不同的执行函数,即可以用new Map()
以属性为key存储对应的执行函数,而每个响应式属性可能对应多个执行函数,固可将箭头函数存入到new set()
中.
- 在track当中首先可以使用
- 收集依赖的过程就是
- 在为响应式属性赋值的时候,会触发set方法中的
trigger
函数去触发更新.- 触发更新其实就是
找到依赖收集过程中存储对象的属性及其对应的执行函数.找到函数之后立即执行
.
- 触发更新其实就是
const isObject = (target)=> target !== null && typeof target === 'object';
const convert = (target) => isObject(target) ? reactive(target) : target;
const isOwnProperty = (target,key) => Reflect.getOwnPropertyDescriptor(target,key);
export function reactive (target) {
if(!isObject(target)) return target
const handler = {
get(target,key,receiver){
// console.log('get',key);
// 递归 依赖收集 返回一个target 或 一个proxy对象
const result = convert(Reflect.get(target,key,receiver))
if (result && activeEffect) {
track(target,key)
}
return result
},
set(target,key,value,receiver){
const oldValue = Reflect.get(target,key,receiver);
// set需要返回true,返回false的时候回报错
let result = true;
if(oldValue !== value) {
// console.log('set',key,value);
result = Reflect.set(target,key,value,receiver);
// 触发依赖
trigger(target,key);
}
return result
},
deleteProperty(target,key){
// deleteProperty需要返回true,返回false的时候回报错
// 判断target是否有key属性
const hasKey = isOwnProperty(target,key);
let result = Reflect.deleteProperty(target,key);
if(hasKey && result){
// console.log('delete',key);
}
return result
}
}
return new Proxy(target,handler);
}
let activeEffect = null
export function effect (callback) {
// 把callback存储起来
activeEffect = callback;
callback();//自执行一遍 触发对应get方法去依赖收集 依赖收集的时候需要用到activeEffect
activeEffect = null; //当依赖收集完之后要清空存储 因为在依赖收集时如果有嵌套属性的话是一个递归的过程
}
// targetMap放在外面方便依赖收集与依赖触发
let targetMap = new WeakMap();
// track:依赖收集 把target存储到一个targetMap
export function track(target,key) {
if(!activeEffect) return
let depsMap = targetMap.get(target)
if(!depsMap){
targetMap.set(target,(depsMap = new Map()))
}
let dep = depsMap.get(key);
if(!dep){
depsMap.set(key,(dep = new Set()))
}
dep.add(activeEffect)
}
// 触发依赖
export function trigger(target,key) {
let depsMap = targetMap.get(target);
if(!depsMap) return
let dep = depsMap.get(key);
if(!dep) return
dep.forEach(effect => {
effect()
});
}
五. 基本数据类型的响应式-ref
ref vs reactive
- ref可以把基本数据类型数据,转换成响应式对象
- ref设置的响应式数据获取时需要使用value属性,模板中使用的时候可以省略value
- ref返回的对象,重新给value属性赋值成对象之后也是响应式的,因为调用了convert设置响应式
- reactive不能把基本类型数据转换成响应式对象
- reactive返回的对象,重新赋值会丢失响应式.因为在set中没有递归convert设置响应式
- reactive返回的Proxy对象解构出来的数据不是响应式,如果需要解构响应式数据的话需要使用toRefs来返回这个对象
使用
<script type="module">
import { reactive, effect, ref } from './reactivity/index.js'
const price = ref(5000)
const count = ref(1)
let total = 0
effect(() => {
total = price.value * count.value
})
console.log(total)
price.value = 4000
console.log(total)
count.value = 2
console.log(total)
</script>
实现原理
- 判断传入的参数是否是ref创建的对象, 如果是的话直接返回
- 如果传入object或array,那么直接调用reactive转成响应式
- 否则返回一个具有__v_isRef:true,get value(),set value()属性的对象
// ref
export function ref(raw) {
// 判断 raw 是否是ref 创建的对象, 如果是的话直接返回
if(isObject(raw) && raw.__v_isRef) return;
let value = convert(raw)
const r = {
__v_isRef: true,
get value(){
track(r,'value')
return value
},
set value(newValue){
if(newValue !== value) {
raw = newValue
value = convert(raw)
trigger(r,'value')
}
}
}
return r
}
六.引用数据类型解构-toRefs
toRefs
toRefs
的作用是把reactive返回的对象的每一个属性转换成类似ref返回的对象,达到能对reactive返回对象解构的目的
使用
<script type="module">
import { reactive, effect, toRefs } from './reactivity/index.js'
const {price,count} = toRefs(
reactive({
name: 'iPhone',
price: 5000,
count: 3
})
)
let total = 0
effect(() => {
total = price.value * count.value
})
console.log(total)
price.value = 4000
console.log(total)
count.value = 1
console.log(total)
</script>
实现原理
- 判断proxy代理对象的是数组还是对象,生成一个空数组或空对象ret
- 用for in遍历传入的proxy代理对象,存储toProxyKeys函数返回的ref对象
- 最后返回ret可解构的响应式数据集
export function toRefs(proxy) {
// 判断proxy代理的是数组还是对象
const ret = proxy instanceof Array ? new Array(proxy.length) : {};
for (const key in toProxyKeys) {
// 转换成类似ref返回的对象
ret[key] = toKeys(proxy,key)
}
return ret
}
function toProxyKeys(proxy,key) {
const r = {
__v_isRef:true,
get value(){
// 直接去代理对象中拿
return proxy[key]
},
set value(newValue){
// proxy中的set会去判断新旧值
proxy[key] = newValue
}
}
return r
}
七. 计算属性computed
computed
的作用是接收一个有返回值的函数作为参数,并监听这个函数内部响应式数据的变化,最后把这个函数执行的结果返回.这个返回值就是计算属性的值.它是响应式的.
使用
<script type="module">
import { reactive, computed } from './reactivity/index.js'
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
const total = computed(() => {
return product.price * product.count
})
console.log(total.value)
product.price = 4000
console.log(total.value)
product.count = 1
console.log(total.value)
</script>
实现原理
- 接收一个有返回值的函数作为参数
- 使用ref函数创建一个响应式数据result
- 使用effect监听这个函数内部响应式数据的变化,变化后计算的值赋给result
- 最后返回这个响应式的值result
export function computed(getter) {
const result = ref();
effect(()=>{
result.value = getter()
})
return result
}