美文网首页
es6代理和反射

es6代理和反射

作者: 安石0 | 来源:发表于2019-05-09 23:19 被阅读0次

    前言:

    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)}
    

    相关文章

      网友评论

          本文标题:es6代理和反射

          本文链接:https://www.haomeiwen.com/subject/tkqwoqtx.html