Zachary
Zachary

Reputation: 153

Are there different types of Arrays in JavaScript?

This is the console output of two different "arrays" made up of the same strings. I do not know how they originated (there is lots of code that I did not write) but I believe only Object2 is a true Array as it has a length function. Calling Object1.length gives 0.

Even though Object1 is called an Array in the console, it appears to actually be an Object with numerical attributes. How do I ensure the Object1 type of array is never used and all arrays look like Object2?

Object1
Array {0: 'ABC', 1: 'IJK', 2: 'XYZ'}
0: "ABC"
1: "IJK"
2: "XYZ"
[[Prototype]]: Array(0)

Object2
(3) ['ABC', 'IJK', 'XYZ']
0: "ABC"
1: "IJK"
2: "XYZ"
length: 3

Edit 1: Object1 and Object2 are just variable names I used for this example.

Calling Array.isArray(Object1) = false while Array.isArray(Object2) = True. How would one even make an Object called Array? [This was answered by CRice in the comments. See Edit 3]

Edit 2: The code is in an AngularJS monstrosity, but here is some code showing how the data is originated. Object1 is a variable holding what should be an array called "someArray" in the model below and it is initialized using the adapter. Object2 was made like a traditional inline array: Object2 = [];

Model

define([], function () {
  return [
    function () {
      function SomeObjectModel(someArray) {
        this.someArray = someArray;
      }

      SomeObjectModel.prototype.serialize = function () {
        return {
          someArray: this.someArray,
        };
      };

      SomeObjectModel.build = function (data) {
        return new SomeObjectModel(data.someArray);
      };

      return SomeObjectModel;
    },
  ];
});

Adapter

var serializedSomeObject = someObjectInstance.serialize();
return $http({
  method: 'PUT',
  url: 'someUrl.com/api',
  data: serializedSomeObject,
}).then(function (response) {
  return SomeObjectModel.build(response.data);
});

Edit 3: @CRice pointed out that this code can reproduce the Object1 instance which calls itself Array but does not behave as such:

var o = { 0: 'ABC', 1: 'IJK', 2: 'XYZ' };
Object.setPrototypeOf(o, Array.prototype);
console.log(o);

Edit 4: I don't know why serializing and then deserializing caused this but my solution is to iterate over the attributes of Object1 and just push them into a new array and call it a day. Here is my solution to convert Object1 to Object2:

var Object2 = [];
var attr = Object.getOwnPropertyNames(Object1);
for(var i=0; i<attr.length; i++){
  Object2.push(Object1[attr[i]]);
}
console.log(Object2);

Thank you CRice for helping me reproduce it in the console!

Upvotes: 3

Views: 126

Answers (1)

Zachary
Zachary

Reputation: 153

I found the cause. There is a module that deep clones complex objects so they can be restored in the future. Can you spot the error in this recursive loop?

function cloneObject(src) {
    let target = {};
    target.__proto__ = src.__proto__;
    for (let prop in src) {
        if (src.hasOwnProperty(prop)) {
            // if the value is a nested object, recursively copy all it's properties
            if (isObject(src[prop])) {
                target[prop] = cloneObject(src[prop]);
            } else {
                target[prop] = src[prop];
            }
        }
    }
    return target;
}

When someArray goes through the cloneObject() function, it creates a new object called Array that does not retain true array properties but instead converts it into an object with proto='Array' (as @CRice pointed out) and nothing more. To fix this we really need to rework the cloneObject function to preserve arrays. This is the corrected deep copy cloneObject function I am now using:

function cloneObject(src) {
    let target = {};
    var isArray = Array.isArray(src);
    if(isArray){
        target = [];
    } else {
        target.__proto__ = src.__proto__;
    }

    for (let prop in src) {
        if (src.hasOwnProperty(prop)) {
            // if the value is a nested object, recursively copy all it's properties
            if (isObject(src[prop])) {
                var propertyValue = cloneObject(src[prop]);
            } else {
                var propertyValue = src[prop];
            }
            
            // if src was an array
            if(isArray){
                target.splice(prop, 0, propertyValue);
            } else {
                target[prop] = propertyValue;
            }
        }
    }
    return target;
}

Upvotes: 1

Related Questions