I've been working angular for probably six or so months and so have been working a lot more with JSON and objects in javascript. This situation I've been running into has been frustrating and I don't understand the reasoning. Maybe it's my inexperience with JS and so I don't know the reasoning or maybe I'm just doing it wrong but I'd appreciate any insight into this.
Example an object:
var data =
{
"object":
{
"Key":"Value",
"stuff":"here"
},
"array":
[
{
"Name":"Pizza Face",
"id":7
},
{
"Name":"Maple bar",
"id":1
}
]
}
So, let's say I want to add an object to the array. Easy, right?
var newObject = {"Name": "Master Sword", "id":2}
data.array.push(newObject);
But, let's say I want to add the object to the array before the array exists. The above method errors out saying Cannot read property 'push' of undefined. Instead, I have to wrap a condition around it.
var newObject = {"Name": "Master Sword", "id":2}
if(data.array){
data.array.push(newObject);
}
else{
data.array = [];
data.array.push(newObject);
}
I am allowed to add new objects to data whenever I want. However, woe unto me should I try to add an object within an object.
Example:
data.newObject = {"Name": "Master Sword", "id":2}
newObject will be created in this case and everyone is happy.
data.newObject.Name = "Master Sword"
FAIL! Cannot set property 'Name' of undefined
So...why doesn't JS just create newObject with the laid out key value pair? Or when I try to push to an array that doesn't exist why not just create the array rather than error out?
I want to be clear, although this is frustrating to me my purpose of this question isn't to complain. It's to ask if I'm doing this wrong. Do I really need a condition to check if the object/array exists before trying to add to it? Is there a better way to do this?
You are trying to create objects via chaining. The problem with that is except the last part in the chain, all the previous ones must be already defined Javascript Objects.
a.b.c.d = new Object(); // a,b,c must be already defined, cannot have cascading here
So, when you say data.newObject.Name = "Master Sword";, the newObject should have been defined earlier (you already had a data object I assume). This is how it has to work (probably in every language that follows at least some semantics). To get around such a problem, you can always create a new generic object say:
var myComplexObject = function() { //add all reusable object constructors here };
Populate your complex object any way you like, e.g. creating a new empty object in this constructor and add Prototypes to this constructor to expand the functionality. Write once and iterate forever over that. You could build a library suited to your requirements. That is how it should be done.
P.S.: Probably, for the checking part you could encapsulate in a try{}catch(e){} block.
The method push is located on a javascript Array object, unsurprisingly that method will not work on either null or undefined. This is the same for pretty much every programming language.
When calling data.newObject = {"Name": "Master Sword", "id":2} you are quite literally setting the property to a new object, this is a very different scenario to the above.
That´s because when you try to reference a key of an object, if that key doesn´t exist then that key will be kind of created and the undefined value will be assigned to it. undefined is just a JS Value type. If you want to check that "undefined" is not an error, just note that "undefined" is written with gray color on the console. (Errors have the red color).
If you assign an object to a key of other object, and you want to add properties to it, that won´t represent a problem at all.
But if you try to add a property to a key that can be initialized with undefined value, then it's reasonable that you will get an error message. Remember, JS allows you to store any value type you want on objects.
I suggest you to read a little bit more about objects, and the undefined value in JavaScript.
Hope that it helps.
Related
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.
Node.js querystring.parse() method returns what looks like to be an object, but one without a constructor. According to https://nodejs.org/api/querystring.html :
"... The object returned by the querystring.parse() method does not prototypically inherit from the JavaScript Object. This means that typical Object methods such as obj.toString(), obj.hasOwnProperty(), and others are not defined and will not work."
This easily causes bugs because typically you would assume that every Object understands some basic methods like toString() and that it has a "constructor" which more or less tells us its "type".
What's the best way to handle these rather incapable Objects? I tried:
let myOb = new Object(dumbObject);
But that produces a result which does not have the toString() -method either and does not have the property 'constructor'.
What's the best way to turn these dumb objects into ordinarily behaving ones? And, why would anybody want to create such objects in the first place?
I think (from the top of my head)
let newObject = JSON.parse(JSON.stringify(dumbObject))
should work.
If you want a more generic way to call toString() on an object, you can call it from Object.prototype using .call().
var s = Object.prototype.toString.call(smartObject);
But why? It's just going to give you "[object Object]" on any plain object. Why is it important to get that string?
var p = {};
var s = Object.create(null);
console.log(p.toString());
console.log(Object.prototype.toString.call(s));
typically you would assume
No, you wouldn't. If you make such assumptions, document them in your interface.
What's the best way to turn these dumb objects into ordinarily behaving ones?
Use Object.assign with an ordinary object as the target. Alternatively, you can also change the prototype by using Object.setPrototypeOf, but that's not recommended.
Or just create the properties like .toString or .constructor that you need. See this example.
And, why would anybody want to create such objects in the first place?
Because you need this safety when using objects instead of Maps. See this example.
Explicitly set the prototype of your dumb object with Object#setPrototypeOf
var dumbObject = Object.create(null);
Object.setPrototypeOf(dumbObject, Object.prototype);
dumbObject.toString() //runs fine
Thanks for all the answers, one of which contained a link to another question which really was my question as well, even if I didn't know that at first. That other question is: How to create a JS object with the default prototype from an object without a prototype?
From it I found the answer which I think is the simplest solution so far: Use Object.assign(). Like this:
let orphan = require('querystring').parse("{}") ;
// console.log ( "Got value: " + orphan ) ;
// Above causes the error:
// -> TypeError: Cannot convert object to primitive value
let oa = (x) => Object.assign({}, x);
console.log ("Got value: " + oa (orphan) ) ;
Note the issue is not particularly about "querystring" but with objects which have no prototype in general. Yes we should probably call these poor objects "orphans" instead of "dumb". But I think "dumb" is still quite good term as well. An object which has no prototype has very few (or no?) methods so it can answer very few if any questions we would like to ask it.
I have an odd situation in my Javascript code which I cannot explain. So far it has been observed only on Safari/Mac.
I have a line like this:
dict[id].sliceHovered = true;
And sometimes it throws an error:
Attempted to assign to readonly property.
Also:
dict is a bare blank object which I create myself with dict={}.
id is supplied by outside data, so it can be anything (I don't yet know which particular value causes this).
sliceHovered is obviously not a name of something that Javascript has built
in.
The objects in the dict are of my own type. They have a sliceHovered member, but it's not a Javascript defined property (as in Object.defineProperty()), just a regular property (the constructor executes this.sliceHovered=false).
"use strict" is on.
Object.freeze(), Object.seal(), Object.preventExtensions() and const are not used anywhere in the entire codebase.
Thus it's extremely puzzling as to how such an error could be thrown here. If I had an indexing error and dict[id] would be undefined or null, the error would be different. My only idea is that since the dict is created as dict={} then it inherits from Object and maybe id maps to some inherited property. But that means that the object returned from dict[id] would have to be read-only itself, because sliceHovered is definitely not a name of an existing Javascript property.
However I cannot think of any Javascript objects that would be intrinsically read-only like that.
Any ideas what could be wrong here?
You can check this situation
My only idea is that since the dict is created as dict={} then it inherits from Object
with: var dict = Object.create(null);
Also try to use Object.getOwnPropertyDescriptor(dict, id) to make sure descriptors have right values.
So I learned a bit about the hidden class concept in v8. It is said that you should declare all properties in the constructor (if using prototype based "pseudo classes") and that you should not delete them or add new ones outside of the constructor. So far, so good.
1) But what about properties where you know the type (that you also shouldn't change) but not the (initial) value?
For example, is it sufficient to do something like this:
var Foo = function () {
this.myString;
this.myNumber;
}
... and assign concrete values later on, or would it be better to assign a "bogus" value upfront, like this:
var Foo = function () {
this.myString = "";
this.myNumber = 0;
}
2) Another thing is with objects. Sometimes I just know that an object wont have a fixed structure, but I want to use it as a hash map. Is there any (non verbose) way to tell the compiler I want to use it this way, so that it isn't optimized (and deopted later on)?
Update
Thanks for your input! So after reading your comments (and more on the internet) I consider these points as "best practices":
Do define all properties of a class in the constructor (also applies for defining simple objects)
You have to assign something to these properties, even if thats just null or undefined - just stating this.myString; is apparently not enough
Because you have to assign something anyways I think assigning a "bogus" value in case you can't assign the final value immediatly cannot hurt, so that the compiler does "know" ASAP what type you want to use. So, for example this.myString = "";
In case of objects, do assign the whole structure if you know it beforehand, and again assign dummy values to it's properties if you don't know them immediatly. Otherwise, for example when intending to use the Object as a hashmap, just do: this.myObject = {};. Think its not worth indicating to the compiler that this should be a hashmap. If you really want to do this, I found a trick that assigns a dummy property to this object and deletes it immediatly afterwards. But I won't do this.
As for smaller Arrays it's apparently recommended (reference: https://www.youtube.com/watch?v=UJPdhx5zTaw&feature=youtu.be&t=25m40s) to preallocate them especially if you know the final size, so for example: this.myArray = new Array(4);
Don't delete properties later on! Just null them if needed
Don't change types after assigning! This will add another hidden class and hurt performance. I think thats best practice anyways. The only case where I have different types is for certain function arguments anyways. In that case I usually convert them to the same target type.
Same applies if you keep adding additional properties later on.
That being said, I also think doing this will lean to cleaner and more organized code, and also helps with documenting.
Yeah, so one little thing I am unsure remains: What if I define properties in a function (for example a kind of configure() method) called within the constructor?
Re 1): Just reading properties, like in your first snippet, does not do anything to the object. You need to assign them to create the properties.
But for object properties it doesn't actually matter much what values you initialise them with, as long as you do initialise them. Even undefined should be fine.
The concrete values are much more relevant for arrays, where you want to make sure to create them with the right elements (and without any holes!) because the VM tries to keep them homogeneous. In particular, never use the Array constructor, because that creates just holes.
Re 2): There are ways to trick the VM into using a dictionary representation, but they depend on VM and version and aren't really reliable. In general, it is best to avoid using objects as maps altogether. Since ES6, there is a proper Map class.
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.