1. Proxy
关联知识:defineproperty
作用:代理器
本质:重载了对象的点运算符(相当在语言层面做出了修改)
应用场景:vue3的响应式数据采用的就是Proxy对象,vue2则是通过defineproperty进行数据代理。两者作用一致,Proxy相当于是defineProperty在数据代理上的优化,属于ES6语法。
前提:必须为Object类型(即对象类型),Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
const proxy = new Proxy({},{ get: function(target,propKey,receiver){ console.log('get') return Reflect.get(target, propKey, receiver); }, set: function(target,propKey,value,receiver){ console.log('get') return Reflect.set(target, propKey, value, receiver); } }) proxy.a = 'hello proxy!' // set console.log(proxy.a) // get // hello proxy!
Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象上的方法,就能在Reflect对象上找到对应的方法。这就使Proxy对象可以方便地调用对应Reflect的方法来完成默认行为,作为修改的基础。无论Proxy怎么修改默认行为,我们总能在Reflect上获取默认行为。
Reflect是ES6为了操作对象而提供新的API
(1) ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
const proxy = new Proxy(target, handler);
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
- 如果handler是一个空对象,则proxy实例相当于普通对象
proxy一共支持13种拦截操作 来源:
- get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']。
- set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
- has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
- deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
- ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
- getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
- defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
- preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
- getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
- isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
- setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
- apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
(2)get操作的使用例子
- 使用get拦截,实现数组读取负数的索引。
function createArray(...elements){ let handler = { get(target,propKey,receiver){ let index = Number(propKey); if(index < 0){ propKey = String(target.length + index) } return Reflect.get(target,propKey,receiver) } } let target = [...elements] return new Proxy(target,handler) } let arr = createArray('a', 'b', 'c'); arr[-1] // c
- 利用 Proxy,可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作。
这个例子稍微有点复杂,关于链式操作的一些细节还需要深入了解
简单的了解一下链式操作 javascript中的链式操作 - 知乎
const pipe = function (value) { var funcStack = []; var oproxy = new Proxy({} , { get : function (pipeObject, fnName) { if (fnName === 'get') { return funcStack.reduce(function (val, fn) { return fn(val); },value); } funcStack.push(window[fnName]); return oproxy; } }); return oproxy; } var double = n => n * 2; var pow = n => n * n; var reverseInt = n => n.toString().split("").reverse().join("") | 0; pipe(3).double.pow.reverseInt.get; // 63
上面例子通过函数传递初始值返回Proxy对象,相当于pip函数进行了劫持,并利用闭包思想对每次的点操作符的属性进行监听,当监听到属性值不为'get'时,将当前函数push到外层的funcStack数组,直到propKey为'get'时开始通过reduce函数遍历执行funcStack中的函数。
这里只列出了get操作的例子 ,所有拦截操作的例子->Proxy - ECMAScript 6入门 (ruanyifeng.com)