Как расширить собственный массив JavaScript в TypeScript
Есть ли способ наследования класса из собственной функции JS.
Для (например) у меня функция js такая, как в my 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();
Мне нужно расширить Array в TS, как расширить собственный объект JS в TS?
Ответы
Ответ 1
Я не думаю, что существует способ наследования существующих интерфейсов, таких как 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. p >
//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
Начиная с TypeScript 1.6, вы можете расширить тип массива, см. Что нового в 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"
Ответ 3
Да, возможно расширить собственный объект JS в TS, однако есть проблема с расширением встроенных типов (включенных в 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]);
Ответ 4
Во время исследования я натолкнулся на замечательный пост Бена Наделя на Расширение массивов JavaScript при сохранении функциональных возможностей для привязки к ячейкам. После некоторой путаницы в том, как успешно преобразовать это в TypeScript, я создал полностью рабочий класс Collection, который может быть подклассом.
Он может делать все, что может сделать Array, включая индексирование по скобкам, в конструкциях циклов (для, while, forEach), карт и т.д.
Основные точки реализации
- Создайте массив в конструкторе, добавьте методы в массив и верните его из конструктора
- Копировать фиктивные объявления методов Array для передачи бит
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
Я сделал его доступным как сущность, в комплекте с тестами и примером реализации подкласса, но здесь я представляю полный источник:
/*
* Utility "class" extending Array with lookup functions
*
* Typescript conversion of Ben Nadel 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 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
не нужны, но я тем не менее поставляю их, поскольку полноценный пример является более удобным, чем коллекция без костей без любые собственные методы.
Ответ 5
Конструкторы, которые возвращают объект, неявно заменяют значение 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;
}
}
}
Имейте в виду, что это проклятие поразит детей ваших детей и детей ваших детей до конца кода; вам нужно сделать исправление в каждом поколении цепочки наследования.
Ответ 6
Если у вас уже есть рабочая реализация Xarray
, я не вижу смысла воссоздать ее в typescript, которая в конечном итоге будет скомпилирована на JavaScript.
Но я вижу, что я могу использовать Xarray
в TypeScript.
Для этого вам просто нужен интерфейс для 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
Ответ 7
В вашем случае хорошей ставкой было бы использовать этот шаблон:
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
Ответ 8
Да, вы можете расширить типы Builtin и сделать это так, чтобы не требовать всех атрибутов XArray, как описано в других ответах, и ближе к тому, как вы это сделаете в javascript.
Typescript позволяет несколько способов сделать это, но для типов Builtin типа Array и Number вам нужно использовать "слияние" и объявить глобальное пространство имен для увеличения типов, см. документы
поэтому для 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 say. Я хочу добавить функцию модуля, которая не ограничена, как остаток%
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, чтобы заставить его вести себя как функция и объект, мы можем использовать типы (см. 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;
Ответ 9
Не знаю, как это нахмурило, но, к примеру, мне нужно было создать массив BulletTypes, чтобы я мог их перебирать. Я сделал следующее:
interface BulletTypesArray extends Array<BulletType> {
DefaultBullet?: Bullet;
}
var BulletTypes: BulletTypesArray = [ GreenBullet, RedBullet ];
BulletTypes.DefaultBullet = GreenBullet;
Очевидно, вы могли бы также создать общий интерфейс, что-то вроде interface SuperArray<T> extends Array<T>
.