关于vue.js:学习Vue30先来了解一下Proxy

产品经理身旁过,需要变更逃不过。
测试姐姐眯眼笑,今晚bug必然多。

据悉Vue3.0的正式版将要在本月(8月)公布,从公布到正式投入到正式我的项目中,还须要肯定的过渡期,但咱们不能始终等到Vue3正式投入到我的项目中的时候才去学习,提前学习,让你更快一步把握Vue3.0,升职加薪迎娶白富美就靠它了。不过在学习Vue3之前,还须要先理解一下Proxy,它是Vue3.0实现数据双向绑定的根底。

本文是作者对于Vue3.0系列的第一篇文章,后续作者将会每周公布一篇Vue3.0相干,如果喜爱,麻烦给小编一个赞,谢谢

理解代理模式

一个例子

作为一个独身钢铁直男程序员,小王最近逐步喜爱上了前台小妹,不过呢,他又和前台小妹不熟,所以决定委托与前端小妹比拟熟的UI小姐姐帮忙给本人搭桥引线。小王于是请UI小姐姐吃了一顿大餐,而后拿出一封情书委托它转交给前台小妹,情书上写的 我喜爱你,我想和你睡觉,不愧钢铁直男。不过这样写必定是没戏的,UI小姐姐吃人嘴短,于是帮忙改了情书,改成了我喜爱你,我想和你一起在晨辉的沐浴下起床,而后交给了前台小妹。尽管有没有撮合胜利不分明啊,不过这个故事通知咱们,小王活该独身狗。

其实下面就是一个比拟典型的代理模式的例子,小王想给前台小妹送情书,因为不熟所以委托UI小姐姐UI小姐姐相当于代理人,代替小王实现了送情书的事件。

引申

通过下面的例子,咱们想想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

ProxyES6新推出的一个个性,能够用它去拦挡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))

再看这样一个场景

请别离应用Object.definePropertyProxy欠缺上面的代码逻辑.

function observe(obj, callback) {}

const obj = observe(
  {
    name: '子君',
    sex: '男'
  },
  (key, value) => {
    console.log(`属性[${key}]的值被批改为[${value}]`)
  }
)

// 这段代码执行后,输入 属性[name]的值被批改为[妹纸]
obj.name = '妹纸'

// 这段代码执行后,输入 属性[sex]的值被批改为[女]
obj.sex = '女'

看了下面的代码,心愿大家能够先自行实现以下,上面咱们别离用Object.definePropertyProxy去实现下面的逻辑.

  1. 应用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 = '女'
  1. 应用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.definePropertyProxy的用法,然而当给对象增加新的属性的时候,区别就进去了,比方

// 增加公众号字段
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外面提供的getset办法了,接下来咱们一一看一下handler外面的办法。

handler 外面的办法列表

handler外面的办法能够有以下这十三个,每一个都对应的一种或多种针对proxy代理对象的操作行为

  1. handler.get

    当通过proxy去读取对象外面的属性的时候,会进入到get钩子函数外面

  2. handler.set

    当通过proxy去为对象设置批改属性的时候,会进入到set钩子函数外面

  3. handler.has

    当应用in判断属性是否在proxy代理对象外面时,会触发has,比方

    const obj = {
      name: '子君'
    }
    console.log('name' in obj)
  4. handler.deleteProperty

    当应用delete去删除对象外面的属性的时候,会进入deleteProperty`钩子函数

  5. handler.apply

    proxy监听的是一个函数的时候,当调用这个函数时,会进入apply钩子函数

  6. handle.ownKeys

    当通过Object.getOwnPropertyNames,Object.getownPropertySymbols,Object.keys,Reflect.ownKeys去获取对象的信息的时候,就会进入ownKeys这个钩子函数

  7. handler.construct

    当应用new操作符的时候,会进入construct这个钩子函数

  8. handler.defineProperty

    当应用Object.defineProperty去批改属性修饰符的时候,会进入这个钩子函数

  9. handler.getPrototypeOf

    当读取对象的原型的时候,会进入这个钩子函数

  10. handler.setPrototypeOf

    当设置对象的原型的时候,会进入这个钩子函数

  11. handler.isExtensible

    当通过Object.isExtensible去判断对象是否能够增加新的属性的时候,进入这个钩子函数

  12. handler.preventExtensions

    当通过Object.preventExtensions去设置对象不能够批改新属性时候,进入这个钩子函数

  13. 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[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,set函数的构造如下

/**
 * target: 指标对象,即通过proxy代理的对象
 * key: 要赋值的属性名称
 * value: 指标属性要赋的新值
 * receiver: 与 get的receiver 基本一致
 */
handle.set(target,key,value, receiver)
示例

某零碎须要录入一系列数值用于数据统计,然而在录入数值的时候,可能录入的存在一部分异样值,对于这些异样值须要在录入的时候进行解决, 比方大于100的值,转换为100, 小于0的值,转换为0, 这时候就能够应用proxyset,在赋值的时候,对数据进行解决

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 = '前端有的玩'

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)

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

其余操作

在上文中,咱们提到了Proxyhandler提供了十三个函数,在下面咱们列举了最罕用的三个,其实每一个的用法都是基本一致的,比方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下面总共提供了十三个静态方法,这十三个静态方法与Proxyhandler下面的十三个办法是一一对应的,通过将ProxyReflect相结合,就能够对对象下面的默认操作进行拦挡解决,当然这也就属于函数元编程的领域了。

总结

有的同学可能会有纳闷,我不会ProxyReflect就学不了Vue3.0了吗?其实懂不懂这个是不影响学习Vue3.0的,然而如果想深刻 去了解Vue3.0,还是很有必要理解这些的。比方常常会有人在应用Vue2的时候问,为什么我数组通过索引批改值之后,界面没有变呢?当你理解到Object.defineProperty的应用形式与限度之后,就会豁然开朗,原来如此。本文之后,小编将为大家带来Vue3.0系列文章,欢送关注,一起学习。同时本文首发于公众号【前端有的玩】,用玩的姿态学前端,就在【前端有的玩】

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理