有没有办法从JS本机函数继承一个类?
例如,我有一个像这样的JS函数:
function Xarray()
{
Array.apply(this, arguments);
//some stuff for insert, add and remove notification
}
Xarray.prototype = new Array();
我尝试将其转换为Typescript但我失败了!!
export class Xarray implements Array {
}
编译器要求我定义所有Array
接口属性。我知道如果我需要这个Xarray.prototype = new Array();
,我必须在TS中扩展Array
。
如何在TS中扩展JS本机对象?
答案 0 :(得分:37)
从TypeScript 1.6开始,您可以扩展数组类型,请参阅What's new in TypeScript
以下是一个例子:
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"
如果您要向ES5或更低级别发射,请使用以下代码:
class MyNewArray<T> extends Array<T> {
constructor(...items: T[]) {
super(...items);
Object.setPrototypeOf(this, MyNewArray.prototype);
}
getFirst() {
return this[0];
}
}
详细了解为何需要here。
答案 1 :(得分:12)
我认为没有办法继承像Array这样的现有接口,
export class Xarray implements Array {
}
您应该创建一个函数并使用其原型继承它。 Typescript也会接受它类似于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();
更新:对此进行了详细讨论,并在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);
希望,这可能对你有所帮助!!!
答案 2 :(得分:8)
是的,可以在TS中扩展本机JS对象,但是存在扩展内置类型(lib.d.ts中包含的类型)的问题,例如Array。阅读此文章以获得解决方法:http://typescript.codeplex.com/workitem/4
因此,可以通过以下方式定义在稍后阶段扩展本机类型对象的类型接口:
/// <reference path="lib.d.ts"/>
interface Array {
sort: (input: Array) => Array;
}
使用一个具体示例,您可以对数组中的某些元素进行排序,这些元素在接口中定义排序函数,然后在对象上实现它。
class Math implements Array {
sort : (x: Array) => Array {
// sorting the array
}
}
var x = new Math();
x.sort([2,3,32,3]);
答案 3 :(得分:5)
在研究这个问题时,我偶然发现了Ben Nadel在Extending JavaScript Arrays While Keeping Native Bracket-Notation Functionality上的优秀帖子。在对如何将其成功转换为TypeScript的初步混淆之后,我创建了一个可以进行子类化的完全工作的Collection类。
它可以完成数组所能做的所有事情,包括用括号括起来,在循环结构中使用(for,while,forEach),map等。
主要实施点是
implements Array
位使用示例:
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
我做了available as a gist,完成了测试和子类的示例实现,但是我在这里提供了完整的源代码:
/*
* 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;
}
当然,不需要Identifiable
,find
和remove
方法上的位,但我提供它们,因为完整的示例比使用更有用。一个没有任何方法的裸骨收藏。
答案 4 :(得分:3)
返回对象的构造函数隐式地将this
的值替换为super()
的调用者。生成的构造函数代码必须捕获super()
返回的任何内容,并将其替换为this
。
内置类使用ES6 new.target
来执行修复,但是ES5代码无法确保new.target具有调用构造函数的值。
这就是为什么你的额外方法消失了 - 你的对象有错误的原型。
您需要做的就是在调用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;
}
}
}
要知道这个咒诅会折磨你的孩子和孩子的孩子,直到守则结束;你必须在每一代继承链中进行修复。
答案 5 :(得分:1)
如果你已经有一个有效的Xarray
实现,我没有看到在typescript中重新创建它的重点,最终将编译回JavaScript。
但我确实看到了能够在TypeScript中使用Xarray
的重点。
为了实现这一目标,您只需要Xarray
的界面。您甚至不需要具体的接口实现,因为现有的js实现将作为一个实现。
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
}
执行此操作后,应该能够通过声明的变量使用自定义类型,而无需在TypeScript中实际实现它。
var xArr = new Xarray();
xArr.apply("blah", "hehe", "LOL");
您可以在此处查看参考资料,了解他们如何输入ECMAScript Array API
:
http://typescript.codeplex.com/SourceControl/changeset/view/2bee84410e02#bin/lib.d.ts
答案 6 :(得分:1)
在你的情况下,一个好的选择是使用这种模式:
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
}
然后你可以这样做:
var list = XArray([3, 4]);
list.second() ; => 4
list[1] = 5;
list.second() ; => 5
但请注意:
list.constructor ; => Array and not XArray
答案 7 :(得分:1)
是的,您可以增加内置类型,并且不会像其他答案中所描述的那样需要XArray的所有设备,并且更接近于如何在javascript中执行此操作。
Typescript允许多种方法来实现这一点,但是对于像Array和Number这样的内置类型,你需要使用&#34;合并&#34;并声明全局命名空间以扩充类型,请参阅the docs
因此对于Array,我们可以添加一个可选的元数据对象和一个获取第一个成员
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];
}
}
我们可以这样使用:
let myarray: number[] = [ 1,2,3 ]
myarray.meta = { desc: "first three ints" }
let first: number = myarray.getFirst()
同样适用于Number我想添加一个模数函数,它不受限于余数%
declare global {
interface Number {
mod(n:number): number
}
}
if(!Number.prototype.mod )
{
Number.prototype.mod = function (n: number) {
return ((this % n) + n) % n;
}
}
我们可以像这样使用它:
let foo = 9;
console.log("-9.mod(5) is "+ foo.mod(5))
对于我们可能想要添加API的函数,即使其行为像函数和对象,我们可以使用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;
}
像这样使用它: -
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
答案 8 :(得分:0)
为了克服原生Array类扩展的问题,我利用了一个装饰器。
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
}
&#13;
BTW如果要使用装饰工厂,这个装饰器可以更通用(对于其他本机类)。
答案 9 :(得分:-2)
不知道这有多皱眉,但是例如我需要创建一个BulletTypes数组,以便我可以循环使用它们。我做的是以下内容:
interface BulletTypesArray extends Array<BulletType> {
DefaultBullet?: Bullet;
}
var BulletTypes: BulletTypesArray = [ GreenBullet, RedBullet ];
BulletTypes.DefaultBullet = GreenBullet;
显然你也可以创建一个通用界面,比如interface SuperArray<T> extends Array<T>
。