Skip to content

Commit a674d60

Browse files
authored
Merge #22: Reflect mutations before invoking the callback
2 parents 8be808a + fc58866 commit a674d60

File tree

3 files changed

+197
-133
lines changed

3 files changed

+197
-133
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ before_script:
66
- 'export PATH=$PWD/node_modules/.bin:$PATH'
77
node_js: 6
88
script:
9-
- xvfb-run npm test
10-
- xvfb-run npm run bench
9+
- npm run test
10+
- npm run bench

src/jsonpatcherproxy.js

Lines changed: 61 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ const JSONPatcherProxy = (function() {
3535
* @param {Object} obj the object you need to find its path
3636
*/
3737
function findObjectPath(instance, obj) {
38-
var pathComponents = [];
39-
var parentAndPath = instance.parenthoodMap.get(obj);
38+
const pathComponents = [];
39+
let parentAndPath = instance.parenthoodMap.get(obj);
4040
while (parentAndPath && parentAndPath.path) {
4141
// because we're walking up-tree, we need to use the array as a stack
4242
pathComponents.unshift(parentAndPath.path);
@@ -59,17 +59,17 @@ const JSONPatcherProxy = (function() {
5959
function setTrap(instance, target, key, newValue) {
6060
const parentPath = findObjectPath(instance, target);
6161

62-
var destinationPropKey = parentPath + '/' + escapePathComponent(key);
63-
{
64-
if (instance.proxifiedObjectsMap.has(newValue)) {
65-
var newValueOriginalObject = instance.proxifiedObjectsMap.get(newValue);
62+
const destinationPropKey = parentPath + '/' + escapePathComponent(key);
6663

67-
instance.parenthoodMap.set(newValueOriginalObject.originalObject, {
68-
parent: target,
69-
path: key
70-
});
71-
}
72-
/*
64+
if (instance.proxifiedObjectsMap.has(newValue)) {
65+
const newValueOriginalObject = instance.proxifiedObjectsMap.get(newValue);
66+
67+
instance.parenthoodMap.set(newValueOriginalObject.originalObject, {
68+
parent: target,
69+
path: key
70+
});
71+
}
72+
/*
7373
mark already proxified values as inherited.
7474
rationale: proxy.arr.shift()
7575
will emit
@@ -79,22 +79,22 @@ const JSONPatcherProxy = (function() {
7979
by default, the second operation would revoke the proxy, and this renders arr revoked.
8080
That's why we need to remember the proxies that are inherited.
8181
*/
82-
const revokableInstance = instance.proxifiedObjectsMap.get(newValue);
83-
/*
84-
Why do we need to check instance.isProxifyingTreeNow?
82+
const revokableInstance = instance.proxifiedObjectsMap.get(newValue);
83+
/*
84+
Why do we need to check instance.isProxifyingTreeNow?
8585
86-
We need to make sure we mark revokables as inherited ONLY when we're observing,
87-
because throughout the first proxification, a sub-object is proxified and then assigned to
88-
its parent object. This assignment of a pre-proxified object can fool us into thinking
89-
that it's a proxified object moved around, while in fact it's the first assignment ever.
86+
We need to make sure we mark revokables as inherited ONLY when we're observing,
87+
because throughout the first proxification, a sub-object is proxified and then assigned to
88+
its parent object. This assignment of a pre-proxified object can fool us into thinking
89+
that it's a proxified object moved around, while in fact it's the first assignment ever.
9090
91-
Checking isProxifyingTreeNow ensures this is not happening in the first proxification,
92-
but in fact is is a proxified object moved around the tree
93-
*/
94-
if (revokableInstance && !instance.isProxifyingTreeNow) {
95-
revokableInstance.inherited = true;
96-
}
91+
Checking isProxifyingTreeNow ensures this is not happening in the first proxification,
92+
but in fact is is a proxified object moved around the tree
93+
*/
94+
if (revokableInstance && !instance.isProxifyingTreeNow) {
95+
revokableInstance.inherited = true;
9796
}
97+
9898
// if the new value is an object, make sure to watch it
9999
if (
100100
newValue &&
@@ -107,70 +107,46 @@ const JSONPatcherProxy = (function() {
107107
});
108108
newValue = instance._proxifyObjectTreeRecursively(target, newValue, key);
109109
}
110+
// let's start with this operation, and may or may not update it later
111+
const operation = {
112+
op: 'remove',
113+
path: destinationPropKey
114+
};
110115
if (typeof newValue == 'undefined') {
111-
if (target.hasOwnProperty(key)) {
116+
// applying De Morgan's laws would be a tad faster, but less readable
117+
if (!Array.isArray(target) && !target.hasOwnProperty(key)) {
118+
// `undefined` is being set to an already undefined value, keep silent
119+
return Reflect.set(target, key, newValue);
120+
} else {
112121
// when array element is set to `undefined`, should generate replace to `null`
113122
if (Array.isArray(target)) {
114-
//undefined array elements are JSON.stringified to `null`
115-
instance.defaultCallback({
116-
op: 'replace',
117-
path: destinationPropKey,
118-
value: null
119-
});
120-
} else {
121-
instance.defaultCallback({
122-
op: 'remove',
123-
path: destinationPropKey
124-
});
123+
// undefined array elements are JSON.stringified to `null`
124+
(operation.op = 'replace'), (operation.value = null);
125125
}
126126
const oldValue = instance.proxifiedObjectsMap.get(target[key]);
127127
// was the deleted a proxified object?
128-
if(oldValue) {
128+
if (oldValue) {
129129
instance.parenthoodMap.delete(target[key]);
130130
instance.disableTrapsForProxy(oldValue);
131131
instance.proxifiedObjectsMap.delete(oldValue);
132132
}
133-
return Reflect.set(target, key, newValue);
134-
} else if (!Array.isArray(target)) {
133+
}
134+
} else {
135+
if (Array.isArray(target) && !Number.isInteger(+key.toString())) {
136+
/* array props (as opposed to indices) don't emit any patches, to avoid needless `length` patches */
135137
return Reflect.set(target, key, newValue);
136138
}
137-
}
138-
/* array props don't emit any patches, to avoid needless `length` patches */
139-
if (Array.isArray(target) && !Number.isInteger(+key.toString())) {
140-
return Reflect.set(target, key, newValue);
141-
}
142-
if (target.hasOwnProperty(key)) {
143-
if (typeof target[key] == 'undefined') {
144-
if (Array.isArray(target)) {
145-
instance.defaultCallback({
146-
op: 'replace',
147-
path: destinationPropKey,
148-
value: newValue
149-
});
150-
} else {
151-
instance.defaultCallback({
152-
op: 'add',
153-
path: destinationPropKey,
154-
value: newValue
155-
});
139+
operation.op = 'add';
140+
if (target.hasOwnProperty(key)) {
141+
if (typeof target[key] !== 'undefined' || Array.isArray(target)) {
142+
operation.op = 'replace'; // setting `undefined` array elements is a `replace` op
156143
}
157-
return Reflect.set(target, key, newValue);
158-
} else {
159-
instance.defaultCallback({
160-
op: 'replace',
161-
path: destinationPropKey,
162-
value: newValue
163-
});
164-
return Reflect.set(target, key, newValue);
165144
}
166-
} else {
167-
instance.defaultCallback({
168-
op: 'add',
169-
path: destinationPropKey,
170-
value: newValue
171-
});
172-
return Reflect.set(target, key, newValue);
145+
operation.value = newValue;
173146
}
147+
const reflectionResult = Reflect.set(target, key, newValue);
148+
instance.defaultCallback(operation);
149+
return reflectionResult;
174150
}
175151
/**
176152
* A callback to be used as th proxy delete trap callback.
@@ -182,13 +158,8 @@ const JSONPatcherProxy = (function() {
182158
function deleteTrap(instance, target, key) {
183159
if (typeof target[key] !== 'undefined') {
184160
const parentPath = findObjectPath(instance, target);
185-
186161
const destinationPropKey = parentPath + '/' + escapePathComponent(key);
187162

188-
instance.defaultCallback({
189-
op: 'remove',
190-
path: destinationPropKey
191-
});
192163
const revokableProxyInstance = instance.proxifiedObjectsMap.get(
193164
target[key]
194165
);
@@ -197,7 +168,7 @@ const JSONPatcherProxy = (function() {
197168
if (revokableProxyInstance.inherited) {
198169
/*
199170
this is an inherited proxy (an already proxified object that was moved around),
200-
we shouldn't revoke it, because even though it was removed from path1, it is indeed used in path2.
171+
we shouldn't revoke it, because even though it was removed from path1, it is still used in path2.
201172
And we know that because we mark moved proxies with `inherited` flag when we move them
202173
203174
it is a good idea to remove this flag if we come across it here, in deleteProperty trap.
@@ -210,8 +181,15 @@ const JSONPatcherProxy = (function() {
210181
instance.proxifiedObjectsMap.delete(target[key]);
211182
}
212183
}
184+
const reflectionResult = Reflect.deleteProperty(target, key);
185+
186+
instance.defaultCallback({
187+
op: 'remove',
188+
path: destinationPropKey
189+
});
190+
191+
return reflectionResult;
213192
}
214-
return Reflect.deleteProperty(target, key);
215193
}
216194
/* pre-define resume and pause functions to enhance constructors performance */
217195
function resume() {
@@ -263,11 +241,10 @@ const JSONPatcherProxy = (function() {
263241
if (!obj) {
264242
return obj;
265243
}
266-
const instance = this;
267244
const traps = {
268245
set: (target, key, value, receiver) =>
269-
setTrap(instance, target, key, value, receiver),
270-
deleteProperty: (target, key) => deleteTrap(instance, target, key)
246+
setTrap(this, target, key, value, receiver),
247+
deleteProperty: (target, key) => deleteTrap(this, target, key)
271248
};
272249
const revocableInstance = Proxy.revocable(obj, traps);
273250
// cache traps object to disable them later.

0 commit comments

Comments
 (0)