// Assigning to a non-existing property of a plain object defines that // property on that object, even if a proxy is on the proto chain. // Create an object that behaves just like obj except it throws (instead of // returning undefined) if you try to get a property that doesn't exist. function throwIfNoSuchProperty(obj) { return new Proxy(obj, { get(t, id) { if (id in t) return t[id]; throw new Error("no such handler method: " + id); } }); } // Use a touchy object as our proxy handler in this test. var hits = 0, savedDesc = undefined; var touchyHandler = throwIfNoSuchProperty({ set: undefined }); var target = {}; var proto = new Proxy(target, touchyHandler); var receiver = Object.create(proto); // This assignment `receiver.x = 2` results in a series of [[Set]] calls, // starting with: // // - receiver.[[Set]]() // - receiver is an ordinary object. // - This looks for an own property "x" on receiver. There is none. // - So it walks the prototype chain, doing a tail-call to: // - proto.[[Set]]() // - proto is a proxy. // - This does handler.[[Get]]("set") to look for a set trap // (that's why we need `set: undefined` on the handler, above) // - Since there's no "set" handler, it tail-calls: // - target.[[Set]]() // - ordinary // - no own property "x" // - tail call to: // - Object.prototype.[[Set]]() // - ordinary // - no own property "x" // - We're at the end of the line: there's nothing left on the proto chain. // - So at last we call: // - receiver.[[DefineOwnProperty]]() // - ordinary // - creates the property // // Got all that? Let's try it. // receiver.x = 2; assertEq(receiver.x, 2); var desc = Object.getOwnPropertyDescriptor(receiver, "x"); assertEq(desc.enumerable, true); assertEq(desc.configurable, true); assertEq(desc.writable, true); assertEq(desc.value, 2);