2

can I use sqare brackets on a custom class like in C#? Or are the square brackets limmeted to Arrays in JavaScript?

The equavilent in C# would be like that:

    public class Contact
    {
        private string[] address = new string[3];
        public string this[int index]
        {
            get
            {
                return address[index];
            }
            set
            {
                address[index] = value;
            }
        }
    }
peni4142
  • 426
  • 1
  • 7
  • 26

1 Answers1

3

Neither JavaScript nor TypeScript has the specific feature you're looking for, but JavaScript does have Proxy objects, which are extremely powerful and can be used for this. You'd need to implement the get and set trap handlers (at minimum), and have the Contact constructor return a proxy rather than the instance itself. (If you return an object from the constructor, that overrides the default behavior of returning the newly-created instance.) The get handler gets called on every property "read" on the proxy, and the set handler gets called on every property "write" on the proxy:

// ==== The implementation
const rexAllDigits = /^\d+$/;
const contactProxyHandlers = {
    get(target, key) {
        if (rexAllDigits.test(key)) {
            return target.address[key];
        }
        return target[key];
    },
    set(target, key, value) {
        if (rexAllDigits.test(key)) {
            return Reflect.set(target.address, key, value);
        }
        return Reflect.set(target, key, value);
    }
};
class Contact {
    constructor() {
        this.address = ["", "", ""];
        return new Proxy(this, contactProxyHandlers);
    }
}

// ==== Usage:
const c = new Contact();
c[0] = "address 0";
console.log(c[0], c.address[0]); // "address 0", "address 0"
c.example = "not an index property";
console.log(c.example);

In that example, I'm redirecting all property gets and sets that use all-digits property names to the address array on the Contact object, but passing through requests for other properties directly to the object.

Your C# version made address private. Soon you'll be able to use private fields to do that in JavaScript, too. Until then, if you want to make it private, you can use a WeakMap to do that, like this:

// ==== The implementation -- private parts
// You'd keep these parts private, for instance in a module and not exported

const contactAddresses = new WeakMap();
const proxyTargets = new WeakMap();
const rexAllDigits = /^\d+$/;
const contactProxyHandlers = {
    get(target, key) {
        if (rexAllDigits.test(key)) {
            return contactAddresses.get(target)[key];
        }
        return target[key];
    },
    set(target, key, value) {
        if (rexAllDigits.test(key)) {
            return Reflect.set(contactAddresses.get(target), key, value);
        }
        return Reflect.set(target, key, value);
    }
};

// ==== The implementation -- public parts
// You'd make this available, for instance exporting it from the module

class Contact {
    constructor() {
        // Initialize the entry in the map. If/when this object is ready
        // for garbage collection, the entry will get removed automatically.
        contactAddresses.set(this, ["", "", ""]);
        const p = new Proxy(this, contactProxyHandlers);
        proxyTargets.set(p, this);
        return p;
    }
    showAddresses() {
        const t = proxyTargets.get(this);
        if (!t) {
            throw new Error("Invalid `this` value for Contact");
        }
        console.log(JSON.stringify(contactAddresses.get(t)));
    }
}

// ==== Usage:
const c = new Contact();
c[0] = "address 0";
console.log(c[0]); // "address 0"
c.showAddresses(); // "address 0", "", ""
c.example = "not an index property";
console.log(c.example);

The version using private fields would also need the proxyTargets WeakMap. It would look like this:

// ==== The implementation
class Contact {
    static #rexAllDigits = /^\d+$/;
    static #contactProxyHandlers = {
        get(target, key) {
            if (Contact.#rexAllDigits.test(key)) {
                return target.#address[key];
            }
            return target[key];
        },
        set(target, key, value) {
            if (Contact.#rexAllDigits.test(key)) {
                return Reflect.set(target.#address, key, value);
            }
            return Reflect.set(target, key, value);
        }
    };
    static #proxyTargets = new WeakMap();
    #address = ["", "", ""];
    constructor() {
        const p = new Proxy(this, Contact.#contactProxyHandlers);
        Contact.#proxyTargets.set(p, this);
        return p;
    }
    showAddresses() {
        const t = Contact.#proxyTargets.get(this);
        if (!t) {
            throw new Error("Invalid `this` value for Contact");
        }
        console.log(JSON.stringify(t.#address));
    }
}

// ==== Usage:
const c = new Contact();
c[0] = "address 0";
console.log(c[0]); // "address 0"
c.showAddresses(); // "address 0", "", ""
c.example = "not an index property";
console.log(c.example);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    I've never even thought about returning the proxy inside the constructor, but it makes perfectly sense for proxies. Learnt something new today. – Silvermind Aug 27 '20 at 10:24