共计 9036 个字符,预计需要花费 23 分钟才能阅读完成。
据悉 Vue3.0
的正式版将要在本月 (8 月) 公布,从公布到正式投入到正式我的项目中,还须要肯定的过渡期,但咱们不能始终等到 Vue3
正式投入到我的项目中的时候才去学习,提前学习,让你更快一步把握 Vue3.0
, 升职加薪迎娶白富美就靠它了。不过在学习Vue3
之前,还须要先理解一下 Proxy
, 它是Vue3.0
实现数据双向绑定的根底。
理解代理模式
一个例子
作为一个独身钢铁直男程序员,小王最近逐步喜爱上了前台小妹,不过呢,他又和前台小妹不熟,所以决定委托与前端小妹比拟熟的 UI
小姐姐帮忙给本人搭桥引线。小王于是请 UI
小姐姐吃了一顿大餐,而后拿出一封情书委托它转交给前台小妹,情书上写的 我喜爱你,我想和你睡觉
,不愧钢铁直男。不过这样写必定是没戏的,UI
小姐姐吃人嘴短,于是帮忙改了情书,改成了 我喜爱你,我想和你一起在晨辉的沐浴下起床
,而后交给了前台小妹。尽管有没有撮合胜利不分明啊,不过这个故事通知咱们,小王活该独身狗。
其实下面就是一个比拟典型的代理模式的例子,小王想给前台小妹送情书,因为不熟所以委托 UI 小姐姐
,UI
小姐姐相当于代理人,代替小王实现了送情书的事件。
作者:前端有的玩
链接:https://zhuanlan.zhihu.com/p/…
起源:知乎
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。
引申
通过下面的例子,咱们想想 Vue
的数据响应原理,比方上面这段代码
const xiaowang = {love: '我喜爱你,我想和你睡觉'}
// 送给小姐姐情书
function sendToMyLove(obj) {console.log(obj.love)
return '流氓,滚'
}
console.log(sendToMyLove(xiaowang))
如果没有 UI
小姐姐代替送情书,显示终局是悲惨的,想想 Vue2.0
的双向绑定,通过 Object.defineProperty
来监听的属性 get
,set
办法来实现双向绑定, 这个 Object.defineProperty
就相当于 UI
小姐姐
const xiaowang = {loveLetter: '我喜爱你,我想和你睡觉'}
// UI 小姐姐代理
Object.defineProperty(xiaowang,'love', {get() {return xiaowang.loveLetter.replace('睡觉','一起在晨辉的沐浴下起床')
}
})
// 送给小姐姐情书
function sendToMyLove(obj) {console.log(obj.love)
return '小伙子还挺有诗情画意的么,不过老娘不喜爱,滚'
}
console.log(sendToMyLove(xiaowang))
尽管仍然是一个悲惨的故事,因为送飞驰的成功率可能会更高一些。然而咱们能够看到,通过 Object.defineproperty
能够对对象的已有属性进行拦挡,而后做一些额定的操作。
存在的问题
在 Vue2.0
中,数据双向绑定就是通过 Object.defineProperty
去监听对象的每一个属性,而后在 get
,set
办法中通过公布订阅者模式来实现的数据响应,然而存在肯定的缺点,比方只能监听已存在的属性,对于新增删除属性就无能为力了,同时无奈监听数组的变动,所以在 Vue3.0
中将其换成了性能更弱小的Proxy
。
理解 Proxy
Proxy
是ES6
新推出的一个个性,能够用它去拦挡js
操作的办法,从而对这些办法进行代理操作。
用 Proxy 重写下面的例子
比方咱们能够通过 Proxy
对下面的送情书情节进行重写:
const xiaowang = {loveLetter: '我喜爱你,我想和你睡觉'}
const proxy = new Proxy(xiaowang, {get(target,key) {if(key === 'loveLetter') {return target[key].replace('睡觉','一起在晨辉的沐浴下起床')
}
}
})
// 送给小姐姐情书
function sendToMyLove(obj) {console.log(obj.loveLetter)
return '小伙子还挺有诗情画意的么,不过老娘不喜爱,滚'
}
console.log(sendToMyLove(proxy))
作者:前端有的玩
链接:https://zhuanlan.zhihu.com/p/…
起源:知乎
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。
再看这样一个场景
请别离应用 Object.defineProperty
和Proxy
欠缺上面的代码逻辑.
function observe(obj, callback) {}
const obj = observe(
{
name: '子君',
sex: '男'
},
(key, value) => {console.log(` 属性 [${key}] 的值被批改为[${value}]`)
}
)
// 这段代码执行后,输入 属性 [name] 的值被批改为[妹纸]
obj.name = '妹纸'
// 这段代码执行后,输入 属性 [sex] 的值被批改为[女]
obj.sex = '女'
看了下面的代码,心愿大家能够先自行实现以下,上面咱们别离用 Object.defineProperty
和Proxy
去实现下面的逻辑.
- 应用
Object.defineProperty
/**
* 请实现这个函数,使上面的代码逻辑失常运行
* @param {*} obj 对象
* @param {*} callback 回调函数
*/
function observe(obj, callback) {const newObj = {}
Object.keys(obj).forEach(key => {
Object.defineProperty(newObj, key, {
configurable: true,
enumerable: true,
get() {return obj[key]
},
// 当属性的值被批改时,会调用 set,这时候就能够在 set 外面调用回调函数
set(newVal) {obj[key] = newVal
callback(key, newVal)
}
})
})
return newObj
}
const obj = observe(
{
name: '子君',
sex: '男'
},
(key, value) => {console.log(` 属性 [${key}] 的值被批改为[${value}]`)
}
)
// 这段代码执行后,输入 属性 [name] 的值被批改为[妹纸]
obj.name = '妹纸'
// 这段代码执行后,输入 属性 [sex] 的值被批改为[女]
obj.name = '女'
- 应用
Proxy
function observe(obj, callback) {
return new Proxy(obj, {get(target, key) {return target[key]
},
set(target, key, value) {target[key] = value
callback(key, value)
}
})
}
const obj = observe(
{
name: '子君',
sex: '男'
},
(key, value) => {console.log(` 属性 [${key}] 的值被批改为[${value}]`)
}
)
// 这段代码执行后,输入 属性 [name] 的值被批改为[妹纸]
obj.name = '妹纸'
// 这段代码执行后,输入 属性 [sex] 的值被批改为[女]
obj.name = '女'
通过下面两种不同实现形式,咱们能够大略的理解到 Object.defineProperty
和Proxy
的用法,然而当给对象增加新的属性的时候,区别就进去了,比方
// 增加公众号字段
obj.gzh = '前端有的玩'
应用 Object.defineProperty
无奈监听到新增属性,然而应用 Proxy
是能够监听到的。比照下面两段代码能够发现有以下几点不同
Object.defineProperty
监听的是对象的每一个属性,而Proxy
监听的是对象本身- 应用
Object.defineProperty
须要遍历对象的每一个属性,对于性能会有肯定的影响 Proxy
对新增的属性也能监听到,但Object.defineProperty
无奈监听到。
初识Proxy
概念与语法
在 MDN
中,对于 Proxy
是这样介绍的: Proxy
对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。什么意思呢?Proxy
就像一个拦截器一样,它能够在读取对象的属性,批改对象的属性,获取对象属性列表,通过 for in
循环等等操作的时候,去拦挡对象下面的默认行为,而后本人去自定义这些行为,比方下面例子中的 set
, 咱们通过拦挡默认的set
, 而后在自定义的set
外面增加了回调函数的调用
Proxy
的语法格局如下
/**
* target: 要兼容的对象,能够是一个对象,数组, 函数等等
* handler: 是一个对象,外面蕴含了能够监听这个对象的行为函数,比方下面例子外面的 `get` 与 `set`
* 同时会返回一个新的对象 proxy, 为了可能触发 handler 外面的函数,必须要应用返回值去进行其余操作,比方批改值
*/
const proxy = new Proxy(target, handler)
在下面的例子外面,咱们曾经应用到了 handler
外面提供的 get
与set
办法了,接下来咱们一一看一下 handler
外面的办法。
作者:前端有的玩
链接:https://zhuanlan.zhihu.com/p/…
起源:知乎
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。
handler 外面的办法列表
handler
外面的办法能够有以下这十三个,每一个都对应的一种或多种针对 proxy
代理对象的操作行为
handler.get
当通过proxy
去读取对象外面的属性的时候,会进入到get
钩子函数外面handler.set
当通过proxy
去为对象设置批改属性的时候,会进入到set
钩子函数外面handler.has
当应用in
判断属性是否在proxy
代理对象外面时,会触发has
,比方
const obj = {name: '子君'}
console.log('name' in obj)
handler.deleteProperty
当应用delete
去删除对象外面的属性的时候,会进入deleteProperty
钩子函数handler.apply
当proxy
监听的是一个函数的时候,当调用这个函数时,会进入apply
钩子函数handle.ownKeys
当通过Object.getOwnPropertyNames
,Object.getownPropertySymbols
,Object.keys
,Reflect.ownKeys
去获取对象的信息的时候,就会进入ownKeys
这个钩子函数handler.construct
当应用new
操作符的时候,会进入construct
这个钩子函数handler.defineProperty
当应用Object.defineProperty
去批改属性修饰符的时候,会进入这个钩子函数handler.getPrototypeOf
当读取对象的原型的时候,会进入这个钩子函数handler.setPrototypeOf
当设置对象的原型的时候,会进入这个钩子函数handler.isExtensible
当通过Object.isExtensible
去判断对象是否能够增加新的属性的时候,进入这个钩子函数handler.preventExtensions
当通过Object.preventExtensions
去设置对象不能够批改新属性时候,进入这个钩子函数handler.getOwnPropertyDescriptor
在获取代理对象某个属性的属性形容时触发该操作,比方在执行Object.getOwnPropertyDescriptor(proxy, "foo")
时会进入这个钩子函数
Proxy
提供了十三种拦挡对象操作的办法,本文次要筛选其中一部分在 Vue3
中比拟重要的进行阐明,其余的倡议能够间接浏览 MDN
对于 Proxy
的介绍。
具体介绍
get
当通过
proxy
去读取对象外面的属性的时候,会进入到get
钩子函数外面
当咱们从一个proxy
代理下面读取属性的时候,就会触发get
钩子函数,get
函数的构造如下
/**
* target: 指标对象,即通过 proxy 代理的对象
* key: 要拜访的属性名称
* receiver: receiver 相当于是咱们要读取的属性的 this, 个别状况
* 下他就是 proxy 对象自身,对于 receiver 的作用,后文将具体解说
*/
handle.get(target, key, receiver)
示例
咱们在工作中常常会有封装 axios
的需要,在封装过程中,也须要对申请异样进行封装,比方不同的状态码返回的异样信息是不同的,如下是一部分状态码及其提示信息:
// 状态码提示信息
const errorMessage = {
400: '谬误申请',
401: '零碎未受权,请从新登录',
403: '回绝拜访',
404: '申请失败,未找到该资源'
}
// 应用形式
const code = 404
const message = errorMessage
console.log(message)
但这存在一个问题,状态码很多,咱们不可能每一个状态码都去枚举进去,所以对于一些异样状态码,咱们心愿能够进行对立提醒,如提醒为 零碎异样,请分割管理员
,这时候就能够应用Proxy
对错误信息进行代理解决
// 状态码提示信息
const errorMessage = {
400: '谬误申请',
401: '零碎未受权,请从新登录',
403: '回绝拜访',
404: '申请失败,未找到该资源'
}
const proxy = new Proxy(errorMessage, {get(target,key) {const value = target[key]
return value || '零碎异样,请分割管理员'
}
})
// 输入 谬误申请
console.log(proxy[400])
// 输入 零碎异样,请分割管理员
console.log(proxy[500])
set
当为对象外面的属性赋值的时候,会触发
set
当给对象外面的属性赋值的时候,会触发 set
,set
函数的构造如下
/**
* target: 指标对象,即通过 proxy 代理的对象
* key: 要赋值的属性名称
* value: 指标属性要赋的新值
* receiver: 与 get 的 receiver 基本一致
*/
handle.set(target,key,value, receiver)
示例
某零碎须要录入一系列数值用于数据统计,然而在录入数值的时候,可能录入的存在一部分异样值,对于这些异样值须要在录入的时候进行解决, 比方大于 100
的值,转换为 100
, 小于0
的值,转换为 0
, 这时候就能够应用proxy
的set
,在赋值的时候,对数据进行解决
const numbers = []
const proxy = new Proxy(numbers, {set(target,key,value) {if(value < 0) {value = 0}else if(value > 100) {value = 100}
target[key] = value
// 对于 set 来说,如果操作胜利必须返回 true, 否则会被视为失败
return true
}
})
proxy.push(1)
proxy.push(101)
proxy.push(-10)
// 输入 [1, 100, 0]
console.log(numbers)
比照Vue2.0
在应用 Vue2.0
的时候,如果给对象增加新属性的时候,往往须要调用 $set
, 这是因为Object.defineProperty
只能监听已存在的属性,而新增的属性无奈监听,而通过 $set
相当于手动给对象新增了属性,而后再触发数据响应。然而对于 Vue3.0
来说,因为应用了 Proxy
,在他的set
钩子函数中是能够监听到新增属性的,所以就不再须要应用$set
const obj = {name: '子君'}
const proxy = new Proxy(obj, {set(target,key,value) {if(!target.hasOwnProperty(key)) {console.log(` 新增了属性 ${key}, 值为 ${value}`)
}
target[key] = value
return true
}
})
// 新增 公众号 属性
// 输入 新增了属性 gzh, 值为前端有的玩
proxy.gzh = '前端有的玩'
作者:前端有的玩
链接:https://zhuanlan.zhihu.com/p/...
起源:知乎
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。
has
当应用
in
判断属性是否在proxy
代理对象外面时,会触发has
/**
* target: 指标对象,即通过 proxy 代理的对象
* key: 要判断的 key 是否在 target 中
*/
handle.has(target,key)
示例
个别状况下咱们在 js
中申明公有属性的时候,会将属性的名字以 _
结尾,对于这些公有属性,是不须要内部调用,所以如果能够暗藏掉是最好的,这时候就能够通过 has
在判断某个属性是否在对象时,如果以 _
结尾,则返回false
const obj = {publicMethod() {},
_privateMethod(){}
}
const proxy = new Proxy(obj, {has(target, key) {if(key.startsWith('_')) {return false}
return Reflect.get(target,key)
}
})
// 输入 false
console.log('_privateMethod' in proxy)
// 输入 true
console.log('publicMethod' in proxy)
作者:前端有的玩
链接:https://zhuanlan.zhihu.com/p/...
起源:知乎
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。
deleteProperty
当应用
delete
去删除对象外面的属性的时候,会进入 deleteProperty` 拦截器
/**
* target: 指标对象,即通过 proxy 代理的对象
* key: 要删除的属性
*/
handle.deleteProperty(target,key)
示例
当初有一个用户信息的对象,对于某些用户信息,只容许查看,但不能删除或者批改,对此应用 Proxy
能够对不能删除或者批改的属性进行拦挡并抛出异样,如下
const userInfo = {
name: '子君',
gzh: '前端有的玩',
sex: '男',
age: 22
}
// 只能删除用户名和公众号
const readonlyKeys = ['name', 'gzh']
const proxy = new Proxy(userInfo, {set(target,key,value) {if(readonlyKeys.includes(key)) {throw new Error(` 属性 ${key}不能被批改 `)
}
target[key] = value
return true
},
deleteProperty(target,key) {if(readonlyKeys.includes(key)) {throw new Error(` 属性 ${key}不能被删除 `)
return
}
delete target[key]
return true
}
})
// 报错
delete proxy.name
比照Vue2.0
其实与 $set
解决的问题相似,Vue2.0
是无奈监听到属性被删除的,所以提供了 $delete
用于删除属性,然而对于 Proxy
,是能够监听删除操作的,所以就不须要再应用$delete
了
其余操作
在上文中,咱们提到了 Proxy
的handler
提供了十三个函数,在下面咱们列举了最罕用的三个,其实每一个的用法都是基本一致的,比方 ownKeys
,当通过Object.getOwnPropertyNames
,Object.getownPropertySymbols
,Object.keys
,Reflect.ownKeys
去获取对象的信息的时候,就会进入 ownKeys
这个钩子函数,应用这个咱们就能够对一些咱们不像裸露的属性进行爱护,比方个别会约定 _
结尾的为公有属性,所以在应用 Object.keys
去获取对象的所有 key
的时候,就能够把所有 _
结尾的属性屏蔽掉。对于残余的那些属性,倡议大家多去看看 MDN
中的介绍。
Reflect
在下面,咱们获取属性的值或者批改属性的值都是通过间接操作 target
来实现的,但实际上 ES6
曾经为咱们提供了在 Proxy
外部调用对象的默认行为的API
, 即Reflect
。比方上面的代码
const obj = {}
const proxy = new Proxy(obj, {get(target,key,receiver) {return Reflect.get(target,key,receiver)
}
})
大家可能看到下面的代码与间接应用 target[key]
的形式没什么区别,但实际上 Reflect
的呈现是为了让 Object
下面的操作更加标准,比方咱们要判断某一个 prop
是否在一个对象中,通常会应用到in
, 即
const obj = {name: '子君'}
console.log('name' in obj)
但下面的操作是一种命令式的语法,通过 Reflect
能够将其转变为函数式的语法,显得更加标准
Reflect.has(obj,'name')
除了 has
,get
之外,其实 Reflect
下面总共提供了十三个静态方法,这十三个静态方法与 Proxy
的handler
下面的十三个办法是一一对应的,通过将 Proxy
与Reflect
相结合,就能够对对象下面的默认操作进行拦挡解决,当然这也就属于函数元编程的领域了。
总结
有的同学可能会有纳闷,我不会 Proxy
和Reflect
就学不了 Vue3.0
了吗?其实懂不懂这个是不影响学习 Vue3.0
的,然而如果想深刻 去了解 Vue3.0
,还是很有必要理解这些的。比方常常会有人在应用Vue2
的时候问,为什么我数组通过索引批改值之后,界面没有变呢?当你理解到 Object.defineProperty
的应用形式与限度之后,就会豁然开朗,原来如此。