Does {} consume less memory than [] for nested objects in Javascript? - javascript

I had some legacy JS code that creates a giant nested object structure with [].
The code goes some like this
var data = [];
data ["first"] = [];
data ["first"]["second"] = [];
data ["first"]["second2"] = "hello";
It is about 250+ KB of javascript, which is fairly large. When I try to wrap it around with requirejs to load into another requirejs module, it throws Out Of Memory error.
The error goes away if I use {} where I was using [].
I did some homework on [] vs. {} over the weekend and the cause seems to be that using associated arrays as nested dictionaries may be leaky in Javascript since array extends a JS Object and may have more update stuff going on when appending new objects into it. But does it explain the memory consumption issue? Or it is related to how Requirejs parse a module's object?
I don't have enough knowledge about doing JS memory instrumentation and make comparisons between using {} or [] in browser engines, so it is hard to reach conclusions. Any hint or suggestion on how to instrument {} vs. [] would be welcome.
Update: I tried some sizeOf() via node yesterday. I used all of the existing ones: "js-sizeof", "object-sizeof", "sizeof"
Code:
var sizeof = require('object-sizeof');
var obj = [];
obj['ball'] = 'hello';
obj['air'] = 'hello';
obj['ball']['fire'] = 'world';
obj['ball']['ice'] = [];
console.log(sizeof(obj));
var obj2 = {};
obj2['ball'] = 'hello';
obj2['air'] = 'hello';
obj2['ball']['fire'] = 'world';
obj2['ball']['ice'] = [];
console.log(sizeof(obj2));
The results is
[]: 34
{}: 34
The sizeOf is actually the same., but maybe something else happened with [] that could trigger out of memory issue. I am not sure if it is the requirejs parsing it that trigger it or some V8 optimization path. I don't think the Lint tools even suggest against this practice so it is rather ambiguous which way is the "right" way in practice

