Proxy 对象拦截器

Proxy 对象拦截器

ES6的对象拦截器,可以拦截哪些情况呢**”**

在这里插入图片描述

01 前言


自从ES6诞生以来,各种新特性也逐渐显现出来,比如ES6中的Proxy对象,是一个重要的技术。之前Vue的数据双向数据绑定是使用Object.defineProperty()来做的,而现在vue3.0使用的是代理Proxy来编写。原因是前者有一些对象或者数组的变化是监听不了的,但是Proxy可以监听整个对象。

Proxy可以理解为,当你试图访问一个对象的时候必须先经过一个拦截或者代理,你才可以进行对对象的操作。这种机制的好处就是可以对外界的访问进行过滤和改写。

02 Proxy实例的方法


proxy实例的构造

ES6提供一个原生的Proxy构造函数,用于生成Proxy实例。

var proxy = new Proxy ( target, handler ) ;

参数解析:

  • target:所要拦截(代理)的对象
  • 处理函数,拦截对应的操作

利用Proxy对象可以拦截的方法有一下13种,下面一一来介绍一下。

1、get()

get方法是意思是读取对象,那么相应的拦截就是在读取对象的时候进行拦截。

var person = {
    name:'alanwu'
}
var proxy = new Proxy(person, {
    get:function(target, property){
        if(property in target){
            return target[property]
        }else{
            throw new Error('对象属性不存在')
        }
    }
})
proxy.name  //'alanwu'
proxy.age   //错误

假如上述代码访问不存在的属性的时候,没有抛出错误的话就会返回undefined。

2、set()

set拦截操作用于拦截对对象赋值的操作。

const obj = {
    a: 101,
    b: 46,
    c: 200
}
var proxy = new Proxy(obj, {
    set(target, key, value) {
        if (value < 100) {
           throw Error(`${value}值不能小于100`)
        } 
        target[key] = value
    }
})
3、apply()

apply方法用于拦截函数的调用、call、apply操作。

const sum = (num1, num2) => {
	return num1 + num2
}
const proxy = new Proxy(sum, {
	apply(target, context, args) {
	// 我们可以通过 Reflect.apply()来调用目标函数
		return Reflect.apply(...arguments) * 2
	}
})
proxy(3,4)  // 14
4、has()

has方法用于拦截hasProperty操作,即判断对象是否具有某个属性时,该方法有效。值得注意的是,对for…in循环是没有效果的。has方法可以判断该属性来自继承的属性,不只是自身的属性。下面是拦截访问私有属性“_prop”

var handler = {
	has(target, key){
		if(key[0]==='_'){
			return false
		}
		return key in target
	}
}
var target = {_prop:'foo', prop:'foo'};
var proxy = new Proxy(target, handler)
'_prop' in proxy  //false
5、construct

该方法的本意是构造,所以会拦截 new 操作符命令。该方法接受两个参数:

  • target:目标对象
  • args:构造函数的参数对象
var handler = {
	construct (target, args, newTarget){
		return new target(...args);
	}
}

例子:

var p = new Proxy(function() {}, {
    construct: function(target, argumentsList, newTarget) {
      console.log('called: ' + argumentsList.join(', '));
      return { value: argumentsList[0] * 10 };
    }
  });
  
  console.log(new p(1).value); // "called: 1"
                               // 10
6、deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。值得注意的是,目标对象自身的不可配置(configurable)的属性不能被deleteProperty方法删除,否则会报错。

var handler = {
	deleteProperty(target, key){
		invariant(key, 'delete');
		return true
	}
}
function invariant(key, action){
    if(key[0]==='_'){
        throw new Error(`Invalid attemp to ${action} private "${key}" property`)
    }
}
var target = {_prop:'foo'}
var proxy = new Proxy(target, handler);
delete proxy._prop  //Error
7、defineProperty

