开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
前言
本文主要根据讲解Proxy-Reflect,来引出vue2-vue3响应式原理。该篇主要是自己得学习总结,供大家参考学习,如有写的不准确的地方欢迎大家指出,相互学习,共同进步!
一. Proxy
1.什么是proxy?
ES6中,新增了一个Proxy类,翻译过来即代理。顾名思义是用于帮助我们创建一个代理的。
- 如果希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(
Proxy对象),之后对该对象的所有操作,都通过Proxy对象来完成; Proxy对象可以监听我们想要对原对象进行哪些操作;
const p = new Proxy(target, handler)
const obj = {
name: "why",
age: 18
}
const objProxy = new Proxy(obj, {})
如果需要侦听某些具体的操作,那么就可以在handler中添加对应的捕捉器(Trap)
而我们实际过程中用的最多得捕获器是get、set、has、deleteProperty,具体用法和其它别的捕获器可点击Proxy查看。
举例:
const obj = {
name: "amy",
age: 18
}
const objProxy = new Proxy(obj, {
// 获取值时的捕获器
get: function(target, key,receiver) {
console.log(`监听到对象的${key}属性被访问了`, target, receiver)
return target[key]
},
// 设置值时的捕获器 receiver是创建出来的代理对象
set: function(target, key, newValue,receiver) {
console.log(`监听到对象的${key}属性被设置值`, target, receiver)
target[key] = newValue
}
})
2.与Object.defineProperty有什么区别?
Object.defineProperty缺陷:
- 不能监听数组变化
- Object.defineProperty只能劫持对象的属性,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历
- Object.defineProperty对新增属性需要手动进行
Observe(因为劫持的是对象的属性,所以新增属性时,需要重新遍历对象,再对新增属性再使用defineproperty进行劫持,导致Vue2在给data中的数组或对象新增属性时,需要用vm.$set来保证新增属性的响应)
所以我们想监听更加丰富的操作,比如新增属性、删除属性,那么就需要Proxy来进行处理更为合适。
二. Reflect
1.什么是Reflect?
Reflect(反射)也是ES6新增的一个API,它是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers (en-US)的方法相同。Reflect不是一个函数对象,因此它是不可构造的。Reflect的所有属性和方法都是静态的(就像Math对象)。
将上方案例中对原对象的操作,都修改为Reflect来操作:
const obj = {
name: "amy",
age: 18
}
const objProxy = new Proxy(obj, {
get: function(target, key, receiver) {
console.log("get---------")
return Reflect.get(target, key)
},
set: function(target, key, newValue, receiver) {
console.log("set---------")
return Reflect.set(target, key, newValue)
}
})
objProxy.name = "kobe"
console.log(objProxy.name)
2.与Object上方法有什么区别?
- Reflect.getPrototypeOf(target)对应 Object.getPrototypeOf()
- Reflect.defineProperty(target, propertyKey, attributes)对应Object.defineProperty()
如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?
这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面,并且Object作为一个构造函数,这些操作实际上放到它身上并不合适,所以在ES6中新增了Reflect。具体一些方法的区别大家可点击: 比较 Reflect 和 Object 方法
3.Reflect.construct()
Reflect.construct() 方法的行为有点像 new 操作符 构造函数,相当于运行 new target(...args),简单来说就是改变构造函数的原型指向。
Reflect.construct(target, argumentsList[, newTarget])
举例:
function Student(name, age) {
this.name = name
this.age = age
}
function Teacher() {
}
// const stu = new Student("why", 18)
// console.log(stu)
// console.log(stu.__proto__ === Student.prototype)
// 执行Student函数中的内容, 但是创建出来对象是Teacher对象
const teacher = Reflect.construct(Student, ["why", 18], Teacher)
console.log(teacher)
console.log(teacher.__proto__ === Teacher.prototype)
三. vue2-vue3 响应式原理
简单提一句什么是响应式:可以自动响应数据变化的代码机制,我们就称之为是响应式的。
首先我们知道需要做到响应式我们需要这些东西:1.响应函数的封装 2.依赖收集类的封装 3.对象依赖管理 4.自动监听对象变化
1.响应函数的封装
// 保存当前需要收集的响应式函数
let activeReactiveFn = null
// 封装一个响应式的函数
function watchFn(fn) {
activeReactiveFn = fn
fn() //默认会被执行一次
activeReactiveFn = null
}
2.依赖收集类的封装
class Depend {
constructor() {
this.reactiveFns = new Set() //使用Set来保存依赖函数, 而不是数组[] ,数组会导致重复添加多次依赖函数
}
//添加
depend() {
if (activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn)
}
}
//通知
notify() {
this.reactiveFns.forEach(fn => {
fn()
})
}
}
3.对象依赖管理
// 封装一个获取depend函数
const targetMap = new WeakMap() //和map的区别自己百度
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
}
4.自动监听对象变化
vue2:
function reactive(obj) {
// {name: "why", age: 18}
// ES6之前, 使用Object.defineProperty
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
}
vue3:
function reactive(obj) {
return new Proxy(obj, {
get: function(target, key, receiver) {
// 根据target.key获取对应的depend
const depend = getDepend(target, key)
// 给depend对象中添加响应函数
// depend.addDepend(activeReactiveFn)
depend.depend()
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()
}
})
}