最近在博客上看到关于ES6代理的文章都是一些关于如何使用Proxy的例子,很少有说明Proxy原理的文章,要知道只有真正掌握了一项技术的原理,才能够写出精妙绝伦的代码,所以我觉得有必要写一篇关于深刻揭露ES6 Proxy的文章。

看完这篇文章你不会学到一些大型的使用Proxy的例子,但是你可以了解以下几方面的内容:

  • 你将知道代理是个什么东西
  • 你将知道所有代理对象可覆盖的方法
  • 一些代理对象使用的小场景
什么是代理?刚开始入行的同学可能就会问了。我觉得回答这个问题之前我们应该先弄清楚另一个问题:什么是对象?有人会回答:对象是属性的集合。好吧,这种回答的确找不出什么bug,但是却太抽象了,ES委员会为对象定义了一个由14种方法组成的集合,它是适用于所有对象的通用接口,你可以调用、删除或覆写普通方法,但是无法操作内部方法。 下面让我们来看一下这14个方法,[[]]代表内部方法,在一般代码中不可见。
  • [[GetPrototypeOf]] ( )

    获取对象的原型时调用,在执行obj[__proto__]或Object.getPrototypeOf(obj)时调用。

  • [[SetPrototypeOf]] (V)

    设置一个对象的原型时调用,在执行obj.prototype=otherObj或则Object.SetPrototypeOf(v)的时候调用

  • [[IsExtensible]] ( )

    获取对象的可扩展性时调用,执行Object.isExtensible(object)时被调用。

  • [[GetOwnProperty]] (P)

    获取自有属性时调用

  • [[PreventExtensions]]()

扩展一个不可扩展的对象时调用

  • [[DefineOwnProperty]] (P, Desc)

    定义自有属性时调用

  • [[HasProperty]](P)

    检测对象是否存在某个属性时调用,如key in obj

  • [[Get]] (P, Receiver)

    获取属性时调用,如obj.key,obj[key]。

  • [[Set]] ( P, V, Receiver)

    为对象的属性赋值时调用,如obj.key=value或obj[key]=value。

  • [[Delete]] (P)

    删除某个属性时调用

  • [[Enumerate]] ()

    列举对象的可枚举属性时调用,如for (var key in obj)。

  • [[OwnPropertyKeys]] ( )

    列举对象的自有属性时调用

  • functionObj.[[Call]](thisValue, arguments)
调用一个函数时被调用,functionObj()或者x.method()。
  • constructorObj.[[Construct]](arguments, newTarget)
使用new操作的时候调用,如new Date()。
在整个 ES6 标准中,只要有可能,任何语法或对象相关的内建函数都是基于这14种内部方法构建的 。但是我们不必记住这些对象的内建属性,我们更应关注是handler与之相对应的方法。

现在我们已经清楚了对象的14个内建方法,那么我们再回到第一个问题:什么是代理?我们可以这样说,代理Proxy是一个构造函数,它可以接受两个参数:目标对象(target) 与句柄对象(handler) ,返回一个代理对象Proxy,主要用于从外部控制对对象内部的访问。
var target = {}, handler = {};
var proxy = new Proxy(target, handler);
那么Proxy、target、handler这三者之间有什么关系呢?Proxy的行为很简单:将Proxy的所有内部方法转发至target 。即调用Proxy的方法就会调用target上对应的方法。那么handler又是用来干嘛的?handler的方法可以覆写任意代理的内部方法。 外界每次通过Proxy访问target
对象的属性时,就会经过 handler 对象,因此,我们可以通过重写handler对象中的一些方法来做一些拦截的操作。以下是一个简单的代理使用例子。
var person = {
name: "Jhon",
age: 23
};
var p = new Proxy(person,{
get: function(target, prop, receiver){
console.log("你访问了person的属性");
return target["name"];
}
});
console.log(p.age);
// 你访问了person的属性
// Jhon