There is no such thing as an "associated array" in JavaScript. [ 1, 2, 3 ] is array literal syntax; it initializes an Array. { foo: "bar" } is object literal syntax; it initializes an Object. A quirk of JavaScript, however, is that Arrays also happen to be Objects, which is why this code "works":
var data = [];
data["first"] = [];
data["first"]["second"] = [];
...but you shouldn't do it, because it doesn't make any sense. You're initializing an empty Array ([]), but then you're not using it like an Array—you're using it like an Object. If you're using property names (data["first"], which is equivalent to data.first) instead of integer keys (data[0]), then you want to use an Object. There is no scenario in which you should initialize an Array when you're going to use it like an Object.
As a rule of thumb, if you need each item to have a name, or need to be able to access them quickly by a name, use an Object ({}) and use strings for keys. If you need to be able to iterate over the items in order, use an Array with integers for keys.
I don't know the exact cause of your out-of-memory error—especially not without seeing your actual code—but it is definitely the case that you should be using an Object ({}), not an Array ([]) when you're not using integer keys. JavaScript engines optimize everything they can, and Arrays and Objects are no exception, so it's not surprising that when you use an Array in a way that the engine doesn't expect it might cause performance or memory problems.
P.S. As a matter of style, consider using property notation (or "dot notation," i.e. foo.bar) instead of subscript notation (i.e. foo["bar"]) when dealing with Objects:
var data = {};
data.first = {};
data.first.second = {};
data.first.second2 = "hello";
This is exactly equivalent to the code you posted, but it's easier to read and might help you remember that Objects and Arrays have different uses. You could also just express this as a single object literal:
var data = {
first: {
second: {},
second2: "hello"
}
};
This is also exactly equivalent and helps you see the "structure" of your object (as long as you're disciplined about indentation).
Most JavaScript style guides say that you should always use "dot notation" unless you have keys that would cause a syntax error. For example, if you have a property named "foo/bar", you obviously can't do this:
var obj.foo/bar = 1;
...because it's a syntax error. So you have to do this:
var obj["foo/bar"] = 1;
...which is perfectly valid. These cases tend to be the exception, so I would encourage to always use dot notation unless you have to use subscript notation.

Related

How can set a property using the same name without changing existing value

Using JavaScript, how can I set property of an object that already has property with the same name? For example, what i want to output is:
var obj = {
name: "foo"
};
obj[name] = "baz";
Normal output is:
console.log(obj) => {name:baz}.
I want to output:
console.log(obj) => {name:foo,name:baz}.
I know that is not the best practice, but is that possible?
You can read about core js concepts and also about basic data types in programming, like Maps and Arrays.
I can try to guess that your task is to store some similar data structures in array:
var list = [{name:'foo'},{name:'baz'}]
You simply can't do that. The latest value of the property will always override the previous one.
Even merging objects with same property names won't help you - shallow merge, or deep merge.
However, there are a few very weird cases with some JavaScript frameworks where this might happen - in Vue.js, for instance. But that misses the point since we're considering only plain Javascript.

Best practice when adding custom method to Array (Built-in object)

I have created a node module with a couple of custom methods for arrays and strings.
First I just used it like a regular module and got the functions from a require like this:
Alt 1.
const invSlice = require('inverted-slice');
let arr1 = [1,2,3,4];
invSlice.iSlice(arr, start, stop);
This works but it would be nicer to call iSlice as a method on the Array object. I solved this by adding the following code in my library:
Array.prototype.iSlice = iSliceBuiltin; // iSliceBuiltin is my function
And the method can now be used like:
Alt 2.
require('inverted-slice');
let arr1 = [1,2,3,4];
arr1.iSlice(start, stop);
Which I think is nicer then Alt 1.
Question
My question is if there is any best practice or guidelines to follow when adding custom methods like in Alt 2 to built-in Objects like Array or String ?
Extending built-in prototypes has always triggered debates, and I think we can conclude it is not considered best practice.
On the other hand it is indeed nice if you can call these custom methods as object methods instead of plain functions.
You might consider a wrapper function that will return an Array instance that has the extra methods defined for it: i.e., not on the prototype, but on the Array instance itself.
Your module could look like this:
function iArray(arr) {
return Object.assign([], arr || [], {
iSlice: iSliceBuiltin,
iSplice: iSpliceBuiltin
});
}
// ... your module functions come here, but excluding the changes to the Array prototype
module.exports = {
iArray
}
Then you would use it like this:
const iArray = require('inverted-slice');
let arr1 = iArray([1,2,3,4]); // enrich array with extra methods
let result = arr1.iSlice(0, 1);
To allow chaining, you could change the return statement in iSliceSpliceHelper to:
return iArray(newArr);
So, now you can write:
let arr1 = iArray([1,2,3,4]); // enrich array with extra methods
let result = arr1.iSlice(0, 1).iSlice(1, 2);
Existing libraries might implement your alternative 1 (e.g. underscore), but many also go for something like I propose here. See for instance Sugar (new Sugar.Array([1,2,3])), or Lazy (Lazy([1,2,3])).
In small doses I think it's not that big of a deal to use Alt 2, but I believe that over usage can create problems. If I remember correctly, they had to completely redo Cut The Rope due to performance problems that I believe stemmed largely in part from prototype extensions. You may also want to consider posting this on https://codereview.stackexchange.com/
A couple references:
http://perfectionkills.com/whats-wrong-with-extending-the-dom/
https://softwareengineering.stackexchange.com/questions/104320/why-is-extending-the-dom-built-in-object-prototypes-a-bad-idea

Javascript persistent-sorting Object "class"

Attending to it's specification, JSON elements (and javascript objects) are unordered so, even in almost all cases, when you iterate over a javascript object, you get elements in the same order they was defined; you definitively cannot trust in that order because engine is allowed to alter it.
This is extremely rare. I have been able to observe it one time, but I don't find that code right now and I don't remember the exact version of JS engine (but it was node). If I manage to find it, I will add it to this post.
That being said, the point is that code relying in this behaviour can (and should) be considered buggy because, even it will work as expected in most engines, it may fail typically because of internal engine optimisations.
For example:
"use strict";
var x = {
b: 23,
a: 7
};
function someSorting(input) {
var output = {};
Object.keys(input).sort().map(
function(k){
output[k] = input[k];
}
);
return output;
};
x = someSorting(x);
// Some smart engine could notice that input and output objects have the
// exact same properties and guess that, attending the unordered nature of
// javascript Object, there is no need to actually execute someSorting();
console.log(x);
// Usually will display: { a: 7, b: 23 }
// But perfectly we could got: { b: 23, a: 7 }
I know there is too many literature (even StackOverflow questions) about this (NON-) issue and "workarrounds" to achieve the expected behaviour by sorting keys in a separate array.
But doing so code goes too messy compared in simply trusting in key order.
I'm pretty sure that this can be achieved in a more elegant fashion by implementing a so-called "sObject" alternative having native Object as its prototype but overloading it's native iterator and setter so that:
When any new property is added, it's key is appended to an Array index mantained under the hood.
When an sObject instance is iterated, our customized iterator uses that index to retrieve elements in the right order.
In summary: Actual Object specification is right because, in most cases, properties order doesn't care. So I think that engine optimisations that could mess it are wonderfull.
But it would be also wonderful to have an alternative sObject with which we could do something like:
var x = new sObject({b: 23, a: 7});
...and trust that we could iterate it in the same exact order or, also / at least, do some sorting task over it and trust that this will not be altered.
Of course!! I'm initalyzing it with a native javascript Object so, in fact, theoretically we can't trust that it will be populated right (even I can't imagine why any engine optimisation should alter it before any operation).
I used that notation for brevity (and, I confess) because I expect that, in that case should work always (even I'm not really sure). However we even could sort it later (which, in most cases we will do that way) or use other kind of initialization like providing a JSON string or an array of objects (or arrays) with single key and value pairs.
My concern is: Such a thing exists yet? I wasn't able to find it. But sure I'm not the first guy thinking in that...
I can try to implement it (I'm thinking about that). I think it's possible and that I could achieve it. But it's not as simple so first I want to be sure that I'm not reinventing the wheel...
So any comments, suggestions, etc... will be welcome.
Sure, you could do all this. You will need some machinery such as Object.observer, which is currently only available in Chrome. We can define this as the following:
function myObject(object) {
// if the object already has keys, bring them in in whatever order.
var keys = Object.keys(object);
// Override Object.keys to return our list of keys.
Object.defineProperty(object, 'keys', { get: function() { return keys; });
// Watch the object for new or deleted properties.
// Add new ones at the end, to preserve order.
Object.observe(object, function(changes) {
changes.forEach(function(change) {
if (change.type === 'add') keys.push(change.name);
if (change.type === 'delete') keys = keys.filter(function(key) {
return key === change.name;
});
});
});
return object;
}
Note that Object.observe is asynchronous, so even after you add a property to the object, it won't be reflected in the custom keys property until after a tick of the clock, although in theory you could use Object.deliverChangedRecords.
The above approach uses a function which adds the new ordered key functionality to an existing object. Of course there are other ways to design this.
This "solution" obviously cannot control the behavior of for...in loops.

MongooseDocumentArray assignment

I have a MongooseDocumentArray found at this.prices. I can clear it with this.prices = [] and it retains all of its methods etc. and remains a MongooseDocumentArray. If I assign it to another variable first, e.g. array = this.prices, then array is also a MongooseDocuementArray, and changing one changes the other (i.e. they appear to be the same object). However, if I then attempt to clear that one with array = [], I find that array is now a plain, empty JS array. Doing array.length = 0 works fine, however. Can someone explain to me why this is and also how doing it with the original object works? I'm guessing this is more of a JS thing than a specifically Mongoose thing, but either way I'm perplexed.
When you first say:
this.prices = [];
... then mongoose is using what's known as a "setter" to intercept the assignment and cast it into a MongooseDocumentArray. Behind the scenes mongoose does this for all setting of paths on documents, not just document arrays. It uses Object.defineProperty to achieve this. Read more on that and its capabilities here.
What happens after that is more straightforward. When you then assign that to another variable:
array = this.prices;
... then you are assigning a reference to the cast this.prices object to array.
But when you say:
array = [];
... then you are changing that reference, causing array to point to a new Array object.
array.length = 0 on the other hand modifies the DocumentArray, but leaves the reference intact.
If you dig around in the source, particularly document.js and the various types, you can begin to figure out how mongoose handles this.

TypeScript: Creating an empty typed container array

I am creating simple logic game called "Three of a Crime" in TypeScript.
When trying to pre-allocated typed array in TypeScript, I tried to do something like this:
var arr = Criminal[];
which gave the error
"Check format of expression term" .
also tried doing this
var arr : Criminal = [];
and this produced "cannot convert any[] to 'Criminal'
what is the 'TypeScript' way to do this?
The existing answers missed an option, so here's a complete list:
// 1. Explicitly declare the type
var arr: Criminal[] = [];
// 2. Via type assertion
var arr = <Criminal[]>[];
var arr = [] as Criminal[];
// 3. Using the Array constructor
var arr = new Array<Criminal>();
Explicitly specifying the type is the general solution for whenever type inference fails for a variable declaration.
The advantage of using a type assertion (sometimes called a cast, but it's not really a cast in TypeScript) works for any expression, so it can be used even when no variable is declared. There are two syntaxes for type assertions, but only the latter will work in combination with JSX if you care about that.
Using the Array constructor is something that will only help you in this specific use case, but which I personally find the most readable. However, there is a slight performance impact at runtime*. Also, if someone were crazy enough to redefine the Array constructor, the meaning could change.
It's a matter of personal preference, but I find the third option the most readable. In the vast majority of cases the mentioned downsides would be negligible and readability is the most important factor.
*: Fun fact; at the time of writing the performance difference was 60% in Chrome, while in Firefox there was no measurable performance difference.
The issue of correctly pre-allocating a typed array in TypeScript was somewhat obscured for due to the array literal syntax, so it wasn't as intuitive as I first thought.
The correct way would be
var arr : Criminal[] = [];
This will give you a correctly typed, empty array stored in the variable 'arr'
I know this is an old question but I recently faced a similar issue which couldn't be solved by this way, as I had to return an empty array of a specific type.
I had
return [];
where [] was Criminal[] type.
Neither return: Criminal[] []; nor return []: Criminal[]; worked for me.
At first glance I solved it by creating a typed variable (as you correctly reported) just before returning it, but (I don't know how JavaScript engines work) it may create overhead and it's less readable.
For thoroughness I'll report this solution in my answer too:
let temp: Criminal[] = [];
return temp;
Eventually I found TypeScript type casting, which allowed me to solve the problem in a more concise and readable (and maybe efficient) way:
return <Criminal[]>[];
Hope this will help future readers!
Please try this which it works for me.
return [] as Criminal[];
Okay you got the syntax wrong here, correct way to do this is:
var arr: Criminal[] = [];
I'm assuming you are using var so that means declaring it somewhere inside the func(),my suggestion would be use let instead of var.
If declaring it as c class property usse acces modifiers like private, public, protected.
For publicly access use like below:
public arr: Criminal[] = [];
TLDR:
// explicitly type the variable arr
var arr: Criminal[] = [];
In depth:
If we have an array with no elements like:
var arr = [];
TS has no way of knowing at compile time what will be in that array and the type of arr will be:
any[]
It is then the job of the programmer to tell TS the type of the array which can be done with a type annotation:
var arr: Criminal[] = [];
If we then were to get an element out of the array Typescript knows the element will be of type Criminal.

Categories

Resources