I have an object and one of the properties is an array. I want to set a new property on that array (customProp = 0). However, I want to do so inside myObject's declaration.
myObject = {
someString: "text",
someArray: ["one","two"],
/* What I've Tried */
someArray: { customProp: 0 } // Overwrites my array with a new object
someArray["customProp"]: 0 // Invalid syntax
someArray: customProp: 0 // Also invalid
}
If I can't create the array then set a property on it, can I do so in one fell swoop (again, staying within the confines of this object)?
I have another (small) question: How can I reference one of the properties still inside the declaration. I want to set otherProp = someString, how would I do so?
myObject = {
someString: "text",
otherString: someString, // someString is undefined
otherString: myObject["someString"], // myObject is undefined
otherString: this["someString"] // Just... undefined
}
I may need to split this into a separate question, but hopefully whoever answers the first will know the answer to the second.
Unfortunately neither of your requests are possible. Object literals in JavaScript are convenient but come with drawbacks, mostly what you've discovered.
When you are inside an object literal, the object doesn't quite exist yet. The JavaScript interpreter hasn't finished ingesting it. So inside the literal, this points to the object just outside the literal, and the literal has no way to refer to other parts of itself.
Fortunately, you can do both of what you want by just doing it after the literal declaration.
myObject = {
someString: 'text',
someArray: ['one', 'two']
};
myObject.someArray.customProp = 0;
myObject.otherString = myObject.someString;
Or if you want, you can wrap all of this inside a constructor function and create your object with new.
function MyObject() {
this.someArray = ['one', 'two'];
this.someArray.otherProp = 0;
this.otherString = this.someString = 'text';
}
var myObject = new MyObject();
Well, arrays are numeric-based, so
someArray["customProp"]: 0
wouldn't work. It should be a Javascript Object {} for string-based keys to work. And then you could just say
someArray: {0:"one",1:"two","customProp":0},
For your second question: I don't think that's possible. The object is not yet initialized, so you can't yet read out of it...
You can set properties on the Array but they will not get iterated using native functions. Your alternatives are an object (as suggested) or an array with an object as one of the members:
["one", "two", {customProp: 0}]
Related
Object reference is lost after an assignment. Any previous references are no longer relevant.
I have the following:
// lib.js
const obj = { prop: { data: { some: 'empty' } } };
function loadObject() {
obj.prop.data = { some: 'load' };
}
modules.exports = { prop: obj.prop, data: obj.prop.data, loadObject() };
and
// main.js
const { prop, data, loadObject } = require('./lib');
loadObject();
console.log(prop.data); // data changed (new reference)
console.log(data); // data not changed (old reference)
How can I modify data without losing it's initial reference?
PS:
I have a bunch of files importing this lib and it's relying on that data.
I'd rather not replace it to prop.data or reread it again in each file that uses it.
You replaced data with a new object. There’s no way to change everything that references an old object to point to a new object instead, so if you want to keep the exact same API, you have to alter the object in place instead of replacing it – maybe like you did before:
// if it’s a single property that’s the same before and after
function loadObject() {
obj.prop.data.some = 'load';
}
// if the properties before are a subset of the properties after
function loadObject() {
Object.assign(obj.prop.data, { some: 'load' });
}
// if you need to change the set of properties entirely
function loadObject() {
for (const key of Object.keys(obj.prop.data)) {
delete obj.prop.data[key];
}
Object.assign(obj.prop.data, { some: 'load' });
}
The problem is not related to different modules systems or objects de-structuring. Please see the following example.
const obj = {prop: {data: {some: 'empty'}}};
const objRef = obj.prop.data;
console.log(objRef);
obj.prop.data.some = 'not empty';
console.log(objRef);
obj.prop.data = {other: 'hello!'};
console.log(objRef);
console.log(obj);
Remember that each time that you use the bracket notation to create objects {} you are implicitly instantiating a new "independent" object. This is still valid for nested objects like the one in your example. Indeed you have multiple nested references of multiple objects.
The 'name' of the nested object (the key of the parent) acts as a variable that contains the reference of the new object that you are instantiating using {}.
I know that this may appear confusing (because it really is), but if you follow the previous snippet I'm sure you will get it. objRef keeps a reference of the "independent" object {some: 'empty'}, that at the same time is stored as a reference in all the other objects until the primary object obj. In this line: obj.prop.data = {other: 'hello!'}; we are completely erasing {some: 'empty'} reference from the primary object, but it is kept in objRef which will be the only remaining reference for that object. We are separating objRef from obj.
Summary:
Each time you use {} -> equivalent to -> new Object()
obj.prop.data.some = 'not empty'; -> Changing property value. Will be seen in all references of data: {} object.
obj.prop.data = {other: 'hello!'}; -> Completely replacing data: {} object reference in main object (destroying reference) (still kept in objRef).
If it is not clear I could try explaining it in a different way. Please let me know.
Having any variable seemingly alter itself on its own (as a consumer of the library would experience) is quite unintuitive, and is often forbidden by linters for the sake of code clarity - if that sort of logic is needed, consider exporting a function which returns the current data instead, eg
modules.exports = {
prop: obj.prop,
getData: () => obj.prop.data,
loadObject
};
(note that to export the function, you need to just reference the function, not call it)
While it would be possible to export a mutable binding if you were using ES6 modules, that's not the case here, and wouldn't be a good idea anyway.
If I wanted to clone any javascript object (that's not null), I would think I could just copy all of its own properties (enumerable and non-enumerable) -- using Object.getOwnPropertyNames -- onto a new empty object.
But I've noticed that an example of a deep cloning function provided by Dojo toolkit (https://davidwalsh.name/javascript-clone) treats RegExp, Date, and Node objects as special cases, and lodash.cloneDeep also has a lot of logic that is a lot more complicated than simply copying properties, including having some special cases of its own and apparently not supporting all types of objects: (https://github.com/lodash/lodash/blob/master/.internal/baseClone.js).
Why is simply copying the object properties not sufficient? What else is there to a javascript object besides its properties that I don't know about?
EDIT: to be clear, I'm talking about deep cloning an object. Sorry for the confusion.
If the top level properties are all value objects like strings and numbers then just copying the top level properties is fine for a clone of an object. If there are any reference objects such as dates, arrays or other objects then all your are doing is copying a reference from one object to another. If you change the reference object on the clone you will mutate the original object.
Take a look at my clone function at https://stackblitz.com/edit/typescript-qmzgf7
If it is an array it clones every item in the array, if it is a date it creates a new date with the same time, if it is an object it clones every property else if just copies the property.
The cloned object can now be mutated without worrying about effects it might have on the original object.
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: (typeof obj === 'object') && obj
? Object.getOwnPropertyNames(obj).reduce((o, prop) => ({ ...o, [prop]: clone(obj[prop]) }), {})
: obj;
let original = { prop1: "Original", objProp: { prop1: "Original" } };
let swallowCopy = { ...original };
let clonedObj = clone(original);
clonedObj.prop1 = "Changed";
clonedObj.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
swallowCopy.prop1 = "Changed";
swallowCopy.objProp.prop1 = "Changed";
console.log(`Original objects properties are '${original.prop1}' and '${original.objProp.prop1}'`);
Notice how modifying the property on the object property shallow copy causes the original to change as well.
The easiest way to clone an object in JS is by using the ... spread operator.
Let's say you have this object:
const object = { foo: 1, bar: 2 }
To clone it, you can simply declare:
const objectClone = {...object}.
This will create all the properties present in the original object onto the clone, as well as their values.
Now the problem is, if you have any object nested in there, the copies will be made by reference. Suppose the original object is this instead:
const student = { studentID: 1, tests: { test1: 90, test2: 95}}
If you create a copy of that object by using the spread operator(or Object.assign, spread is just syntactic sugar), the nested object will actually point to the object inside the original object! So repeating this:
const studentClone = {...student}
And now you edit a property of the nested object inside the clone:
studentClone.tests.test1 = 80
This will change the value in both clone, and original object, as the nested object is really just pointing to 1 object in memory.
Now what those utilities, like _.cloneDeep will do, is iterate through all inner objects in the object you're cloning, and repeat the process. You could technically do it yourself, but you wouldn't be able to do it on objects with many nested objects easily. Something like this:
const studentClone = {...studentClone, tests: {...studentClone.tests}}
This would create new objects, with no reference problems.
Hope this helped!
EDIT: Just adding, object spreading would only work properly for prototype objects, of course. Each instantiated objects,such as arrays, Date objects etc, would have their own way of cloning.
Arrays can be copied similarly, through [...array]. It does follow the same rules regarding to references. For dates, you can simply pass the original date object into the Date constructor again:
const clonedDate = new Date(date)
This is where the third-party utilities will come in handy, as they'll usually handle most use cases.
This answer does a good job of explaining two of the problems with cloning a normal JavaScript object: prototype properties and circular references. But to answer your question regarding certain built-in types, the TL;DR answer is that there are 'under the hood' properties that you have no programmatic access to.
Consider:
let foo = [1, 2];
let bar = {};
Object.assign(bar, foo);
Object.setPrototypeOf(bar, foo.constructor.prototype); // aka Array.prototype
bar[0]; // 1
bar instanceof Array; // true
bar.map(x => x + 1); // [] ????
Empty array? Why? Just to make sure we're not crazy
foo.map(x => x + 1); // [2, 3]
The reason why map (and the other array methods) fail to work is that an Array isn't simply an object: it has internal slot properties for the stuff you put in it that you don't get to see as the JavaScript programmer. As another example, every JavaScript object has an internal [[Class]] property that says what kind of object it is. Fortunately for us, there's a loophole in the spec that allows us indirect access to it: the good ol Object.prototype.toString.call hack. So let's see what that has to say about various stuff:
Object.prototype.toString.call(true); // [object Boolean]
Object.prototype.toString.call(3); // [object Number]
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(/\w/); // [object RegExp]
Object.prototype.toString.call(JSON); // [object JSON]
Object.prototype.toString.call(Math); // [object Math]
Let's see what it says about our foo and bar:
Object.prototype.toString.call(foo); // [object Array]
Object.prototype.toString.call(bar); // [object Object] Doh!
There's no way to 'convert' a random object to an Array... or a Date... or an HTMLElement... or a regex. Now, there are in fact ways to clone all of those things, but they require special logic: you can't just copy properties, or even set the prototype, because they have internal logic you can't access or directly replicate.
In normal everyday JavaScript programming we don't worry too much about this stuff, it's the kind of thing that's generally of interest to library authors (or language implementers). We everyday working stiffs just use a library to cover the edge cases and call it a day. But every once in a while the abstractions we use leak and the ugly bubbles through. This is however a great illustration of why you should probably use battle-tested libraries rather than trying to roll your own.
An object in javascript includes fields and functions together, and every field could be another object (Like Date type). If you copy a date field, it will be a reference type assignment.
Example:
var obj1 = { myField : new Date('2018/9/17') };
var obj2 = {};
obj2.myField = obj1.myField;
Now, if we change "obj2.myField" like this:
obj2.myField.setDate(obj2.myField.getDate() + 2);
console.log(obj1.myField); // Result =====> Wed Sep 19 2018 00:00:00 GMT+0430
As you see, obj1 and obj2 still are linked.
Correct way to copy a date field:
obj2.myField = new Date(obj1.myField.getTime());
Most native objects(like you have mentioned - I don't know for is the correct naming for them; maybe built-in?) are treated as "simple": it does not make sense to copy Date object property-by-property. In the same time they all are mutable in some way.
let a = {test: new Date(1)}; // test === Thu Jan 01 1970 00:00:00GMT
let copy_a = {test: a.test}; // looks like cloned
a.test.setDate(12); // let's mutate original date
console.log(copy_a.test); // Thu Jan 12 1970 00:00:00GMT ooops modified as well
So you either should handle that exceptions(special cases) explicitly or take a risk of side effects for some cases.
Can anyone explain objects and properties to me in Javascript in layman's terms? The Javascript MDN documentation is confusing me.
I am trying to solve a problem in a Javascript tutorial about objects and properties The question is below and my attempt at the code, I think I followed the MDN as far as I can grasp it. Any help would be appreciated!
Question: Add the value of the property argument as a key on the object argument.
The value of the new property should be set to null.
Return object after adding the new property.
Input Example:
{}, 'hello'
{ x: 5 }, 'y'
Output Example:
{ hello: null }
{ x: 5, y: null }
note: the property name is NOT 'property'. The name is the value of the argument called property (a string).
My code:
//NOTE: the function addProperty(object, property) was already in the console and I have to write the solution inside of it.
function addProperty(object, property) {
// code here
let result = addProperty({x: 5}, 'y');
obj[property] = null;
return obj;
}
addProperty(x, 'y');
Layman's explanation:
An object is a collection of properties. You can give a name to the object to keep things organized. For example, lets create a person object.
var person = {};
The object has no properties right now. To further describe the person we can add properties to the object.
person.Name = 'Zim';
person.Age = 29;
person.Gender = 'Male';
person.Weight = 80;
Now this object has some properties to help describe it. A different way to write the same thing:
var person = { Name: 'Zim', Age: 29, Gender: 'Male', Weight: 80 };
If we had to create a program that displays a list of people, storing all of our information inside objects would help keep things organized.
Object properties are sometimes referred to as keys.
Adding properties to objects:
You can add a property to an object using brackets, just like you had in your addProperty function. If you just need it to add a property, set that property to null and return the result it would look something like this:
function addProperty(object, property) {
// code here
object[property] = null;
return object;
}
This would let us create a properies on our object from above by calling
addProperty(person, 'Occupation');
addProperty(person, 'Income');
addProperty(person, 'Height');
I think you're over-thinking it.
First the question:
Add the value of the property argument as a key on the object
argument. The value of the new property should be set to null. Return
object after adding the new property.
emphasis added
So, property will be the KEY (of a key/value pair), and the VALUE will be null of object, which we are also passing in as an argument.
One way to interrogate key/value pairs on a javascript object is through the square-brackets []. So, if you have a key/value pair: { foo: "bar" }, you can get "bar" by: object['foo']. You can also create new key/value pairs like this, so your function can look like:
function addProperty(object, property) {
object[property] = null;
return object;
}
var obj = {};
obj = addProperty(obj, "hello");
console.log(obj);
console.log(addProperty({x: 5}, 'y'));
What our function is doing is taking the object passed into it (as an argument), creating a new KEY with our property argument, and setting its VALUE to null, and simply returning the object.
*Side note -
Be careful, the code you have posted will create an endless recursive loop, as you keep calling the same function with no way to break out of it.
This particular example is simple:
var one={};
var two={x:5};
function addProperty(object property){
obj[property]=null;
return obj;
}
addProperty(one, 'hello');
addProperty(two, 'y');
Objects in javascript are really flexible, their properties can be added or removed, even when set from the very beginning.
If you take:
var x={};
x will be an object with nothing in it, but if instead of that you write:
var x={
inner:'b'
};
x will be an object with a property called inner which value is 'b', now, if we want to access that property we could do something like this:
var valueOfInner=x.inner;
or
var valueOfInner=x['inner'];
The same if we want to change the value of that property:
x.inner=8;
or
x['inner']=8;
Now, you'll notice that when we use x.['inner'], we could very well use instead:
var propertyName='inner';
x[propertyName]=8;
So you can access and modify a property of an object without actually knowing exactly which property you're manipulating.
Finally, if you're (willing or by accident) trying to set the value of a property that doesn't exists, the property will be automatically be created, for example:
x['blah']=456;
Will create the property blah even when it wasn't defined at first.
Edit: yes, you can define the object and define its properties later:
var x={};
//more code or something
x['y']=777;//now x has a y property with the value 777
a property is a key:
let obj = {
name: 'Jordan',
age: 15
}
obj.name is a property, same goes for obj.age.
My simple example:
let a = { foo : 5}, b = { stuff : 7};
let func = function(obj, prop) { ++obj[prop]; }
func(a, 'foo');
func(b, 'stuff');
When I call func I set the property name as string literal directly. If I want to rename the property late (in my code source) then I have to change the string literal too. Is it possible to get the property name as a string in runtime instead of using the string value as a literal?
UPD (for clarity)
In my project, I have some classes each of them has a property which contains an array. But this property has the different name for each class. I have a logic for handling these arrays content. This logic is the same for each class. Right now I pass the property name as a string literal, but if I later rename these properties in my code source then I must to change and the string literals too. If I forget to do it I will have a problem. So I want to get rid of the use of string literals in this task.
I think what you're trying to do is a bad idea. If something behaves the same in different objects, call it the same name. But hey, there's an ES6 concept for that!
Symbols are used by JavaScript to support similar behaviour in different "classes". So let's do that here.
Let's create a Symbol:
const incrementableProp = Symbol("incrementableProp")
First, let's store the name of the property in your object that should have the behaviour:
const a = {
specialProp: [],
[incrementableProp]: "specialProp"
}
The Symbol itself will always be the same, so the increment function can find it reliably:
function incrementProp(obj) {
if(incrementableProp in obj)
obj[obj[incrementableProp]]++
else throw new TypeError("This object does not support an incremental property.")
}
Lastly, let's make sure you need to change the name only once by removing the Symbol definition in the object. We'll use a decorator for that:
function special(target, key, descriptor) {
target[incrementableProp] = key
return descriptor
}
So now you can do this:
const a = {
#special specialProp: []
}
I have been working with a fair bit of JSON parsing and passing in Javascript within Node.js and browsers recently and bumped into this conundrum.
Any objects I created using a constructor, cannot be fully serialized fully via JSON.stringify, UNLESS I initialised all values within the constructor individually! This means my prototype becomes essentially useless in designing these classes.
Can someone shed some light on why the following doesn't serialize as I expect?
var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a);
What happens:
a_string == { "initialisedValue" : "You can see me!" }
I would expect:
a_string == { "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
Update (01-10-2019):
Finally noticed #ncardeli 's Answer, which does allow us to do something like the following to achieve my above requirement (in 2019!):
Replace
var a_string = JSON.stringify(a);
with
var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));
Full code:
var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));
console.log(a_string)
Simply because this is the way JSON works. From the ES5 spec:
Let K be an internal List of Strings consisting of the names of all the own properties of value whose [[Enumerable]] attribute is true.
This makes sense, because there is no mechanism in the JSON specification for preserving information that would be required to parse a JSON string back into a JavaScript object if inherited properties were included. In your example, how would this parsed:
{ "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
There is no information to parse it into anything other than a flat object with 2 key-value pairs.
And if you think about it, JSON is not intended to map directly to JavaScript objects. Other languages must be able to parse JSON strings into simple structures of name-value pairs. If JSON strings contained all the information necessary to serialize complete JavaScript scope chains, other languages may be less capable of parsing that into something useful. In the words of Douglas Crockford on json.org:
These [hash tables and arrays] are universal data structures. Virtually all modern programming languages support them in one form or another. It makes sense that a data format that is interchangeable with programming languages also be based on these structures.
I'd like to add that, even though JSON.stringify will only stringify the object's own properties, as explained in the accepted answer, you can alter the behavior of the stringification process by specifying an array of String as the second parameter of JSON.stringify (called a replacer array).
If you specify an array of String with the whitelist of properties to stringify, the stringification algorithm will change its behavior and it will consider properties in the prototype chain.
From ES5 spec:
If PropertyList is not undefined, then
a. Let K be PropertyList.
Else
a. Let K be an internal List of Strings consisting of the names of
all the own properties of value whose [[Enumerable]] attribute is
true. The ordering of the Strings should be the same as that used by
the Object.keys standard built-in function.
If you know the name of the properties of the object to stringify beforehand, you can do something like this:
var a_string = JSON.stringify(a, ["initialisedValue", "uninitialisedValue"]);
// a_string == { "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
There is another possibility to still have properties from the prototype to be stringified.
As of the JSON spec (15.12.3 stringify http://es5.github.io/#x15.12.3):
[...]
2. If Type(value) is Object, then
a. Let toJSON be the result of calling the [[Get]] internal method of value with argument "toJSON".
b. If IsCallable(toJSON) is true
i. Let value be the result of calling the [[Call]] internal method of toJSON passing value as the this value and with an argument list consisting of key.
[...]
So yu can write your own JSON stringifier function. A general implementation could be:
class Cat {
constructor(age) {
this.age = age;
}
get callSound() {
// This does not work as this getter-property is not enumerable
return "Meow";
}
toJSON() {
const jsonObj = {}
const self = this; // If you can use arrow functions just use 'this'-keyword.
// Object.keys will list all 'enumerable' properties
// First we look at all own properties of 'this'
Object.keys(this).forEach(function(k) {
jsonObj[k] = self[k];
});
// Then we look at all own properties of this's 'prototype'
Object.keys(Object.getPrototypeOf(this)).forEach(function(k) {
jsonObj[k] = self[k];
});
return JSON.stringify(jsonObj);
}
}
Object.defineProperty(Cat.prototype, 'callSound2', {
// The 'enumerable: true' is important!
value: "MeowMeow", enumerable: true
});
let aCat = new Cat(4);
console.log(JSON.stringify(aCat));
// prints "{\"age\":4,\"callSound2\":\"MeowMeow\"}"
This works as long as the right properties (those you actually want to be JSON stringified) are enumerable and those you dont want to be stringified aren't. So you need to be careful which properties you make enumerable or become implicitly enumerable when you assign values to 'this'.
Another possibility is to assign each property you actually want to be stringifed manually one by one. Which might be less error prone.