Heim  >  Artikel  >  Web-Frontend  >  Lassen Sie uns über Proxy und Reflect in Javascript sprechen

Lassen Sie uns über Proxy und Reflect in Javascript sprechen

WBOY
WBOYnach vorne
2022-02-09 17:27:002472Durchsuche

Dieser Artikel vermittelt Ihnen relevantes Wissen über Proxy und Reflect in JavaScript. Ich hoffe, er wird Ihnen hilfreich sein.

Lassen Sie uns über Proxy und Reflect in Javascript sprechen

ECMAScript hat der ES6-Spezifikation zwei neue Funktionen hinzugefügt, Proxy und Reflect. Diese beiden neuen Funktionen verbessern die Steuerbarkeit des Objektzugriffs in JavaScript und machen die Kapselung von JS-Modulen und -Klassen strenger und einfacher Fehlerberichterstattung beim Bedienen von Objekten besser kontrollierbar.

Proxy

Proxy, wie der Name schon sagt, Proxy. Diese Schnittstelle kann ein Proxy-Objekt für das angegebene Objekt erstellen, z. B. den Zugriff auf Eigenschaften, das Zuweisen von Werten zu Eigenschaften und Funktionsaufrufe, werden abgefangen und dann an die von uns definierten Funktionen übergeben entsprechende Operationen,
JavaScript Die Eigenschaften von Objekten geben Objekten viel Raum für Manipulationen. Gleichzeitig bietet JavaScript auch viele Methoden, mit denen wir Objekte nach Belieben hinzufügen, Attribute nach Belieben löschen und den Prototyp ändern können des Objekts nach Belieben ... Allerdings hatte die von der Object-Klasse bereitgestellte API zuvor viele Mängel:

  • Wenn Sie Object.defineProperty verwenden möchten, um alle Eigenschaften in einer bestimmten Namenssammlung zu definieren, können Sie nur Getter und festlegen Setter für alle Eigenschaften durch Aufzählung, und da für jede Eigenschaft nur eine Funktion erstellt werden kann, führt eine zu große Sammlung zu Leistungsproblemen.
  • Nachdem Object.defineProperty die Eigenschaft definiert hat und Sie weiterhin über normale Zugriffsfunktionen verfügen möchten, können Sie die Daten nur in einem anderen Eigenschaftsnamen des Objekts speichern oder benötigen ein anderes Objekt zum Speichern der Daten, insbesondere in Fällen, in denen Sie dies nur möchten Monitoreigenschaften unbequem.
  • Object.defineProperty kann nicht nicht neu definierbare Eigenschaften in einer Klasse ändern, beispielsweise die Längeneigenschaft eines Arrays.
  • Bei Eigenschaften, die noch nicht existieren und unvorhersehbare Namen haben, kann Object.defineProperty nicht helfen.
  • Einige Verhaltensweisen können nicht geändert oder verhindert werden, z. B. das Aufzählen von Eigenschaftsnamen und das Ändern von Objektprototypen. Das Aufkommen der

Proxy-Schnittstelle löst diese Probleme gut:

  • Die Proxy-Schnittstelle klassifiziert alle Vorgänge am Objekt in mehrere Kategorien, fängt bestimmte Vorgänge über die vom Proxy bereitgestellten Traps ab und verarbeitet sie dann in der von uns definierten Verarbeitung Durch logische Beurteilungen von Funktionen können komplexe Funktionen implementiert und mehr Verhaltensweisen als zuvor gesteuert werden.
  • Das von Proxy erstellte Proxy-Objekt existiert in Form eines Vermittlers und ist nicht für die Speicherung von Daten verantwortlich. Wir müssen das Proxy-Objekt nur externen Benutzern zur Verfügung stellen, damit externe Benutzer unter der Kontrolle des Proxy-Objekts auf unsere Originalobjekte zugreifen können .

Die Proxy-Schnittstelle ist ein Konstruktor in der JS-Umgebung:

