需求:
有个对象,希望监听这个对象的属性被设置或获取、删除、新增的过程
属性描述符方式
用属性描述符中的存储属性描述符可以做到
Object.defineProperty(obj, key, {get/set})
其中get/set可以监听对象属性的获取或设置
const obj = {
name: 'zsf',
age: 18
}
Object.keys(obj).forEach((key) => {
let value = obj[key]
Object.defineProperty(obj, key, {
get: function () {
console.log(`${ key }属性被获取了`)
return value
},
set: function (newValue) {
console.log(`${ key }属性被设置值了`)
value = newValue
}
})
})
console.log(obj.name)
obj.name = 'hhh'
console.log(obj.name)
但是,这种方式存在比较大缺点:
- Object.defineProperty设计的初衷,不是为了监听一个对象中所有属性的,而是定义普通的属性
- 但是想监听其它操作,如新增、删除属性,Object.defineProperty做不到
Proxy
es6新增,是个类
- 监听一个对象的操作
- 创建一个代理对象
- 该对象的所有操作,都是通过代理对象完成
- 代理对象可以监听原对象进行了哪些操作
基本使用
- 第2个参数是捕获器对象,会重写原的对象set和get方法
- target参数:代理的对象
- key参数:代理的对象的key
const obj = {
name: 'zsf',
age: 18
}
const objProxy = new Proxy(obj, {
// 获取值时捕获器
get: function (target, key, receiver) {
console.log('获取')
return target[key]
},
// 设置值时的捕获器
set: function (target, key, newValue, receiver) {
console.log('设置')
target[key] = newValue
}
})
objProxy.age = 20
console.log(objProxy.age)
其它捕获器
- has-in的捕获器
- deleteProperty捕获器
const obj = {
name: 'zsf',
age: 18
}
const objProxy = new Proxy(obj, {
// has-in捕获器
has: function (target, key) {
console.log('存在')
return key in target
},
// deleteProperty捕获器
deleteProperty: function (target, key) {
console.log('删除')
delete target[key]
}
})
console.log('name' in objProxy)
delete objProxy.age
还有其它捕获器
| getPropertyOf() | Object.getPropertyOf()的捕获器 |
|---|---|
| setPropertyOf() | Object.setPropertyOf()的捕获器 |
| isExtensible() | Object.isExtensible()的捕获器 |
| preventExtensions() | Object.preventExtensions()的捕获器 |
| getOwnPropertyDescriptor() | Object.getOwnPropertyDescriptor()的捕获器 |
| defineProperty() | Object.defineProperty()的捕获器 |
其它就不列啦
Reflect
es6新增,是个对象,反射
这个reflect有什么用呢?
提供很多操作js对象的方法,有点像Object操作对象的方法
已经有Object了,为什么还要reflect呢?
- 早期ECMA规范没有考虑到对对象本身的一些操作如何设计更加规范,所以将这些api都放到Object上
- 但是Object作为构造函数,这些操作放在它身上并不合适
- 所以,es6新增了reflect对象,将这些操作集中到reflect身上
具体哪些方法放到reflect,去MDN
常见方法
Proxy有哪些捕获器,Reflect就有哪些方法
基本使用
Proxy出现的本意是不对原对象进行操作,但是这两个操作就是对原对象的操作
return target[key]target[key] = newValue
所以需要Reflect对原对象做一个映射,并且有操作对象那些方法
const obj = {
name: 'zsf',
age: 18
}
const objProxy = new Proxy(obj, {
// 获取值时捕获器
get: function (target, key) {
return Reflect.get(target, key, receiver)
},
// 设置值时的捕获器
set: function (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
}
})
objProxy.age = 20
console.log(objProxy.age)
参数receiver的作用
将指向原对象obj的this改成指向代理对象objProxy,这样让代理对象变得有意义,不然会直接绕过代理对象去使用原对象
看这段代码
const obj = {
_name: 'zsf',
get_name () {
return this._name
},
set_name (newName) {
this._name = newName
}
}
const objProxy = new Proxy(obj, {
get (target, key) {
return Reflect.get(target, key)
},
set (target, key, newValue) {
Reflect.set(target, key, newValue)
}
})
console.log(objProxy.name)// hhh
console.log(obj._name)// zsf
- 访问objProxy.name
- 来到get捕获器里面
- get捕获器通过reflect.get(target, key)会去访问obj.get_name()
- 注意,可不是访问_name
reflect的construct
应用场景
es6转es5
function Student(name, age) {
this.name = name
this.age = age
}
function Teachear() {
}
const stu = new Student('zsf', 18)
console.log(stu)
打印出来的stu是Student类型,但是现在有这样一个需求:
依然使用Student()创建出stu对象,但是希望是Teacher类型
3个参数:
- 1构造类型
- 2参数列表[]
- 3目标类型
function Student(name, age) {
this.name = name
this.age = age
}
function Teachear() {
}
const obj = Reflect.construct(Student, ['zsf', 18], Teachear)
console.log(obj)//Teachear {name: 'zsf', age: 18}
响应式
看这个场景
- m有一个初识值,有一段代码使用了m
- m有了新的值时,这段代码可以自动重新执行
这就实现了m的响应式
但是,更多的是需要对象的响应式
思路
1.响应式函数的封装
将需要响应式的函数(或用到对象属性的代码块)收集起来
function watchFn (fn) {
//收集进数组
}
watchFn(function () {
console.log(objProxy.name)
})
2.依赖收集与通知类的封装
class Depend {
constructor () {
this.reativeFns = []
}
// 收集
addDepend (reactiveFn) {
this.reativeFns.push(reactiveFn)
}
// 通知
notify () {
this.reativeFns.forEach((fn) => {
fn()
})
}
}
const depend = new Depend()
function watchFn (fn) {
depend.addDepend(fn)
}
3.自动监听对象的变化
- Proxy(Vue3)
- Object.definePropety(Vue2)
// 监听对象的属性改变:
const objProxy = new Proxy(obj, {
get (target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
depend.notify()
}
})
4.依赖收集的管理
每一个对象属性应该对应一个depend对象
还有多个对象
对应关系:
- 每个属性对应一个depend对象
- 每个对象对应多个属性
属性与depend的关系用map保存
一个对象:
const obj = {
name: 'zsf',
age: 18
}
const objMap = new Map()
objMap.set('name', nameDepend)
objMap.set('age', ageDepend)
const depend = objMap.get('name')
depend.notify()
多个对象:
const obj = {
name: 'obj',
age: 18
}
const info = {
name: 'info',
size: 18
}
const objMap = new Map()
objMap.set('name', nameDepend)
objMap.set('age', ageDepend)
const infoMap = new Map()
objMap.set('name', nameDepend)
objMap.set('size', sizeDepend)
而objMap、infoMap和对应的对象的关系再用WeakMap
const targetMap = WeakMap()
target.set(obj, objMap)
target.set(info, infoMap)
这时,当obj.name发生变化
// 取出objMap,再取出nameDepend,然后通知变化
targetMap.get(obj).get('name').notify()
封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据target对象获取map
let map = targetMap.get(target)
//edge case 第一次创建时map是空的,因为targetMap还没保存过
if (!map) {
map = new Map()
targetMap.set(target, map)
}
// 根据key获取depend对象
let depend = map.get(key)
// edge case 第一次创建时map是空的
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
如何将需要响应式的函数添加到对应的depend对象里呢?
let activeReactiveFn = null
function watchFn (fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
// 监听对象的属性改变: Proxy(Vue3)/Object.definePropety(Vue2)
const objProxy = new Proxy(obj, {
get (target, key, receiver) {
const depend = getDepend(target, key)
depend.addDepend(activeReactiveFn)
return Reflect.get(target, key, receiver)
},
set (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// 此时收集的依赖是空的
const depend = getDepend(target, key)
depend.notify()
}
})
完整代码
class Depend {
constructor () {
this.reativeFns = []
}
// 收集
addDepend (reactiveFn) {
this.reativeFns.push(reactiveFn)
}
// 通知
notify () {
this.reativeFns.forEach((fn) => {
fn()
})
}
}
// 封装一个收集需要响应式的函数的函数
const depend = new Depend()
let activeReactiveFn = null
function watchFn (fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据target对象获取map
let map = targetMap.get(target)
//edge case 第一次创建时map是空的,因为targetMap还没保存过
if (!map) {
map = new Map()
targetMap.set(target, map)
}
// 根据key获取depend对象
let depend = map.get(key)
// edge case 第一次创建时map是空的
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
// 对象的响应式
const obj = {
name: 'zsf',
age: 18
}
const info = {
name: 'info',
size: 18
}
// 监听对象的属性改变: Proxy(Vue3)/Object.definePropety(Vue2)
const objProxy = new Proxy(obj, {
get (target, key, receiver) {
const depend = getDepend(target, key)
depend.addDepend(activeReactiveFn)
return Reflect.get(target, key, receiver)
},
set (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// 此时收集的依赖是空的
const depend = getDepend(target, key)
depend.notify()
}
})
watchFn(function () {
console.log(objProxy.name)
})
watchFn(function () {
console.log(objProxy.age)
})
重构依赖收集类
但是还有个问题:同一函数里要是使用了多次同个属性,这个函数就会多次放进需要重新执行的数组里,换句话说,就是重复了
将数组换成Set
// 需要收集的响应式函数
let activeReactiveFn = null
class Depend {
constructor () {
this.reativeFns = new Set()
}
// 收集
depend () {
if (activeReactiveFn) {
this.reativeFns.add(activeReactiveFn)
}
}
// 通知
notify () {
this.reativeFns.forEach((fn) => {
fn()
})
}
}
// 封装一个收集需要响应式的函数的函数
const depend = new Depend()
function watchFn (fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据target对象获取map
let map = targetMap.get(target)
//edge case 第一次创建时map是空的,因为targetMap还没保存过
if (!map) {
map = new Map()
targetMap.set(target, map)
}
// 根据key获取depend对象
let depend = map.get(key)
// edge case 第一次创建时map是空的
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
// 对象的响应式
const obj = {
name: 'zsf',
age: 18
}
const info = {
name: 'info',
size: 18
}
// 监听对象的属性改变: Proxy(Vue3)/Object.definePropety(Vue2)
const objProxy = new Proxy(obj, {
get (target, key, receiver) {
const depend = getDepend(target, key)
depend.depend()
return Reflect.get(target, key, receiver)
},
set (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// 此时收集的依赖是空的
const depend = getDepend(target, key)
depend.notify()
}
})
watchFn(() => {
console.log(objProxy.name, '---')
console.log(objProxy.name, '+++')
})
objProxy.name = 'hhh'
监听多个对象
上面只是针对一个对象实现响应式
再新增一个代理对象即可,但是,要是有很多对象需要响应式,就得封装代理对象那部分代码了,并且自动化
function reactive(obj) {
return new Proxy(obj, {
get (target, key, receiver) {
const depend = getDepend(target, key)
depend.depend()
return Reflect.get(target, key, receiver)
},
set (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// 此时收集的依赖是空的
const depend = getDepend(target, key)
depend.notify()
}
})
}
当需要新增响应式对象,只需要这样即可
const objProxy = reactive({
name: 'zsf',
age: 18
})
Vue3响应式原理
// 需要收集的响应式函数
let activeReactiveFn = null
// 依赖收集和通知类
class Depend {
constructor () {
this.reativeFns = new Set()
}
// 收集
depend () {
if (activeReactiveFn) {
this.reativeFns.add(activeReactiveFn)
}
}
// 通知
notify () {
this.reativeFns.forEach((fn) => {
fn()
})
}
}
// 封装一个代理对象,并自动化的函数
function reactive(obj) {
return new Proxy(obj, {
get (target, key, receiver) {
const depend = getDepend(target, key)
depend.depend()
return Reflect.get(target, key, receiver)
},
set (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver)
// 此时收集的依赖是空的
const depend = getDepend(target, key)
depend.notify()
}
})
}
// 封装一个收集需要响应式的函数的函数
const depend = new Depend()
function watchFn (fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据target对象获取map
let map = targetMap.get(target)
//edge case 第一次创建时map是空的,因为targetMap还没保存过
if (!map) {
map = new Map()
targetMap.set(target, map)
}
// 根据key获取depend对象
let depend = map.get(key)
// edge case 第一次创建时map是空的
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
// 对象的响应式
const fooProxy = reactive({
name: 'foo'
})
watchFn(() => {
console.log(fooProxy.name)
})
fooProxy.name = 'hhh'
vue2响应式原理
不是用Proxy和reflect,而是Object.defineProperty
// 需要收集的响应式函数
let activeReactiveFn = null
// 依赖收集和通知类
class Depend {
constructor () {
this.reativeFns = new Set()
}
// 收集
depend () {
if (activeReactiveFn) {
this.reativeFns.add(activeReactiveFn)
}
}
// 通知
notify () {
this.reativeFns.forEach((fn) => {
fn()
})
}
}
// 封装一个代理对象,并自动化的函数
function reactive(obj) {
Object.keys(obj).forEach((key) => {
let value = obj[key]
Object.defineProperty(obj, key, {
get () {
const depend = getDepend(obj, key)
depend.depend()
return value
},
set (newValue) {
value = newValue
const depend = getDepend(obj, key)
depend.notify()
}
})
})
return obj
}
// 封装一个收集需要响应式的函数的函数
const depend = new Depend()
function watchFn (fn) {
activeReactiveFn = fn
fn()
activeReactiveFn = null
}
// 封装一个获取depend函数
const targetMap = new WeakMap()
function getDepend(target, key) {
// 根据target对象获取map
let map = targetMap.get(target)
//edge case 第一次创建时map是空的,因为targetMap还没保存过
if (!map) {
map = new Map()
targetMap.set(target, map)
}
// 根据key获取depend对象
let depend = map.get(key)
// edge case 第一次创建时map是空的
if (!depend) {
depend = new Depend()
map.set(key, depend)
}
return depend
}
// 对象的响应式
const fooProxy = reactive({
name: 'hhh'
})
watchFn(() => {
console.log(fooProxy.name)
})
fooProxy.name = 'foo'