The ES6 In Depth series of Jason Orendorf’s publications is dedicated to ES6, which was added to the JavaScript programming language in 6 iterations of ECMAScript.
Today we will do such things:

')
A bit difficult for the first example. I will explain in more detail later, for now let's see what kind of object we created.
> obj.count = 1; setting count! > ++obj.count; getting count! setting count! 2
What's going on here?We intercept access to the properties of this object.
We overload the operator “.”
How does this happen?The coolest thing in computing is called virtualization. This rather general purpose technique is used to do amazing things. Here is how it works.
1. Take any image.

Photo:
Martin Nikolaj Bech2. We draw a line around this image.

3. Now we replace everything inside this area or outside with something completely unexpected. There is only one rule: the backward compatibility rule. The substitution should be such that no one notices the change.

Photo:
Beverley GoodwinYou are probably already familiar with similar tricks from classic films about computer science such as The Truman Show or The Matrix, in which the character is inside a certain area and the surrounding world is replaced by the complex illusion of normality.
Perhaps to meet the requirement of the backward compatibility rule, your substitution should be carefully considered. But the main trick is to draw the correct line.
By line is meant the boundary of the API - interface. Interfaces describe the interaction of two parts of a code. So, if the system already has the described interface, we can assume that the line is drawn. You know that you can replace any part without affecting the other.
When the interface does not exist, it must be invented. The coolest hacks in programming have been to draw the boundary of the API where there is none, and embed this interface into the existing one, making great efforts.
Virtualization ,
virtual memory ,
Docker ,
Vagrind ,
rr - all of these projects, in varying degrees, are involved in launching and managing new and quite often unexpected interfaces in the existing system. In some cases, it took years, new features of operating systems, and even hardware, so that all this could work as it should.
The best tricks with virtualization bring a new understanding of what should be virtualized. To write to any API, there must be an understanding. Once you understand, you can do amazing things.
ES6 provides virtualization support for its underlying object concept.
What is an object?
Photo:
Joe deSousaNo, really. Wait a minute. Think it over. Scroll further as you know the answer.
For me, this is a very difficult question, and I have never heard an answer that could satisfy me sufficiently.
Is amazing? Defining fundamental things is always difficult. Take a look at at least some of the first definitions in
Euclid's Elements. The ECMAScript language specification has fallen into a good company, so it can afford to define an object as “a member of type Object”.
Further in this specification it is said that "the object is a collection of properties". Not so bad if you need a definition right now. We will return to this question later.
As I said before,
to write an API to something, there must be an understanding. I promise: when we deal with all this, we will better understand the objects and we can do incredible things with them.
Let's follow in the footsteps of the ECMAScript standards committee and see what it would cost us to define an API for JavaScript objects. What kind of methods do we need? What can objects do?
This is somewhat dependent on the object. Objects of DOM elements can do one thing, AudioNode objects can do another. But some fundamental abilities are common.
• Objects have properties. Property values can be received, set, deleted, etc.
• Objects have prototypes. This is how JavaScript inheritance works.
• Some objects are functions or constructors and can be called.
Almost everything that JavaScript programs do with objects happens by using properties, prototypes, and functions. Even the special behavior of an Element or AudioNode object is achieved by simply invoking methods that are just inherited properties of a function.
So, when the ECMAScript standards committee defined a set of 14 internal methods that represent a common interface for all objects, no one was surprised that they eventually decided to focus on these three fundamental things.
A complete list can be found
in Tables 5 and 6 of the ES6 standard . Here I will explain some of them. Strange double brackets [[]] make it clear that this is an internal method that is hidden from regular JavaScript code. You cannot call, delete or overwrite them in the same way as a normal method.
•
obj. [[Get]] (key, receiver) - get property value.
Called when the JS code executes: obj.prop or obj [key].
Obj is the object in which the search is performed; The receiver is the object from which the search for the value of this property was started. Sometimes you have to look in several objects. Obj can be an object or a chain of recevier prototypes.
•
obj. [[Set]] (key, value, receiver) - set the property of the object.
Called when the JS code executes: obj.prop = value or obj [key] = value.
In the assignment of the form obj.prop + = 2, the [[Get]] method is executed first, followed by the call to [[Set]. Same for ++ and -.
•
obj. [[HasProperty]] (key) - check for the existence of a property.
Called when the JS code executes: key in obj.
•
obj. [[Enumerate]] () is a list of obj.
Called when the JS code executes: for (key in obj) ...
Returns an iterator object. So the for-in loop gets the names of the properties of the objects.
•
obj. [[GetPrototypeOf]] ( ) - returns the obj prototype.
Called when the JS code executes: obj.
__proto__ or
Object.getPrototypeOf (obj ).
•
functionObj. [[Call]] (thisValue, arguments) - function call.
Called when the JS code executes: functionObj () or x.method ().
Optional. Not every object is a function.
•
constructorObj. [[Construct]] (arguments, newTarget) - constructor call.
Called when the JS code executes: new Date (2890, 6, 2), for example.
Optional. Not every object is a constructor.
The argument is involved in the process of creating subclasses. We will discuss this issue in the following articles.
Perhaps you can guess what the other 7 are.
In the entire ES6 standard, each line of syntax or built-in function that does something with objects is described in terms of 14 internal methods. ES6 has a clear border around the "brains" of the object. What a proxy allows you to do is replace the standard “brains” with an arbitrary code.
Recall that when we talk about redefining these internal methods, we are talking about redefining the internal syntax of obj.prop, built-in functions like
Object.keys () and the like.
ProxyES6 defines a new global constructor,
Proxy , which takes two arguments: a target object and a handler object. A simple example looks something like this:
var target = {}, handler = {}; var proxy = new Proxy(target, handler);
For now, let's drop the handler object and focus on how the proxy and target objects relate.
I can explain proxy behavior with one sentence. All internal proxy methods are sent to the target. That is, if something calls proxy. [[Enumerate]] (), the call will return target. [[Enumerate]] ().
Let's try. Do something that will call the proxy. [[Set]] ()
proxy.color = "pink";
OK, what happened? proxy. [[Set]] () should have called target. [[Set]] (), and this call should have created a new property in target. Is not it?
> target.color "pink"
The same happens for all internal methods. This proxy will behave almost the same as its target.
For the correctness of the created illusion, there are still limitations. You may find that the proxy! == target. And the proxy sometimes will not pass some type checks that the target will pass. Even if the target of a given proxy, for example, a DOM element, the proxy is not an instance of Element, so some things like document.body.appendChild (proxy) will cause a TypeError error.
Proxy HandlersLet's go back to the handler object. This is what makes proxy useful. The methods of the handler object can override any internal methods of the proxy object.
For example, if you want to intercept all attempts to set the properties of an object, this can be accomplished by defining the handler.set () method.
var target = {}; var handler = { set: function (target, key, value, receiver) { throw new Error("Please don't set properties on this object."); } }; var proxy = new Proxy(target, handler); > proxy.name = "angelina"; Error: Please don't set properties on this object.
A complete list of handler methods can be found
on the Proxy page in MDN . There are 14 methods that repeat the 14 internal methods defined in the ES6 standard.
All handler methods are optional. If the internal method is not intercepted by the handler, it is sent to the target, as you can see above.
Example: “Impossible” Autocomplete ObjectsNow we know enough about proxy to try to use them for something really strange, something that would be impossible without using it.
Exercise first. We describe the function Tree ():
> var tree = Tree(); > tree { } > tree.branch1.branch2.twig = "green"; > tree { branch1: { branch2: { twig: "green" } } } > tree.branch1.branch3.twig = "yellow"; { branch1: { branch2: { twig: "green" }, branch3: { twig: "yellow" }}}
Notice how the intermediate objects branch1, branch2 and branch3 are magically created on their own when necessary. Convenient, right? How could this theoretically work?
Until today, there was no way this could be achieved. But with proxy, these are just a few lines of code. You need to write them in tree. [[Get]] (). If you want, you can try to implement it yourself before continuing reading.

Photo:
Chiot's RunHere is my solution:
function Tree() { return new Proxy({}, handler); } var handler = { get: function (target, key, receiver) { if (!(key in target)) { target[key] = Tree();
Notice the call to Reflect.get () at the end. It turns out that there is a very important and common need for everyone to say “now the default behavior is required, namely the delegation of target'y” in the handler proxy methods. To do this, ES6 defines a new
Reflect object with 14 own methods that can be used just for this purpose.
Example: Read-only viewIt seems I could have developed a false impression that the proxy was easy to use. You need to perform another exercise to verify this statement.
This time the task will be more complicated: you need to implement the function readOnlyView (object), which accepts the object, and returns the proxy of this object, which behaves in the same way, except for the possibility to modify it. So, for example, he should behave something like this:
> var newMath = readOnlyView(Math); > newMath.min(54, 40); 40 > newMath.max = Math.min; Error: can't modify read-only view > delete newMath.sin; Error: can't modify read-only view
How could this be implemented?
First, it is necessary to intercept the call to all internal methods that would alter the target object when called. Their 5, here they are:
function NOPE() { throw new Error("can't modify read-only view"); } var handler = {
It works. We warn installation, change and so on in the read-only view.
What are the pitfalls in this scheme?
The biggest problem is that the [[Get]] method, like the others, can still return objects that can be modified later. So even if any object x is read-only, its x.prop property can be writable. This is a big problem!
To solve it, you need to describe the method handler.get ()
var handler = { ...
But this is not enough. The same code is needed for other methods, including
getPrototypeOf and getOwnPropertyDescriptor.
This raises additional problems. When a getter or method is invoked using such a proxy, the value of this passed to this method or getter will be the proxy itself. But, as we saw earlier, many different methods produce a type check that the proxy fails. It would be better to replace the target object for the proxy in this place. Do you know how to do this?
From this exercise, we can take out that creating a proxy is easy, but creating a proxy with intuitive behavior is quite difficult.
Sundries• What are proxy really good for?They are certainly useful when you want to log calls to objects. They are very convenient for debag. Testing frameworks can use them to
create mocks.They will also be useful if you need behavior that is slightly different from what an ordinary object does. For example, lazy filling objects.
I really don’t like what I’m going to say now, but one of the best ways to see what happens in the proxy code is to ... wrap the handler proxy object into another proxy object that displays every call to the handler object to the console.
Proxy can also be used if you want to restrict access to the object, as was done in the example with readOnlyView. This type of use is rare in application code, but Firefox uses proxy in order to implement
security boundaries between different domains. They are a key part of our security model.
• Proxies WeakMaps. In our readOnlyView example, we created a new proxy object every time an object was accessed. You can save more memory if you cache each created proxy in WeakMap, so no matter how many times an object is passed to readOnlyView, only one proxy will be created for it.
This is one of the cases that might encourage you to use WeakMap.
• Recalled proxies. ES6 also defines another function: Proxy.revocable (target, handler), which creates a proxy, as well as a call to new Proxy (target, handler), except that the proxy can be recalled later. (Proxy.revocable returns an object with the .proxy property and the .revoke method.) Once the proxy is revoked, it simply stops working, all its internal methods are reset.
• Object invariants. In some situations, ES6 requires the handler proxy methods to report results related to the state of the target object. This is done to ensure compliance with the rules of immutability in all objects, including the proxy. For example, a proxy cannot claim to be non-extensible if its target really is.
The immediate rules are too complex to be described in this article, but if you suddenly get an error message like the proxy, it is the cause. The best tool here is to change what the proxy says about itself. It is also possible to modify the target to reflect what the proxy reports.
So what is an object?I think when we left this question, the answer was: “An object is a collection of properties”.
I don’t like this definition very much, even if I omit the challenge and the prototypes. I find the word “collection” too generous, considering how poorly a proxy can be defined. His handler's methods can do anything (they can return random results).
The ECMAScript standards committee has expanded the scope of opportunities by defining what an object can do, standardizing its methods, and adding virtualization as a top-level feature that everyone can use.
Now objects can be anything.
Perhaps the most honest answer to the question “What is an object?” Is the 12 required internal methods as a definition. An object in JavaScript is something that has the ability to perform [[Get]], [[Set]], and so on ...
________________________________________
Have we deepened our understanding of what an object is? I'm not sure! Have we done unbelievable things? Definitely! We did things that previously could not be done in JavaScript.
Can I use proxy today?
Not! At least not on the Web! Only Firefox and Microsoft Edge support proxy, and the polyphile is of course not.
Using proxy in node.js or io.js requires the inclusion of the disabled (--harmony_proxies) and
harmony-reflect polyphiles option, as V8 implements an older version of the Proxy specification. (The previous version of this article contained errors on this score. Thanks to Mörre and Aaron Powell for correcting me in the comments.)
So feel free to experiment with the proxy! Create mirror rooms, where there will be thousands of copies of each object, all similar to each other to such an extent that it will be impossible to debug! Right now. So far, there is a small chance that a certain amount of your rash proxy code will end up in production.
The proxy was first implemented in 2010 by Andreas Galem with code review Blake Kaplan. Later they were completely reworked by the standards committee. Eddie Bruel implemented the new specification in 2012.
I implemented Reflect with Jeff Walden's code review. These changes will be in Firefox Nightly from this weekend - all with the exception of Reflect.enumerate (), which is not yet implemented.
Next time, let's talk about the most controversial feature in ES6 - classes, and who would have imagined it better than a person involved in its implementation in Firefox? So, please, do not miss the following article from Mozilla engineer Eric Faust, in which he will talk in detail about classes in ES6.