该方法用于拦截Object.defineProperty操作。下面操作添加新的属性会触发defineProperty函数,所以会报错。如果目标对象不可扩展(extensible)或不可写(writable)或不可配置(configurable),也不可添加新属性,否则也会报错。

var handler = {
	defineProperty(target, key, descriptor){
		return false
	}
}
var target {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar'
8、getOwnPropertyDescriptor

该方法用于拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。

var handler = {
    getOwnPropertyDescriptor(target, key){
        if(key[0]==='_'){
            return
        }
        return Object.getOwnPropertyDescriptor(target, key)
    }
}
var target = {_foo:'bar', baz:'tar'};
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat'); //undefined
Object.getOwnPropertyDescriptor(proxy, '_foo'); //undefined
Object.getOwnPropertyDescriptor(proxy, 'baz'); //{value:'tar', writable:true,enumerable:true,configurable:true}
9、getPrototypeOf()

该方法用于拦截获取对象的原型。有如下几种情况:

  • Object.prototype. __ proto __
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
var proto = {}
var p = new Proxy({}, {
    getPrototypeOf(target){
        return proto
    }
})
Object.getPrototypeOf(p) === proto //true

值得注意的是getPrototype方法返回值必须是对象或者null,否则会报错。目标对象不可扩展(extensible),那么该方法必须返回对象的原型对象。

10、isExtensible()

该方法用于拦截Object.isExtensible()操作。

var p = new Proxy({},{
    isExtensible: function(target){
        return true
    }
})

该方法返回值必须是布尔值,否则会转成布尔值。如果返回值与目标对象的IsExtensible(target)不一致,也会抛出错误。

11、ownKeys()

该方法用于拦截对象自身属性的读取操作。可以拦截以下操作:

  • Object.getOwnPropertyNames ()
  • Object.getOwnPropertySymbols ()
  • Object.keys ()
let target = {
    a:1,
    b:2,
    c:3
}
let handler = {
    ownKeys(target){
        return ['a']
    }
}
let proxy = new Proxy(target,handler);
Object.keys(proxy) //['a']
12、preventExtensions()

该方法拦截 Object.preventExtensions () ,该方法必须返回个布尔值,否则会被自动转为布尔值。只有目标对象不可扩展时,即 Object.isExtensible(proxy)为false, proxy.preventExtensions 才能返回 true ,否则会报错。

var p =new Proxy({}, { 
	preventExtensions: function(target) { 
		return true; 
    }
}) 
Object.preventExtensions(p) //报错
13、setPrototypeOf()

该方法主要用于拦截 Object.setPrototypeOf 方法。下面的代码中,只要修改 target 的原型对象就会错。

var handler = { 
	setPrototypeOf (target, proto) { 
		throw new Error ('Changing the prototype is forbidden'); 
	}
}
var proto = {} ; 
var target = function () {}; 
var proxy = new Proxy(target , handler) ; 
Object.setPrototypeOf(proxy, proto );  //Error : Changing the prototype is forbidden 
14、Proxy.revocable()

Proxy.revocable 方法返回 一个可取消 Proxy实例。

let target = {};
let handler = {};
let { proxy, revoke } = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo //123
revoke();
proxy.foo //TypeError: Revoked

Proxy.revocable 方法返回一个对象,其 proxy 属性是 Proxy 实例, revoke 属性是一个函数,可以取消 Proxy 实例。上面的代码中,当执行 revoke 函数后再访问 Proxy 实例,就会抛出一个错误。

Proxy.revocable 个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

03 小结


上述的实例方法在很多地方都有很大的作用,特别是对对象进行拦截的场景的时候。ES6语法在近年来已经很多使用,特别是vue3.0会使用proxy作为数据的响应式写法。

一般面试还都会问你Object.defineProperty有什么缺点,然后问你有没有关注vue3.0,知道3.0中的响应式使用什么写法。还有Proxy有什么方法之类的。

参考文章

  • 阮一峰 ES6入门标准 ——Proxy

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章