18

In JavaScript, values of objects and arrays can be indexed like the following: objOrArray[index]. Is there an identity "index" value?

In other words:

Is there a value of x that makes the following always true?

let a = [1, 2, 3, 4];
/* Is this true? */ a[x] == a

let b = { a: 1, b: 2, c: 3 };
/* Is this true? */ b[x] == b

Definition of an identity in this context: https://en.wikipedia.org/wiki/Identity_function

Shivam
  • 1,345
  • 7
  • 28
  • 2
    Unless there is an element (keyed or index) that itself refers to the container, the expression will never be true (as in the shown code). – user2864740 Oct 05 '21 at 01:34
  • 8
    I’m voting to close this question because all mathematical terminologies does not necessarily have an application in computer science – Mister Jojo Oct 05 '21 at 01:35
  • 4
    One way to create this hypothetical “identity” index: `a = [1,2,3]; a.myself = a; x = "myself"; a[x] === a` .. (this is true for, and only for, the shown value of x) – user2864740 Oct 05 '21 at 01:37
  • 1
    You _could_ do something like `const getObjectWithIdentityIndex = (obj) => { const identity = Symbol("identity"); return { object: new Proxy(obj, { get(target, property, receiver){ if(property === identity){ return receiver; } return Reflect.get(target, property); }, /* TODO: Implement all the other Proxy handlers. */ }), identity }; }, { object: a, identity: x } = getObjectWithIdentityIndex([ 1, 2, 3, 4 ]);`. Then `a[x] === a` is `true`, `x` is always fixed for that object, but `a` is neither an ordinary array nor an ordinary object. But _why_ would anyone do this? – Sebastian Simon Oct 05 '21 at 01:42
  • 23
    This question is being discussed on [meta](https://meta.stackoverflow.com/questions/411999). – cigien Oct 05 '21 at 01:50
  • 3
    First of all, `substr` is deprecated. Don’t use this method. Secondly, `s.description.substring(0, 50)` doesn’t need this `length` check. I don’t see how an “identity” would even help here. This is no longer property access, it’s the conditional operator — two completely different operators. – Sebastian Simon Oct 05 '21 at 02:06
  • 1
    `s.description.replace(/^(.{50})(.+)$/su, (_, firstPart, rest) => firstPart + (rest && "…"))`? – Sebastian Simon Oct 05 '21 at 02:28
  • 1
    there is no build in identity index but you are create your own if required. ``` const identityIndex = Symbol(); let a = [1, 2, 3, 4]; a[identityIndex] = a; console.log(a[identityIndex] === a); console.log(a[identityIndex][identityIndex] === a); let b = { a: 1, b: 2, c: 3 }; b[identityIndex] = b; console.log(b[identityIndex] === b); console.log(b[identityIndex][identityIndex] === b); ``` – Abdul Rauf Oct 06 '21 at 04:50
  • `s.description.replace(/^(.{50}).+/su, "$1…")`, even (based on @SebastianSimon’s). – Ry- Dec 16 '21 at 21:35

2 Answers2

31

There is no such thing built-in, as there is rarely a need for it (and sometimes even a need against it).0 It is nevertheless possible to roll your own ‘identity’ key:

const self = Symbol('self');

Object.defineProperty(Object.prototype, self, {
    enumerable: false,
    get() { "use strict"; return this; }
});

This will work on all primitives (other that null and undefined) and most JavaScript objects: that is, other than proxies or those that bypass the usual prototype chain by means of e.g. Object.create(null). Any object later in the prototype chain will also be able to disable the functionality, e.g. by doing { [self]: void 0 }; all these caveats mean that x[self] === x is by no means a universal law. But this is probably the best you can do.

Modifying Object.prototype is usually considered a bad idea, but the above manages to avoid most of its badness: adding the property at a symbol key (and making it explicitly non-enumerable as well) prevents it from unexpectedly showing up in iterations and lookups that walk the prototype chain, helping ensure no code should be impacted that does not specifically look for this property.


0 Even if such a feature existed, it would not be a good solution for the asker’s original use case: a ‘cut to 50 characters or take the whole string if shorter’ operation can be expressed as s.description.substring(0, s.description.length > 50 ? 50 : void 0) (or in fact just s.description.substring(0, 50)). It wouldn’t be any easier to express even with such a feature: depending on the condition, you still need to invoke the substring method, not just look it up, but not invoke the ‘self’ non-method. And given that you need to append an ellipsis at the end in the former case, you would still have to perform the condition check outside the substring call, making any shorthand rather ineffective. All that said, tricks like described in this answer do find some real use.

user3840170
  • 26,597
  • 4
  • 30
  • 62
  • 1
    `but the above manages to avoid most of its badness` For posterity, it's worth mentioning the case where this would "break". If, for whatever reason, someone used `Object.getOwnPropertySymbols(obj.constructor.prototype)` they would see the `self` symbol. I wouldn't put it past people to have written this code. – Qix - MONICA WAS MISTREATED Oct 07 '21 at 13:30
19

The indexing operation doesn't have an identity element. The domain and range of indexing is not necessarily the same -- the domain is arrays and objects, but the range is any type of object, since array elements and object properties can hold any type. If you have an array of integers, the domain is Array, while the range is Integer, so it's not possible for there to be an identity. a[x] will always be an integer, which can never be equal to the array itself.

And even if you have an array of arrays, there's no reason to expect any of the elements to be a reference to the array itself. It's possible to create self-referential arrays like this, but most are not. And even if it is, the self-reference could be in any index, so there's no unique identity value.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 1
    Well, there is the [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) value which is technically an index of an object but doesn't give any of the actual values – Shivam Oct 05 '21 at 01:34
  • 6
    @Shivam Not sure how that relates. – Barmar Oct 05 '21 at 01:36
  • 1
    @Shivam _“the range is Integer”_ refers to `a[x]`, not to `x`. `x` is always either a symbol or coerced to a string. – Sebastian Simon Oct 05 '21 at 01:45
  • 1
    I know, but if `x` is a Symbol, then the range should not apply to it. – Shivam Oct 05 '21 at 01:46
  • 2
    This needs more clarification… 1. Your `a` is an Array (i.e. an Object), your `b` an Object (_this_ is the domain). 2. `x` is either a Symbol or a String. 3. `a[x]` and `b[x]` could be anything — _this_ is the range, not `x`. If `x` is one of `"0"`, `"1"`, `"2"`, `"3"`, or `"length"`, then `a[x]` is a Number. If `x` is one of the remaining strings from `Object.getOwnPropertyNames(Array.prototype)` or `Object.getOwnPropertyNames(Object.prototype)`, or a symbol from `Object.getOwnPropertySymbols(Array.prototype)`, then `a[x]` is always an Object. If `x` is anything else, `a[x]` is Undefined. – Sebastian Simon Oct 05 '21 at 02:19
  • 5
    ‘The domain and range of indexing is not necessarily the same’ – this is a silly non-obstacle in a dynamically-typed, dynamically-dispatched language that allows adding arbitrary properties to objects. And the prototype chain renders the second obstacle irrelevant as well. – user3840170 Oct 06 '21 at 09:22