你看,我们虽然访问了age属性,它却输出了额外的字符串和属性name的值,这就是拦截器的作用。


对应对象内建的14个方法,handler也有14个方法可以覆盖,下面我们将会一一讲解。

1、handler.get() 方法用于拦截对象的读取属性操作。
var p = new Proxy(target, {
get: function(target, property, receiver) {
}
});
  • target,目标对象
  • property,被获取的属性名
  • receiver,Proxy或者继承Proxy的对象
  • 可以返回任何值
  • this绑定到handler对象上
约束条件,proxy会抛出 TypeError
  • 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同。
  • 如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined。
2、handler.set() 方法用于拦截设置属性值的操作
var p = new Proxy(target, {
set: function(target, property, value, receiver) {
}
});
  • target,目标对象
  • property,被设置的属性名
  • value,被设置的新值
  • receiver,最初被调用的对象。通常是proxy本身,但handler的set方法也有可能在原型链上或以其他方式被间接地调用(因此不一定是proxy本身)
  • this绑定到handler对象上
  • 返回一个布尔值,返回true代表此次设置属性成功了,如果返回false且设置属性操作发生在严格模式下,那么会抛出一个TypeError
约束,违背proxy会抛出一个TypeError:
  • 若目标属性是不可写及不可配置的,则不能改变它的值
  • 如果目标属性没有配置存储方法,即set方法是undefined的,则不能设置它的值
  • 在严格模式下,若set方法返回false,则会抛出一个 TypeError 异常
3、 handler.has() 方法可以看作是针对 in 操作的钩子
var p = new Proxy(target, {
has: function(target, prop) {
}
});
  • targe,t目标对象
  • prop,需要检查是否存在的属性
  • this绑定到handler对象上
  • 返回一个boolean值
约束:
  • 如果目标对象的某一属性本身不可被配置,则该属性不能够被代理隐藏
  • 如果目标对象为不可扩展对象,则该对象的属性不能够被代理隐藏
4、handler.getPrototypeOf() 是一个代理方法,当读取代理对象的原型时,该方法就会被调用
var p = new Proxy(obj, {
getPrototypeOf(target) {
...
}
});
  • target,被代理的目标对象
  • this 指向的是它所属的处理器对象
  • 必须返回一个对象值或者返回 null
约束:
  • getPrototypeOf() 方法返回的不是对象也不是 null
  • 目标对象是不可扩展的,且 getPrototypeOf() 方法返回的原型不是目标对象本身的原型

5、handler.defineProperty() 用于拦截对对象的 Object.defineProperty() 操作
var p = new Proxy(target, {
defineProperty: function(target, property, descriptor) {
}
});
  • target,目标对象
  • property,待检索其描述的属性名
  • descriptor,待定义或修改的属性的描述符
  • this 绑定在
    handler 对象上
  • 返回一个boolean值
约束:
  • 如果目标对象不可扩展, 将不能添加属性、
  • 不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象的不可配置的属性存在的话
  • 如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的
  • 如果一个属性在目标对象中存在对应的属性,那么 Object.defineProperty(target,
    prop, descriptor)
     将不会抛出异常
  • 在严格模式下, false 作为 handler.defineProperty 方法的返回值的话将会抛出 TypeError 异
6、handler.deleteProperty() 方法用于拦截对对象属性的 delete 操作
var p = new Proxy(target, {
deleteProperty: function(target, property) {
}
});
  • target,目标对象
  • property,待删除的属性名
  • this 被绑定在
    handler上
  • 返回一个boolean值
约束:
  • 如果目标对象的属性是不可配置的,那么该属性不能被删除
7、handler.setPrototypeOf() 用于拦截对对象的 Object.setPrototypeOf() 操作
var p = new Proxy(target, {
setPrototypeOf: function(target, prototype) {
}
});
  • target,目标对象
  • prototype,待设置的属性名或者null
  • this 被绑定在
    handler上
  • 返回一个boolean值
