I got this question when I was testing a React component with Jest, and Jest yelled at me that it could not deal with the imported CSS module (which is managed by webpack). The problem was solved with object-identity-proxy.
Here’s the description of object-identity-proxy from their npm page:

An identity object using ES6 proxies. Useful for testing trivial webpack imports. For instance, you can tell Jest to mock this object as imported CSS modules; then all your className lookups on the imported styles object will be returned as-is.

So what is an ES6 proxy?
Here’s what MDN says:

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

Syntax

1
const p = new Proxy(target, handler)

Parameters

target:
A target object to wrap with Proxy. It can be any sort of object, including a native array, a function, or even another proxy.
handler:
An object whose properties are functions define the behavior of proxy p when an operation is performed on it.

Operations that could be trapped with handler

MDN listed all the handler object methods, i.e. fundamental operations that could be traps by handler:

  • handler.getPrototypeOf()
    A trap for Object.getPrototypeOf.
  • handler.setPrototypeOf()
    A trap for Object.setPrototypeOf.
  • handler.isExtensible()
    A trap for Object.isExtensible.
  • handler.preventExtensions()
    A trap for Object.preventExtensions.
  • handler.getOwnPropertyDescriptor()
    A trap for Object.getOwnPropertyDescriptor.
  • handler.defineProperty()
    A trap for Object.defineProperty.
  • handler.has()
    A trap for the in operator.
  • handler.get()
    A trap for getting property values.
  • handler.set()
    A trap for setting property values.
  • handler.deleteProperty()
    A trap for the delete operator.
  • handler.ownKeys()
    A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
  • handler.apply()
    A trap for a function call.
  • handler.construct()
    A trap for the new operator.

Now check the object-identity-proxy

Here’s the code I found in node_modules/identity-obj-proxy/src/index.js:

1
2
3
4
5
6
7
8
idObj = new Proxy({}, {
get: function getter(target, key) {
if (key === '__esModule') {
return false;
}
return key;
}
});

So this is what they mean by ‘returned as-is’!

Write my first two simple proxies

Now I’ll write two simple proxies to practice.

Default Dictionary

1
2
3
4
5
6
7
8
9
10
11
const myDictionary={dog: "woof", cat:"meow", duck:"quack"};
const myDefaultDictionary = new Proxy(myDictionary, {
get: function getter(target, key) {
return key in target?
target[key]:"hello";
},
set: function setter(obj, prop, value) {
if(value.length < 4) obj[prop]=value+'...';
else obj[prop]=value;
}
})

Now I can get and set the animal sounds:

1
2
3
4
5
6
7
console.log(myDefaultDictionary.cat); //'meow'
console.log(myDefaultDictionary.anran); //'hello'

myDefaultDictionary.anran = 'hi';
console.log(myDefaultDictionary.anran); //'hi...'
myDefaultDictionary['yihao']='yee hee';
console.log(myDefaultDictionary.yihao); //'yee hee'

Check on argument

1
2
3
4
5
6
7
8
9
function getALuckyNumber(name) {
console.log(`${name}'s lucky number is ${Math.floor(Math.random() * 30)}`);
}

const getLuckyNumberAnranMachine = new Proxy(getALuckyNumber,
{apply: function(target, thisArg, argList)
{if(argList[0].toLowerCase() === 'anran') console.log(`anran, how many times have I tell you it's 18?`);
else target(argList[0]);}}
);

Now I always get a fixed lucky number (and get told off when I ask it):

1
2
3
getLuckyNumberAnranMachine("yihao"); // yihao's lucky number is 12
getLuckyNumberAnranMachine("judy"); // judy's lucky number is 3
getLuckyNumberAnranMachine("anran"); // anran, how many times have I tell you it's 18?

A nice extending constructor example by MDN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function extend(sup, base) { 
var descriptor = Object.getOwnPropertyDescriptor(
base.prototype, 'constructor'
);
base.prototype = Object.create(sup.prototype);
var handler = {
construct: function(target, args) {
var obj = Object.create(base.prototype);
this.apply(target, obj, args);
return obj;
},
apply: function(target, that, args) {
sup.apply(that, args);
base.apply(that, args);
}
};
var proxy = new Proxy(base, handler);
descriptor.value = proxy;
Object.defineProperty(base.prototype, 'constructor', descriptor);
return proxy;
}

var Person = function(name) {
this.name = name;
};

var Boy = extend(Person, function(name, age) {
this.age = age;
});

Boy.prototype.gender = 'M';

var Peter = new Boy('Peter', 13);

console.log(Peter.gender); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13

Next Step

Haven’t fully understand the MDN extending constructor example yet:
What is Object.getOwnPropertyDescriptor? How to best understand what is proptype in JavaScript?