Typescript: Avoid comparison by reference - javascript

I need to store a list of points and check if a new point is already included in that list
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
window.onload = () => {
var points : Point[] = [];
points.push(new Point(1,1));
var point = new Point(1,1);
alert(points.indexOf(point)); // -1
}
Obviously typescript uses comparison by reference but in this case that doesn't make sense. In Java or C# I would overload the equals method, in typescript that doesn't seem to be possible.
I considered to loop through the array with foreach and check each entry for equality, but that seems rather complicated and would bloat the code.
Is there something like equals in typescript ? How can I implement my own comparisons ?

Typescript doesn't add any functionality to JavaScript. It's just "typed" and some syntax improvements.
So, there's not a way to override equals in an equivalent way to what you might have done in C#.
However, you would have ended up likely using a Hash or a strongly-typed Dictionary in C# to do an efficient lookup (in addition to the array potentially), rather than using an "index of" function.
For that, I'd just suggest you use an associative array structure to store the Points.
You'd do something like:
class Point {
constructor(public x:Number = 0,
public y:Number = 0 ) {
}
public toIndexString(p:Point):String {
return Point.pointToIndexString(p.x, p.y);
}
static pointToIndexString(x:Number, y:Number):String {
return x.toString() + "#" + y.toString();
}
}
var points:any = {};
var p: Point = new Point(5, 5);
points[p.toIndexString()] = p;
If a Point doesn't exist, checking the points associative array will returned undefined.
A function wrapping the array would be simple:
function findPoint(x:Number, y:Number):Point {
return points[Point.pointToIndexString(x, y)];
}
Looping through all points would be easy to:
// define the callback (similar in concept to defining delegate in C#)
interface PointCallback {
(p:Point):void;
}
function allPoints(callback:PointCallback):void {
for(var k in points) {
callback(points[k]);
}
}
allPoints((p) => {
// do something with a Point...
});

You could wrap the collection in a PointList that only allows Point objects to be added via an add method, which checks to ensure no duplicates are added.
This has the benefit of encapsulating the "No duplicates" rule in a single place, rather than hoping that all calling code will check before adding a duplicate, which would duplicate the rule in many places.
class Point {
constructor(public x: number, public y: number) {
}
}
class PointList {
private points: Point[] = [];
get length() {
return this.points.length;
}
add(point: Point) {
if (this.exists(point)) {
// throw 'Duplicate point';
return false;
}
this.points.push(point);
return true;
}
exists(point: Point) {
return this.findIndex(point) > -1;
}
findIndex(point: Point) {
for (var i = 0; i < this.points.length; i++) {
var existingPoint = this.points[i];
if (existingPoint.x === point.x && existingPoint.y === point.y) {
return i;
}
}
return -1;
}
}
var pointList = new PointList();
var pointA = new Point(1, 1);
var pointB = new Point(1, 1);
var pointC = new Point(1, 2);
pointList.add(pointA);
alert(pointList.length); // 1
pointList.add(pointB);
alert(pointList.length); // 1
pointList.add(pointC);
alert(pointList.length); // 2

One thing you can do is try out linq.js. With that, you can do something like this:
var foundPoint = Enumerable.From(points)
.SingleOrDefault(function(p) {
return p.x == targetX && p.y == targety;
});
... you could then just implement this function on your object
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
static equals(points: Point[], candidate: Point): boolean {
var foundPoint = Enumerable.From(points)
.SingleOrDefault((p: Point): boolean => {
return p.x == candidate.x && p.y == candidate.y;
});
return foundPoint != null;
}
}
... and use it like this
var points = createPointsArray();
var point = getSomePoint();
// is point already in array?
var isPointInPoints = Point.equals(points, point)