ƒ Proxy ( target: Object, handlers: Object ) : Proxy

Dieser Konstruktor hat zwei Parameter, der erste ist das Objekt, das wir als Proxy verwenden möchten, und der zweite ist ein Objekt, das Funktionen enthält, die verschiedene Vorgänge verarbeiten.
Das Folgende ist ein Beispiel für einen Aufruf:

//需要代理的目标
var target = { msg: "I wish I was a bird!" }; 
//包含处理各种操作的函数的对象
var handler = {
	//处理其中一种操作的函数,此处是访问属性的操作
	get(target, property) {
		//在控制台打印访问了哪个属性
		console.log(`你访问了 ${property} 属性`);
		//实现操作的功能
		return target[property];
	}
}
//构造代理对象
var proxy = new Proxy( target , handler);
//访问代理对象
proxy.msg
//控制台: 你访问了 msg 属性
//← I wish I was a bird!

Im obigen Beispiel wird zuerst ein Objekt erstellt und dem Ziel zugewiesen, und dann wird ein Proxy-Objekt mit dem Ziel als Ziel erstellt und dem Proxy zugewiesen. In dem Objekt, das dem Proxy-Konstruktor als zweiter Parameter bereitgestellt wird, gibt es ein Attribut mit dem Namen „get“, bei dem es sich um eine Funktion handelt. „get“ ist der Name eines Traps in der Proxy-Schnittstelle, auf den wir uns beziehen Suchen Sie für die Attribute im Objekt nach den Attributen mit demselben Attributnamen wie dem Trap-Namen, legen Sie automatisch den entsprechenden Trap fest und verwenden Sie die Funktion für das Attribut als Trap-Verarbeitungsfunktion. Traps können bestimmte Vorgänge am Proxy-Objekt abfangen, die Details des Vorgangs in Parameter umwandeln und an unsere Verarbeitungsfunktion übergeben, sodass die Verarbeitungsfunktion den Vorgang abschließen kann, sodass wir verschiedene Verhaltensweisen des Objekts über die Verarbeitungsfunktion steuern können.
Im obigen Beispiel ist die beim Erstellen des Proxy-Objekts bereitgestellte Get-Funktion die Funktion, die den Zugriff auf die Attribute des Objekts abfängt und das Zielobjekt und das Attribut übergibt Name, auf den zugegriffen werden soll zu den Get-Funktionsparametern, und der Rückgabewert der Funktion wird als Ergebnis des Zugriffs verwendet.

Proxy verfügt über insgesamt 13 Arten von Fallen:

