Is there any way to create an array-like object in JavaScript, without using the built-in array? I'm specifically concerned with behavior like this:
var sup = new Array(5);
//sup.length here is 0
sup[0] = 'z3ero';
//sup.length here is 1
sup[1] = 'o3ne';
//sup.length here is 2
sup[4] = 'f3our';
//sup.length here is 5
The particular behavior I'm looking at here is that sup.length changes without any methods being called. I understand from this question that the [] operator is overloaded in the case of arrays, and this accounts for this behavior. Is there a pure-javascript way to duplicate this behavior, or is the language not flexible enough for that?
According to the Mozilla docs, values returned by regex also do funky things with this index. Is this possible with plain javascript?
[] operator is the native way to access to object properties. It is not available in the language to override in order to change its behaviour.
If what you want is return computed values on the [] operator, you cannot do that in JavaScript since the language does not support the concept of computed property. The only solution is to use a method that will work the same as the [] operator.
MyClass.prototype.getItem = function(index)
{
return {
name: 'Item' + index,
value: 2 * index
};
}
If what you want is have the same behaviour as a native Array in your class, it is always possible to use native Array methods directly on your class. Internally, your class will store data just like a native array does but will keep its class state. jQuery does that to make the jQuery class have an array behaviour while retaining its methods.
MyClass.prototype.addItem = function(item)
{
// Will add "item" in "this" as if it was a native array
// it will then be accessible using the [] operator
Array.prototype.push.call(this, item);
}
Yes, you can subclass an array into an arraylike object easily in JavaScript:
var ArrayLike = function() {};
ArrayLike.prototype = [];
ArrayLike.prototype.shuffle = // ... and so on ...
You can then instantiate new array like objects:
var cards = new Arraylike;
cards.push('ace of spades', 'two of spades', 'three of spades', ...
cards.shuffle();
Unfortunately, this does not work in MSIE. It doesn't keep track of the length property. Which rather deflates the whole thing.
The problem in more detail on Dean Edwards' How To Subclass The JavaScript Array Object. It later turned out that his workaround wasn't safe as some popup blockers will prevent it.
Update: It's worth mentioning Juriy "kangax" Zaytsev's absolutely epic post on the subject. It pretty much covers every aspect of this problem.
Now we have ECMAScript 2015 (ECMA-262 6th Edition; ES6), we have proxy objects, and they allow us to implement the Array behaviour in the language itself, something along the lines of:
function FakeArray() {
const target = {};
Object.defineProperties(target, {
"length": {
value: 0,
writable: true
},
[Symbol.iterator]: {
// http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype-##iterator
value: () => {
let index = 0;
return {
next: () => ({
done: index >= target.length,
value: target[index++]
})
};
}
}
});
const isArrayIndex = function(p) {
/* an array index is a property such that
ToString(ToUint32(p)) === p and ToUint(p) !== 2^32 - 1 */
const uint = p >>> 0;
const s = uint + "";
return p === s && uint !== 0xffffffff;
};
const p = new Proxy(target, {
set: function(target, property, value, receiver) {
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-array-exotic-objects-defineownproperty-p-desc
if (property === "length") {
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-arraysetlength
const newLen = value >>> 0;
const numberLen = +value;
if (newLen !== numberLen) {
throw RangeError();
}
const oldLen = target.length;
if (newLen >= oldLen) {
target.length = newLen;
return true;
} else {
// this case gets more complex, so it's left as an exercise to the reader
return false; // should be changed when implemented!
}
} else if (isArrayIndex(property)) {
const oldLenDesc = Object.getOwnPropertyDescriptor(target, "length");
const oldLen = oldLenDesc.value;
const index = property >>> 0;
if (index > oldLen && oldLenDesc.writable === false) {
return false;
}
target[property] = value;
if (index > oldLen) {
target.length = index + 1;
}
return true;
} else {
target[property] = value;
return true;
}
}
});
return p;
}
I can't guarantee this is actually totally correct, and it doesn't handle the case where you alter length to be smaller than its previous value (the behaviour there is a bit complex to get right; roughly it deletes properties so that the length property invariant holds), but it gives a rough outline of how you can implement it. It also doesn't mimic behaviour of [[Call]] and [[Construct]] on Array, which is another thing you couldn't do prior to ES6—it wasn't possible to have divergent behaviour between the two within ES code, though none of that is hard.
This implements the length property in the same way the spec defines it as working: it intercepts assignments to properties on the object, and alters the length property if it is an "array index".
Unlike what one can do with ES5 and getters, this allows one to get length in constant time (obviously, this still depends on the underlying property access in the VM being constant time), and the only case in which it provides non-constant time performance is the not implemented case when newLen - oldLen properties are deleted (and deletion is slow in most VMs!).
Is this what you're looking for?
Thing = function() {};
Thing.prototype.__defineGetter__('length', function() {
var count = 0;
for(property in this) count++;
return count - 1; // don't count 'length' itself!
});
instance = new Thing;
console.log(instance.length); // => 0
instance[0] = {};
console.log(instance.length); // => 1
instance[1] = {};
instance[2] = {};
console.log(instance.length); // => 3
instance[5] = {};
instance.property = {};
instance.property.property = {}; // this shouldn't count
console.log(instance.length); // => 5
The only drawback is that 'length' will get iterated over in for..in loops as if it were a property. Too bad there isn't a way to set property attributes (this is one thing I really wish I could do).
The answer is: there's no way as of now. The array behavior is defined in ECMA-262 as behaving this way, and has explicit algorithms for how to deal with getting and setting of array properties (and not generic object properties). This somewhat dismays me =(.
Mostly you don't need a predefined index-size for arrays in javascript, you can just do:
var sup = []; //Shorthand for an empty array
//sup.length is 0
sup.push(1); //Adds an item to the array (You don't need to keep track of index-sizes)
//sup.length is 1
sup.push(2);
//sup.length is 2
sup.push(4);
//sup.length is 3
//sup is [1, 2, 4]
If you're concerned about performance with your sparse array (though you probably shouldn't be) and wanted to ensure that the structure was only as long as the elements you handed it, you could do this:
var sup = [];
sup['0'] = 'z3ero';
sup['1'] = 'o3ne';
sup['4'] = 'f3our';
//sup now contains 3 entries
Again, it's worth noting that you won't likely see any performance gain by doing this. I suspect that Javascript already handles sparse arrays quite nicely, thank you very much.
You could also create your own length method like:
Array.prototype.mylength = function() {
var result = 0;
for (var i = 0; i < this.length; i++) {
if (this[i] !== undefined) {
result++;
}
}
return result;
}
Interface and implementation
The case is a simple implementation of the original array packaging, you can replace the data structure and refer to the common interface can be implemented.
export type IComparer<T> = (a: T, b: T) => number;
export interface IListBase<T> {
readonly Count: number;
[index: number]: T;
[Symbol.iterator](): IterableIterator<T>;
Add(item: T): void;
Insert(index: number, item: T): void;
Remove(item: T): boolean;
RemoveAt(index: number): void;
Clear(): void;
IndexOf(item: T): number;
Sort(): void;
Sort(compareFn: IComparer<T>): void;
Reverse(): void;
}
export class ListBase<T> implements IListBase<T> {
protected list: T[] = new Array();
[index: number]: T;
get Count(): number {
return this.list.length;
}
[Symbol.iterator](): IterableIterator<T> {
let index = 0;
const next = (): IteratorResult<T> => {
if (index < this.Count) {
return {
value: this[index++],
done: false,
};
} else {
return {
value: undefined,
done: true,
};
}
};
const iterator: IterableIterator<T> = {
next,
[Symbol.iterator]() {
return iterator;
},
};
return iterator;
}
constructor() {
return new Proxy(this, {
get: (target, propKey, receiver) => {
if (typeof propKey === "string" && this.isSafeArrayIndex(propKey)) {
return Reflect.get(this.list, propKey);
}
return Reflect.get(target, propKey, receiver);
},
set: (target, propKey, value, receiver) => {
if (typeof propKey === "string" && this.isSafeArrayIndex(propKey)) {
return Reflect.set(this.list, propKey, value);
}
return Reflect.set(target, propKey, value, receiver);
},
});
}
Reverse(): void {
throw new Error("Method not implemented.");
}
Insert(index: number, item: T): void {
this.list.splice(index, 0, item);
}
Add(item: T): void {
this.list.push(item);
}
Remove(item: T): boolean {
const index = this.IndexOf(item);
if (index >= 0) {
this.RemoveAt(index);
return true;
}
return false;
}
RemoveAt(index: number): void {
if (index >= this.Count) {
throw new RangeError();
}
this.list.splice(index, 1);
}
Clear(): void {
this.list = [];
}
IndexOf(item: T): number {
return this.list.indexOf(item);
}
Sort(): void;
Sort(compareFn: IComparer<T>): void;
Sort(compareFn?: IComparer<T>) {
if (typeof compareFn !== "undefined") {
this.list.sort(compareFn);
}
}
private isSafeArrayIndex(propKey: string): boolean {
const uint = Number.parseInt(propKey, 10);
const s = uint + "";
return propKey === s && uint !== 0xffffffff && uint < this.Count;
}
}
Case
const list = new List<string>(["b", "c", "d"]);
const item = list[0];
Reference
proxy
[Symbol.iterator]()
Sure, you can replicate almost any data structure in JavaScript, all the basic building blocks are there. What you'll end up will be slower and less intuitive however.
But why not just use push/pop ?
Related
I have a Class called Myclass
class Myclass {
constructor(
public title: string,
) { }
}
in the next example, I want to change the result of the spread operation
let myobject = new Myclass('hello');
console.log({...myobject});
result wanted for example
{
new_title_name : 'hello'
}
This cannot be done. The ECMA-262 specification describes only one way a spread operator can work with objects, with no ability to override it.
If you want to change the set of key-value pairs spread out, you need to provide a different object. Such an object can be generated by a function, method or a property:
class Myclass {
get data() {
const result = {};
for (const k in this) {
if (typeof this[k] === 'number')
result[k.toUpperCase()] = this[k];
}
return result;
}
};
const obj = new Myclass();
obj.a = [];
obj.b = null;
obj.c = 13;
obj.d = 'test';
console.info({ ...obj.data });
I have two objects that are the same type and I want to copy the content of one of them to the other.
const Invoice1 = new InvoiceModel();
const Invoice2 = new InvoiceModel();
now in order to have something like : Invoice2 = Invoice1
After reading :
How do I correctly clone a JavaScript object?
I tried to use any of below commands but all of them say that invoice2 is not defined at runtime:
Invoice2 = { ...Invoice1 }; //OR
Invoice2 = Object.assign({}, Invoice1); //OR
Invoice2 = JSON.parse(JSON.stringify(Invoice1));
finally I used this function to copy the content of objects by reading this article (https://medium.com/#Farzad_YZ/3-ways-to-clone-objects-in-javascript-f752d148054d):
function CopyObject(src, target) {
for (let prop in src) {
if (src.hasOwnProperty(prop)) {
target[prop] = src[prop];
}
}
return target;
}
I wonder is there any cleaner way to do that except using above function?
I have read many post regarding this issue but all of them create a new object.
I recommend creating a method in the prototype in InvoiceModel that does this automatically for you.
class InvoiceModel {
constructor(num) {
this.num = num
}
duplicate() {
return Object.assign(Object.create(this), this)
}
}
const obj1 = new InvoiceModel(10)
console.log(obj1.num)
const obj1Copy = obj1.duplicate()
console.log(obj1Copy.num)
console.log(obj1Copy === obj1)
If the objects are just plain old data objects - with no methods or private state - you can just use a deep object clone method as specified here.
However, by the looks of things you are using classes with constructors, which implies you have methods and state. This is more tricky, because it suggests you may be relying on the constructor being re-run, e.g. to store private state in a closure, to allocate other resources, or rely on some kind of side effects. In that case you will need some kind of Invoice.prototype.clone method, that knows how to inject state into a new instance, and reruns the constructor function for the class - as per #andrew's answer.
I would avoid cloning objects with the syntax target = {...src}, which one commenter suggested. This will cause you trouble as soon as you have non-scalar reference members like sub-objects or arrays (as you will be copying pointers to the originals, not cloning their values). The same flaw applies to that CopyObject function you have picked up.
I have implemented a deep copier of objects, it does not override anything in the target option, but if you need that, you can achieve that as well:
var defaults = {
options: {
remove: true,
enable: false,
instance: {}
},
log: {
warn: true,
error: true
}
};
var config = {
options: {
remove: false,
instance: null
}
};
function applyDefaults(d, t) {
if ((typeof d !== "object") && (typeof d !== "array")) {
return d;
}
if (t === undefined) {
if (typeof d === "object") {
t = {};
} else if (typeof d === "array") {
t = [];
}
}
for (var key in d) {
if (t[key] === undefined) {
t[key] = d[key];
} else {
applyDefaults(d[key], t[key]);
}
}
return t;
}
applyDefaults(defaults, config);
console.log(config);
However, this will not copy "private" stuff, not defined as members of this.
Given Code
var isEmptyArray = function (array) {
if (typeof array !== 'undefined' && array.length > 0) {
}
Usage
isEmptyArray(myArray);
Wanted Result
How can I re-write above to be able to use:
myArray.isEmptyArray();
Just javascript:
Array.prototype.isEmptyArray = function() {
return this.length === 0;
}
Typescript:
interface Array<T> {
isEmptyArray(): boolean;
}
Array.prototype.isEmptyArray = function() {
return this.length === 0;
}
Edit
The above solution will work for all instances of Array, for example:
let a = [];
console.log(a.isEmptyArray()); // true
a.push(1);
console.log(a.isEmptyArray()); // false
You can create your own array class and then implement needed methods just there (without affecting other Array instances):
class MyArray<T> extends Array<T> {
public isEmptyArray(): boolean {
return this.length === 0;
}
}
let a1 = [];
console.log(a1.isEmptyArray()); // Uncaught TypeError: a.isEmptyArray is not a function
let a2 = new MyArray<any>();
console.log(a2.isEmptyArray()); // true
This approach is good when you're using other js libraries which are not aware of the changes you've made in the Array prototype.
I have the following javascript code:
function testClass() {
this.SaveValue = function (value) {
var isInstance = value instanceof TestEnum;
if (!isInstance) {
return;
}
}
}
TestEnum = {
VALUE_0: 0,
VALUE_1: 1,
VALUE_2: 2
}
I create an instance of this object in the following way:
$(function () {
var a = new testClass();
a.SaveValue(TestEnum.VALUE_1);
});
All I'd like to do is test that the value passed to the SaveValue function is actually the type of TestEnum. However, when I run this code I get the following error: Uncaught TypeError: Expecting a function in instanceof check, but got 1
Am I going about this the right way? I tried typeof but it only returns number which is not particularly useful to me.
You could create the values as instances of the "class":
function TestEnum(value) {
this._value = value;
}
TestEnum.prototype.valueOf = function() {
return this._value;
}
TestEnum.prototype.toString = function() {
return 'TestEnum_' + this._value;
}
TestEnum.VALUE_0 = new TestEnum(0);
TestEnum.VALUE_1 = new TestEnum(1);
The following would work then:
TestEnum.VALUE_0 instanceof TestEnum
But it also means you'd have to explicitly access the numerical value of one value with .valueOf. In some cases JS will do this automatically for you (like in 5 + TestEnum.VALUE_1). Overriding toString so that you can use a value as property might also be necessary.
It really depends on your use case whether this is a viable solution.
Alternatively, if just want to test whether a value is part of the enum, you can have an additional property which holds all possible values:
TestEnum.values = {0: true, 1: true, ...};
And then test it with
value in TestEnum.values
// or more reliable (fails for inherited `Object` properties)
TestEnum.values.hasOwnProperty(value);
You could even automate this:
function collectValues(obj) {
var values = {}; // or Object.create(null) if available
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
values[obj[prop]] = true;
}
}
return values;
}
TestEnum.values = collectValues(TestEnum);
This will only reliably work for primitive values though and won't distinguish between the string "1" and the number 1.
You are passing a number to the function in
a.SaveValue(TestEnum.VALUE_1);
Since TestEnum is simply an Object, and you are referencing a number property on that object, you're calling your function with a number. You should instead create a TestEnumValue object and use that for your Object's properties:
JSFiddle link for below
function testClass() {
this.SaveValue = function (value) {
var isInstance = value instanceof TestEnumValue;
if (!isInstance) {
return;
}
}
}
TestEnumValue = function(arg) {
arg = arg ? arg : 0; // sensible default
this.key = 'VALUE_' + arg;
this.val = arg;
}
Level = {
NumSpiders : new TestEnumValue(0),
NumCreepers: new TestEnumValue(1),
NumZombies : new TestEnumValue(2),
NumChickens: new TestEnumValue // uses default enum value
};
$(function() {
var a = new testClass();
a.SaveValue(Level.NumSpiders);
$('#hi').text(Level.NumSpiders.key);
});
Playing around with this, I noticed that you can leverage the fact that an enum compiles into an object that binds the values both ways combined with a hasOwnProperty check.
export enum TEST_ENUM{
ZERO, // 0
ONE, // 1
TWO, // 2
}
let a = 1;
let b = TEST_ENUM.TWO // 2
let c = 5 // incorrect value
TEST_ENUM.hasOwnProperty(a); // TRUE
TEST_ENUM.hasOwnProperty(b); // TRUE
TEST_ENUM.hasOwnProperty(c); // FALSE
This comes with a few caveats though;
// An object's keys are always strings...
// Although this shouldn't not matter usually (e.g. parsed user input)
TEST_ENUM.hasOwnProperty("2"); // TRUE
// And the enum is bound two-way so:
let input = "TWO";
if (TEST_ENUM.hasOwnProperty(input) { // TRUE
let result = input // "TWO"
// result is now the enum's value, instead of the key.
result = TEST_ENUM[input]; // this would be the correct assignment
};
Of course you can fix both of these with a typeof check, in case of a string assign it TEST_ENUM[mystring].
Note that my intellisense didn't autocomplete the hasOwnProperty function on an enum, but it doesn't complain about it either, and it's available on all browsers.
Edit
Here's an example of how you could do it.
function TestEnum(val) {
this.vals = this.vals || [];
if (this.vals.indexOf(val) == -1) console.log('nope: ' + val);
else console.log('ok: ' + val);
}
(function() {
var vals = {
VALUE_0: 0,
VALUE_1: 1,
VALUE_2: 2
};
TestEnum.prototype.vals = [];
for (var key in vals) {
TestEnum[key] = vals[key];
TestEnum.prototype.vals.push(vals[key]);
}
})();
Now new TestEnum(TestEnum.VALUE_0); is OK, but if you try, say, new TestEnum(3), then it throws an exception.
This is a bit backwards -- x instanceof y means that x has been created as x = new y(). Since TestEnum isn't even a function, you can't create an instance of it, so this isn't going to work.
What you could do is maybe something like this:
function MyEnum(enumVal) { this.val = enumVal; }
a.SaveValue( new MyEnum(TestEnum.VALUE_1) );
Then check using isInstance = value instanceof MyEnum.
I can't seem to find the way to overload the [] operator in javascript. Anyone out there know?
I was thinking on the lines of ...
MyClass.operator.lookup(index)
{
return myArray[index];
}
or am I not looking at the right things.
You can do this with ES6 Proxy (available in all modern browsers)
var handler = {
get: function(target, name) {
return "Hello, " + name;
}
};
var proxy = new Proxy({}, handler);
console.log(proxy.world); // output: Hello, world
console.log(proxy[123]); // output: Hello, 123
Check details on MDN.
You can't overload operators in JavaScript.
It was proposed for ECMAScript 4 but rejected.
I don't think you'll see it anytime soon.
The simple answer is that JavaScript allows access to children of an Object via the square brackets.
So you could define your class:
MyClass = function(){
// Set some defaults that belong to the class via dot syntax or array syntax.
this.some_property = 'my value is a string';
this['another_property'] = 'i am also a string';
this[0] = 1;
};
You will then be able to access the members on any instances of your class with either syntax.
foo = new MyClass();
foo.some_property; // Returns 'my value is a string'
foo['some_property']; // Returns 'my value is a string'
foo.another_property; // Returns 'i am also a string'
foo['another_property']; // Also returns 'i am also a string'
foo.0; // Syntax Error
foo[0]; // Returns 1
foo['0']; // Returns 1
Use a proxy. It was mentioned elsewhere in the answers but I think that this is a better example:
var handler = {
get: function(target, name) {
if (name in target) {
return target[name];
}
if (name == 'length') {
return Infinity;
}
return name * name;
}
};
var p = new Proxy({}, handler);
p[4]; //returns 16, which is the square of 4.
We can proxy get | set methods directly. Inspired by this.
class Foo {
constructor(v) {
this.data = v
return new Proxy(this, {
get: (obj, key) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key]
else
return obj[key]
},
set: (obj, key, value) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key] = value
else
return obj[key] = value
}
})
}
}
var foo = new Foo([])
foo.data = [0, 0, 0]
foo[0] = 1
console.log(foo[0]) // 1
console.log(foo.data) // [1, 0, 0]
As brackets operator is actually property access operator, you can hook on it with getters and setters. For IE you will have to use Object.defineProperty() instead. Example:
var obj = {
get attr() { alert("Getter called!"); return 1; },
set attr(value) { alert("Setter called!"); return value; }
};
obj.attr = 123;
The same for IE8+:
Object.defineProperty("attr", {
get: function() { alert("Getter called!"); return 1; },
set: function(value) { alert("Setter called!"); return value; }
});
For IE5-7 there's onpropertychange event only, which works for DOM elements, but not for other objects.
The drawback of the method is you can only hook on requests to predefined set of properties, not on arbitrary property without any predefined name.
one sneaky way to do this is by extending the language itself.
step 1
define a custom indexing convention, let's call it, "[]".
var MyClass = function MyClass(n) {
this.myArray = Array.from(Array(n).keys()).map(a => 0);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
...
var foo = new MyClass(1024);
console.log(foo["[]"](0));
step 2
define a new eval implementation. (don't do this this way, but it's a proof of concept).
var MyClass = function MyClass(length, defaultValue) {
this.myArray = Array.from(Array(length).keys()).map(a => defaultValue);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
var foo = new MyClass(1024, 1337);
console.log(foo["[]"](0));
var mini_eval = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = eval(values[0]);
var i = eval(values[2]);
// higher priority than []
if (target.hasOwnProperty('[]')) {
return target['[]'](i);
} else {
return target[i];
}
return eval(values[0])();
} else {
return undefined;
}
} else {
return undefined;
}
} else {
return undefined;
}
};
mini_eval("foo[33]");
the above won't work for more complex indexes but it can be with stronger parsing.
alternative:
instead of resorting to creating your own superset language, you can instead compile your notation to the existing language, then eval it. This reduces the parsing overhead to native after the first time you use it.
var compile = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = values[0];
var i = values[2];
// higher priority than []
return `
(${target}['[]'])
? ${target}['[]'](${i})
: ${target}[${i}]`
} else {
return 'undefined';
}
} else {
return 'undefined';
}
} else {
return 'undefined';
}
};
var result = compile("foo[0]");
console.log(result);
console.log(eval(result));
You need to use Proxy as explained, but it can ultimately be integrated into a class constructor
return new Proxy(this, {
set: function( target, name, value ) {
...}};
with 'this'. Then the set and get (also deleteProperty) functions will fire. Although you get a Proxy object which seems different it for the most part works to ask the compare ( target.constructor === MyClass ) it's class type etc. [even though it's a function where target.constructor.name is the class name in text (just noting an example of things that work slightly different.)]
So you're hoping to do something like
var whatever = MyClassInstance[4];
?
If so, simple answer is that Javascript does not currently support operator overloading.
Have a look at Symbol.iterator. You can implement a user-defined ##iterator method to make any object iterable.
The well-known Symbol.iterator symbol specifies the default iterator for an object. Used by for...of.
Example:
class MyClass {
constructor () {
this._array = [data]
}
*[Symbol.iterator] () {
for (let i=0, n=this._array.length; i<n; i++) {
yield this._array[i]
}
}
}
const c = new MyClass()
for (const element of [...c]) {
// do something with element
}