ES6之Proxy

2019-05-221893次阅读ES6javascript

ES6原生提供Proxy构造函数,用来生成Proxy实例。

var proxy = new Proxy(target, handler);

Proxy对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

 

针对对象

Proxy可以被认为是Object.defineProperty()的升级版。外界对某个对象的访问,都必须经过这层拦截。因此它是针对整个对象,而不是对象的某个属性,所以也就不需要对keys进行遍历。这解决了Object.defineProperty的问题一文中的第二个问题。

let obj = {
  name: 'Eason',
  age: 30
}

let handler = {
  get(target, key, receiver){
    console.log('get', key)
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver){
    console.log('set', key, value)
    return Reflect.set(target, key, value, receiver)
  }
}

let proxy = new Proxy(obj, handler)
proxy.name = 'Zoe' // set name Zoe
proxy.age = 18 // set age 18

如上代码,Proxy 是针对 obj 的。因此无论 obj 内部包含多少个 key ,都可以走进 set(省了一个 Object.keys() 的遍历)。

 

支持数组

let arr = [1,2,3]

let proxy = new Proxy(arr, {
    get (target, key, receiver) {
        console.log('get', key)
        return Reflect.get(target, key, receiver)
    },
    set (target, key, value, receiver) {
        console.log('set', key, value)
        return Reflect.set(target, key, value, receiver)
    }
})

proxy.push(4)
// 能够打印出很多内容
// get push(寻找 proxy.push 方法)
// get length(获取当前的 length)
// set 3 4(设置 proxy[3] = 4)
// set length 4 (设置 proxy.length = 4)

Proxy不需要对数组的方法进行重载,省去了众多hack,减少代码量等于减少了维护成本,而且标准的就是最好的。这解决了Object.defineProperty的问题一文中的第一个问题。

 

嵌套支持

本质上,Proxy 也是不支持嵌套的,这点和 Object.defineProperty() 是一样的。因此也需要通过逐层遍历来解决。Proxy 的写法是在 get 里面递归调用 Proxy 并返回,代码如下:

let obj = {
  info: {
    name: 'eason',
    blogs: ['webpack', 'babel', 'cache']
  }
}

let handler = {
  get (target, key, receiver) {
    console.log('get', key)
    // 递归创建并返回
    if(typeof target[key] === 'object' && target[key] !== null) {
        return new Proxy(target[key], handler)
    }
    return Reflect.get(target, key, receiver)
  },
  set (target, key, value, receiver) {
    console.log('set', key, value)
    return Reflect.set(target, key, value, receiver)
  }
}

let proxy = new Proxy(obj, handler)

//以下两句都能够进入 set
proxy.info.name = 'Zoe'
proxy.info.blogs.push('proxy')

Proxy 还拥有以下优势:

  • Proxy 的第二个参数可以有 13 种拦截方法,这比起 Object.defineProperty() 要更加丰富

  • Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法。

这第二个优势源于它是新标准。但新标准同样也有劣势,那就是:

  • Proxy 的兼容性不如 Object.defineProperty() (caniuse 的数据表明,QQ 浏览器和百度浏览器并不支持 Proxy,这对国内移动开发来说估计无法接受,但两者都支持 Object.defineProperty())

  • 不能使用 polyfill 来处理兼容性

这些比较仅针对“数据劫持的实现”这个需求而言。Object.defineProperty() 除了定义get和set之外,还能实现其他功能,因此即便不考虑兼容性的情况下,本文并不是想说一个可以完全淘汰另一个。

 

一道面试题

什么样的a可以满足 (a === 1 && a === 2 && a === 3) === true 呢?(注意是 3 个 =,也就是严格相等)

既然是严格相等,类型转换什么的基本不考虑了。一个自然的想法就是每次访问a返回的值都不一样,那么肯定会想到数据劫持。(可能还有其他解法,但这里只讲数据劫持的方法)

let current = 0

Object.defineProperty(window, 'a', {
  get(){
    current++    
    return current
  }
})

console.log(a === 1 && a === 2 && a === 3) // true

使用 Proxy 也可以,但因为 Proxy 的语法是返回一个新的对象,因此要做到 a === 1 可能比较困难,做到 obj.a === 1 还是 OK 的,反正原理是一样的,也不必纠结太多。

 

多继承

Javascript 通过原型链实现继承,正常情况一个对象(或者类)只能继承一个对象(或者类)。但通过这两个方法都可以实现一种黑科技,允许一个对象继承两个对象。下面的例子使用 Proxy 实现。

let foo = {
  foo () {
    console.log('foo')
  }
}

let bar = {
  bar () {
    console.log('bar')
  }
}
    
// 正常状态下,对象只能继承一个对象,要么有 foo(),要么有 bar()
let sonOfFoo = Object.create(foo);
sonOfFoo.foo();// foo
let sonOfBar = Object.create(bar);
sonOfBar.bar();// bar

//黑科技开始
let sonOfFooBar = new Proxy({}, {
  get (target, key) {
    return target[key] || foo[key] || bar[key];
  }
})
    
// 我们创造了一个对象同时继承了两个对象,foo() 和 bar() 同时拥有
sonOfFooBar.foo();// foo 有foo方法,继承自对象foo
sonOfFooBar.bar();// bar 也有bar方法,继承自对象bar

当然实际有啥用处我暂时还没想到,且考虑到代码的可读性,多数可能只存在于炫技或者面试题中吧我猜……

 

隐藏私有变量

既然能够操纵 get,自然就可以实现某些属性可以访问,而某些不可以,这就是共有和私有属性的概念。实现起来也很简单:

function getObject(rawObj, privateKeys) {
  return new Proxy(rawObj, {
  
    get (target, key, receiver) {
  
        if (privateKeys.indexOf(key) !== -1) {
            throw new ReferenceError(`${key} 是私有属性,不能访问。`)
        }
                
        return target[key]
    }
         
  })
}
         
let rawObj = {
  name: 'Zoe',
  age: 18,
  isFemale: true
}
         
let obj = getObject(rawObj, ['age'])
console.log(obj.name) // Zoe
console.log(obj.age) // 报错

转载自:https://zhuanlan.zhihu.com/p/47041290

上一篇: Object.defineProperty的问题  下一篇: js节流函数  

ES6之Proxy相关文章