Auf Objekteigenschaften zugreifentarget .property
oder target[property]
Trap-Name und entsprechender Funktionsparameter Abgefangene Operation Operationsbeispiel
get(target, property)get(target, property) 访问对象属性 target.property
target[property]
set(target, property, value, receiver) 赋值对象属性 target.property = value
target[property] = value
has(target, property) 判断对象属性是否存在 property in target
isExtensible(target) 判断对象可否添加属性 Object.isExtensible(target)
preventExtensions(target) 使对象无法添加新属性 Object.preventExtensions(target)
defineProperty(target, property, descriptor) 定义对象的属性 Object.defineProperty(target, property, descriptor)
deleteProperty(target, property) 删除对象的属性 delete target.property
delete target[property]
Object.deleteProperty(target, property)
getOwnPropertyDescriptor(target, property) 获取对象自有属性的描述符 Object.getOwnPropertyDescriptor(target, property)
ownKeys(target) 枚举对象全部自有属性 Object.getOwnPropertyNames(target).
concat(Object.getOwnPropertySymbols(target))
getPrototypeOf(target) 获取对象的原型 Object.getPrototypeOf(target)
setPrototypeOf(target) 设置对象的原型 Object.setPrototypeOf(target)
apply(target, thisArg, argumentsList) 函数调用 target(...arguments)
target.apply(target, thisArg, argumentsList)
construct(target, argumentsList, newTarget) 构造函数调用 new target(...arguments)
🎜🎜🎜set(target, property, value, Receiver)🎜🎜Objekteigenschaften zuweisen🎜🎜 target.property = value
oder target[property] = value🎜🎜🎜🎜has(target, property)🎜🎜Judgement Ob die Objekteigenschaft existiert🎜🎜Eigenschaft im Ziel🎜🎜🎜🎜isExtensible(target)🎜🎜Bestimmen Sie, ob das Objekt Attribute hinzufügen kann🎜🎜Object.isExtensible(target) 🎜🎜🎜🎜<code>preventExtensions(target)🎜🎜Machen Sie es unmöglich, dem Objekt neue Eigenschaften hinzuzufügen🎜🎜Object.preventExtensions(target)🎜🎜🎜🎜 defineProperty( target, property, descriptor)🎜🎜Definieren Sie die Eigenschaften des Objekts🎜🎜Object.defineProperty(target, property, descriptor)🎜🎜🎜🎜deleteProperty (target, property) code>🎜🎜Löschen Sie die Eigenschaften des Objekts🎜🎜delete target.property
oder delete target[property]oder Object.deleteProperty( target, property)🎜🎜🎜🎜getOwnPropertyDescriptor(target, property)🎜🎜Den Deskriptor der eigenen Eigenschaften des Objekts abrufen🎜🎜Object.getOwnPropertyDescriptor(target, property) code>🎜🎜🎜🎜ownKeys(target)🎜🎜Alle eigenen Eigenschaften des Objekts aufzählen🎜🎜Object.getOwnPropertyNames(target ).
concat (Object.getOwnPropertySymbols(target))🎜🎜🎜🎜getPrototypeOf(target)🎜🎜Den Prototyp des Objekts abrufen🎜 🎜Object.getPrototypeOf(target)🎜🎜 🎜🎜setPrototypeOf(target)🎜🎜Legen Sie den Prototyp des Objekts fest🎜🎜Object.setPrototypeOf(target) code>🎜🎜🎜🎜<code>apply(target, thisArg, argumentsList)🎜🎜Funktionsaufruf🎜🎜target(...arguments)
oder target .apply(target, thisArg, argumentsList)🎜🎜🎜🎜construct(target, argumentsList, newTarget)🎜🎜Konstruktoraufruf🎜🎜new target(...arguments)🎜🎜🎜🎜<p>在上面列出的陷阱里是有拦截函数调用一类操作的,但是只限代理的对象是函数的情况下有效,Proxy 在真正调用我们提供的接管函数前是会进行类型检查的,所以通过代理让普通的对象拥有函数一样的功能这种事就不要想啦。<br> 某一些陷阱对处理函数的返回值有要求,如果不符合要求则会抛出 TypeError 错误。限于篇幅问题,本文不深入阐述,需要了解可自行查找资料。</p> <p>除了直接 new Proxy 对象外,Proxy 构造函数上还有一个静态函数 revocable,可以构造一个能被销毁的代理对象。</p> <pre class="brush:php;toolbar:false">Proxy.revocable( target: Object, handlers: Object ) : Object Proxy.revocable( target, handlers ) → { proxy: Proxy, revoke: ƒ () }</pre> <p>这个静态函数接收和构造函数一样的参数,不过它的返回值和构造函数稍有不同,会返回一个包含代理对象和销毁函数的对象,销毁函数不需要任何参数,我们可以随时调用销毁函数将代理对象和目标对象的代理关系断开。断开代理后,再对代理对象执行任何操作都会抛出 TypeError 错误。</p> <pre class="brush:php;toolbar:false">//创建代理对象 var temp1 = Proxy.revocable({a:1}, {}); //← {proxy: Proxy, revoke: ƒ} //访问代理对象 temp1.proxy.a //← 1 //销毁代理对象 temp1.revoke(); //再次访问代理对象 temp1.proxy.a //未捕获的错误: TypeError: Cannot perform 'get' on a proxy that has been revoked</pre> <p>弄清楚了具体的原理后,下面举例一个应用场景。<br> 假设某个需要对外暴露的对象上有你不希望被别人访问的属性,就可以找代理对象作替身,在外部访问代理对象的属性时,针对不想被别人访问的属性返回空值或者报错:</p> <pre class="brush:php;toolbar:false">//目标对象 var target = { msg: &quot;我是鲜嫩的美少女!&quot;, secret: &quot;其实我是800岁的老太婆!&quot; //不想被别人访问的属性 }; //创建代理对象 var proxy = new Proxy( target , { get(target, property) { //如果访问 secret 就报错 if (property == &quot;secret&quot;) throw new Error(&quot;不允许访问属性 secret!&quot;); return target[property]; } }); //访问 msg 属性 proxy.msg //← 我是鲜嫩的美少女! //访问 secret 属性 proxy.secret //未捕获的错误: 不允许访问属性 secret!</pre> <p>在上面的例子中,我针对对 secret 属性的访问进行了报错,守护住了“美少女”的秘密,让我们歌颂 Proxy 的伟大!<br> 只不过,Proxy 只是在程序逻辑上进行了接管,上帝视角的控制台依然能打印代理对象完整的内容,真是遗憾……(不不不,这挺好的!)</p> <pre class="brush:php;toolbar:false">proxy//控制台: Proxy {msg: '我是鲜嫩的美少女!', secret: '其实我是800岁的老太婆!'}</pre> <p>以下是关于 Proxy 的一些细节问题:</p> <ul> <li> <mark>Proxy 在处理属性名的时候会把除 Symbol 类型外的所有属性名都转化成字符串</mark>,所以处理函数在判断属性名时需要尤其注意。</li> <li>对代理对象的任何操作都会被拦截,一旦代理对象被创建就没有办法再修改它本身。</li> <li>Proxy 的代理是非常底层的,在没有主动暴露原始目标对象的情况下,没有任何办法越过代理对象访问目标对象(在控制台搞骚操作除外)。</li> <li>Proxy 代理的目标只能是对象,不能是 JavaScript 中的原始类型。</li> </ul> <h1>Reflect</h1> <p>学过其他语言的人看到 Reflect 这个词可能会首先联想到“反射”这个概念,但 JavaScript 由于语言特性是不需要反射的,所以这里的 Reflect 其实和反射无关,是 JavaScript 给 Proxy 配套的一系列函数。<br> Reflect 在 JS 环境里是一个全局对象,包含了与 Proxy 各种陷阱配套的函数。</p> <pre class="brush:php;toolbar:false">Reflect: Object Reflect → { apply: ƒ apply(), construct: ƒ construct(), defineProperty: ƒ defineProperty(), deleteProperty: ƒ deleteProperty(), get: ƒ (), getOwnPropertyDescriptor: ƒ getOwnPropertyDescriptor(), getPrototypeOf: ƒ getPrototypeOf(), has: ƒ has(), isExtensible: ƒ isExtensible(), ownKeys: ƒ ownKeys(), preventExtensions: ƒ preventExtensions(), set: ƒ (), setPrototypeOf: ƒ setPrototypeOf(), Symbol(Symbol.toStringTag): &quot;Reflect&quot; }</pre> <p>可以看到,Reflect 上的所有函数都对应一个 Proxy 的陷阱。这些函数接受的参数,返回值的类型,都和 Proxy 上的别无二致,可以说 Reflect 就是 Proxy 拦截的那些操作的原本实现。</p> <p>那 Reflect 存在的意义是什么呢?<br> 上文提到过,Proxy 上某一些陷阱对处理函数的返回值有要求。如果想让代理对象能正常工作,那就不得不按照 Proxy 的要求去写处理函数。或许会有人觉得只要用 Object 提供的方法不就好了,然而不能这么想当然,因为某些陷阱要求的返回值和 Object 提供的方法拿到的返回值是不同的,而且有些陷阱还会有逻辑上的要求,和 Object 提供的方法的细节也有所出入。举个简单的例子:Proxy 的 defineProperty 陷阱要求的返回值是布尔类型,成功就是 true,失败就是 false。而 Object.defineProperty 在成功的时候会返回定义的对象,失败则会报错。如此应该能够看出为陷阱编写实现的难点,如果要求简单那自然是轻松,但是要求一旦复杂起来那真是想想都头大,大多数时候我们其实只想过滤掉一部分操作而已。Reflect 就是专门为了解决这个问题而提供的,因为 Reflect 里的函数都和 Proxy 的陷阱配套,返回值的类型也和 Proxy 要求的相同,所以如果我们要实现原本的功能,直接调用 Reflect 里对应的函数就好了。</p> <pre class="brush:php;toolbar:false">//需要代理的对象 var target = { get me() {return &quot;我是鲜嫩的美少女!&quot;} //定义 me 属性的 getter }; //创建代理对象 var proxy = new Proxy( target , { //拦截定义属性的操作 defineProperty(target, property, descriptor) { //如果定义的属性是 me 就返回 false 阻止 if (property == &quot;me&quot;) return false; //使用 Reflect 提供的函数实现原本的功能 return Reflect.defineProperty(target, property, descriptor); } }); //尝试重新定义 me 属性 Object.defineProperty(proxy , &quot;me&quot;, {value: &quot;我是800岁的老太婆!&quot;}) //未捕获的错误: TypeError: 'defineProperty' on proxy: trap returned falsish for property 'me' //尝试定义 age 属性 Object.defineProperty(proxy , &quot;age&quot;, {value: 17}) //← Proxy {age: 17} //使用 Reflect 提供的函数来定义属性 Reflect.defineProperty(proxy , &quot;me&quot;, {value: &quot;我是800岁的老太婆!&quot;}) //← false Reflect.defineProperty(proxy , &quot;age&quot;, {value: 17}) //← true</pre> <p>在上面的例子里,由于我很懒,所以我在接管定义属性功能的地方“偷工减料”用了 Reflect 提供的 defineProperty 函数。用 Object.defineProperty 在代理对象上定义 me 属性时报了错,表示失败,而定义 age 属性则成功完成了。可以看到,除了被报错的 me 属性,对其他属性的定义是可以成功完成的。我还使用 Reflect 提供的函数执行了同样的操作,可以看到 Reflect 也无法越过 Proxy 的代理,同时也显示出了 Reflect 和传统方法返回值的区别。</p> <p>虽然 Reflect 的好处很多,但是它也有一个问题:JS 全局上的 Reflect 对象是可以被修改的,可以替换掉里面的方法,甚至还能把 Reflect 删掉。</p> <pre class="brush:php;toolbar:false">//备份原本的 Reflect.get var originGet = Reflect.get; //修改 Reflect.get Reflect.get = function get(target ,property) { console.log(&quot;哈哈,你的 get 已经是我的形状了!&quot;); return originGet(target ,property); }; //调用 Reflect.get Reflect.get({a:1}, &quot;a&quot;) //控制台: 哈哈,你的 get 已经是我的形状了! //← 1 //删除 Reflect 变量 delete Reflect //← true //访问 Reflect 变量 Reflect //未捕获的错误: ReferenceError: Reflect is not defined</pre> <p>基于上面的演示,不难想到,可以通过修改 Reflect 以欺骗的方式越过 Proxy 的代理。所以如果你对安全性有要求,建议在使用 Reflect 时,第一时间将全局上的 Reflect 深度复制到你的闭包作用域并且只使用你的备份,或者将全局上的 Reflect 冻结并锁定引用。</p> <p>相关推荐:<a href="https://www.php.cn/course/list/17.html" target="_blank">javascript学习教程</a><br></p>

Das obige ist der detaillierte Inhalt vonLassen Sie uns über Proxy und Reflect in Javascript sprechen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen