Eloquent JavaScript, Sequence Interface - javascript

I am working my way through Eloquent JavaScript, and I am having questions about problem #6.3 (second edition).
Here is the problem:
Design an interface that abstracts iteration over a collection of
values. An object that provides this interface represents a sequence,
and the interface must somehow make it possible for code that uses
such an object to iterate over the sequence, looking at the element
values it is made up of and having some way to find out when the end
of the sequence is reached.
When you have specified your interface, try to write a function
logFive that takes a sequence object and calls console.log on its
first five elements—or fewer, if the sequence has fewer than five
elements.
Then implement an object type ArraySeq that wraps an array and allows
iteration over the array using the interface you designed. Implement
another object type RangeSeq that iterates over a range of integers
(taking from and to arguments to its constructor) instead.
I wrote this solution:
function ArraySeq(collection) {
this.values = collection;
}
ArraySeq.prototype.iterate = function(start, end, action) {
var n = Math.min(this.values.length, end);
for (var i = start; i < n; i++) {
action(this.values[i]);
}
};
function RangeSeq(from, to) {
var array = [];
for (var i = from; i <= to; i++)
array.push(i);
ArraySeq.call(this, array);
}
RangeSeq.prototype = Object.create(ArraySeq.prototype);
function logFive(sequenceObject) {
sequenceObject.iterate(0, 5, console.log);
}
//This code to test how the solution works was provided by the author
logFive(new ArraySeq([1, 2]));
// → 1
// → 2
logFive(new RangeSeq(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104
The solutions by the author are different, here are both of them:
// I am going to use a system where a sequence object has two methods:
//
// * next(), which returns a boolean indicating whether there are more
// elements in the sequence, and moves it forward to the next
// element when there are.
//
// * current(), which returns the current element, and should only be
// called after next() has returned true at least once.
function logFive(sequence) {
for (var i = 0; i < 5; i++) {
if (!sequence.next())
break;
console.log(sequence.current());
}
}
function ArraySeq(array) {
this.pos = -1;
this.array = array;
}
ArraySeq.prototype.next = function() {
if (this.pos >= this.array.length-1)
return false;
this.pos++;
return true;
};
ArraySeq.prototype.current = function() {
return this.array[this.pos];
};
function RangeSeq(from, to) {
this.pos = from - 1;
this.to = to;
}
RangeSeq.prototype.next = function() {
if (this.pos >= this.to)
return false;
this.pos++;
return true;
};
RangeSeq.prototype.current = function() {
return this.pos;
};
logFive(new ArraySeq([1, 2]));
// → 1
// → 2
logFive(new RangeSeq(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104
// This alternative approach represents the empty sequence as null,
// and gives non-empty sequences two methods:
//
// * head() returns the element at the start of the sequence.
//
// * rest() returns the rest of the sequence, or null if there are no
// elemements left.
//
// Because a JavaScript constructor can not return null, we add a make
// function to constructors of this type of sequence, which constructs
// a sequence, or returns null if the resulting sequence would be
// empty.
function logFive2(sequence) {
for (var i = 0; i < 5 && sequence != null; i++) {
console.log(sequence.head());
sequence = sequence.rest();
}
}
function ArraySeq2(array, offset) {
this.array = array;
this.offset = offset;
}
ArraySeq2.prototype.rest = function() {
return ArraySeq2.make(this.array, this.offset + 1);
};
ArraySeq2.prototype.head = function() {
return this.array[this.offset];
};
ArraySeq2.make = function(array, offset) {
if (offset == null) offset = 0;
if (offset >= array.length)
return null;
else
return new ArraySeq2(array, offset);
};
function RangeSeq2(from, to) {
this.from = from;
this.to = to;
}
RangeSeq2.prototype.rest = function() {
return RangeSeq2.make(this.from + 1, this.to);
};
RangeSeq2.prototype.head = function() {
return this.from;
};
RangeSeq2.make = function(from, to) {
if (from > to)
return null;
else
return new RangeSeq2(from, to);
};
logFive2(ArraySeq2.make([1, 2]));
// → 1
// → 2
logFive2(RangeSeq2.make(100, 1000));
// → 100
// → 101
// → 102
// → 103
// → 104
My questions:
My solution produces exactly the same results as the author's solutions. I tested all three with various test cases. I did utilize the material of the chapter when writing my solution (the chapter talks about OOP techniques in JavaScript, inheritance and such). Given all this, is my solution wrong or not?
Why are his solutions written the way they are written? I mean, is there something wrong with my approach? Not abstract enough? Is it a bad thing that my method accepts arguments? His methods do not require arguments. Are there any drawbacks to my solution comparing to his? (I must confess, I barely understood the problem, so my solution probably reflects the level of my understanding of the problem.)
Thank you!

Your solution is not correct, because the whole point of the iterator protocol is to iterate lazily, so that RangeSeq(1, 10000000) doesn't fill up memory with all elements if we only need five.
The author's code is ok, but unnecessarily verbose. Iterator protocols are usually implemented like this:
an Iterable is an object that has the iterator() method
Iterable.iterator() returns an Iterator object
Iterator provides the method .next which returns a pair (done, value). Alternatively, .next can throw an exception when called on an exhausted Iterator.
Examples:
function RangeSeq(from, to) {
this.iterator = function() {
var i = from;
return {
next: function() { return [i >= to, i++] }
}
}
}
function ArraySeq(ary) {
this.iterator = function() {
var i = 0;
return {
next: function() { return [ i >= ary.length, ary[i++]] }
}
}
}
function logN(seq, n) {
var it = seq.iterator();
while (n--) {
let [done, value] = it.next();
if (done) break;
console.log(value)
}
}
logN(new RangeSeq(100, 100000000), 5);
console.log('--------------');
logN(new ArraySeq([11,22,33,44,55,66,77,88,99]), 5);
That said, modern JS engines have this kind of stuff built-in, so the Range example can be written much simpler as
function RangeSeq(from, to) {
this[Symbol.iterator] = function*() {
for (let i = from; i < to; i++)
yield i;
}
}
n = 0;
rs = new RangeSeq(100, 10000000);
for (let x of rs) {
if (n++ >= 5) break;
console.log(x)
}

Related

what does a recursive closure returns to?

I want to write a function (persistence) that takes in a positive parameter num and returns its multiplicative persistence, which is the number of times you must multiply the digits in num until you reach a single digit.
For example:
persistence(39) === 3 // because 3*9 = 27, 2*7 = 14, 1*4=4
// and 4 has only one digit
persistence(999) === 4 // because 9*9*9 = 729, 7*2*9 = 126,
// 1*2*6 = 12, and finally 1*2 = 2
persistence(4) === 0 // because 4 is already a one-digit number
I wrote this:
function persistence(num) {
//code me
var f;
f= countPersistence(num);
var toReturn= f(num); console.log("received value: "+toReturn);
return toReturn;
}
function countPersistence(num){
var count=0;
return function g(num){
var numt=num+"";
numt=numt.split("");
if(numt.length>1){
count++;
for(var i=0; i<numt.length-1; i++){
numt[i+1]=numt[i]*numt[i+1];
}
arguments.callee(numt[numt.length-1]);
}
else
{ console.log("returned value: "+count); return count;}
}
}
As you can see running this code, the returned value of the inner function is not exactly what expected.
Indeed, a function should return to where it is called from, right?. But in this case since it's recursive it is called from itself.
I have no idea how to retrieve the actual value (without using global variable)
You do not return a value when you call your inner function recursively. You could fix it like this (removing the else block and making it common code), so that always the last updated value of count is returned:
function persistence(num) {
//code me
var f;
f= countPersistence(num);
var toReturn= f(num);
return toReturn;
}
function countPersistence(num){
var count=0;
return function g(num){
var numt=num+"";
numt=numt.split("");
if(numt.length>1){
count++;
for(var i=0; i<numt.length-1; i++){
numt[i+1]=numt[i]*numt[i+1];
}
arguments.callee(numt[numt.length-1]);
}
return count;
}
}
console.log(persistence(39)); // 3
console.log(persistence(999)); // 4
console.log(persistence(4)); // 0
But arguments.callee is deprecated, and moreover you are making things overly complicated with nested functions.
You can do it like this:
function persistence(num){
return num < 10 ? 0
: 1 + persistence(String(num).split('').reduce((a, b) => a*b));
}
console.log(persistence(39)); // 3
console.log(persistence(999)); // 4
console.log(persistence(4)); // 0
You are not returning on the recursion line
return arguments.callee(numt[numt.length-1]);
and as I stated in the comments arguments.callee is deprecated so you should use the function name.
return g(numt[numt.length-1]);

Reduce function to take an undefined initial value

I want to create a function (reduce) that does the following:
Where:
var collection = [1, 2, 3];
and
function iterator(total, element) {
return total + element;
};
if initial is defined as 3:
reduce(collection, iterator, 3)
will do this:
3 + 1
4 + 2
6 + 3 = 9
if initial is undefined:
reduce(collection, iterator)
will do this:
1 + 2
3 + 3 = 6
Here is my code:
var reduce = function(collection, iterator, initial) {
if (initial === undefined) {
var total = 0;
} else {
var total = initial;
}
each(collection, function(element) {
total = iterator(total, element);
});
return total;
}
It works, but you can see that I've hard-coded total = 0, but I want this code to work in other scenarios (for example, multiplication, where I wouldn't want 0 to make the whole product 0).
This is how I would implement it:
alert(reduce([1,2,3], add, 3)); // 9
alert(reduce([1,2,3], add)); // 6
function add(a, b) {
return a + b;
}
function reduce(array, iterator, initial) {
var length = array.length;
var index = 0;
if (arguments.length < 3) {
if (length > 0) var result = array[index++]; // Note 1
else throw new Error("Reduce of empty array with no initial value");
} else var result = initial;
while (index < length) result = iterator(result, array[index++]);
return result;
}
The code is pretty self explanatory. Nevertheless, here's how it works, if the number of arguments passed are less than 3 it means that initial was not given. Hence we set result to array[0] and increment index. If array is empty then we throw an error instead. Otherwise we set the result to the initial value passed to the function. Everything else works as normal.
Note 1: The reason we don't modify initial (i.e. write initial = array[index++]) is because if we use arguments in a function and also modify the parameters of the function then the function will not be optimized in V8. Hence, it will execute slower.
Hope this helps.

Why is this recursive function not stopping?

I attempted to create a recursive function to iterate through a data set, but it does not properly break out and is infinite
jsfiddle of code shown
var data = [{a: 1,b: 1}, {a: 2,b: 2}],z = 0;
function some(a, b, cbk) {
console.log(a + ':' +b);
cbk();
}
function main() {
var cbk = function () {
if (z < data.length) {
main();
} else {
console.log('end');
}
z++;
}
some(data[z].a, data[z].b, cbk);
}
main();
Why is this an infinite loop?
jsFiddle Demo
There were a few things going on here that made the recursion fail involving the iteration control. By starting with z = 0, and comparing to .length, z will need to be pre-increased prior to the conditional check if( z < .length ).
The reason is that following the path of recursion, z is never incremented and so the recursion is infinite causing a lockout of the page. So, z needs to be handled before the recursive call takes place, preferably before the comparison to the .length.
In your original version, this was taking place not only after the if statement, but also after the recursive call. Fixing this iterator will fix your recursion.
if (++z < data.length) {
I was told to undelete this because it's answerable although I am gaining much hate, I found have noticed my mistake and did this.
var data = [{
a: 1,
b: 1
}, {
a: 2,
b: 2
}],
z = 0;
function some(a, b, cbk) {
console.log(a + ':' +b);
cbk();
}
function main() {
var cbk = function () {
z++;
if (z < data.length) {
main();
} else {
console.log('end');
}
}
some(data[z].a, data[z].b, cbk);
}
main();
I don't know how fluent at reading code you are, but I've tried to break down the concept of linear recursion into as generic a way as I could imagine
function recurse(data, /* fns */ step, worker, joiner, /* vals */ first, empty) {
function recursor(data, current) {
var result = worker(data, current), // do work with current iteration
next = step(data, current); // find the next iteration
if (next !== null) // if found
return joiner( // return
result, // the result from this time
recursor(data, next) // + the result from next time
);
else // if not found
return joiner( // return
result, // just this result
empty // this join is helpful for joining arrays/objects/etc
);
}
return recursor(data, first); // start it
}
So, an example based on what you were doing
var data = [{a: 1,b: 1}, {a: 2,b: 2}];
function init(data) {
function logger(data, i) { // a function describing what to do
var e = data[i]; // with this iteration
console.log(e.a + ':' + e.b);
return e.a;
}
function step(data, i) { // a function describing how to find
i = i + 1; // the next iteration
if (i < data.length) return i;
return null; // null = end
}
function add(a, b) { // a function describing how to join
return a + b; // two iterations together
}
return recurse(data, step, logger, add, 0, 0);
}
init(data); // run everything safely in it's own closure
/*
1:1
2:2
3 // === 1 + 2 + 0, from return of logger and result of add
*/
Of course in practice you could do a lot of this in-place rather than a function for each thing, and the step can usually be simplified so much that you don't need to var for result and next because you don't have to cache anything for an if test.

Lazy Cartesian product of arrays (arbitrary nested loops)

There are other questions about this in other languages, and other non-lazy JavaScript versions, but no lazy JavaScript versions that I have found.
Given an array of an arbitrary number of arbitrary-sized arrays:
var sets = [ [2,3,4,5], ['sweet','ugly'], ['cats','dogs','hogs'] ];
and a callback function:
function holla( n, adj, noun ){
console.log( [n,adj,noun].join(' ') );
}
what's an elegant way to iterate the entire product space without creating a huge array of all possible combinations first?
lazyProduct( sets, holla );
// 2 sweet cats
// 2 sweet dogs
// 2 sweet hogs
// 2 ugly cats
// 2 ugly dogs
// 2 ugly hogs
// 3 sweet cats
// 3 sweet dogs
// 3 sweet hogs
// 3 ugly cats
// 3 ugly dogs
// 3 ugly hogs
// 4 sweet cats
// 4 sweet dogs
// 4 sweet hogs
// 4 ugly cats
// 4 ugly dogs
// 4 ugly hogs
// 5 sweet cats
// 5 sweet dogs
// 5 sweet hogs
// 5 ugly cats
// 5 ugly dogs
// 5 ugly hogs
Note that these combinations are the same as the results you would get if you had nested loops:
var counts = [2,3,4,5];
var adjectives = ['sweet','ugly'];
var animals = ['cats','dogs','hogs'];
for (var i=0;i<counts.length;++i){
for (var j=0;j<adjectives.length;++j){
for (var k=0;k<animals.length;++k){
console.log( [ counts[i], adjectives[j], animals[k] ].join(' ') );
}
}
}
The benefits of the Cartesian product are:
It lets you nest an arbitrary number of loops (perhaps you don't know how many items you'll iterate)
It lets you change the order of looping (e.g. loop by adjectives first) without having to edit your code or write out all possible combinations of looping order.
Benchmarks
You can see the benchmarks for the answers below here:
http://jsperf.com/lazy-cartesian-product/26
A combination of recursion and iteration will do the job.
function lazyProduct(sets, holla) {
var setLength = sets.length;
function helper(array_current, set_index) {
if (++set_index >= setLength) {
holla.apply(null, array_current);
} else {
var array_next = sets[set_index];
for (var i=0; i<array_next.length; i++) {
helper(array_current.concat(array_next[i]), set_index);
}
}
}
helper([], -1);
}
Demo: http://jsfiddle.net/nV2XU/
var sets = [ [2,3,4,5], ['sweet','ugly'], ['cats','dogs','hogs'] ];
function holla( n, adj, noun ){
console.log( [n,adj,noun].join(' ') );
}
lazyProduct(sets,holla);
Coincidentally working on the same thing over the weekend. I was looking to find alternative implementations to my [].every-based algo which turned out to have abyssmal performance in Firefox (but screams in Chrome -- more than twice as fast as the next).
The end result is http://jsperf.com/lazy-cartesian-product/19 . It's similar to Tomalak's approach but there is only one arguments array which is mutated as the carets move instead of being generated each time.
I'm sure it could be improved further by using the clever maths in the other algos. I don't quite understand them though, so I leave it to others to try.
EDIT: the actual code, same interface as Tomalak's. I like this interface because it could be breaked anytime. It's only slightly slower than if the loop is inlined in the function itself.
var xp = crossProduct([
[2,3,4,5],['angry','happy'],
['monkeys','anteaters','manatees']]);
while (xp.next()) xp.do(console.log, console);
function crossProduct(sets) {
var n = sets.length, carets = [], args = [];
function init() {
for (var i = 0; i < n; i++) {
carets[i] = 0;
args[i] = sets[i][0];
}
}
function next() {
if (!args.length) {
init();
return true;
}
var i = n - 1;
carets[i]++;
if (carets[i] < sets[i].length) {
args[i] = sets[i][carets[i]];
return true;
}
while (carets[i] >= sets[i].length) {
if (i == 0) {
return false;
}
carets[i] = 0;
args[i] = sets[i][0];
carets[--i]++;
}
args[i] = sets[i][carets[i]];
return true;
}
return {
next: next,
do: function (block, _context) {
return block.apply(_context, args);
}
}
}
I've created this solution:
function LazyCartesianIterator(set) {
var pos = null,
len = set.map(function (s) { return s.length; });
this.next = function () {
var s, l=set.length, p, step;
if (pos == null) {
pos = set.map(function () { return 0; });
return true;
}
for (s=0; s<l; s++) {
p = (pos[s] + 1) % len[s];
step = p > pos[s];
if (s<l) pos[s] = p;
if (step) return true;
}
pos = null;
return false;
};
this.do = function (callback) {
var s=0, l=set.length, args = [];
for (s=0; s<l; s++) args.push(set[s][pos[s]]);
return callback.apply(set, args);
};
}
It's used like this:
var iter = new LazyCartesianIterator(sets);
while (iter.next()) iter.do(callback);
It seems to work well but it is not very thoroughly tested, tell me if you find bugs.
See how it compares: http://jsperf.com/lazy-cartesian-product/8
Here's my solution, using recursion. I'm not fond of the fact that it creates an empty array on the first pass, or that it uses the if inside the for loop (instead of unrolling the test into two loops for speed, at the expense of DRYness) but at least it's sort of terse:
function lazyProduct(arrays,callback,values){
if (!values) values=[];
var head = arrays[0], rest = arrays.slice(1), dive=rest.length>0;
for (var i=0,len=head.length;i<len;++i){
var moreValues = values.concat(head[i]);
if (dive) lazyProduct(rest,callback,moreValues);
else callback.apply(this,moreValues);
}
}
Seen in action: http://jsfiddle.net/RRcHN/
Edit: Here's a far faster version, roughly 2x–10x faster than the above:
function lazyProduct(sets,f,context){
if (!context) context=this;
var p=[],max=sets.length-1,lens=[];
for (var i=sets.length;i--;) lens[i]=sets[i].length;
function dive(d){
var a=sets[d], len=lens[d];
if (d==max) for (var i=0;i<len;++i) p[d]=a[i], f.apply(context,p);
else for (var i=0;i<len;++i) p[d]=a[i], dive(d+1);
p.pop();
}
dive(0);
}
Instead of creating custom arrays for each recursive call it re-uses a single array (p) for all params. It also lets you pass in a context argument for the function application.
Edit 2: If you need random access into your Cartesian product, including the ability to perform iteration in reverse, you can use this:
function LazyProduct(sets){
for (var dm=[],f=1,l,i=sets.length;i--;f*=l){ dm[i]=[f,l=sets[i].length] }
this.length = f;
this.item = function(n){
for (var c=[],i=sets.length;i--;)c[i]=sets[i][(n/dm[i][0]<<0)%dm[i][1]];
return c;
};
};
var axes=[[2,3,4],['ugly','sappy'],['cats','dogs']];
var combos = new LazyProduct(axes);
// Iterating in reverse order, for fun and profit
for (var i=combos.length;i--;){
var combo = combos.item(i);
console.log.apply(console,combo);
}
//-> 4 "sappy" "dogs"
//-> 4 "sappy" "cats"
//-> 4 "ugly" "dogs"
...
//-> 2 "ugly" "dogs"
//-> 2 "ugly" "cats"
Decoding the above, the nth combination for the Cartesian product of arrays [a,b,...,x,y,z] is:
[
a[ Math.floor( n / (b.length*c.length*...*y.length*z.length) ) % a.length ],
b[ Math.floor( n / (c.length*...*x.length*y.length*z.length) ) % b.length ],
...
x[ Math.floor( n / (y.length*z.length) ) % x.length ],
y[ Math.floor( n / z.length ) % y.length ],
z[ n % z.length ],
]
You can see a pretty version of the above formula on my website.
The dividends and moduli can be precalculated by iterating the sets in reverse order:
var divmod = [];
for (var f=1,l,i=sets.length;i--;f*=l){ divmod[i]=[f,l=sets[i].length] }
With this, looking up a particular combination is a simple matter of mapping the sets:
// Looking for combination n
var combo = sets.map(function(s,i){
return s[ Math.floor(n/divmod[i][0]) % divmod[i][1] ];
});
For pure speed and forward iteration, however, see the accepted answer. Using the above technique—even if we precalculate the list of dividends and moduli once—is 2-4x slower than that answer.

Javascript prototype operator performance: saves memory, but is it faster?

I read here (Douglas Crockford) using prototype operator to add methods to Javascript classes saves also memory.
Then I read in this John Resig's article "Instantiating a function with a bunch of prototype properties is very, very, fast", but is he talking about using prototype in the standard way, or is he talking about his specific example in his article?
For example, is creating this object:
function Class1()
{
this.showMsg = function(string) { alert(string); }
}
var c = new Class1();
c.showMsg();
slower than creating this object, then?
function Class1() {}
Class1.prototype.showMsg = function(string) { alert(string); }
var c = new Class1();
c.showMsg();
P.S.
I know prototype is used to create inheritance and singleton object etc. But this question does not have anyhting to do with these subjects.
EDIT: to whom it might be interested also in performance comparison between a JS object and a JS static objet can read this answer below. Static object are definitely faster, obviously they can be usued only when you don't need more than one instance of the object.
Edit in 2021:
This question was asked in 2010 when class was not available in JS. Nowadays, class has been so optimized that there is no excuse not to use it. If you need to use new, use class. But back in 2010 you had two options when binding methods to their object constructors -- one was to bind functions inside the function constructor using this and the other was to bind them outside the constructor using prototype. #MarcoDemaio's question has very concise examples. When class was added to JS, early implementations were close in performance, but usually slower. That's not remotely true anymore. Just use class. I can think of no reason to use prototype today.
It was an interesting question, so I ran some very simple tests (I should have restarted my browsers to clear out the memory, but I didn't; take this for what it's worth). It looks like at least on Safari and Firefox, prototype runs significantly faster [edit: not 20x as stated earlier]. I'm sure a real-world test with fully-featured objects would be a better comparison. The code I ran was this (I ran the tests several times, separately):
var X,Y, x,y, i, intNow;
X = function() {};
X.prototype.message = function(s) { var mymessage = s + "";}
X.prototype.addition = function(i,j) { return (i *2 + j * 2) / 2; }
Y = function() {
this.message = function(s) { var mymessage = s + "";}
this.addition = function(i,j) { return (i *2 + j * 2) / 2; }
};
intNow = (new Date()).getTime();
for (i = 0; i < 10000000; i++) {
y = new Y();
y.message('hi');
y.addition(i,2)
}
console.log((new Date()).getTime() - intNow); //FF=5206ms; Safari=1554
intNow = (new Date()).getTime();
for (i = 0; i < 10000000; i++) {
x = new X();
x.message('hi');
x.addition(i,2)
}
console.log((new Date()).getTime() - intNow);//FF=3894ms;Safari=606
It's a real shame, because I really hate using prototype. I like my object code to be self-encapsulated, and not allowed to drift. I guess when speed matters, though, I don't have a choice. Darn.
[Edit] Many thanks to #Kevin who pointed out my previous code was wrong, giving a huge boost to the reported speed of the prototype method. After fixing, prototype is still around significantly faster, but the difference is not as enormous.
I would guess that it depends on the type of object you want to create. I ran a similar test as Andrew, but with a static object, and the static object won hands down. Here's the test:
var X, Y, Z, x, y, z;
X = function() {};
X.prototype.message = function(s) {
var mymessage = s + "";
}
X.prototype.addition = function(i, j) {
return (i * 2 + j * 2) / 2;
}
Y = function() {
this.message = function(s) {
var mymessage = s + "";
}
this.addition = function(i, j) {
return (i * 2 + j * 2) / 2;
}
};
Z = {
message: function(s) {
var mymessage = s + "";
},
addition: function(i, j) {
return (i * 2 + j * 2) / 2;
}
}
function TestPerformance() {
var closureStartDateTime = new Date();
for (var i = 0; i < 100000; i++) {
y = new Y();
y.message('hi');
y.addition(i, 2);
}
var closureEndDateTime = new Date();
var prototypeStartDateTime = new Date();
for (var i = 0; i < 100000; i++) {
x = new X();
x.message('hi');
x.addition(i, 2);
}
var prototypeEndDateTime = new Date();
var staticObjectStartDateTime = new Date();
for (var i = 0; i < 100000; i++) {
z = Z; // obviously you don't really need this
z.message('hi');
z.addition(i, 2);
}
var staticObjectEndDateTime = new Date();
var closureTime = closureEndDateTime.getTime() - closureStartDateTime.getTime();
var prototypeTime = prototypeEndDateTime.getTime() - prototypeStartDateTime.getTime();
var staticTime = staticObjectEndDateTime.getTime() - staticObjectStartDateTime.getTime();
console.log("Closure time: " + closureTime + ", prototype time: " + prototypeTime + ", static object time: " + staticTime);
}
TestPerformance();
This test is a modification of code I found at:
Link
Results:
IE6: closure time: 1062, prototype time: 766, static object time: 406
IE8: closure time: 781, prototype time: 406, static object time: 188
FF: closure time: 233, prototype time: 141, static object time: 94
Safari: closure time: 152, prototype time: 12, static object time: 6
Chrome: closure time: 13, prototype time: 8, static object time: 3
The lesson learned is that if you DON'T have a need to instantiate many different objects from the same class, then creating it as a static object wins hands down. So think carefully about what kind of class you really need.
So I decided to test this as well. I tested creation time, execution time, and memory use. I used Nodejs v0.8.12 and the mocha test framework running on a Mac Book Pro booted into Windows 7. The 'fast' results are using prototypes and the 'slow' ones are using module pattern. I created 1 million of each type of object and then accessed the 4 methods in each object. Here are the results:
c:\ABoxAbove>mocha test/test_andrew.js
Fast Allocation took:170 msec
·Fast Access took:826 msec
state[0] = First0
Free Memory:5006495744
·Slow Allocation took:999 msec
·Slow Access took:599 msec
state[0] = First0
Free Memory:4639649792
Mem diff:358248k
Mem overhead per obj:366.845952bytes
? 4 tests complete (2.6 seconds)
The code is as follows:
var assert = require("assert"), os = require('os');
function Fast (){}
Fast.prototype = {
state:"",
getState:function (){return this.state;},
setState:function (_state){this.state = _state;},
name:"",
getName:function (){return this.name;},
setName:function (_name){this.name = _name;}
};
function Slow (){
var state, name;
return{
getState:function (){return this.state;},
setState:function (_state){this.state = _state;},
getName:function (){return this.name;},
setName:function (_name){this.name = _name;}
};
}
describe('test supposed fast prototype', function(){
var count = 1000000, i, objs = [count], state = "First", name="Test";
var ts, diff, mem;
it ('should allocate a bunch of objects quickly', function (done){
ts = Date.now ();
for (i = 0; i < count; ++i){objs[i] = new Fast ();}
diff = Date.now () - ts;
console.log ("Fast Allocation took:%d msec", diff);
done ();
});
it ('should access a bunch of objects quickly', function (done){
ts = Date.now ();
for (i = 0; i < count; ++i){
objs[i].setState (state + i);
assert (objs[i].getState () === state + i, "States should be equal");
objs[i].setName (name + i);
assert (objs[i].getName () === name + i, "Names should be equal");
}
diff = Date.now() - ts;
console.log ("Fast Access took:%d msec", diff);
console.log ("state[0] = " + objs[0].getState ());
mem = os.freemem();
console.log ("Free Memory:" + mem + "\n");
done ();
});
it ('should allocate a bunch of objects slowly', function (done){
ts = Date.now ();
for (i = 0; i < count; ++i){objs[i] = Slow ();}
diff = Date.now() - ts;
console.log ("Slow Allocation took:%d msec", diff);
done ();
});
it ('should access a bunch of objects slowly', function (done){
ts = Date.now ();
for (i = 0; i < count; ++i){
objs[i].setState (state + i);
assert (objs[i].getState () === state + i, "States should be equal");
objs[i].setName (name + i);
assert (objs[i].getName () === name + i, "Names should be equal");
}
diff = Date.now() - ts;
console.log ("Slow Access took:%d msec", diff);
console.log ("state[0] = " + objs[0].getState ());
var mem2 = os.freemem();
console.log ("Free Memory:" + mem2 + "\n");
console.log ("Mem diff:" + (mem - mem2) / 1024 + "k");
console.log ("Mem overhead per obj:" + (mem - mem2) / count + 'bytes');
done ();
});
});
Conclusion: This backs up what others in this post have found. If you are constantly creating objects then the prototype mechanism is clearly faster. If your code spends most of its time accessing objects then the module pattern is faster. If you are sensitive about memory use, the prototype mechanism uses ~360 bytes less per object.
Intuitively, it seems that it would be more memory-efficient and faster to create functions on the prototype: the function's only created once, not each time a new instance is created.
However, there will be a slight performance difference when it's time to access the function. When c.showMsg is referenced, the JavaScript runtime first checks for the property on c. If it's not found, c's prototype is then checked.
So, creating the property on the instance would result in slightly faster access time - but this might only be an issue for a very deep prototype hierarchy.
We need to separate object construction and usage.
When declaring a function on a prototype, it is shared between all instances. When declaring a function in a constructor, this is recreated every time new instance is made. Given that, we need to benchmark construction and usage separately to have better results. That is what I did and want to share the results with you. This benchmark does not test for speed of construction.
function ThisFunc() {
this.value = 0;
this.increment = function(){
this.value++;
}
}
function ProtFunc() {
this.value = 0;
}
ProtFunc.prototype.increment = function (){
this.value++;
}
function ClosFunc() {
var value = 0;
return {
increment:function(){
value++;
}
};
}
var thisInstance = new ThisFunc;
var iterations = 1000000;
var intNow = (new Date()).getTime();
for (i = 0; i < iterations; i++) {
thisInstance.increment();
}
console.log(`ThisFunc: ${(new Date()).getTime() - intNow}`); // 27ms node v4.6.0
var protInstance = new ProtFunc;
intNow = (new Date()).getTime();
for (i = 0; i < iterations; i++) {
protInstance.increment();
}
console.log(`ProtFunc: ${(new Date()).getTime() - intNow}`); // 4ms node v4.6.0
var closInstance = ClosFunc();
intNow = (new Date()).getTime();
for (i = 0; i < iterations; i++) {
closInstance.increment();
}
console.log(`ClosFunc: ${(new Date()).getTime() - intNow}`); // 7ms node v4.6.0
From these results we can see that the prototype version is the fastest (4ms), but the closure version is very close (7ms). You may still need to benchmark for your particular case.
So:
We can use prototype version when we need to have every bit of performance or share functions between instances.
We can use other versions when what we want is the features they provide. (private state encapsulation, readability etc.)
PS: I used Andrew's answer as a reference. Used the same loops and notation.
I ran my own tests.
The first conclusion is, that static access is actually slower than real prototyping. Interestingly, the Version 23 of this test has a flawed prototyping (Variable X) in it, which just returns the completely overridden prototype object over and over again and when I was creating my test, this prototyping was still slower than my "real prototype" test.
Anyway, to the answer: Unless my test is flawed, it shows that real prototyping is fastest. It beats or is at least equal to the static object when ignoring instantiation. this-assignments on instantiation and private variables are both much slower. I wouldn't have guessed private variables would be this slow.
It might be of interest that I extended the prototype Object with jQuery.extend in between and it was about the same speed as the direct assignment. The extend was outside the test itself, of course. At least this is a way to circumvent writing annoying ".prototype."-Parts all the time.
High Resolution Browser Performance API Tests
None of the tests here are taking advantage of the performance API for high resolution testing so I wrote one that will show current fastest results for many different scenarios including 2 that are faster than any of the other answers on most runs.
Fasted in each category (10,000 iterations)
Property access only (~0.5ms): { __proto__: Type }
Looping object creation with property access (<3ms): Object.create(Type)
The code uses ES6 without babel transpilation to ensure accuracy. It works in current chrome. Run the test below to see the breakdown.
function profile () {
function test ( name
, define
, construct
, { index = 0
, count = 10000
, ordinals = [ 0, 1 ]
, constructPrior = false
} = {}
) {
performance.clearMarks()
performance.clearMeasures()
const symbols = { type: Symbol('type') }
const marks = (
{ __proto__: null
, start: `${name}_start`
, define: `${name}_define`
, construct: `${name}_construct`
, end: `${name}_end`
}
)
performance.mark(marks.start)
let Type = define()
performance.mark(marks.define)
let obj = constructPrior ? construct(Type) : null
do {
if(!constructPrior)
obj = construct(Type)
if(index === 0)
performance.mark(marks.construct)
const measureOrdinal = ordinals.includes(index)
if(measureOrdinal)
performance.mark(`${name}_ordinal_${index}_pre`)
obj.message('hi')
obj.addition(index, 2)
if(measureOrdinal)
performance.mark(`${name}_ordinal_${index}_post`)
} while (++index < count)
performance.mark(marks.end)
const measureMarks = Object.assign (
{ [`${name}_define`]: [ marks.start, marks.define ]
, [`${name}_construct`]: [ marks.define, marks.construct ]
, [`${name}_loop`]: [ marks.construct, marks.end ]
, [`${name}_total`]: [ marks.start, marks.end ]
}
, ordinals.reduce((reduction, i) => Object.assign(reduction, { [`${name}_ordinal_${i}`]: [ `${name}_ordinal_${i}_pre`, `${name}_ordinal_${i}_post` ] }), {})
)
Object.keys(measureMarks).forEach((key) => performance.measure(key, ...measureMarks[key]))
const measures = performance.getEntriesByType('measure').map(x => Object.assign(x, { endTime: x.startTime + x.duration }))
measures.sort((a, b) => a.endTime - b.endTime)
const durations = measures.reduce((reduction, measure) => Object.assign(reduction, { [measure.name]: measure.duration }), {})
return (
{ [symbols.type]: 'profile'
, profile: name
, duration: durations[`${name}_total`]
, durations
, measures
}
)
}
const refs = (
{ __proto__: null
, message: function(s) { var mymessage = s + '' }
, addition: function(i, j) { return (i *2 + j * 2) / 2 }
}
)
const testArgs = [
[ 'constructor'
, function define() {
return function Type () {
this.message = refs.message
this.addition = refs.addition
}
}
, function construct(Type) {
return new Type()
}
]
, [ 'prototype'
, function define() {
function Type () {
}
Type.prototype.message = refs.message
Type.prototype.addition = refs.addition
return Type
}
, function construct(Type) {
return new Type()
}
]
, [ 'Object.create'
, function define() {
return (
{ __proto__: null
, message: refs.message
, addition: refs.addition
}
)
}
, function construct(Type) {
return Object.create(Type)
}
]
, [ 'proto'
, function define() {
return (
{ __proto__: null
, message: refs.message
, addition: refs.addition
}
)
}
, function construct(Type) {
return { __proto__: Type }
}
]
]
return testArgs.reduce(
(reduction, [ name, ...args ]) => (
Object.assign( reduction
, { [name]: (
{ normal: test(name, ...args, { constructPrior: true })
, reconstruct: test(`${name}_reconstruct`, ...args, { constructPrior: false })
}
)
}
)
)
, {})
}
let profiled = profile()
const breakdown = Object.keys(profiled).reduce((reduction, name) => [ ...reduction, ...Object.keys(profiled[name]).reduce((r, type) => [ ...r, { profile: `${name}_${type}`, duration: profiled[name][type].duration } ], []) ], [])
breakdown.sort((a, b) => a.duration - b.duration)
try {
const Pre = props => React.createElement('pre', { children: JSON.stringify(props.children, null, 2) })
ReactDOM.render(React.createElement(Pre, { children: { breakdown, profiled } }), document.getElementById('profile'))
} catch(err) {
console.error(err)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="profile"></div>
I'm sure that as far as instantiating the object goes, it's way faster and also consumes less memory, no doubts about that, but I would think that the javascript engine needs to loop through all the properties of the object to determine if the property/method invoked is part of that object and if not, then go check for the prototype. I am not 100% sure about this but I'm assuming that's how it works and if so, then in SOME cases where your object has a LOT of methods added to it, instantiated only once and used heavily, then it could possibly be a little slower, but that's just a supposition I haven't tested anything.
But in the end, I would still agree that as a general rules, using prototype will be faster.
Funny thing though. It depends not that much on which type of object you create and it matters how you write an example. Likewise i ran similar test as shmuel613 who wrote a similair test as Andrew. The first test is creating a single instance of a constructor, a class and an object literal and then measures the speed of execution from the constructor's instance functions, class's prototype methods and object literal's static functions:
var Y, Z, x, y, z;
class X {
message(s) {
var mymessage = s + "";
};
addition(i, j) {
return (i * 2 + j * 2) / 2;
};
};
Y = function () {
this.message = function (s) {
var mymessage = s + "";
};
this.addition = function (i, j) {
return (i * 2 + j * 2) / 2;
};
};
Z = {
message(s) {
var mymessage = s + "";
},
addition(i, j) {
return (i * 2 + j * 2) / 2;
}
}
function TestPerformance() {
console.time("Closure time:");
y = new Y(); // create a single instance
for (var i = 0; i < 100000; i++) {
// I am comparing a single instance with the other single instances
y.message('hi');
y.addition(i, 2);
}
console.timeEnd("Closure time:");
console.time("Prototype time:");
x = new X(); // create a single instance
for (var i = 0; i < 100000; i++) {
// I am comparing a single instance with the other single instances
x.message('hi');
x.addition(i, 2);
}
console.timeEnd("Prototype time:");
console.time("Static object time:");
for (var i = 0; i < 100000; i++) {
z = Z; // obviously you don't really need this
z.message('hi');
z.addition(i, 2);
}
console.timeEnd("Static object time:");
}
TestPerformance();
The second test measures the speed of execution of creating many instances of a constructor, a class and object literals followed by executing the instance functions, prototype methods and static methods:
var Y, x, y, z;
class X {
message(s) {
var mymessage = s + "";
};
addition(i, j) {
return (i * 2 + j * 2) / 2;
};
};
Y = function () {
this.message = function (s) {
var mymessage = s + "";
};
this.addition = function (i, j) {
return (i * 2 + j * 2) / 2;
};
};
function TestPerformance() {
console.time("Closure time:");
//y = new Y()
for (var i = 0; i < 100000; i++) {
y = new Y(); // creating an instance
y.message('hi');
y.addition(i, 2);
}
console.timeEnd("Closure time:");
console.time("Prototype time:");
//x = new X();
for (var i = 0; i < 100000; i++) {
x = new X(); // creating an instance
x.message('hi');
x.addition(i, 2);
}
console.timeEnd("Prototype time:");
console.time("Static object time:");
for (var i = 0; i < 100000; i++) {
z = {
message(s) {
var mymessage = s + "";
},
addition(i, j) {
return (i * 2 + j * 2) / 2;
}
}; // creating an instance such as from factory functions
z.message('hi');
z.addition(i, 2);
}
console.timeEnd("Static object time:");
}
TestPerformance();
The lesson learned is that DON'T blindly evolve a prejudice against something without being thorough. The execution speed from instance functions of a constructor (pre ES2016 classes) and the speed from prototype methods of a class are really just as fast as the execution speed from static functions of a object. However the creation speed followed by execution speed of a constructor instance with instance functions versus the creation speed of a class instance with prototype methods versus the creation speed of object literals with static methods shows rather that classes with prototype methods are faster created and executed on Chrome, Microsoft edge, and Opera. The creation speed of an object literal with static methods is only faster at Mozilla firefox
So, creating the property on the instance would result in slightly faster access time - but this might only be an issue for a very deep prototype hierarchy.
Actually the result is different then we could expect - access time to prototyped methods is faster then accessing to the methods attached exactly to the object (FF tested).

Categories

Resources