约束:
  • 如果目标对象是不可扩展的,则原型的参数必须和Object.getPrototypeOf(target)相同
8、handler.getOwnPropertyDescriptor() 用于拦截对对象的 Object.getOwnPropertyDescriptor() 操作
var p = new Proxy(target, {
getOwnPropertyDescriptor: function(target, prop) {
}
});
  • target,目标对象
  • prop,返回属性名的描述符
  • this绑定到处理函数
  • 必须返回一个 对象 或 undefined
约束:
  • getOwnPropertyDescriptor 必须返回一个 object
    或 undefined
  • 如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在

  • 如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在

  • 如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在

  • 属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在

  • Object.getOwnPropertyDescriptor(target)的结果可以使用
    Object.defineProperty 应用于目标对象,也不会抛出异常

9、handler.keys() 用于拦截对对象的 Object.keys()操作
var p = new Proxy(target, {
ownKeys: function(target) {
}
});
  • target,目标对象
  • this绑定到处理函数
  • 返回一个数组
约束:
  • 返回值必须是一个数组
  • 数组的每一个元素必须是字符串或者Symbol
  • 数组必须包含目标对象的所有非可配置属性的键
  • 如果目标对象不可扩展,则数组必须包含目标对象自身属性的所有键,并且没有其他值
10、handler.isExtensible() 用于拦截对对象的 Object.isExtensible()操作
var p = new Proxy(target, {
isExtensible: function(target) {
}
});
  • target,目标对象
  • this绑定到处理函数
  • 返回一个boolean值
约束:
  • Object.isExtensible(proxy) 必须返回和Object.isExtensible(target)相同的值
11、handler.apply() 方法用于拦截函数的调用
var p = new Proxy(target, {
apply: function(target, thisArg, argumentsList) {
}
});
  • target,目标对象(函数)
  • thisArg,被调用时的上下文对象
  • argumentsList,被调用时的参数列表
  • this绑定到handler对象
  • 可以返回任何值
约束
12、 handler.construct()用于来接new操作
var p = new Proxy(target, {
construct: function(target, argumentsList, newTarget) {
}
});
  • target,目标对象
  • argumensList,构造器参数列表
  • newTarget,最初调用的构造函数
  • this绑定到handler
  • 返回一个对象
约束:
  • 返回值必须是一个对象
13、handler.preventExtensions()) 用于拦截对对象的handler.preventExtensions()操作
var p = new Proxy(target, {
preventExtensions: function(target) {
}
});
  • target,目标对象
  • this绑定到handler对象
  • 返回一个boolean值
约束:
  • Object.isExtensible(proxy)为假,则Object.preventExtensions(proxy)只能返回true
14、headler.enumerate()

以上我们列举了所有可选择覆盖的14个headler方法,所有代理的操作都是基于这14个方法进行的,如果你打算进阶的话,那么现在可以去看一些大神的库了。






