git ssb

1+

Matt McKegg / mutant



Commit c4f4b8e26329eb68faa7079102acbbffaf9679ca

computed: optimise memory usage/allocation using an internal prototype

Matt McKegg committed on 9/5/2016, 6:25:44 AM
Parent: 29ead56fd386f4558cfde5e753602038666b4070

Files changed

computed.jschanged
computed.jsView
@@ -12,173 +12,182 @@
1212
1313 computed.NO_CHANGE = {}
1414
1515 function computed (observables, lambda, opts) {
16+ var instance = new ProtoComputed(observables, lambda, opts)
17+ return instance.MutantComputed.bind(instance)
18+}
19+
20+// optimise memory usage
21+function ProtoComputed (observables, lambda, opts) {
1622 if (!Array.isArray(observables)) {
1723 observables = [observables]
1824 }
25+ this.values = []
26+ this.releases = []
27+ this.computedValue = []
28+ this.inner = null
29+ this.updating = false
30+ this.live = false
31+ this.lazy = false
32+ this.initialized = false
33+ this.listeners = []
34+ this.observables = observables
35+ this.lambda = lambda
36+ this.opts = opts
37+ this.boundOnUpdate = this.onUpdate.bind(this)
38+ this.boundOnInnerUpdate = this.onInnerUpdate.bind(this)
39+ this.boundUpdateNow = this.updateNow.bind(this)
40+}
1941
20- var values = []
21- var releases = []
22- var computedValue = null
23-
24- var inner = null
25- var releaseInner = null
26- var updating = false
27-
28- var live = false
29- var lazy = false
30- var initialized = false
31- var listeners = []
32-
33- var result = function (listener) {
42+ProtoComputed.prototype = {
43+ MutantComputed: function (listener) {
3444 if (!listener) {
35- return getValue()
45+ return this.getValue()
3646 }
3747
3848 if (typeof listener !== 'function') {
3949 throw new Error('Listeners must be functions.')
4050 }
4151
42- listeners.push(listener)
43- listen()
52+ this.listeners.push(listener)
53+ this.listen()
4454
45- return function remove () {
46- for (var i = 0, len = listeners.length; i < len; i++) {
47- if (listeners[i] === listener) {
48- listeners.splice(i, 1)
49- break
50- }
55+ return this.removeListener.bind(this, listener)
56+ },
57+ removeListener: function (listener) {
58+ for (var i = 0, len = this.listeners.length; i < len; i++) {
59+ if (this.listeners[i] === listener) {
60+ this.listeners.splice(i, 1)
61+ break
5162 }
52- if (!listeners.length) {
53- unlisten()
54- }
5563 }
56- }
57-
58- return result
59-
60- // scoped
61-
62- function listen () {
63- if (!live) {
64- for (var i = 0, len = observables.length; i < len; i++) {
65- if (isObservable(observables[i])) {
66- releases.push(observables[i](onUpdate))
64+ if (!this.listeners.length) {
65+ this.unlisten()
66+ }
67+ },
68+ listen: function () {
69+ if (!this.live) {
70+ for (var i = 0, len = this.observables.length; i < len; i++) {
71+ if (isObservable(this.observables[i])) {
72+ this.releases.push(this.observables[i](this.boundOnUpdate))
6773 }
6874 }
69- if (inner) {
70- releaseInner = inner(onInnerUpdate)
75+ if (this.inner) {
76+ this.releaseInner = this.inner(this.boundOnInnerUpdate)
7177 }
72- live = true
73- lazy = true
78+ this.live = true
79+ this.lazy = true
7480 }
75- }
81+ },
82+ unlisten: function () {
83+ if (this.live) {
84+ this.live = false
7685
77- function unlisten () {
78- if (live) {
79- live = false
80-
81- if (releaseInner) {
82- releaseInner()
83- releaseInner = null
86+ if (this.releaseInner) {
87+ this.releaseInner()
88+ this.releaseInner = null
8489 }
8590
86- while (releases.length) {
87- releases.pop()()
91+ while (this.releases.length) {
92+ this.releases.pop()()
8893 }
8994 }
90- }
91-
92- function update () {
95+ },
96+ update: function () {
9397 var changed = false
94- for (var i = 0, len = observables.length; i < len; i++) {
95- var newValue = resolve(observables[i])
96- if (newValue !== values[i] || isMutable(newValue)) {
98+ for (var i = 0, len = this.observables.length; i < len; i++) {
99+ var newValue = resolve(this.observables[i])
100+ if (newValue !== this.values[i] || this.isMutable(newValue)) {
97101 changed = true
98- values[i] = newValue
102+ this.values[i] = newValue
99103 }
100104 }
101105
102- if (changed || !initialized) {
103- initialized = true
104- var newComputedValue = lambda.apply(null, values)
106+ if (changed || !this.initialized) {
107+ this.initialized = true
108+ var newComputedValue = this.lambda.apply(null, this.values)
105109
106110 if (newComputedValue === computed.NO_CHANGE) {
107111 return false
108112 }
109113
110- if (newComputedValue !== computedValue || (isMutable(newComputedValue) && !isObservable(newComputedValue))) {
111- if (releaseInner) {
112- releaseInner()
113- inner = releaseInner = null
114+ if (newComputedValue !== this.computedValue || (this.isMutable(newComputedValue) && !isObservable(newComputedValue))) {
115+ if (this.releaseInner) {
116+ this.releaseInner()
117+ this.inner = this.releaseInner = null
114118 }
115119
116120 if (isObservable(newComputedValue)) {
117121 // handle returning observable from computed
118- computedValue = newComputedValue()
119- inner = newComputedValue
120- if (live) {
121- releaseInner = inner(onInnerUpdate)
122+ this.computedValue = newComputedValue()
123+ this.inner = newComputedValue
124+ if (this.live) {
125+ this.releaseInner = this.inner(this.boundOnInnerUpdate)
122126 }
123127 } else {
124- computedValue = newComputedValue
128+ this.computedValue = newComputedValue
125129 }
126130 return true
127131 }
128132 }
129133 return false
130- }
131-
132- function onInnerUpdate (value) {
133- if (value !== computedValue || isMutable(computedValue)) {
134- computedValue = value
135- broadcast(listeners, computedValue)
136- }
137- }
138-
139- function onUpdate () {
140- if (opts && opts.nextTick) {
141- if (!updating) {
142- updating = true
143- setImmediate(updateNow)
134+ },
135+ onUpdate: function () {
136+ if (this.opts && this.opts.nextTick) {
137+ if (!this.updating) {
138+ this.updating = true
139+ setImmediate(this.boundUpdateNow)
144140 }
145141 } else {
146- updateNow()
142+ this.updateNow()
147143 }
148- }
149-
150- function updateNow () {
151- updating = false
152- if (update()) {
153- broadcast(listeners, computedValue)
144+ },
145+ onInnerUpdate: function (value) {
146+ if (value !== this.computedValue || this.isMutable(this.computedValue)) {
147+ this.computedValue = value
148+ this.broadcast()
154149 }
155- }
156-
157- function getValue () {
158- if (!live || lazy) {
159- lazy = false
160- update()
150+ },
151+ updateNow: function () {
152+ this.updating = false
153+ if (this.update()) {
154+ this.broadcast()
161155 }
162- return computedValue
163- }
164-
165- function isMutable (value) {
166- if (opts && opts.immutableTypes && opts.immutableTypes.some(type => value instanceof type)) {
156+ },
157+ getValue: function () {
158+ if (!this.live || this.lazy) {
159+ this.lazy = false
160+ this.update()
161+ }
162+ return this.computedValue
163+ },
164+ isMutable: function (value) {
165+ if (this.opts && this.opts.immutableTypes && isInstanceOfAny(value, this.opts.immutableTypes)) {
167166 return false
168167 } else {
169168 return isReferenceType(value)
170169 }
170+ },
171+ broadcast: function () {
172+ // cache listeners in case modified during broadcast
173+ var listeners = this.listeners.slice(0)
174+ for (var i = 0, len = listeners.length; i < len; i++) {
175+ listeners[i](this.computedValue)
176+ }
171177 }
172178 }
173179
174180 function isReferenceType (value) {
175181 return typeof value === 'object' && value !== null
176182 }
177183
178-function broadcast (listeners, value) {
179- // cache listeners in case modified during broadcast
180- listeners = listeners.slice(0)
181- for (var i = 0, len = listeners.length; i < len; i++) {
182- listeners[i](value)
184+function isInstanceOfAny (object, types) {
185+ var result = false
186+ for (var i = 0; i < types.length; i++) {
187+ if (object instanceof types[i]) {
188+ result = true
189+ break
190+ }
183191 }
192+ return result
184193 }

Built with git-ssb-web