javascriptarraysproperties

Assign intrinsic array property of object in javascript


I've worked out a different way of getting around this for my immediate problem, but I can't resist asking the question for the aficionados out there! This is an advanced question.

So just about everything (other than primitive types) in javascript is an object, including an array. As such, you can assign properties to it.

var arr = [];
arr[1] = 42;
arr[5] = 666;
arr.firstName = 'ebenezer';
arr.lastName = 'scrooge';

var arr2 = [];
arr2.firstName = 'daffy';
arr2.lastName = 'duck';

var obj2 = {};
obj2.firstName = 'smokey';
obj2.lastName = 'bear';

I'm describing the array property of arr as the 'intrinsic' array:

arr[1]   // returns 42

So my question is: can I assign just the intrinsic array (the whole array but not any other properties) to the other objects, eg:

arr2.arrayProp (??something like this??) = arr[];
arr2[1] // want it to return 42
arr2.firstName // want it to return 'daffy'

obj2[](??something like this??) = arr[];
obj2[1] // want it to return 42
obj2.firstName // want it to return 'smokey'

Clearly if I assign the whole array or object, it just creates a reference to the original object and all its properties.
I'm wondering if there is some 'under the hood' way to assign just the array property.

I think the answer is 'no', but surprise me.

EDIT

OK, this has been very illuminating. I've awarded the answer to @HauWu since you made a good contribution.

In the end, the whole premise of my question stands or falls on this: Given

arr[1] = 'intIndex'; 
arr['1'] = 'strIndex';
console.log(arr[1], arr['1']);

Do you get

   A: intIndex    strIndex
or B: strIndex    strIndex

It turns out that you get B, but up until I intended to correct @HauWu and checked it first, I could have sworn that it was A. How could I misunderstand things so badly?

I went and checked the Mozilla documentation on Array (which I, perhaps mistakely, generally consider authoritative), and it says this:

Array objects cannot use arbitrary strings as element indexes (as in an associative array) but must use nonnegative integers (or their respective string form). Setting or accessing via non-integers will not set or retrieve an element from the array list itself, but will set or access a variable associated with that array's object property collection. The array's object properties and list of array elements are separate, and the array's traversal and mutation operations cannot be applied to these named properties.

This is the fourth time I've looked at it, and I've only just noticed the part where it says: (or their respective string form).
Rather I saw the part where it said: Setting or accessing via non-integers will not set or retrieve an element from the array list itself, but will set or access a variable associated with that array's object property collection.

This was the root of my misunderstanding - that array elements were partitioned off from object properties and held in a special internal construct accessed only by integer index, where properties were accessed only by string index.

To digress for a minute, arrays are extremely important in any programming language because they are comprised of fixed sized chunks, so a simple multiply operation will get you the element you want. Array reference is usually an order of magnitude faster than dictionary (hashed collection) reference, so this distinction is extremely important.
It's now hard to know exactly what form of lookup is used internally in Javascript.

Off to look at the ECMAScript specification. Arrays are classified as an exotic object and gives special treatment of integer-equivalent keys, using CanonicalNumericIndexString and ToIndex internally to evaluate the integer-equivalent keys.

I did run some performance tests to check the lookup speed of array indexed data versus text keyed data, but I don't fully trust the results due to possible JIT compiler optimisation. Will post results back here if I get to a point where I think they are reliable.

However I am pretty sure there is some significant caching going on with property keys, and array integer-equivalent keys in particular (excellent article here on the caching in Google's V8 implementation).

FINAL EDIT: It does appear that running benchmark code is somewhat of a fool's errand. JS compilers are optimised in very strange ways that may not make sense at first glance, but are no doubt ruthlessly benchmarked and optimised against statistically significant common high workloads.
This article gives a great rundown of performance consideration by someone who is clearly across it all. In particular, it is clear that JS can switch without warning between various optimisaton models in response to activity in the data.

So, to summarise:

To answer the original question, there is no way of copying the array elements as a single assignment, because they are simply object properties. The accepted answer offered a reasonable workaround.

I have heard it said that Javascript is very closely related to LISP - a language that was used decades ago for AI research. I think this reinforces that view very much - Javascript at its root simply passes around from place to place objects extensible with properties. There is an obligatory amount of primitive type support and then everything else is simply syntactic sugar and some judicious optimisation.

I offer the following for your amusement courtesy of the commenters to Dreaming In Javascript:

var foo = { toString: function() { return “hello”; } };
var hi = { hello: “hola” };

hi[foo]; // returns “hola”

and

var A=[];
A[NaN]=”notanumerbery”;
console.log(A[parseInt(“:P”)]);
// notanumerbery

Solution

  • Could this be what you wanted?

    var arr = [];
    arr[1] = 42;
    arr[5] = 666;
    arr.firstName = 'ebenezer';
    arr.lastName = 'scrooge';
    
    var arr2 = [];
    arr2.firstName = 'daffy';
    arr2.lastName = 'duck';
    
    var obj2 = {};
    obj2.firstName = 'smokey';
    obj2.lastName = 'bear';
    
    // assign only array indices
    Object.assign(arr2, [...arr]);
    Object.assign(obj2, [...arr]);
    
    console.log('arr2[1] -> ' + arr2[1]);
    console.log('arr2.firstName -> ' + arr2.firstName);
    console.log('obj2[1] -> ' + obj2[1]);
    console.log('obj2.firstName -> ' + obj2.firstName);

    However, instead of [empty, 42, empty x 3, 666], it creates all missing indices: [undefined, 42, undefined, undefined, undefined, 666]. If that's not what you wanted, the following approach could avoid this:

    function assignArrayIndices(target, source) {
      source.forEach((value, key) => target[key] = value);
    }
    
    var arr = [];
    arr[1] = 42;
    arr[5] = 666;
    arr.firstName = 'ebenezer';
    arr.lastName = 'scrooge';
    
    var arr2 = [];
    arr2.firstName = 'daffy';
    arr2.lastName = 'duck';
    
    var obj2 = {};
    obj2.firstName = 'smokey';
    obj2.lastName = 'bear';
    
    // assign only array indices
    assignArrayIndices(arr2, arr);
    assignArrayIndices(obj2, arr);
    
    
    console.log('arr2[1] -> ' + arr2[1]);
    console.log('arr2.firstName -> ' + arr2.firstName);
    console.log('obj2[1] -> ' + obj2[1]);
    console.log('obj2.firstName -> ' + obj2.firstName);
    
    console.log('arr2 -> ' + arr2);
    console.log('obj2 -> ' + obj2);