Skip to content

Commit adaca0b

Browse files
committed
allow the observation of symbol keyed and function valued props (except well known symbols)
1 parent 0283fea commit adaca0b

File tree

5 files changed

+477
-460
lines changed

5 files changed

+477
-460
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/builtIns/collections.js

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,24 @@ import {
44
} from '../reactionRunner'
55
import { proxyToRaw } from '../internals'
66

7-
const getPrototypeOf = Object.getPrototypeOf
87
const hasOwnProperty = Object.prototype.hasOwnProperty
98

109
const instrumentations = {
1110
has (key) {
1211
const target = proxyToRaw.get(this)
13-
const proto = getPrototypeOf(this)
12+
const proto = Reflect.getPrototypeOf(this)
1413
registerRunningReactionForOperation({ target, key, type: 'has' })
1514
return proto.has.apply(target, arguments)
1615
},
1716
get (key) {
1817
const target = proxyToRaw.get(this)
19-
const proto = getPrototypeOf(this)
18+
const proto = Reflect.getPrototypeOf(this)
2019
registerRunningReactionForOperation({ target, key, type: 'get' })
2120
return proto.get.apply(target, arguments)
2221
},
2322
add (key) {
2423
const target = proxyToRaw.get(this)
25-
const proto = getPrototypeOf(this)
24+
const proto = Reflect.getPrototypeOf(this)
2625
const hadKey = proto.has.call(target, key)
2726
// forward the operation before queueing reactions
2827
const result = proto.add.apply(target, arguments)
@@ -33,7 +32,7 @@ const instrumentations = {
3332
},
3433
set (key, value) {
3534
const target = proxyToRaw.get(this)
36-
const proto = getPrototypeOf(this)
35+
const proto = Reflect.getPrototypeOf(this)
3736
const hadKey = proto.has.call(target, key)
3837
const oldValue = proto.get.call(target, key)
3938
// forward the operation before queueing reactions
@@ -47,7 +46,7 @@ const instrumentations = {
4746
},
4847
delete (key) {
4948
const target = proxyToRaw.get(this)
50-
const proto = getPrototypeOf(this)
49+
const proto = Reflect.getPrototypeOf(this)
5150
const hadKey = proto.has.call(target, key)
5251
const oldValue = proto.get ? proto.get.call(target, key) : undefined
5352
// forward the operation before queueing reactions
@@ -59,7 +58,7 @@ const instrumentations = {
5958
},
6059
clear () {
6160
const target = proxyToRaw.get(this)
62-
const proto = getPrototypeOf(this)
61+
const proto = Reflect.getPrototypeOf(this)
6362
const hadItems = target.size !== 0
6463
const oldTarget = target instanceof Map ? new Map(target) : new Set(target)
6564
// forward the operation before queueing reactions
@@ -71,37 +70,37 @@ const instrumentations = {
7170
},
7271
forEach () {
7372
const target = proxyToRaw.get(this)
74-
const proto = getPrototypeOf(this)
73+
const proto = Reflect.getPrototypeOf(this)
7574
registerRunningReactionForOperation({ target, type: 'iterate' })
7675
return proto.forEach.apply(target, arguments)
7776
},
7877
keys () {
7978
const target = proxyToRaw.get(this)
80-
const proto = getPrototypeOf(this)
79+
const proto = Reflect.getPrototypeOf(this)
8180
registerRunningReactionForOperation({ target, type: 'iterate' })
8281
return proto.keys.apply(target, arguments)
8382
},
8483
values () {
8584
const target = proxyToRaw.get(this)
86-
const proto = getPrototypeOf(this)
85+
const proto = Reflect.getPrototypeOf(this)
8786
registerRunningReactionForOperation({ target, type: 'iterate' })
8887
return proto.values.apply(target, arguments)
8988
},
9089
entries () {
9190
const target = proxyToRaw.get(this)
92-
const proto = getPrototypeOf(this)
91+
const proto = Reflect.getPrototypeOf(this)
9392
registerRunningReactionForOperation({ target, type: 'iterate' })
9493
return proto.entries.apply(target, arguments)
9594
},
9695
[Symbol.iterator] () {
9796
const target = proxyToRaw.get(this)
98-
const proto = getPrototypeOf(this)
97+
const proto = Reflect.getPrototypeOf(this)
9998
registerRunningReactionForOperation({ target, type: 'iterate' })
10099
return proto[Symbol.iterator].apply(target, arguments)
101100
},
102101
get size () {
103102
const target = proxyToRaw.get(this)
104-
const proto = getPrototypeOf(this)
103+
const proto = Reflect.getPrototypeOf(this)
105104
registerRunningReactionForOperation({ target, type: 'iterate' })
106105
return Reflect.get(proto, 'size', target)
107106
}

src/handlers.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@ import {
77
} from './reactionRunner'
88

99
const hasOwnProperty = Object.prototype.hasOwnProperty
10+
const wellKnownSymbols = new Set(
11+
Object.getOwnPropertyNames(Symbol)
12+
.map(key => Symbol[key])
13+
.filter(value => typeof value === 'symbol')
14+
)
1015

1116
// intercept get operations on observables to know which reaction uses their properties
1217
function get (target, key, receiver) {
1318
const result = Reflect.get(target, key, receiver)
14-
// do not register (observable.prop -> reaction) pairs for these cases
15-
if (typeof key === 'symbol' || typeof result === 'function') {
19+
// do not register (observable.prop -> reaction) pairs for well known symbols
20+
// these symbols are frequently retrieved in low level JavaScript under the hood
21+
if (typeof key === 'symbol' && wellKnownSymbols.has(key)) {
1622
return result
1723
}
1824
// register and save (observable.prop -> runningReaction)
@@ -40,10 +46,6 @@ function get (target, key, receiver) {
4046

4147
function has (target, key) {
4248
const result = Reflect.has(target, key)
43-
// do not register (observable.prop -> reaction) pairs for these cases
44-
if (typeof key === 'symbol') {
45-
return result
46-
}
4749
// register and save (observable.prop -> runningReaction)
4850
registerRunningReactionForOperation({ target, key, type: 'has' })
4951
return result
@@ -66,13 +68,11 @@ function set (target, key, value, receiver) {
6668
const oldValue = target[key]
6769
// execute the set operation before running any reaction
6870
const result = Reflect.set(target, key, value, receiver)
69-
// do not queue reactions if it is a symbol keyed property
70-
// or the target of the operation is not the raw receiver
71+
// do not queue reactions if the target of the operation is not the raw receiver
7172
// (possible because of prototypal inheritance)
72-
if (typeof key === 'symbol' || target !== proxyToRaw.get(receiver)) {
73+
if (target !== proxyToRaw.get(receiver)) {
7374
return result
7475
}
75-
7676
// queue a reaction if it's a new property or its value changed
7777
if (!hadKey) {
7878
queueReactionsForOperation({ target, key, value, receiver, type: 'add' })
@@ -95,8 +95,8 @@ function deleteProperty (target, key) {
9595
const oldValue = target[key]
9696
// execute the delete operation before running any reaction
9797
const result = Reflect.deleteProperty(target, key)
98-
// only queue reactions for non symbol keyed property delete which resulted in an actual change
99-
if (typeof key !== 'symbol' && hadKey) {
98+
// only queue reactions for delete operations which resulted in an actual change
99+
if (hadKey) {
100100
queueReactionsForOperation({ target, key, oldValue, type: 'delete' })
101101
}
102102
return result

src/reactionRunner.js

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,75 +2,75 @@ import {
22
registerReactionForOperation,
33
getReactionsForOperation,
44
releaseReaction
5-
} from './store';
5+
} from './store'
66

77
// reactions can call each other and form a call stack
8-
const reactionStack = [];
9-
let isDebugging = false;
8+
const reactionStack = []
9+
let isDebugging = false
1010

11-
export function runAsReaction(reaction, fn, context, args) {
11+
export function runAsReaction (reaction, fn, context, args) {
1212
// do not build reactive relations, if the reaction is unobserved
1313
if (reaction.unobserved) {
14-
return Reflect.apply(fn, context, args);
14+
return Reflect.apply(fn, context, args)
1515
}
1616

1717
// only run the reaction if it is not already in the reaction stack
1818
// TODO: improve this to allow explicitly recursive reactions
1919
if (reactionStack.indexOf(reaction) === -1) {
2020
// release the (obj -> key -> reactions) connections
2121
// and reset the cleaner connections
22-
releaseReaction(reaction);
22+
releaseReaction(reaction)
2323

2424
try {
2525
// set the reaction as the currently running one
2626
// this is required so that we can create (observable.prop -> reaction) pairs in the get trap
27-
reactionStack.push(reaction);
28-
return Reflect.apply(fn, context, args);
27+
reactionStack.push(reaction)
28+
return Reflect.apply(fn, context, args)
2929
} finally {
3030
// always remove the currently running flag from the reaction when it stops execution
31-
reactionStack.pop();
31+
reactionStack.pop()
3232
}
3333
}
3434
}
3535

3636
// register the currently running reaction to be queued again on obj.key mutations
37-
export function registerRunningReactionForOperation(operation) {
37+
export function registerRunningReactionForOperation (operation) {
3838
// get the current reaction from the top of the stack
39-
const runningReaction = reactionStack[reactionStack.length - 1];
39+
const runningReaction = reactionStack[reactionStack.length - 1]
4040
if (runningReaction) {
41-
debugOperation(runningReaction, operation);
42-
registerReactionForOperation(runningReaction, operation);
41+
debugOperation(runningReaction, operation)
42+
registerReactionForOperation(runningReaction, operation)
4343
}
4444
}
4545

46-
export function queueReactionsForOperation(operation) {
46+
export function queueReactionsForOperation (operation) {
4747
// iterate and queue every reaction, which is triggered by obj.key mutation
48-
getReactionsForOperation(operation).forEach(queueReaction, operation);
48+
getReactionsForOperation(operation).forEach(queueReaction, operation)
4949
}
5050

51-
function queueReaction(reaction) {
52-
debugOperation(reaction, this);
51+
function queueReaction (reaction) {
52+
debugOperation(reaction, this)
5353
// queue the reaction for later execution or run it immediately
5454
if (typeof reaction.scheduler === 'function') {
55-
reaction.scheduler(reaction);
55+
reaction.scheduler(reaction)
5656
} else if (typeof reaction.scheduler === 'object') {
57-
reaction.scheduler.add(reaction);
57+
reaction.scheduler.add(reaction)
5858
} else {
59-
reaction();
59+
reaction()
6060
}
6161
}
6262

63-
function debugOperation(reaction, operation) {
63+
function debugOperation (reaction, operation) {
6464
if (reaction.debugger && !isDebugging) {
6565
try {
66-
isDebugging = true;
67-
reaction.debugger(operation);
66+
isDebugging = true
67+
reaction.debugger(operation)
6868
} finally {
69-
isDebugging = false;
69+
isDebugging = false
7070
}
7171
}
7272
}
7373

74-
export function hasRunningReaction() {
75-
return reactionStack.length > 0;
74+
export function hasRunningReaction () {
75+
return reactionStack.length > 0
7676
}

0 commit comments

Comments
 (0)