From f4ccf7ff2afd6151e094faa62e4fc60b8ffa41a0 Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Tue, 27 Sep 2016 09:17:26 +0900 Subject: [PATCH 1/3] Using "undefined" as default for listeners to make sure that the memory is not unnecessarily preallocated. --- lib/index.js | 10 ++++++---- test/index.js | 6 ++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index 1de15ea..a038bf4 100644 --- a/lib/index.js +++ b/lib/index.js @@ -17,8 +17,8 @@ module.exports = observable */ function observable (initialValue) { - var listeners = [] - var nextListeners = listeners + var listeners + var nextListeners var val = initialValue observableValue.subscribe = subscribe @@ -27,7 +27,9 @@ function observable (initialValue) { function observableValue (nextVal) { if (arguments.length > 0) { val = nextVal - next(val) + if (nextListeners !== undefined) { + next(val) + } } return val @@ -84,7 +86,7 @@ function observable (initialValue) { function canMutate () { if (nextListeners === listeners) { - nextListeners = slice(listeners) + nextListeners = listeners ? slice(listeners) : [] } } } diff --git a/test/index.js b/test/index.js index beffed6..d1fe932 100644 --- a/test/index.js +++ b/test/index.js @@ -76,3 +76,9 @@ test('repeated unsubscribtion should be okay', function (t) { unsubscribe() t.end() }) + +test('it should work without subscriber', function (t) { + var o = Observable(0) + o(1) + t.end() +}) From 1918e8307a52afb86336a7ba4cd05a7d008d1c70 Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Tue, 27 Sep 2016 09:25:47 +0900 Subject: [PATCH 2/3] Allowing listeners to be reduced to none and the memory of the array is dereferenced. --- lib/index.js | 8 ++++++++ test/index.js | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index a038bf4..85cd11a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -59,6 +59,12 @@ function observable (initialValue) { isSubscribed = false canMutate() + + if (nextListeners.length === 1) { + nextListeners + return + } + var index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } @@ -87,6 +93,8 @@ function observable (initialValue) { function canMutate () { if (nextListeners === listeners) { nextListeners = listeners ? slice(listeners) : [] + } else if (nextListeners === undefined) { + nextListeners = [] } } } diff --git a/test/index.js b/test/index.js index d1fe932..8b62d3d 100644 --- a/test/index.js +++ b/test/index.js @@ -5,6 +5,8 @@ var Observable = require('..') var test = require('tap').test +function noop () {} + /** * Tests */ @@ -71,9 +73,20 @@ test('should throw an exception if something else but a function is added as lis test('repeated unsubscribtion should be okay', function (t) { var o = Observable(0) - var unsubscribe = o.subscribe(function () {}) + var unsubscribe = o.subscribe(noop) + unsubscribe() unsubscribe() + t.end() +}) + +test('should work after listeners were drained and readded again', function (t) { + var o = Observable(0) + var unsubscribe = o.subscribe(noop) + o(1) unsubscribe() + o(2) + unsubscribe = o.subscribe(noop) + o(3) t.end() }) From 28ea2b090cf64b0c12f380c492f9389136579a36 Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Tue, 27 Sep 2016 09:45:09 +0900 Subject: [PATCH 3/3] Reduction of memory: don't create an Array for the first listener --- lib/index.js | 34 +++++++++++++++++---------- test/index.js | 63 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/lib/index.js b/lib/index.js index 85cd11a..43f5b0e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -49,7 +49,13 @@ function observable (initialValue) { var isSubscribed = true canMutate() - nextListeners.push(listener) + if (nextListeners === undefined) { + nextListeners = listener + } else if (isFunction(nextListeners)) { + nextListeners = [nextListeners, listener] + } else { + nextListeners.push(listener) + } return function unsubscribe () { if (!isSubscribed) { @@ -60,13 +66,15 @@ function observable (initialValue) { canMutate() - if (nextListeners.length === 1) { - nextListeners - return + if (nextListeners === listener) { + nextListeners = undefined + } else { + var index = nextListeners.indexOf(listener) + nextListeners.splice(index, 1) + if (nextListeners.length === 1) { + nextListeners = nextListeners[0] + } } - - var index = nextListeners.indexOf(listener) - nextListeners.splice(index, 1) } } @@ -78,7 +86,11 @@ function observable (initialValue) { function next (val) { listeners = nextListeners - foreach(call, listeners) + if (Array.isArray(listeners)) { + foreach(call, listeners) + } else { + listeners(val) + } function call (listener) { listener(val) @@ -91,10 +103,8 @@ function observable (initialValue) { */ function canMutate () { - if (nextListeners === listeners) { - nextListeners = listeners ? slice(listeners) : [] - } else if (nextListeners === undefined) { - nextListeners = [] + if (nextListeners !== undefined && nextListeners === listeners && !isFunction(nextListeners)) { + nextListeners = slice(listeners) } } } diff --git a/test/index.js b/test/index.js index 8b62d3d..446daa1 100644 --- a/test/index.js +++ b/test/index.js @@ -7,6 +7,21 @@ var test = require('tap').test function noop () {} +function recordOrder () { + var list = [] + return { + list: list, + record: function (name) { + return function (val) { + list.push({ + name: name, + val: val + }) + } + } + } +} + /** * Tests */ @@ -32,22 +47,14 @@ test('should pass new value to subscribers', function (t) { test('should allow to unsubscribe while listening', function (t) { t.plan(1) var o = Observable(0) - var order = [] - var record = function (name) { - return function (val) { - order.push({ - name: name, - val: val - }) - } - } - o.subscribe(record('A')) + var order = recordOrder() + o.subscribe(order.record('A')) var unsubscribe = o.subscribe(function (val) { - record('B')(val) + order.record('B')(val) unsubscribe() setImmediate(function () { o(2) - t.deepEqual(order, [ + t.deepEqual(order.list, [ {name: 'A', val: 1}, {name: 'B', val: 1}, {name: 'C', val: 1}, @@ -56,7 +63,7 @@ test('should allow to unsubscribe while listening', function (t) { ]) }) }) - o.subscribe(record('C')) + o.subscribe(order.record('C')) o(1) }) @@ -90,6 +97,36 @@ test('should work after listeners were drained and readded again', function (t) t.end() }) +test('should work with 0, 1 or many listeners', function (t) { + var o = Observable(0) + var unsub1 + var unsub2 + var order = recordOrder() + o(1) // zero listener + unsub1 = o.subscribe(order.record('A')) + o(2) // 1 listener + unsub2 = o.subscribe(order.record('B')) + o(3) // 2 listener + unsub1() + o(4) // 1 listener again + unsub2() + o(5) // 0 listener again + unsub1 = o.subscribe(order.record('C')) + o(6) + unsub2 = o.subscribe(order.record('D')) + o(7) + t.deepEqual(order.list, [ + {name: 'A', val: 2}, + {name: 'A', val: 3}, + {name: 'B', val: 3}, + {name: 'B', val: 4}, + {name: 'C', val: 6}, + {name: 'C', val: 7}, + {name: 'D', val: 7} + ]) + t.end() +}) + test('it should work without subscriber', function (t) { var o = Observable(0) o(1)