Solution using a Set
I thought that a Set would be a perfect fit for this kind of problem since:
A value in the Set may only occur once; it is unique in the Set's collection. — MDN
However, you can still add [0, 1, 2, 3] multiples times to a Set (see this answer) since Sets use the SameValueZero(x, y) comparison algorithm to compare values (see also this answer). That's why you have to implement your own version of a Set.
Custom Set implemenation
I ported Johnny Bueti's solution to Sets. I don't overwrite any prototype as that is considered bad practice and can have unwanted side-effects.
Inside util.ts:
export interface Equatable {
/**
* Returns `true` if the two objects are equal, `false` otherwise.
*/
equals(object: any): boolean
}
export class SetCustomEquals<T extends Equatable> extends Set<T>{
add(value: T) {
if (!this.has(value)) {
super.add(value);
}
return this;
}
has(otherValue: T): boolean {
for (const value of this.values()) {
if (otherValue.equals(value)) {
return true;
}
}
return false;
}
}
Usage
import { Equatable, SetCustomEquals } from "./util";
class MyClass implements Equatable {
equals(other: MyClass): boolean {
... (your custom code)
}
}
const mySet= new SetCustomEquals<MyClass>();
const myObject = new MyClass();
mySet.add(myObject);

I would implement my own comparison method. In this instance I'm extending the Array.prototype - which I would not suggest unless you know exactly what you're doing - but you can very well create your own class that inherits from Array and implements a .contains() method like the one defined below.
interface Equatable {
/**
* Returns `true` if the two objects are equal, `false` otherwise.
*/
equals(object: any): boolean
}
class Point implements Equatable {
public equals(toPoint: Point): boolean {
return this.x === toPoint.x && this.y === toPoint.y;
}
}
// Augment the global scope
declare global {
interface Array<T extends Equatable> {
/**
* Returns `true` if an object is found, `false` otherwise. This method uses the `.equals()` method to compare `Equatable` objects for equality.
*/
contains(object: T): boolean
}
}
// Extend the Array.prototype.
Array.prototype.contains = function(object: Equatable) {
return this.findIndex(element => {
return element.equals(object);
}) !== -1;
}
The Equatable interface allows you to extend this behaviour to any other object:
class User implements Equatable {
public equals(toUser: User): boolean {
return this.uuid === toUser.uuid;
}
}

Related

OOP Javascript - Getter & Setters

I have been studying some oop principles in javascript but I have some problems related to restriction of my code. In C# and Java we can easily do this kind of restrictions and see the results but in javascript I dont understand completely what is going on under the hood.
Anyway, I am trying to create a code that people can not change it outside basically. This is why I learnt that we should use local variables instead of creating object properties and by using getFunction as a method we can just read from outside.
Another approach is using "Object.defineProperty" (Getters & Setters)
All these work perfectly for primitive types but I guess we have some problem. I can not restrict my code read only. Without writing any setter methods I can change values because of the reference type feature. So how can I approach this problem ?
// Abstraction
function Circle(radius) {
this.radius = radius;
let defaultLocation = { x: 0, y: 0 };
let color = 'red';
this.getDefaultLocation = function() {
return defaultLocation;
}
this.draw = function(){
for (let key in defaultLocation) {
console.log(key, defaultLocation[key]);
}
console.log('drawn');
};
// This can be changed from outside without set(){}
Object.defineProperty(this, 'defaultLocation', {
get(){
console.log('get function');
return defaultLocation;
}
});
// This needs set(){} to be changed
Object.defineProperty(this, 'color', {
get(){
console.log('get function');
return color;
},
set(value){
color = value;
}
});
}
const circle = new Circle(10);
You said
I am trying to create a code that people can not change it outside basically.
Refer to another stack overflow question. You can use the ES6 classes syntax, like so
class Circle {
constructor(radius) {
this.radius = radius;
}
get defaultLocation() {
return { x: 0, y: 0 }; // Read-only
}
get color() {
return 'red'; // Read-only
}
}
let test = new Circle(2);
console.log(test.defaultLocation); // {x: 0, y: 0}
test.defaultLocation = 10; // No error but won't do anything
console.log(test.defaultLocation); // {x: 0, y: 0}
If you only want a only a get() function, you can use a constructor function (not the latest ECMAScript syntax) with block-scope (let) variables being private instance variables. The key is NOT to use the this keyword.
function Circle(r = 0) {
// Private
let raduis = r
// Public
this.getRaduis = function() {
return raduis
}
}
let circle = new Circle(1)
console.log(circle.getRaduis())
console.log(circle.raduis)
Output:
1
undefined
You can add another option to defineProperty. This effectively creates a final static constant. Just set writeable = false.
Object.defineProperty(Circle, 'constants', {
value : {
constant : 0,
operation : () => {
console.log('Some non-primitive')
}
},
writable : false,
enumerable : false,
configurable : false
});
Circle.constants.operation()
Output:
Some non-primitive
See the "Writable attribute" section of the documentation.

