get | Read a value |
set | Write a value |
has | in operator |
##deleteProperty
Object.getPrototypeOf( ) |
|
getPrototypeOf
Object.getPrototypeOf() |
|
setPrototypeOf
Object.setPrototypeOf() |
|
isExtensible
Object.isExtensible() |
|
preventExtensions
Object.preventExtensions() | |
getOwnPropertyDescriptor
Object.getOwnPropertyDescriptor() |
|
defineProperty
Object.defineProperty |
|
ownKeys
Object.keys() Object.getOwnPropertyNames() and Object.getOwnPropertySymbols() |
|
apply
Call a function |
|
construct
new a function |
|
那么使用Proxy
可以解决Vue2
中的哪些问题,总结一下:
- Proxy是对整个对象的代理,而Object.defineProperty只能代理某个属性。
- 对象上新增属性,Proxy可以监听到,Object.defineProperty不能。
- 数组新增修改,Proxy可以监听到,Object.defineProperty不能。
- 若对象内部属性要全部递归代理,Proxy可以只在调用的时候递归,而Object.definePropery需要一次完成所有递归,Proxy相对更灵活,提高性能。
递归代理
var target = {
a:1,
b:{
c:2,
d:{e:3}
}
}
var handler = {
get:function(target, prop, receiver){
console.log('触发get:',prop)
return Reflect.get(target,prop)
},
set:function(target,key,value,receiver){
console.log('触发set:',key,value)
return Reflect.set(target,key,value,receiver)
}
}
var proxy = new Proxy(target,handler)
proxy.b.d.e = 4
// 输出 触发get:b , 由此可见Proxy仅代理了对象外层属性。
Copy after login
以上写法只代理了对象的外层属性,所以要想深层代理整个对象的所有属性,需要进行递归处理:
var target = {
a:1,
b:{
c:2,
d:{e:3}
},
f: {z: 3}
}
var handler = {
get:function(target, prop, receiver){
var val = Reflect.get(target,prop)
console.log('触发get:',prop)
if(val !== null && typeof val==='object') {
return new Proxy(val,handler) // 代理内层属性
}
return Reflect.get(target,prop)
},
set:function(target,key,value,receiver){
console.log('触发set:',key,value)
return Reflect.set(target,key,value,receiver)
}
}
var proxy = new Proxy(target,handler)
proxy.b.d.e = 4
// 输出 触发get b,get d, get e
Copy after login
从递归代理可以看出,如果要代理对象的内部属性,Proxy可以只在属性被调用时去设置代理(惰性),访问了e
,就仅递归代理b
下面的属性,不会额外代理其他没有用到的深层属性,如z
。
关于 Reflect
的作用和意义
-
规范语言内部方法的所属对象,不全都堆放在Object对象或Function等对象的原型上。如
Function.prototype.apply
Object.defineProperty
Copy after login
修改某些Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。
让Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
vue3的reative
和ref
Vue3 的 reactive 和 ref 正是借助了Proxy来实现。
reactive
作用:创建原始对象的响应式副本,即将「引用类型」数据转换为「响应式」数据
参数: reactive参数必须是对象或数组
reative函数实现:
// 判断是否为对象
const isObject = val => val !== null && typeof val === 'object';
// 判断key是否存在
const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key);
export function reactive(target) {
// 首先先判断是否为对象
if (!isObject(target)) return target;
const handler = {
get(target, key, receiver) {
console.log(`获取对象属性${key}值`)
// 收集依赖 ...
const result = Reflect.get(target, key, receiver)
// 深度监听(惰性)
if (isObject(result)) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
console.log(`设置对象属性${key}值`)
// 首先先获取旧值
const oldValue = Reflect.get(target, key, reactive)
let result = Reflect.set(target, key, value, receiver);
if (result && oldValue !== value) {
// 更新操作 ...
}
return result
},
deleteProperty(target, key) {
console.log(`删除对象属性${key}值`)
// 先判断是否有key
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
// 更新操作 ...
}
return result
},
// 其他方法
// ...
}
return new Proxy(target, handler)
}
const obj = { a: { b: { c: 6 } } };
const proxy = reactive(obj);
proxy.a.b.c = 77;
// 获取对象属性a值
// 获取对象属性b值
// 设置对象属性c值 77
Copy after login
至此,引用类型的对象我们已经可以把它转化成响应式对象了,Proxy对象只能代理引用类型的对象,对于基本数据类型如何实现响应式呢?
vue的解决方法是把基本数据类型变成一个对象:这个对象只有一个value属性,value属性的值就等于这个基本数据类型的值。然后,就可以用reative
方法将这个对象,变成响应式的Proxy对象。
实际上就是: ref(0) --> reactive( { value:0 })
ref
作用:把基本类型的数据变为响应式数据。
参数:
1.基本数据类型
2.引用类型
3.DOM的ref属性值
ref 实现 Vue3 源码
export function ref(value?: unknown) {
return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
Copy after login
大体思路就是,调用ref函数时会new 一个类,这个类监听了value属性的 get 和 set ,实现了在get中收集依赖,在set中触发依赖,而如果需要对传入参数深层监听的话,就会调用我们上面提到的reactive
方法。
即:
ref(0); // 通过监听对象(类)的value属性实现响应式
ref({a: 6}); // 调用reactive方法对对象进行深度监听
Copy after login
根据上面的思路我们可以自己来简单实现下:
// 自定义ref
function ref(target) {
const result = { // 这里在源码中体现为一个类 RefImpl
_value: reactive(target), // target传给reactive方法做响应式处理,如果是对象的话就变成响应式
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
// 测试
const ref = ref(9);
ref.value = 6;
const ref = ref({a: 4});
ref.value.a = 6;
Copy after login
ref 方法包装的数据,需要使用.value
来访问,但在模板中不需要,Vue解析时会自动添加。
总结
-
reactive
将引用类型值变为响应式,使用Proxy
实现
-
ref
可将基本类型和引用类型都变成响应式,通过监听类的value属性的get
和set
实现,但是当传入的值为引用类型时实际上内部还是使用reactive
方法进行的处理
- 推荐基本类型使用
ref
,引用类型使用 reactive
(学习视频分享:web前端开发、编程基础视频)