public class PersonList : List<Person> { public string Name; public PersonList(string name){ this.Name = name; } public int GetSumYears() { var total = 0; foreach (var p in this) total += p.BirthDate.Year; return total; } }Among other thing a bug in IE8. I personally focus on ECMAScript 5, so this is not my concern for this post, I also want to use ECMAScript 5, property get.
A blog post by Juriy Zaytsev, How ECMAScript 5 still does not allow to subclass an array will tell you every thing about it.
I also like to mention a post from Andrea Giammarchi Habemus Array ... unlocked length in IE8, subclassed Array for every browser, which proposes a solution to implement a Stack object which I re-used to implement my List() object.
This post is my implementation of the C# List<T> in JavaScript. First here is the kind of code I want to be able to write.
The source code is now part of my library fJs.lib on github.
var l = new List(); for(var i=0; i<5; i++){ l.add(i); } l.addRange(5, 6, 7, 8, 9); print(l.toString()); // 0,1,2,3,4,5,6,7,8,9 l.removeAt(0); l.remove(9); print(l.toString()); // 1,2,3,4,5,6,7,8 var l2 = l.filter(function(v){ return v % 2 == 0; }); print(l2.toString()); // 2,4,6,8 var l3 = l.map(function(v){ return v*v; }); print(l3.toString()); // 1,4,9,16,25,36,49,64,81,81 print(l instanceof List); // true print(l instanceof Array); // trueMy first idea was to start with something like that
function List() { var _list = []; _list.add = function(v){ this.push(v); } return _list; } var l = new List(); print(l instanceof Array); // true print(l instanceof List); // falseBut the object returned by the List() constructor is not an instance of List, which is confusing. So I re-used the Stack implementation of Andrea Giammarchi which solve this problem. Creating the object is a little bit more complex, but then adding the methods remain simple. Here is the List object properties and methods.
List properties: count method: add addRange all any concat contains exists findAll findIndex remove removeAll removeAt reverse filter mapThe source code
Here is the source code with some unit tests. I ran the code on NodeJS.
// // Class List a match for the C# .NET List<T> - Frederic Torres 2011 // Mit Style License // // based on // - Andrea Giammarchi's Stack // http://webreflection.blogspot.com/2008/05/habemus-array-unlocked-length-in-ie8.html // - How ECMAScript 5 still does not allow to subclass an array // http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/ // var List = (function(){ var MAX_SIGNED_INT_VALUE = Math.pow(2, 32) - 1; function __isFunction(f) { return typeof f === 'function'; } function __toUint32(value) { return value >>> 0; } function __isInt(v){ return String(__toUint32(v)) === v; } function __removeAt(that, index) { that.splice(index ,1); } function _list(length) { if (arguments.length === 1 && typeof length === "number") { this.length = -1 < length && length === length << 1 >> 1 ? length : this.push(length); } else if (arguments.length) { this.push.apply(this, arguments); } Object.defineProperty(this, "count", { get: function(){ return this.length; }, }); } function _array() { }; _array.prototype = []; _list.prototype = new _array(); _list.prototype.length = 0; _list.prototype.toString = function () { return this.slice(0).toString(); } _list.prototype.add = function (v) { this.push(v); } _list.prototype.addRange = function () { var i; for(i=0; i < arguments.length; i++) this.add(arguments[i]); }; _list.prototype.clear = function (v) { this.length = 0; } _list.prototype.removeAt = function (index) { if(this.count==0) throw new Error("Cannot removeAt from empty List"); if(index>=0 && index < this.count) __removeAt(this, index); else throw new Error("invalid index "+index+" for List"); } _list.prototype.remove = function (val) { var elementRemoved = 0, index = this.indexOf(val); if(index===-1) return 0; while(index!==-1){ __removeAt(this, index); elementRemoved++; index = this.indexOf(val); } return elementRemoved; } _list.prototype.contains = function (val) { return this.indexOf(val) !== -1; } _list.prototype.concat = function (l) { var i; for(i in this) if(__isInt(i)) this.add(l[i]); } _list.prototype.findIndex = function (lambda) { var i, r = new List(); if(!__isFunction(lambda)) throw new Error("exists() requires a function as parameter"); for(i in this) if((__isInt(i))&&(lambda(this[i]))) r.add(i); return r; } _list.prototype.exists = function (lambda) { var i, r = new List(); if(!__isFunction(lambda)) throw new Error("exists() requires a function as parameter"); for(i in this) if((__isInt(i)) && (lambda(this[i]))) return true; return false; } _list.prototype.removeAll = function (lambda) { var goOn = true, i; if(!__isFunction(lambda)) throw new Error("exists() requires a function as parameter"); while(goOn){ goOn = false; for(i in this){ if((__isInt(i)) && (lambda(this[i]))) { __removeAt(this, i); goOn = true; break; } } } } _list.prototype.all = function (lambda) { var i, r = new List(); if(!__isFunction(lambda)) throw new Error("exists() requires a function as parameter"); for(i in this) if((__isInt(i)) && (!lambda(this[i]))) return false; return true; } _list.prototype.any = function (lambda) { var i, r = new List(); if(!__isFunction(lambda)) throw new Error("exists() requires a function as parameter"); for(i in this) if((__isInt(i)) && (lambda(this[i]))) return true; return false; } _list.prototype.reverse = function () { var values = [], i; for(i in this) if(__isInt(i)) values.unshift(this[i]); this.clear(); this.push.apply(this, values); } _list.prototype.filter = function (lambda) { var i, r = new List(); if(!__isFunction(lambda)) throw new Error("filter() requires a function as parameter"); for(i in this) if(__isInt(i)) if(lambda(this[i])) r.add(this[i]); return r; } _list.prototype.findAll = function (lambda) { // Just to have the same .net method return this.filter(lambda); } _list.prototype.map = function (lambda) { var i, r = new List(); if(!__isFunction(lambda)) throw new Error("map() requires a function as parameter"); for(i in this) if(__isInt(i)) r.add(lambda(this[i])); return r; } _list.prototype.constructor = _list; return _list; })();The unit tests.
For now it is the unit tests of the poor until I include this in my own JavaScript library and probably add it on github.
function isNodeJs() { return (typeof require === "function" && typeof Buffer === "function" && typeof Buffer.byteLength === "function" && typeof Buffer.prototype !== "undefined" && typeof Buffer.prototype.write === "function"); } function print(s){ console.log(s); } if(isNodeJs()){ var Assert = { isTrue : function(e){ if(!e) throw new Error("IsTrue failed"); }, isFalse : function(e){ if(!!e) throw new Error("IsTrue failed"); }, areEqual : function(v1,v2){ if(v1!==v2) throw new Error("expected:"+v1+" is not equal to actual:"+v2); } } var l = new List(1, 2, 3); Assert.areEqual(3, l.count); Assert.areEqual("1,2,3", l.toString()); var l = new List(); Assert.areEqual(0, l.count); l.add(1); l.add(2); l.add(3); Assert.areEqual("1,2,3", l.toString()); Assert.areEqual(3, l.count); var l = new List(1, 2, 3); l.removeAt(1); Assert.areEqual(2, l.count); Assert.areEqual("1,3", l.toString()); var l = new List(1, 2, 3); Assert.areEqual(1,l.remove(2)); Assert.areEqual(2, l.count); Assert.areEqual("1,3", l.toString()); var l = new List(1, 2, 3, 1, 1); Assert.areEqual(3,l.remove(1)); Assert.areEqual(2, l.count); Assert.areEqual("2,3", l.toString()); var l1 = new List(1, 2, 3); var l2 = new List(4, 5, 6); l1.concat(l2); print(l1.toString()); Assert.areEqual("1,2,3,4,5,6", l1.toString()); var l = new List(1, 2, 3); Assert.isTrue(l instanceof List); Assert.isTrue(l instanceof Array); var l = new List(1, 2, 3, 4); var ll = l.filter(function(v){ return v % 2 == 0; }); Assert.areEqual("2,4", ll.toString()); var l = new List(1, 2, 3, 4); var ll = l.map(function(v){ return v*v; }); Assert.areEqual("1,4,9,16", ll.toString()); var l = new List(); l.addRange(1, 2, 3); Assert.areEqual("1,2,3", l.toString()); var l = new List(1, 2, 3); l.clear(); Assert.areEqual(0, l.count); Assert.areEqual("", l.toString()); l.add(1); Assert.areEqual("1", l.toString()); Assert.areEqual(1, l.count); l.addRange(2, 3, 4); Assert.areEqual("1,2,3,4", l.toString()); var l = new List(1, 2, 3); Assert.isTrue(l.contains(2)); Assert.isFalse(l.contains(12)); var l = new List(1, 2, 3); Assert.isTrue( l.exists(function(v){ return v == 2; })); Assert.isFalse(l.exists(function(v){ return v == 12; })); var l = new List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); l.name='fred'; Assert.isTrue ( l.all(function(v){ return v > 0; })); Assert.isFalse( l.all(function(v){ return v > 5; })); var l = new List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); l.name='fred'; Assert.isTrue ( l.any(function(v){ return v > 0; })); Assert.isFalse( l.any(function(v){ return v > 15; })); var l = new List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Assert.areEqual( "1,3,5,7,9", l.findIndex(function(v){ return v % 2 == 0; }).toString() ); var l = new List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); l.removeAll(function(v){ return v % 2 == 0; }); Assert.areEqual( "1,3,5,7,9", l.toString() ); var l = new List(1, 2, 3, 4, 5); l.reverse(); Assert.areEqual( "5,4,3,2,1", l.toString() ); print("---------------------"); var l = new List(); for(var i=0; i < 5; i++){ l.add(i); } l.addRange(5, 6, 7, 8, 9); print(l.toString()); // 0,1,2,3,4,5,6,7,8,9 l.removeAt(0); l.remove(9); print(l.toString()); // 1,2,3,4,5,6,7,8 var l2 = l.filter(function(v){ return v % 2 == 0; }); print(l2.toString()); // 2,4,6,8 var l3 = l.map(function(v){ return v*v; }); print(l3.toString()); // 1,4,9,16,25,36,49,64,81,81 print(l instanceof List); // true print(l instanceof Array); // true }
No comments:
Post a Comment