Proxy
Proxy is used to modify the default behavior of certain operations, akin to metaprogramming, i.e., programming the language itself. It sets up an interception layer in front of the target object, requiring all external access to pass through this layer, allowing for filtering and rewriting of access. The term “Proxy” implies acting as an agent, handling certain operations on behalf of the target, and can be translated as “proxy handler.”
var obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
},
apply: function(target, thisBinding, args) {
return args[0];
},
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
},
construct: function(target, args) {
return {value: args[1]};
},
deleteProperty (target, key) {
invariant(key, 'delete');
delete target[key];
return true;
},
defineProperty (target, key, descriptor) {
return false;
},
getOwnPropertyDescriptor (target, key) {
if (key[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, key);
},
getPrototypeOf(target) {
return proto;
},
isExtensible: function(target) {
console.log("called");
return true;
},
ownKeys (target) {
return Reflect.ownKeys(target).filter(key => key[0] !== '_');
},
preventExtensions: function(target) {
return true;
},
setPrototypeOf (target, proto) {
throw new Error('Changing the prototype is forbidden');
}
});Proxy-Supported Interception Operations
Proxy supports the following 13 interception operations:
get(target, propKey, receiver): Intercepts property reads, e.g.,proxy.fooorproxy['foo'].set(target, propKey, value, receiver): Intercepts property writes, e.g.,proxy.foo = vorproxy['foo'] = v, returning a boolean.has(target, propKey): InterceptspropKey in proxy, returning a boolean.deleteProperty(target, propKey): Interceptsdelete proxy[propKey], returning a boolean.ownKeys(target): InterceptsObject.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy), andfor...inloops, returning an array of property names. UnlikeObject.keys(), it includes all own properties, not just enumerable ones.getOwnPropertyDescriptor(target, propKey): InterceptsObject.getOwnPropertyDescriptor(proxy, propKey), returning the property descriptor.defineProperty(target, propKey, propDesc): InterceptsObject.defineProperty(proxy, propKey, propDesc)andObject.defineProperties(proxy, propDescs), returning a boolean.preventExtensions(target): InterceptsObject.preventExtensions(proxy), returning a boolean.getPrototypeOf(target): InterceptsObject.getPrototypeOf(proxy), returning an object.isExtensible(target): InterceptsObject.isExtensible(proxy), returning a boolean.setPrototypeOf(target, proto): InterceptsObject.setPrototypeOf(proxy, proto), returning a boolean.apply(target, object, args): Intercepts function calls on a Proxy instance, e.g.,proxy(...args),proxy.call(object, ...args), orproxy.apply(...).construct(target, args): Intercepts constructor calls, e.g.,new proxy(...args).
Proxy.revocable()
Proxy.revocable() returns an object with a proxy property (a Proxy instance) and a revoke function to cancel the proxy. After calling revoke, accessing the proxy throws an error.
this Issue
While Proxy can intercept access to a target object, it is not a fully transparent proxy. Without interception, it cannot guarantee identical behavior to the target object, primarily because this inside the target object points to the Proxy instance.
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // trueWeb Service Client Proxy Interception
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl + '/' + propKey);
}
});
}Reflect
Reflect Concept
- Moves some internal
Objectmethods (e.g.,Object.defineProperty) to theReflectobject. Currently, some methods exist on bothObjectandReflect, but new methods will only be added toReflect, allowing access to internal language methods. - Modifies some
Objectmethod results for consistency. For example,Object.defineProperty(obj, name, desc)throws an error if a property cannot be defined, whileReflect.defineProperty(obj, name, desc)returnsfalse. - Converts
Objectoperations to function behavior. For instance,name in objanddelete obj[name]are imperative, whereasReflect.has(obj, name)andReflect.deleteProperty(obj, name)are functional. Reflectmethods correspond one-to-one withProxymethods, allowingProxyto callReflectmethods for default behavior, serving as a foundation for customization. Regardless of howProxyalters behavior,Reflectprovides the default.
// Old approach
Object.defineProperty(target, property, attributes);
// New approach
Reflect.defineProperty(target, property, attributes)
// Old approach
'assign' in Object // true
// New approach
Reflect.has(Object, 'assign') // true
// Old approach
Function.prototype.apply.call(Math.floor, undefined, [1.75]) // 1
// New approach
Reflect.apply(Math.floor, undefined, [1.75]) // 1Reflect Static Methods
Reflect.apply(target, thisArg, args)Reflect.construct(target, args)Reflect.get(target, name, receiver)Reflect.set(target, name, value, receiver)Reflect.defineProperty(target, name, desc)Reflect.deleteProperty(target, name)Reflect.has(target, name)Reflect.ownKeys(target)Reflect.isExtensible(target)Reflect.preventExtensions(target)Reflect.getOwnPropertyDescriptor(target, name)Reflect.getPrototypeOf(target)Reflect.setPrototypeOf(target, prototype)
Implementing the Observer Pattern with Proxy
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}Promise Objects
Basic Usage of Promises
Promise.resolve('foo')
// Equivalent to
new Promise(resolve => resolve('foo'))
const p = Promise.reject('Error occurred');
// Equivalent to
const p = new Promise((resolve, reject) => reject('Error occurred'))
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
const g = function* () {
try {
const foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
const it = generator();
function go(result) {
if (result.done) return result.value;
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);Promise Methods
Promise.try(): Handles exceptions.
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// nextPromise.prototype.then(): Adds callbacks for when the Promise state changes.Promise.prototype.catch(): Specifies the callback for errors.Promise.prototype.finally(): Specifies an operation to run regardless of the Promise’s final state.Promise.all(): Wraps multiple Promises into a new Promise, resolving only when all input Promises resolve.
// Create an array of Promises
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});Promise.race(): Wraps multiple Promises into a new Promise, adopting the state of the first Promise to settle (resolve or reject).Promise.allSettled(): Wraps multiple Promises, resolving when all input Promises settle (either fulfilled or rejected).Promise.any(): Wraps multiple Promises, resolving when any input Promise fulfills; rejects only if all input Promises reject.
Iterator and for…of Loop
An Iterator is a mechanism providing a unified access interface for various data structures. Any data structure with an Iterator interface can be traversed.
The traversal process is as follows:
- Create a pointer object pointing to the start of the data structure. The Iterator is essentially a pointer.
- The first call to the pointer’s
nextmethod moves it to the first member. - Subsequent calls to
nextmove the pointer to the next member. - Continue calling
nextuntil the pointer reaches the end.
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}Native data structures with Iterator interfaces:
- Array
- Map
- Set
- String
- TypedArray
- Function
argumentsobject - NodeList object
var str = new String("hi");
[...str] // ["h", "i"]
str[Symbol.iterator] = function() {
return {
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
[...str] // ["bye"]
str // "hi"
let obj = {
* [Symbol.iterator]() {
yield 'hello';
yield 'world';
}
};
for (let x of obj) {
console.log(x);
}Iterator Object’s return() and throw()
function readLinesSync(file) {
return {
[Symbol.iterator]() {
return {
next() {
return { done: false };
},
return() {
file.close();
return { done: true };
}
};
},
};
}for…of Loop
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
console.log(a); // 0 1 2 3
}
for (let a of arr) {
console.log(a); // a b c d
}Generator
Basic Usage
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
a.forEach(function (item) {
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
});
};
for (var f of flat(arr)){
console.log(f);
}
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]Before ES6, asynchronous programming typically used the following methods:
- Callback functions
- Event listeners
- Publish/subscribe
- Promise objects