深度揭秘ES6代理Proxy的更多相关文章

  1. 详解es6中Proxy代理对象的作用

    在es6中新添加了Proxy,那么它有什么作用啊?Proxy本意为代理,而es6中的Proxy也就是代理对象,那么代理对象感觉听起来很模糊,在这里就解释一下Proxy代理对象的作用. Proxy的主要 ...

  2. 代理(Proxy)和反射(Reflection)

    前面的话 ES5和ES6致力于为开发者提供JS已有却不可调用的功能.例如在ES5出现以前,JS环境中的对象包含许多不可枚举和不可写的属性,但开发者不能定义自己的不可枚举或不可写属性,于是ES5引入了O ...

  3. ES6的Proxy

    最近在Javascript的设计编程中,用到的那个单例模式,感觉就类似一种代理的思想[其实就是缓存的一种机制],单例模式就是: function getSingle(fn){ var result; ...

  4. [每日一题]面试官问:谈谈你对ES6的proxy的理解?

    [每日一题]面试官问:谈谈你对ES6的proxy的理解? 关注「松宝写代码」,精选好文,每日一题 作者:saucxs | songEagle 一.前言 2020.12.23 日刚立的 flag,每日一 ...

  5. Webpack代理proxy配置,解决本地跨域调试问题,同时允许绑定host域名调试

    Webpack代理proxy配置,解决本地跨域调试问题,同时允许绑定host域名调试 会撸码的小马 关注 2018.05.29 17:30* 字数 212 阅读 1488评论 0喜欢 2 接到上一章, ...

  6. 浅谈Java代理一:JDK动态代理-Proxy.newProxyInstance

    浅谈Java代理一:JDK动态代理-Proxy.newProxyInstance java.lang.reflect.Proxy:该类用于动态生成代理类,只需传入目标接口.目标接口的类加载器以及Inv ...

  7. 初识代理——Proxy

    无处不在的模式——Proxy 最近在看<设计模式之禅>,看到代理模式这一章的时候,发现自己在写spring项目的时候其实很多时候都用到了代理,无论是依赖注入.AOP还是其他,可以说是无处不 ...

  8. JAVA设计模式-动态代理(Proxy)示例及说明

    在Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析文章的最后部分,我们提到了动态代理的概念,下面我们就简单了解一下动态代理. 一,概念 代理设计模式的目的就是在不直接操作对象的前 ...

  9. JAVA设计模式-动态代理(Proxy)源码分析

    在文章:JAVA设计模式-动态代理(Proxy)示例及说明中,为动态代理设计模式举了一个小小的例子,那么这篇文章就来分析一下源码的实现. 一,Proxy.newProxyInstance方法 @Cal ...

随机推荐

  1. 源码安装H2O Http 服务端程序到Ubuntu服务器

    首先安装全家桶 apt install -y build-essential zlib1g-dev libpcre3 libpcre3-dev unzip cmake libncurses5-dev ...

  2. 【实验吧】Reverse400

    在网上下载,pyinstxtractor.py,对Reverse400.exe进行反汇编 得到其源代码为 $ cat Revesre03 data = \ "\x1c\x7a\x16\x77 ...

  3. 树状数组初步_ZERO

    原博客:树状数组 1 一维树状数组 1 什么是树状数组        树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组A[1..n],那么查询A[1]+-+A[n]的时,间是log级 ...

  4. WILL吃桃_KEY

    WILL 吃桃 (peach.pas/c/cpp) [ 题目描述] Will 很喜欢吃桃, 某天 Will 来到了一片森林, 森林中有 N 颗桃树, 依次编号为 1,2,„,N.每棵树上有数量不等的桃 ...

  5. java 方法重载overload

    一.方法的重载 方法名一样,但参数不一样,这就是重载(overload). 所谓的参数不一样,主要有两点:第一是参数的个数不一样,第二是参数的类型不一样.只要这两方面有其中的一方面不一样就可以构成方法 ...

  6. maven详解之结构

    maven 父子关系 父项目中打包方式必须是pom  如 <packaging>pom</packaging>,父项目中使用<modules><module& ...

  7. appium启动运行log分析

      1.手动启动appium 服务 > Launching Appium server with command: C:\Program Files (x86)\Appium\node.exe ...

  8. python堆栈实现

    百度百科定义: 堆栈是一个在计算机科学中经常使用的抽象数据类型.堆栈中的物体具有一个特性: 最后一个放入堆栈中的物体总是被最先拿出来, 这个特性通常称为后进先出(LIFO)队列. 堆栈中定义了一些操作 ...

  9. python之路第二篇(基础篇)

    入门知识: 一.关于作用域: 对于变量的作用域,执行声明并在内存中存在,该变量就可以在下面的代码中使用. if 10 == 10: name = 'allen' print name 以下结论对吗? ...

  10. STM8学习

    今天正式学习STM8,用的是风驰STM8S208R开发板. 在编译例程遇到了如下这样的问题. " #error clnk debug\stm8s_demo.lkf:47 can't open ...