es6代理和反射

2019-05-09  本文已影响0人  安石0

前言:

vue 3.o据说已经将Object.defineProperty换成了proxy
为什么要换呢?
优势如下:

除此之外数组本身也还有一个问题,可以看一个例子

arr = [1,2,3,4]
arr[4] = 5 // 5
arr.length // 5
arr.length = 1
arr[1] // undefind

数组的length发生变化,项会发生变化有些项不存在了,项的变化也会让length变化,(数组被成为是奇异对象),现在es6中也其他对象也可以实现这种行为。
我们的主角登场:代理和发射。

代理

定义:

对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)

简单来说:进行一些操作先得经过这一层
例子:

let p = new Proxy(target, handler);
target
用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理),存储作用。
handler
一个对象,其属性是当执行一个操作时定义代理的行为的函数。
包含陷阱(traps)的占位符对象,
traps: 提供属性访问的方法。这类似于操作系统中陷阱的概念。

反射

反射api以Reflect对象的形式出现,对象中方法的默认特性与相同的底层操作一致,而代理可以覆盖这些操作。

一个简单的代理:

let target = {}
p = new Proxy(target, {})
p.name = 'zlj'
p.name // "zlj"
target.name // "zlj"
target.name = '张大彪'
p.name // "张大彪"
target.name // "张大彪"

上面的例子:代理只是简单地将操作转发给目标。
(先不纠结具体语法:)
1 使用set陷阱验证属性

target ={}
p = new Proxy(target, {
  set(trapTarget, key, value, receiver) {
    // console.log(trapTarget, key, value, receiver)
    // trapTarget就是target, receiver 就是p
    if(!trapTarget.hasOwnProperty(key)) {
      if(({}).toString.call(value)!=='[object Number]') {
        throw new TypeError('属性必须是数字')
       }
      //添加属性
      return Reflect.set(trapTarget, key, value, receiver)
     }
  }
})

数组特性就可以用set陷阱实现
2 使用get陷阱
js对象里面有一个十分诡异现象:

obj = {}
obj.xxx // undefind

其实xxx属性是不存在的,当然你可以用Object.defineProperty定义该属性的get方法,但是这个方法得一个一个的列举出来,类似于事件绑定在li上,我增加,你又得重新绑了,那能不能类似于事件代理一样呢,把事件绑在ul元素上?proxy的get陷阱轻松实现:

var proxy = new Proxy({}, {
  get (target, key, receiver) {
    if (!(key in receiver)) {
      throw new TypeError(key + ':不存在 )
    }
    return Reflect.get(target, key, receiver)
  }
})
proxy.name = 'xxx'
proxy.age // 报错
proxy.name = 'xxx' // "xxx"

3 has 陷阱
先看一个例子:

var target = {
  name: 'zlj'
}
'name' in target // true
'toString' in target // true

toString方法继承自Object.prototype
可以使用Proxy的has陷阱:

target = {name: "zlj", age: 27}
p = new Proxy(target, {
  has (target, key) {
     if (key === 'name') return false // 可以改变部分
    if (target.hasOwnProperty(key)) {
      return Reflect.has(target, key) // 返回in操作符的默认行为
    }
  }
})
'name' in p // false
'age' in p // true
'toString' in p // false

4 删除陷阱
看个例子:

var target = {
  name: 'zlj'
}
Object.defineProperty(target, 'name', {configurable: false})
delete target.name // false
// 尝试删除一个不能删除的属性仅仅返回false
target.age =27
delete target.age // true
target.age // undefined
'use strict'
delete target.name
// 在严格模式下,尝试删除会报错

proxy也有删除陷阱

target = {
  name: 'zlj',
  age:27
}
p = new Proxy(target, {
  deleteProperty(target, key) {
    if (key === 'name') {
      return false
    } else {
     // 与delete的默认行为一致
     return Reflect.deleteProperty(target, key)
   }
  }
})
delete p.name // false
delete p.age // true

5 原型代理陷阱
es5中有Object.getPrototypeOf(obj)方法

该方法返回指定对象的原型(内部[[Prototype]]属性的值)

es6中新增了Object​.set​PrototypeOf()

该方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null

obj = {age: 17}
Object.getPrototypeOf(obj).constructor
ƒ Object() { [native code] }
Object.setPrototypeOf(obj, Array).constructor
Object.getPrototypeOf(obj)
ƒ Array() { [native code] } // 原型已经变成了Array
obj.toString() // 报错

proxy也有代理陷阱


p = new Proxy(target, {
  getPrototypeOf(target) {
    console.log(target, 111)
  //Reflect.getPrototypeOf方法返回默认行为 
    return null
  },
  setPrototypeOf (target, proto) {
    return false
   // Reflect.setPrototypeOf返回默认行为
  }
})
targetProto = Object.getPrototypeOf(target)
pProto = Object.getPrototypeOf(p)  // null
Object.setPrototypeOf(pProto, null)  // 报错
Object.setPrototypeOf(pProto, Array) // 报错

差异:

Object.getPrototypeOf(1) // Number 会做类型转换 
Object.getPrototypeOf('1') // String 会做类型转换
Reflect.getPrototypeOf(1) // 报错  不会转换
Reflect.getPrototypeOf('1') // 报错 不会转换
Object.setPrototypeOf(obj1, obj2) // 返回obj1
Reflect.setPrototypeOf(obj1, obj2) // 返回true or false

因为object.getPrototypeOf和setPrototypeOf方法是给使用者使用的,Reflect则是改变了语言内部行为反馈。
6 对象可扩展性陷阱:

可扩展性:方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。

看一个例子:

obj = {}
p = new Proxy(obj, {
  isExtensible (target) {
    return Reflect.isExtensible(target) // 与默认行为一致
  },
  preventExtensible (target) {
    return Reflect.preventExtensions(target) // 与默认行为一致,false表示不能操作
  }
})
Object.isExtensible(p) // true
Object.isExtensible(obj) // true
p.name = 'xxx'
p // Proxy {name: "xxx"}
Object.preventExtensions(obj)
obj.age = 1
obj // {name: "xxx"}
Object.isExtensible(obj) // false
Object.isExtensible(p) // false

7 属性描述符陷阱:
描述符:Object.defineProperty(obj, key, decription)
一个默认例子:

obj = {name: 'xxx'}
p = new Proxy(obj, {
  defineProperty (target, key, option) {
    return Reflect.defineProperty(target, key, option)
  },
  getOwnPropertyDescriptor(target, key) {
    return Reflect.getOwnPropertyDescriptor(target, key)
  }
})
Object.defineProperty(p, 'props', {
  value:{}
})
p.props // {}
Object.getOwnPropertyDescriptor(p, 'props')// {value: {…}, writable: false, enumerable: false, configurable: false}

添加限制:

obj = {name: 'xxx'}
xxx = Symbol(1)
p = new Proxy(obj, {
  defineProperty (target, key, option) {
    if (typeof key === 'symbol') {
      return true // return false会报错, return true不报错,但是实际没有执行
    }
  // 调用该方法才算执行了defineProperty方法 
    return Reflect.defineProperty(target, key, option)
  },
  getOwnPropertyDescriptor(target, key) {
  return {
   name: xxx
  }
    // return Reflect.getOwnPropertyDescriptor(target, key)
  }
})
// Proxy {name: "xxx"}
Object.defineProperty(p, xxx, {
  value: 1
})
p // Proxy {name: "xxx"}没有新增成功

Object.defineProperty与Reflect.defineProperty区别:
前者返回你操作的对象,后者返回操作成功(true)或失败(false)。
8 ownKeys陷阱
看一个例子:

obj = {name: 'xxx'}
p = new Proxy(obj, {
 ownKeys (target) {
// 需要返回一个数组或类数组
   return Reflect.ownKeys(target).filter(key => key[0] !== '_')
 }
})
p.age = 99
Proxy {name: "xxx", age: 99}
p._isRoot = false
p // Proxy {name: "xxx", age: 99, _isRoot: false}
Object.keys(p) // ["name", "age"] // 过滤_xxx属性
obj // {name: "xxx", age: 99, _isRoot: false}
Object.keys(obj) // ["name", "age", "_isRoot"]
// for也是一样被过滤掉了
for(let key in p ){
 console.log(key)
}
name
age
for(let key in obj ){
 console.log(key)
}
name
age
_isRoot

9 函数代理中apply和constructor陷阱
我们知道:js中函数,直接调用时,执行的是[[call]]方法,new关键字调用执行的是construtor方法。apply陷阱和constructor陷阱可以覆写这些内部方法。
看一个例子:

name = 'zlj'
target = function () {return this.name}
p = new Proxy(target, {
  apply (fn, context, argument) {
   // 做你想做的事情
    console.log(fn, context, argument)
    return Reflect.apply(fn, context, argument)
  },
  construct (fn, options) {
    console.log(fn, options) 
    return Reflect.construct(fn, options)
  }
})
typeof p // "function"
p(1,2) // ƒ () {return this.name} undefined (2) [1, 2]
"zlj"
a = new p(1,2,3) // ƒ () {return this.name} (3) [1, 2, 3]
target {}

不使用new调用构造函数:

function foods (...options) {
  if (new.target === undefined) {
    throw new TypeError('must new')
  }
  this.values = options
}
summer = new foods('apple', 'orange') 
winter = foods('pea', 'www') //  报错
// 可内部修改调用方式
p = new Proxy(foods, {
 apply (target, context, argument) {
   return Reflect.construct(target, argument)
 }
})
spring = p('pea', 'apple') // foods {values: Array(2)}
上一篇下一篇

猜你喜欢

热点阅读