(Typescript) Setter is not called on a sub-property of a class instance

this is a difficult question that I know is a problem for lots of programs (I will elaborate on this at the end). I want to create a custom setter in typescript but the datatype of the property being set is not just a number, string, bool it is actually a class object. This works fine - but if a property of the class instance is modified then the setter is not called. Here is an example of such a situation:
//This class contains two properties
class Point
{
public x : number;
public y : number;
constructor(x : number, y : number) { this.x = x; this.y = 0; }
}
//How here is another class that contains a Point
//But it is private and a getter/setter is used
class PointStore
{
private _foo : Point;
public get foo() : Point { return this._foo; }
//Here is the problem, the setter is only called when the whole of foo is changed
public set foo(n : Point) { this._foo = n; console.log("Foo has been set!"); }
constructor() { this._foo = new Point(0, 0); }
}
//Use case
let bar : PointStore = new PointStore();
bar.foo = new Point(10, 10); //Logs "Foo has been set!"
bar.foo.x = 20; //Doesn't log anything
The problem is pretty clear from the example but I just want to say the following:
Is there anyway around this at all? Because I have seen from APIs such as Unity3D they have opted to make their 'Point' class have only private members and so data can only be set through the constructor e.g:
//the 'Unity' solution
transform.position = new Vector2(10, 10); //Okay
transform.position.x = 20; //Error
But this is not at all a perfect solution to the problem, as it makes programming with the 'Point' class much more difficult from then on.
If anyone has a trick to solve this, it would be greatly appreciated.
The setter will be used only when you assign a value to the property. One way you can circumvent this is by using Object.assign, like so:
bar.foo = new Point(10, 10);
bar.foo = Object.assign(bar.foo, {x: 20})
You can also go deeper:
bar.foo = Object.assign(bar.foo, {x: {z: 20} })
You can use a Proxy for that:
class PointStore {
private _foo: Point;
constructor() {
this.createProxy(new Point(0, 0));
}
public get foo(): Point { return this._foo; }
public set foo(point: Point) {
this.createProxy(point);
console.log("Foo has been set!");
}
private createProxy(point: Point) {
this._foo = new Proxy(point, {
set: function (target: Point, property: string, value: any) {
target[property] = value;
console.log("Foo has been set (using proxy)!");
return true;
}
});
}
}
(code in playground)
The great thing about typescript is that, 2 types are compatible if they have the same shape. So, based on what you described, I think this seems to fit the bill and this will not break your code because PointStore here is compatible with Point class
class PointStore
{
private x : number;
private y : number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
};
public get x() { return this.point.x; };
public set x(x: number ) {
// your custom logic here
this.point.x = x;
};
// setter and getter for other y omitted
}

Define a property on an object that is both a value accessor and a function

