I'm trying to extend built-in array class and create custom collections based native array class.
I'm able to create collection with custom name, it works well, but I can't use my custom methods. If I use findAnimal method it gives me method is not a function
error. If I log collection and inspect prototypes I can't see my custom methods.
I realized that I transpile down the code into the es5 and if I not, works well, Problem occurs with the ts compiler's es5 downgraded code.
If we use babel to downgrade code into es5, transpiled code works well. Either ts compiler missing something when transpiling the code or I missing some vital configuration.
interface Animal {
name: string;
weight: number
}
class AnimalCollection extends Array <Animal> {
constructor(name, ...items) {
super(...items);
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: name
})
}
findAnimal(name): Animal {
return this.find(a => a.name === name) || null;
}
}
const animalsArray = [
{name: 'TD-23', weight: 60},
{name: 'TD-25', weight: 50},
{name: 'TXD-26', weight: 120},
{name: 'TYD-26', weight: 40}
];
const animals = new AnimalCollection('Deers', ...animalsArray)
console.log(animals.findAnimal('TD-23'));
// Uncaught TypeError: animals.findAnimal is not a function
ES5 Downgraded
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
var AnimalCollection = /** @class */ (function (_super) {
__extends(AnimalCollection, _super);
function AnimalCollection(name) {
var items = [];
for (var _i = 1; _i < arguments.length; _i++) {
items[_i - 1] = arguments[_i];
}
var _this = _super.apply(this, items) || this;
Object.defineProperty(_this, 'name', {
enumerable: false,
writable: false,
value: name
});
return _this;
}
AnimalCollection.prototype.findAnimal = function (name) {
return this.find(function (a) { return a.name === name; }) || null;
};
return AnimalCollection;
}(Array));
var animalsArray = [
{ name: 'TD-23', weight: 60 },
{ name: 'TD-25', weight: 50 },
{ name: 'TXD-26', weight: 120 },
{ name: 'TYD-26', weight: 40 }
];
var animals = new (AnimalCollection.bind.apply(AnimalCollection, __spreadArrays([void 0, 'Deers'], animalsArray)))();
console.log(animals.findAnimal('TD-23'));
To answer your modified question, TypeScript doesn't let you extend built-ins like Array
, Error
etc. The reason is written here .
In ES2015, constructors which return an object implicitly substitute the value of this for any callers of super(...). It is necessary for generated constructor code to capture any potential return value of super(...) and replace it with this.
As a result, subclassing Error, Array, and others may no longer work as expected. This is due to the fact that constructor functions for Error, Array, and the like use ECMAScript 6's new.target to adjust the prototype chain; however, there is no way to ensure a value for new.target when invoking a constructor in ECMAScript 5. Other downlevel compilers generally have the same limitation by default.
So if you must extend built-in Array
under ES5 environment, then you can try Babel for compiling your code. However be aware that it has it's limitation as stated here .
Built-in classes such as Date, Array, DOM etc cannot be properly subclassed due to limitations in ES5 (for the transform-classes plugin). You can try to use babel-plugin-transform-builtin-extend based on Object.setPrototypeOf and Reflect.construct, but it also has some limitations.
While the code itself is perfectly find and also executes fine in the browser, I think the error you are getting is because of the TypeScript compiler.
For the source code
interface Animal {
name: string;
weight: number
}
class AnimalCollection extends Array <Animal> {
constructor(name: string, ...items: Animal[]) {
super(...items);
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: name
})
}
findAnimal(name:string): Animal | null {
return this.find(a => a.name === name) || null;
}
}
const animalsArray = [
{name: 'TD-23', weight: 60},
{name: 'TD-25', weight: 50},
{name: 'TXD-26', weight: 120},
{name: 'TYD-26', weight: 40}
];
const animals = new AnimalCollection('Deers', ...animalsArray)
console.log(animals.findAnimal('TD-23'));
If the compiler target option is set to ES5
, then it produces code that breaks the implementation. The code it generates is
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
var AnimalCollection = /** @class */ (function (_super) {
__extends(AnimalCollection, _super);
function AnimalCollection(name) {
var items = [];
for (var _i = 1; _i < arguments.length; _i++) {
items[_i - 1] = arguments[_i];
}
var _this = _super.apply(this, items) || this;
Object.defineProperty(_this, 'name', {
enumerable: false,
writable: false,
value: name
});
return _this;
}
AnimalCollection.prototype.findAnimal = function (name) {
return this.find(function (a) { return a.name === name; }) || null;
};
return AnimalCollection;
}(Array));
var animalsArray = [
{ name: 'TD-23', weight: 60 },
{ name: 'TD-25', weight: 50 },
{ name: 'TXD-26', weight: 120 },
{ name: 'TYD-26', weight: 40 }
];
var animals = new (AnimalCollection.bind.apply(AnimalCollection, __spreadArrays([void 0, 'Deers'], animalsArray)))();
console.log(animals.findAnimal('TD-23'));
However, if we set the target
in tsconfig.json
to something equal to or greater than ES2015
, then the code it generates is
"use strict";
class AnimalCollection extends Array {
constructor(name, ...items) {
super(...items);
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: name
});
}
findAnimal(name) {
return this.find(a => a.name === name) || null;
}
}
const animalsArray = [
{ name: 'TD-23', weight: 60 },
{ name: 'TD-25', weight: 50 },
{ name: 'TXD-26', weight: 120 },
{ name: 'TYD-26', weight: 40 }
];
const animals = new AnimalCollection('Deers', ...animalsArray);
console.log(animals.findAnimal('TD-23'));
Which of course works. So I think there is an issue in TypeScript compiler for version ES5 or less, which breaks the implementation. I have tried using Babel for compilation and it works for ES5.
I can see that you was missing some any
, this is the code that works for me:
interface Animal {
name: string;
weight: number
}
class AnimalCollection extends Array <Animal> {
constructor(name: string, ...items : Array <Animal>) { // <-- missing types
super(...items);
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: name
})
}
findAnimal(name : any): Animal|null { // <-- missing null
return this.find(a => a.name === name) || null;
}
}
const animalsArray = [
{name: 'TD-23', weight: 60},
{name: 'TD-25', weight: 50},
{name: 'TXD-26', weight: 120},
{name: 'TYD-26', weight: 40}
];
const animals = new AnimalCollection('Deers', ...animalsArray)
console.log(animals.findAnimal('TD-23'));
However this code generates the following JS:
"use strict";
class AnimalCollection extends Array {
constructor(name, ...items) {
super(...items);
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: name
});
}
findAnimal(name) {
return this.find(a => a.name === name) || null;
}
}
const animalsArray = [
{ name: 'TD-23', weight: 60 },
{ name: 'TD-25', weight: 50 },
{ name: 'TXD-26', weight: 120 },
{ name: 'TYD-26', weight: 40 }
];
const animals = new AnimalCollection('Deers', ...animalsArray);
console.log(animals.findAnimal('TD-23'));
Which does not generate any error
it's how you wrote your function that causes the error
interface Animal { name: string; weight: number } class AnimalCollection extends Array <Animal> { constructor(name, ...items) { super(...items); Object.defineProperty(this, 'name', { enumerable: false, writable: false, value: name }) } findAnimal:Animal=(name:String)=> { return this.find(a => a.name === name) || null; } } const animalsArray = [ {name: 'TD-23', weight: 60}, {name: 'TD-25', weight: 50}, {name: 'TXD-26', weight: 120}, {name: 'TYD-26', weight: 40} ]; const animals = new AnimalCollection('Deers', ...animalsArray) console.log(animals.findAnimal('TD-23')); // Uncaught TypeError: animals.findAnimal is not a function
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.