es6代理和反射

前言:

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

  • 我们可以看到,Proxy直接可以劫持整个对象,并返回一个新对象,不管是操作便利程度还是底层功能上都远强于Object.defineProperty。
  • Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性实现响应式。
  • 当我们对数组进行操作(push、shift、splice等)时,会触发对应的方法名称和length的变化,我们可以借此进行操作,Object.defineProperty无法生效,更多参考

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

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)}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容