产品经理身旁过,需求变动逃不过。
测试姐姐眯眼笑,今晚bug必然多。
据悉Vue3.0
的正式版将要在本月(8月)发布,从发布到正式投入到正式项目中,还须要必定的过渡期,但咱们不能一直等到Vue3
正式投入到项目中的时候才去学习,提早学习,让你更快一步掌握Vue3.0
,升职加薪迎娶白富美就靠它了。不过在学习Vue3
以前,还须要先了解一下Proxy
,它是Vue3.0
实现数据双向绑定的基础。javascript
本文是做者关于Vue3.0系列的第一篇文章,后续做者将会每周发布一篇Vue3.0相关,若是喜欢,麻烦给小编一个赞,谢谢
做为一个单身钢铁直男程序员,小王最近逐渐喜欢上了前台小妹,不过呢,他又和前台小妹不熟,因此决定委托与前端小妹比较熟的UI
小姐姐帮忙给本身搭桥引线。小王因而请UI
小姐姐吃了一顿大餐,而后拿出一封情书委托它转交给前台小妹,情书上写的 我喜欢你,我想和你睡觉
,不愧钢铁直男。不过这样写确定是没戏的,UI
小姐姐吃人嘴短,因而帮忙改了情书,改为了我喜欢你,我想和你一块儿在晨辉的沐浴下起床
,而后交给了前台小妹。虽然有没有撮合成功不清楚啊,不过这个故事告诉咱们,小王活该单身狗。前端
其实上面就是一个比较典型的代理模式的例子,小王想给前台小妹送情书,由于不熟因此委托UI小姐姐
,UI
小姐姐至关于代理人,代替小王完成了送情书的事情。java
经过上面的例子,咱们想一想Vue
的数据响应原理,好比下面这段代码ios
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
。axios
Proxy
是ES6
新推出的一个特性,能够用它去拦截js
操做的方法,从而对这些方法进行代理操做。
好比咱们能够经过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))
请分别使用Object.defineProperty
和Proxy
完善下面的代码逻辑.app
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
里面的方法。
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
的介绍。
当经过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[code] 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
函数的结构以下
/** * 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 = '前端有的玩'
当使用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)
当使用
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
中的介绍。
在上面,咱们获取属性的值或者修改属性的值都是经过直接操做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
的使用方式与限制以后,就会恍然大悟,原来如此。本文以后,小编将为你们带来Vue3.0
系列文章,欢迎关注,一块儿学习。同时本文首发于公众号【前端有的玩】,用玩的姿式学前端,就在【前端有的玩】