There is an API (d3.map to be specific), which implements a method size. However I would like to extend it so that it remains backwards compatible, but it can also be used as a value accessor/getter (because that is how it's specified in ES6 now):
var map = d3.map();
map.set(0, 1);
console.log(map.size()); // Old code, should still work
xhr.send(JSON.stringify(map.size)); // Should also work (as a getter)
Is this possible?
There are no way to do what you like.
The only way out is replace size() with getSize()
class Map{
public get size(){
return 1;
}
public set size(size:number){
}
getSize():number{
return 1;
}
}
Update
There is one solution. Just use key length for new features, and use key size for backward compatibility. It would be looks like a C# List class.
class Map {
private count = 0;
public get length(): number {
return this.count;
}
public set length(length: number) {
this.count = length;
}
public size(): number;
public size(size: number): void;
public size(size?: number): any {
if (size) {
this.count = size;
} else {
return this.count;
}
}
}
Your example
var map = d3.map();
map.set(0, 1);
console.log(map.size()); // Old code, should still work
xhr.send(JSON.stringify(map.length)); // Should also work (as a getter)

How to define private constructors in javascript?

I have defined pure objects in JS which expose certain static methods which should be used to construct them instead of the constructor. How can I make a constructor for my class private in Javascript?
var Score = (function () {
// The private constructor
var Score = function (score, hasPassed) {
this.score = score;
this.hasPassed = hasPassed;
};
// The preferred smart constructor
Score.mkNewScore = function (score) {
return new Score(score, score >= 33);
};
return Score;
})();
Update: The solution should still allow me to test for x instanceof Score. Otherwise, the solution by #user2864740 of exposing only the static constructor works.
One can use a variable (initializing) inside a closure which can throw an error if the constructor was called directly instead of via a class method:
var Score = (function () {
var initializing = false;
var Score = function (score, hasPassed) {
if (!initializing) {
throw new Error('The constructor is private, please use mkNewScore.');
}
initializing = false;
this.score = score;
this.hasPassed = hasPassed;
};
Score.mkNewScore = function (score) {
intializing = true;
return new Score(score, score >= 33);
};
return Score;
})();
Is there a solution which will allow me to say x instanceof Score?
Yes. Conceptually, #user2864740 is right, but for instanceof to work we need to expose (return) a function instead of a plain object. If that function has the same .prototype as our internal, private constructor, the instanceof operator does what is expected:
var Score = (function () {
// the module API
function PublicScore() {
throw new Error('The constructor is private, please use Score.makeNewScore.');
}
// The private constructor
var Score = function (score, hasPassed) {
this.score = score;
this.hasPassed = hasPassed;
};
// Now use either
Score.prototype = PublicScore.prototype; // to make .constructor == PublicScore,
PublicScore.prototype = Score.prototype; // to leak the hidden constructor
PublicScore.prototype = Score.prototype = {…} // to inherit .constructor == Object, or
PublicScore.prototype = Score.prototype = {constructor:null,…} // for total confusion :-)
// The preferred smart constructor
PublicScore.mkNewScore = function (score) {
return new Score(score, score >= 33);
};
return PublicScore;
}());
> Score.mkNewScore(50) instanceof Score
true
> new Score
Error (…)
Simply don't expose the constructor function. The core issue with the original code is the "static method" is defined as a property of the constructor (which is used as a "class") as opposed a property of the module.
Consider:
return {
mkNewScore: Score.mkNewScore
// .. and other static/module functions
};
The constructor can still be accessed via .constructor, but .. meh. At this point, might as well just let a "clever user" have access.
return {
mkNewScore: function (score) {
var s = new Score(score, score >= 33);
/* Shadow [prototype]. Without sealing the object this can
be trivially thwarted with `del s.constructor` .. meh.
See Bergi's comment for an alternative. */
s.constructor = undefined;
return s;
}
};
In order to create a private constructor in JS, I like to create a private key that is only accessible in the class (function) file and provide a static factory function as the only allowed way to construct said class:
// in PrivateConstructorClass.js
// Use a Symbol as this will always be unique.
// If you don't have Symbol in your runtime,
// use a random string that nobody can reliably guess,
// such as the current time plus some other random values.
const PRIVATE_CONSTRUCTOR_KEY = Symbol()
class PrivateConstructorClass {
constructor(arg1, arg2, argN, constructorKey) {
if (constructorKey !== PRIVATE_CONSTRUCTOR_KEY) {
throw new Error('You must use the PrivateConstructorClass.create() to construct an instance.')
}
this.arg1 = arg1
this.arg2 = arg2
this.argN = argN
}
static create(arg1, arg2, argN) {
return new PrivateConstructorClass(arg1, arg2, argN, PRIVATE_CONSTRUCTOR_KEY)
}
}
// From Another JS File:
try {
const myFailedInstanceA = new PrivateConstructorClass('foo', 123, {
size: 'n'
})
} catch (err) {
console.error('Failed:', err.message)
}
const myFactoryInstance = PrivateConstructorClass.create('foo', 123, {
size: 'n'
})
console.log('Success:', myFactoryInstance)
Another possible simple approach is to use predicate function instead of instanceof. For typescript it can be a type guard and type synonym instead of a class can be exported:
// class is private
class _Score {
constructor() {}
}
export type Score = _Score
export function isScore(s): s is Score {
return s instanceof _Score
}
So to be fair the simplest answer is usually the best. An object literal is always a single instance. Not much reason for anything more complex other than, perhaps allocation of memory on demand.
That being said, here is a classical implementation of a singleton using ES6.
The instance "field" is "private". This really means we hide the instance as a property of the constructor. Somewhere not Constructor.prototype, which will be available to the instance through prototipical inheritance.
The constructor is "private". We really are just throwing an error when the caller is not the static getInstance method.
Also of note. It’s important to understand what the keyword this means in different contexts.
In the constructor, this points to the instance being created.
In the static getInstance method, this points to the left of the dot, Universe constructor function which, is an object like most things in JS and can hold properties.
class Universe {
constructor() {
if (!((new Error).stack.indexOf("Universe.getInstance") > -1)) {
throw new Error("Constructor is private. Use static method getInstance.");
}
this.constructor.instance = this;
this.size = 1;
}
static getInstance() {
if (this.instance) {
return this.instance;
}
return new this;
}
expand() {
this.size *= 2;
return this.size;
}
}
//console.log(Universe.getInstance())
//console.log(Universe.getInstance().expand())
//console.log(Universe.getInstance())
//console.log(new Universe())
const getInstance= () => { console.log('hi');
console.log("From singleton: ", Universe.getInstance()); return new Universe() };
console.log(getInstance())

Extend native JavaScript array

Is there any way to inherit a class from JS native function?
For example, I have a JS function like this:
function Xarray()
{
Array.apply(this, arguments);
//some stuff for insert, add and remove notification
}
Xarray.prototype = new Array();
I tried to convert it to Typescript but i failed!!
export class Xarray implements Array {
}
The compiler asks me to define all Array interface properties. I know if I need this Xarray.prototype = new Array();, I have to extend Array in TS.
How to extend the JS native object in TS?
Starting in TypeScript 1.6, you can extend the Array type, see What's new in TypeScript
Here's an example:
class MyNewArray<T> extends Array<T> {
getFirst() {
return this[0];
}
}
var myArray = new MyNewArray<string>();
myArray.push("First Element");
console.log(myArray.getFirst()); // "First Element"
If you are emitting to ES5 or below, then use the following code:
class MyNewArray<T> extends Array<T> {
constructor(...items: T[]) {
super(...items);
Object.setPrototypeOf(this, MyNewArray.prototype);
}
getFirst() {
return this[0];
}
}
Read more about why this is necessary here.
I don't think there is a way to inherit existing interfaces like Array,
export class Xarray implements Array {
}
You should create a function and inherit it with its prototype. Typescript also will accept it which is similar to javascript.
function Xarray(...args: any[]): void; // required in TS 0.9.5
function Xarray()
{
Array.apply(this, arguments);
// some stuff for insert, add and remove notification
}
Xarray.prototype = new Array();
UPDATE: This one is discussed well and provided the best solution for this at jqfaq.com.
//a dummy class it to inherite array.
class XArray {
constructor() {
Array.apply(this, arguments);
return new Array();
}
// we need this, or TS will show an error,
//XArray["prototype"] = new Array(); will replace with native js arrray function
pop(): any { return "" };
push(val): number { return 0; };
length: number;
}
//Adding Arrray to XArray prototype chain.
XArray["prototype"] = new Array();
//our Class
class YArray extends XArray {
///Some stuff
}
var arr = new YArray();
//we can use the array prop here.
arr.push("one");
arr.push("two");
document.writeln("First Elemet in array : " + arr[0]);
document.writeln("</br>Array Lenght : " + arr.length);
Hope, this might help you!!!
Yes it's possible to extend a native JS object in TS, however there is an issue extending built-in types (those included in lib.d.ts) like Array. Read this post for workaround: http://typescript.codeplex.com/workitem/4
So defining a type interface which extends a native type object at a later stage can be done in the following way:
/// <reference path="lib.d.ts"/>
interface Array {
sort: (input: Array) => Array;
}
Using on a concrete example, you can sort some elements on an array which define a sort function in an interface and later implements it on an object.
class Math implements Array {
sort : (x: Array) => Array {
// sorting the array
}
}
var x = new Math();
x.sort([2,3,32,3]);
While researching this, I came across Ben Nadel's excellent post on Extending JavaScript Arrays While Keeping Native Bracket-Notation Functionality. After some initial confusion on how to succesfully convert this into TypeScript, I created a fully working Collection class that can be subclassed.
It can do everything an Array can, including indexing by brackets,use in loop constructions (for, while, forEach), maps, etc.
The main implementation points are
Create an array in the constructor, add the methods to the array and return that from the constructor
Copy dummy declarations of Array methods to pass the implements Array bit
Example of usage:
var foo = new Foo({id : 1})
var c = new Collection();
c.add(foo)
c.length === 1; // => true
foo === c[0]; // => true
foo === c.find(1); // => true
I made it available as a gist, complete with tests and an example implementation of a subclass, but I present the full source here:
/*
* Utility "class" extending Array with lookup functions
*
* Typescript conversion of Ben Nadel's Collection class.
* https://gist.github.com/fatso83/3773d4cb5f39128b3732
*
* #author Carl-Erik Kopseng
* #author Ben Nadel (javascript original)
*/
export interface Identifiable {
getId : () => any;
}
export class Collection<T extends Identifiable> implements Array<T> {
constructor(...initialItems:any[]) {
var collection = Object.create(Array.prototype);
Collection.init(collection, initialItems, Collection.prototype);
return collection;
}
static init(collection, initialItems:any[], prototype) {
Object.getOwnPropertyNames(prototype)
.forEach((prop) => {
if (prop === 'constructor') return;
Object.defineProperty(collection, prop, { value: prototype[prop] })
});
// If we don't redefine the property, the length property is suddenly enumerable!
// Failing to do this, this would fail: Object.keys([]) === Object.keys(new Collection() )
Object.defineProperty(collection, 'length', {
value: collection.length,
writable: true,
enumerable: false
});
var itemsToPush = initialItems;
if (Array.isArray(initialItems[0]) && initialItems.length === 1) {
itemsToPush = initialItems[0];
}
Array.prototype.push.apply(collection, itemsToPush);
return collection;
}
// Find an element by checking each element's getId() method
public find(id:any):T;
// Find an element using a lookup function that
// returns true when given the right element
public find(lookupFn:(e:T) => boolean):T ;
find(x:any) {
var res, comparitor;
if (typeof x === 'function') {
comparitor = x;
} else {
comparitor = (e) => {
return e.getId() === x;
}
}
res = [].filter.call(this, comparitor);
if (res.length) return res[0];
else return null;
}
// Add an element
add(value:T);
// Adds all ements in the array (flattens it)
add(arr:T[]);
add(arr:Collection<T>);
add(value) {
// Check to see if the item is an array or a subtype thereof
if (value instanceof Array) {
// Add each sub-item using default push() method.
Array.prototype.push.apply(this, value);
} else {
// Use the default push() method.
Array.prototype.push.call(this, value);
}
// Return this object reference for method chaining.
return this;
}
remove(elem:T):boolean;
remove(lookupFn:(e:T) => boolean):boolean ;
remove(x:any):boolean {
return !!this._remove(x);
}
/**
* #return the removed element if found, else null
*/
_remove(x:any):T {
var arr = this;
var index = -1;
if (typeof x === 'function') {
for (var i = 0, len = arr.length; i < len; i++) {
if (x(this[i])) {
index = i;
break;
}
}
} else {
index = arr.indexOf(x);
}
if (index === -1) {
return null;
}
else {
var res = arr.splice(index, 1);
return res.length ? res[0] : null;
}
}
// dummy declarations
// "massaged" the Array interface definitions in lib.d.ts to fit here
toString:()=> string;
toLocaleString:()=> string;
concat:<U extends T[]>(...items:U[])=> T[];
join:(separator?:string)=> string;
pop:()=> T;
push:(...items:T[])=> number;
reverse:()=> T[];
shift:()=> T;
slice:(start?:number, end?:number)=> T[];
sort:(compareFn?:(a:T, b:T) => number)=> T[];
splice:(start?:number, deleteCount?:number, ...items:T[])=> T[];
unshift:(...items:T[])=> number;
indexOf:(searchElement:T, fromIndex?:number)=> number;
lastIndexOf:(searchElement:T, fromIndex?:number)=> number;
every:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> boolean;
some:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> boolean;
forEach:(callbackfn:(value:T, index:number, array:T[]) => void, thisArg?:any)=> void;
map:<U>(callbackfn:(value:T, index:number, array:T[]) => U, thisArg?:any)=> U[];
filter:(callbackfn:(value:T, index:number, array:T[]) => boolean, thisArg?:any)=> T[];
reduce:<U>(callbackfn:(previousValue:U, currentValue:T, currentIndex:number, array:T[]) => U, initialValue:U)=> U;
reduceRight:<U>(callbackfn:(previousValue:U, currentValue:T, currentIndex:number, array:T[]) => U, initialValue:U)=> U;
length:number;
[n: number]: T;
}
Of course, the bits on Identifiable, the find and remove methods are not needed, but I supply them none the less as a full fledged example is a tad more usable than a bare-bones Collection without any methods of its own.
Constructors that return an object implicitly substitute the value of this for callers of super(). Generated constructor code has to capture whatever super() returns and replace it with this.
Built-in classes use ES6 new.target to do the fixup but there's no way for ES5 code to ensure that new.target has a value calling the constructor.
This is why your extra methods vanish - your object has the wrong prototype.
All you need to do is fix the prototype chain after calling super().
export class RoleSet extends Array {
constructor() {
super();
Object.setPrototypeOf(this, RoleSet.prototype);
}
private containsRoleset(roleset:RoleSet){
if (this.length < roleset.length) return false;
for (var i = 0; i < roleset.length; i++) {
if (this.indexOf(roleset[i]) === -1) return false;
}
return true;
}
public contains(item: string | RoleSet): boolean {
if (item) {
return typeof item === "string" ?
this.indexOf(item) !== -1 :
this.containsRoleset(item);
} else {
return true;
}
}
}
Be aware that this curse shall afflict thy children and thy children's children until the end of code; you have to do the fixup in every generation of an inheritance chain.
In your case, a good bet would be to use this pattern:
function XArray(array) {
array = array || [];
//add a new method
array.second = function second() {
return array[1];
};
//overwrite an existing method with a super type pattern
var _push = array.push;
array.push = function push() {
_push.apply(array, arguments);
console.log("pushed: ", arguments);
};
//The important line.
return array
}
Then you can do:
var list = XArray([3, 4]);
list.second() ; => 4
list[1] = 5;
list.second() ; => 5
note however that:
list.constructor ; => Array and not XArray
If you already have a working Xarray implementation, I don't see the point in recreating it in typescript, which eventually will compile back to JavaScript.
But I do see the point in being able to use the Xarray in TypeScript.
In order to accomplish this, you simply need an interface for your Xarray. You don't even need to have a concrete implementation of your interface since your existing js implementation will serve as one.
interface Xarray{
apply(...arguments : any[]) : void;
//some stuff for insert, add and ...
}
declare var Xarray: {
new (...items: any[]): Xarray;
(...items: any[]): Xarray;
prototype: Array; // This should expose all the Array stuff from ECMAScript
}
After doing this, should be able to use your custom defined type through the declared variable without actually implementing it in TypeScript.
var xArr = new Xarray();
xArr.apply("blah", "hehe", "LOL");
You might look for reference here to see how they typed the ECMAScript Array API:
http://typescript.codeplex.com/SourceControl/changeset/view/2bee84410e02#bin/lib.d.ts
Yes you can augment the Builtin types and do it in a way that doesn't require all the paraphernalia of an XArray as described in the other answers and is closer to how you would do it in javascript.
Typescript allows a number of ways to do this, but for the Builtin types like Array and Number you need to use "merging" and declare the global namespace to augment the types, see the docs
so for Array we can add an optional metadata object and a get first member
declare global {
interface Array<T> {
meta?: any|null ,
getFirst(): T
}
}
if(!Array.prototype.meta )
{
Array.prototype.meta = null
}
if(!Array.prototype.getFirst )
{
Array.prototype.getFirst = function() {
return this[0];
}
}
we can use this like so:
let myarray: number[] = [ 1,2,3 ]
myarray.meta = { desc: "first three ints" }
let first: number = myarray.getFirst()
The same goes for Number say I want to add a modulo function that isn't limited like the remainder %
declare global {
interface Number {
mod(n:number): number
}
}
if(!Number.prototype.mod )
{
Number.prototype.mod = function (n: number) {
return ((this % n) + n) % n;
}
}
and we can use it like so:
let foo = 9;
console.log("-9.mod(5) is "+ foo.mod(5))
For Functions that we may want to add an API to ie to make it behave like a function and an object we can use hybrid types (see docs)
// augment a (number) => string function with an API
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
//helper function to get my augmented counter function with preset values
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
use it like so:-
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
With purpose to overcome the problem of extension of the native Array class, I took advantage of a decorator.
function extendArray(constructor: Function) {
Object.getOwnPropertyNames(constructor.prototype)
.filter(name => name !== 'constructor')
.forEach(name => {
const attributes = Object.getOwnPropertyDescriptor(constructor.prototype, name);
Object.defineProperty(Array.prototype, name, attributes);
});
}
#extendArray
export class Collection<T> extends Array<T> {
constructor(...args: T[]) {
super(...args);
}
// my appended methods
}
BTW This decorator can be made more generic (for other native classes) if to use a decorator factory.
Don't know how frowned upon this is but for example I needed to create an array of BulletTypes so that I could cycle through them. What I did is the following:
interface BulletTypesArray extends Array<BulletType> {
DefaultBullet?: Bullet;
}
var BulletTypes: BulletTypesArray = [ GreenBullet, RedBullet ];
BulletTypes.DefaultBullet = GreenBullet;
Obviously you could could also make a generic interface, something like interface SuperArray<T> extends Array<T>.

Categories

Resources