Extending built-in natives in ES6 with Babel
Extending native classes is not supported by Babel.
It was removed in version 5.2.17 (see this commit)
It was removed because it was not working properly, see the bug: https://phabricator.babeljs.io/T1424
It's unlikely it will be ever added because it's not a feature that can be simulated. We will have to wait for native support in browsers (some already support it now in experimental mode). That also means it will currently behave differently in different browsers.
How can I extend the Array class in Babel?
I'm not sure about Babel support but node.js started supporting (partially) Array
subclassing since version 4.3.2 and implemented full subclassing support in version 6.8.1 (http://node.green/#Array-is-subclassable). So if you're using node 6 Array
subclassing should work.
If Babel is failing you you can always do it the "classical" way:
// Code tested and works in 6.3.1:
function Stack = () {};
Stack.prototype = Object.create(Array.prototype);
Stack.prototype.last = function () {
return this[this.length - 1];
}
var s = new Stack();
Extending MediaSource with Babel -- how to properly call super()?
Generally extending builtin types does not work with compiled classes from Babel, so you'd need to configure Babel to not process classes, and limit your application to only browsers that support classes.
Assuming your target browsers all support ES6 class syntax, the easiest approach would be to use babel-preset-env
configured for those target environments.
You can also try to use transform-builtin-extend
in your Babel config for MediaSource
, though that does tend to vary with exactly which things can be extended.
Extending Built-in Array With ES6 Classes - Method is not a function error
UPDATED ANSWER
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.
OLD ANSWER
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.
Babel can't properly extend Array?
Babel.js can't extend the built-in classes properly:
Built-in classes such as
Date
,Array
,DOM
etc cannot be properly subclassed due to limitations in ES5 (for thees2015-classes
plugin). You can try to usebabel-plugin-transform-builtin-extend
based onObject.setPrototypeOf
andReflect.construct
, but it also has some limitations.-- https://babeljs.io/docs/usage/caveats/#classes
ES6 Class extends Array: workaround for ES5 Babel transpile
After some discussions here I was able to build a solution satisfied both of the requirements: keep original ES6 class as a state for new functionality and have this new functionality on the prototype as well as it is for the Array.prototype methods.
class CacheProto {
add(item) {
if(!item.doNotRemove)
this.push(item)
}
printLast() {
if(this.length > 0)
console.log(this[this.length - 1].text)
}
}
function Cache() {
const instance = [];
instance.push.apply(instance, arguments);
Object.setPrototypeOf(instance, Cache.prototype);
return instance;
}
Cache.prototype = Object.create(Array.prototype);
Object.getOwnPropertyNames(CacheProto.prototype).forEach(methodName =>
Cache.prototype[methodName] = CacheProto.prototype[methodName]
);
The only difference between this CacheProto
and the original class from the Question is that the CacheProto
class does not extend the Array
.
The end plunker could be obtained here. It contains this solution and all intermediate variants.
Mocha+Babel replaces class that extends array by array
I'm assuming you're using Babel to transpile this to ES5.
Array
doesn't behave quite the same as other constructors. There isn't really an equivalent syntax for doing this in ES5, because to some extent it was not even really possible in ES5. As such, what Babel generates in attempt to create equivalent syntax will not work the way you'd expect it to.
If you take a look at the generated code, you'll find this line:
return _possibleConstructorReturn(this, (A.__proto__ || Object.getPrototypeOf(A)).apply(this, arguments));
At this point, A.__proto__
will be Array
, and the constructor was given no arguments, so this is calling Array.apply(this, [])
and passing the result to the call
argument of _possibleConstructorReturn
.
In _possibleConstructorReturn
, you'll find this line:
return call && (typeof call === "object" || typeof call === "function") ? call : self; }
In most cases, calling apply
on a constructor (invoking it without new
) will return undefined. Thus, this function will return the self
parameter, which is the this
from the A
contructor invocation.
Array
does not behave like a normal constructor, however. If you invoke it without new
, it will still return an Array. So what you get coming out of your A
constructor is just a plain old array.
There may be ways around this, but Babel does not support any. To be honest, if you need your code to work in ES5, you probably shouldn't try to extend arrays like this. :\
What often works as an alternative is to write a simple class that stores an array as a property, with wrapper methods for the array operations you need. Say a get
method to use in place of square bracket property access, a push
that simply invokes push
on the wrapped array.
Extending Error in Javascript with ES6 syntax & Babel
Based on Karel Bílek's answer, I'd make a small change to the constructor
:
class ExtendableError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = (new Error(message)).stack;
}
}
}
// now I can extend
class MyError extends ExtendableError {}
var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);
This will print MyError
in the stack, and not the generic Error
.
It will also add the error message to the stack trace - which was missing from Karel's example.
It will also use captureStackTrace
if it's available.
With Babel 6, you need transform-builtin-extend (npm) for this to work.
transform-builtin-extend seems does not working
You have to set the approximate
option to false
. From the plugin GitHub repository:
On older browsers that do not support reassigning the prototype of an existing object, you will need to enable the approximate mode, which will fall back to the Babel 5 behavior of using simple ES5 inheritance to
approximate
extending a class, though your results may vary depending on your goals.
Related Topics
How to Remove Element from an Array in JavaScript
How to Add Many Functions in One Ng-Click
JavaScript Regular Expressions and Sub-Matches
Why Is Immutability So Important (Or Needed) in JavaScript
New Line in JavaScript Alert Box
Executing JavaScript from Python
Es6: Conditional & Dynamic Import Statements
How to Close a Dropdown on Click Outside
How to Animate Scrolltop with Jquery
How to Define Setter/Getter on Prototype
JavaScript If Statement with Multiple Permissible Conditions
JavaScript Xmlhttprequest Using JSONp
How to Remove Spaces from a String Using JavaScript
How to Get the Current Time Only in JavaScript
Using Chrome, How to Find to Which Events Are Bound to an Element