map.jsView |
---|
1 | 1 | var resolve = require('./resolve') |
2 | 2 | var LazyWatcher = require('./lib/lazy-watcher') |
3 | | -var isReferenceType = require('./lib/is-reference-type') |
| 3 | +var isSame = require('./lib/is-same') |
| 4 | +var addCollectionMethods = require('./lib/add-collection-methods') |
4 | 5 | |
5 | 6 | module.exports = Map |
6 | 7 | |
7 | 8 | function Map (obs, lambda, opts) { |
| 9 | + |
| 10 | + |
| 11 | + var comparer = opts && opts.comparer || null |
8 | 12 | var releases = [] |
| 13 | + var invalidateReleases = new global.WeakMap() |
9 | 14 | var binder = LazyWatcher(update, listen, unlisten) |
10 | 15 | |
11 | 16 | var lastValues = new global.Map() |
| 17 | + var rawSet = new global.Set() |
| 18 | + |
12 | 19 | var items = [] |
13 | 20 | |
14 | 21 | var raw = [] |
15 | 22 | var values = [] |
30 | 37 | } |
31 | 38 | return binder.addListener(listener) |
32 | 39 | } |
33 | 40 | |
34 | | - result.get = function (index) { |
35 | | - return raw[index] |
36 | | - } |
| 41 | + addCollectionMethods(result, raw, binder.checkUpdated) |
37 | 42 | |
38 | | - result.getLength = function (index) { |
39 | | - return raw.length |
40 | | - } |
41 | | - |
42 | | - result.includes = function (valueOrObs) { |
43 | | - return !!~raw.indexOf(valueOrObs) |
44 | | - } |
45 | | - |
46 | | - result.indexOf = function (valueOrObs) { |
47 | | - return raw.indexOf(valueOrObs) |
48 | | - } |
49 | | - |
50 | 43 | return result |
51 | 44 | |
52 | 45 | |
53 | 46 | |
55 | 48 | if (typeof obs === 'function') { |
56 | 49 | releases.push(obs(binder.onUpdate)) |
57 | 50 | } |
58 | 51 | rebindAll() |
| 52 | + |
| 53 | + if (opts && opts.onListen) { |
| 54 | + opts.onListen() |
| 55 | + } |
59 | 56 | } |
60 | 57 | |
61 | 58 | function unlisten () { |
62 | 59 | while (releases.length) { |
63 | 60 | releases.pop()() |
64 | 61 | } |
65 | 62 | rebindAll() |
66 | | - lastValues.clear() |
| 63 | + |
| 64 | + if (opts && opts.onUnlisten) { |
| 65 | + opts.onUnlisten() |
| 66 | + } |
67 | 67 | } |
68 | 68 | |
69 | 69 | function update () { |
70 | 70 | var changed = false |
79 | 79 | var item = get(obs, i) |
80 | 80 | var currentItem = items[i] |
81 | 81 | items[i] = item |
82 | 82 | |
83 | | - if (typeof item === 'object' || item !== currentItem) { |
| 83 | + if (!isSame(item, currentItem, comparer)) { |
84 | 84 | if (maxTime && Date.now() - startedAt > maxTime) { |
85 | 85 | queueUpdateItem(i) |
86 | 86 | } else { |
87 | 87 | updateItem(i) |
91 | 91 | } |
92 | 92 | |
93 | 93 | if (changed) { |
94 | 94 | |
95 | | - var oldLength = items.length |
| 95 | + var oldLength = raw.length |
| 96 | + var newLength = getLength(obs) |
96 | 97 | Array.from(lastValues.keys()).filter(notIncluded, obs).forEach(deleteEntry, lastValues) |
97 | | - items.length = getLength(obs) |
98 | | - values.length = items.length |
99 | | - raw.length = items.length |
100 | | - for (var index = items.length; index < oldLength; index++) { |
| 98 | + items.length = newLength |
| 99 | + values.length = newLength |
| 100 | + raw.length = newLength |
| 101 | + for (var index = newLength; index < oldLength; index++) { |
101 | 102 | rebind(index) |
102 | 103 | } |
| 104 | + Array.from(rawSet.values()).filter(notIncluded, raw).forEach(notifyRemoved) |
103 | 105 | } |
104 | 106 | |
105 | 107 | return changed |
106 | 108 | } |
124 | 126 | setImmediate(flushQueue) |
125 | 127 | } |
126 | 128 | } |
127 | 129 | |
| 130 | + function invalidateOn (item, obs) { |
| 131 | + if (!invalidateReleases.has(item)) { |
| 132 | + invalidateReleases.set(item, []) |
| 133 | + } |
| 134 | + invalidateReleases.get(item).push(obs(invalidate.bind(null, item))) |
| 135 | + } |
| 136 | + |
| 137 | + function addInvalidateCallback (item) { |
| 138 | + return invalidateOn.bind(null, item) |
| 139 | + } |
| 140 | + |
| 141 | + function notifyRemoved (item) { |
| 142 | + rawSet.delete(item) |
| 143 | + invalidateReleases.delete(item) |
| 144 | + if (opts && opts.onRemove) { |
| 145 | + opts.onRemove(item) |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + function invalidate (item) { |
| 150 | + var changed = false |
| 151 | + var length = getLength(obs) |
| 152 | + lastValues.delete(item) |
| 153 | + for (var i = 0; i < length; i++) { |
| 154 | + if (get(obs, i) === item) { |
| 155 | + updateItem(i) |
| 156 | + changed = true |
| 157 | + } |
| 158 | + } |
| 159 | + if (changed) { |
| 160 | + binder.broadcast() |
| 161 | + } |
| 162 | + } |
| 163 | + |
128 | 164 | function updateItem (i) { |
129 | 165 | var item = get(obs, i) |
130 | | - if (isReferenceType(item)) { |
131 | | - raw[i] = lambda(item) |
| 166 | + if (!lastValues.has(item) || !isSame(item, item, comparer)) { |
| 167 | + var newValue = lambda(item, addInvalidateCallback(item)) |
| 168 | + if (newValue !== raw[i]) { |
| 169 | + raw[i] = newValue |
| 170 | + } |
| 171 | + rawSet.add(newValue) |
| 172 | + lastValues.set(item, raw[i]) |
| 173 | + rebind(i) |
132 | 174 | } else { |
133 | | - if (!lastValues.has(item)) { |
134 | | - lastValues.set(item, lambda(item)) |
135 | | - } |
136 | 175 | raw[i] = lastValues.get(item) |
137 | 176 | } |
138 | 177 | values[i] = resolve(raw[i]) |
139 | | - rebind(i) |
140 | 178 | } |
141 | 179 | |
142 | 180 | function rebind (index) { |
143 | 181 | if (watches[index]) { |
159 | 197 | } |
160 | 198 | |
161 | 199 | function updateValue (obs, index) { |
162 | 200 | return obs(function (value) { |
163 | | - if (values[index] !== value || typeof value === 'object') { |
| 201 | + if (!isSame(values[index], value, comparer)) { |
164 | 202 | values[index] = value |
165 | 203 | binder.broadcast() |
166 | 204 | } |
167 | 205 | }) |