Javascript Object Assign and Structural Sharing - javascript

I've been experimenting with concepts of immutability with javascript objects. I was wondering if the following code example implements what I believe is known as "Structural Sharing" (see: https://www.youtube.com/watch?v=e-5obm1G_FY&start=1123).
const objectFirst = {
key1: 1,
key2: 2
}
const updateObject = (lastObject) => {
const updatedObject = {...lastObject, ...{ key2: 4 }} // Object.assign({}, lastObject, { key2: 4 })
return updatedObject
}
const objectNext = updateObject(objectFirst)
objectNext is now { key1: 1, Key2: 4 } and objectFirst is unchanged. But has key1 been duplicated? Or, is their now essentially a reference to key1's location in memory shared by both objects?
I'm basically just asking if this approach implements some sort of "Structural Sharing"? One could see that if this were not the case, then it would lead to significant memory bloat.

No, because 1 is stored as a simple value on objectFirst, not a reference to an object, so when you do {...objectFirst} you're copying the value of objectFirst.key1 to objectNext.key1.
However, if objectFirst.key1 was an object instead, then the shallow copy would copy the object reference instead of creating a new object. As a result, objectFirst.key1 and objectNext.key2 would both reference the same object.
const objectFirst = {
key1: 1,
key2: 2,
sharedObject: {foo: 'original string'}
}
// we make a shallow copy with a different key2 value.
const objectSecond = {...objectFirst, key2: 4};
// changing a value in objectSecond doesn't affect objectFirst
objectSecond.key1 = 100;
console.log(objectFirst.key1); // 1
// but sharedObject is shared
objectSecond.sharedObject.foo = "new string";
console.log(objectFirst.sharedObject); // {foo: "new string"}

console.log(objectFirst['key1'] === objectNext['key1']); // true
console.log(objectFirst['key2'] === objectNext['key2']); // false
This means objectFirst and objectNext share the key1 property.

Related

How to update/set the values of multiple Objects (with the same parameters) with the same values JavaScript/Typescript

What is the best way/how can I update two Objects with the same set of values?
The only method I know of, is by setting each property of each object concurrently. As per example below. Below I am using a method to populate the Objects, by passing the values as parameter in the method.
PLEASE NOTE: the individual parameter I pass in the method (populateIndividualDetails(individual: SelectedMemberIndividualData)) consists of many parameters I do not need, and is not in the format I desire. Hence the use of a method to assign the properties.
Additional Note: Both Objects I wish to populate have the same parameters, and is in the exact same format. The Objects have nested parameters.
Perhaps one could copy the 1st Object after it has been populated? 🤔
Example:
model = {
initials: '',
name: '',
address: {
streetName: '',
...
}
...
}
initialValues= {
initials: '',
name: '',
address: {
streetName: '',
...
}
...
}
populateIndividualDetails(individual: SelectedMemberIndividualData) {
this.model.initials = individual.initials;
this.initialValue.initials = individual.initials;
...
}
Rather than populating model and initialValues with empty key-value pairs, you could instead consider making an array of the properties which you want to be set in both the model and initialValues objects. Inside of populateIndividualDetails() you can then loop over this array with a for loop, and grab each property from the passed in individual object which you can then set on your model and initialValues objects.
const desiredProps = ["a", "b", "d"]; // contains "initials", etc...
const model = {};
const initialValues = {};
function populateIndividualDetails(individual) {
for(const prop of desiredProps) {
model[prop] = individual[prop];
initialValues[prop] = individual[prop];
}
}
populateIndividualDetails({a: 1, b: 2, c: 3, d: 4}); // ignore "c" value
console.log(model);
console.log(initialValues);
EDIT
If you need model and initialValues to be populated initially, then it might be better to create one and deep-clone the other (as you mentioned you can have nested object properties) and then use recursion to handle the nested objects:
const model = {a: '', b: {c: ''}, e: ''};
const initialValues = JSON.parse(JSON.stringify(model)); // deep-clone model, both are their own object references.
function populateIndividualDetails(o1, o2, individual) {
Object.keys(o1).forEach(key => {
if(Object(o1[key]) === o1[key])
populateIndividualDetails(o1[key], o2[key], individual[key])
else
o1[key] = o2[key] = individual[key];
});
}
populateIndividualDetails(model, initialValues, {a: 1, b: {c: 2, d: 3}, e: 4, f: 5}); // ignore "f" value
console.log(model);
console.log(initialValues);
You can just destructurate and pick the properties of individual the following way:
function populateIndividualDetails({initials}: SelectedMemberIndividualData) {
this.model.initials = initials;
this.initialValue.initials = initials;
}
but if you have several properties you might want this but this will replace the entire model and initial value objects and they will point to the same object…
function populateIndividualDetails(individual: SelectedMemberIndividualData) {
const {a, b, c} = individual;
const copy = {a, b ,c}
this.model= copy;
this.initialValue= copy;
}
What you are probably looking for is to just add some properties to the existing model and initialValue objects
function populateIndividualDetails(individual: SelectedMemberIndividualData) {
const {a, b, c} = individual;
const propertiesToAdd = {a, b ,c}
this.model= {...this.model, ...propertiesToAdd};
this.initialValue= {...this.initialValue, ...propertiesToAdd};
}
Note:
anObject = {...anObject, ...anotherObject};// can be replaced with
Object.assign(anObject,anotherObject);
Using lodash you could also do this:
function getDefaultProperties(anObject: SelectedMemberIndividualData){
return _.pick(anObject, ['a','b','c'])
}
function populateIndividualDetails(individual: SelectedMemberIndividualData){
const defaultProperties = getDefaultProperties(individual);
[this.model, this.initialValue].forEach((value) => {
_.assign(value, defaultProperties);
})
}

Key Interpolation Javascript

I was reading key interpolation in javascript and the article, it says following is a key interpolation
const example = {key1: value1, key2: value2}
and to add new value to the above object we will do the following
const example1 = {...example, [key3]:value3}
and this gives o/p as:
{key1: value1, key2: value2,key3: value3}
The article says bracket is required for key interpolation.
I tried the same things as
const example1 = {...example, key3:value3}
and it gives the same result.
Is there any difference between them or they are the same as they give the same o/p?
it says following is a key interpolation const example = {key1: value1, key2: value2}
I don't see any "key interpolation" there.
...and to add new value to the above object we will do the following const example1 = {...example, [key3]:value3} and this gives o/p as {key1: value1, key2: value2,key3: value3}.
Only if you have a variable called key3 which has the value "key3" in it, which seems like a really confusing example for an article to have.
The article says bracket is required for key interpolation.
I tried the same things as const example1 = {...example, key3:value3} and it gives the same result. Is there any difference between them or they are the same as they give the same o/p?
The difference is whether you're providing the name of the property (the key) using literal notation (no []) or computed notation (with []):
// Literal notation, adds a property called `key` to the object
const obj1 = {key: "value"};
console.log(obj1.key); // "value"
// Computed notation, adds a property whose name comes from the
// contents of the variable `key` to the object:
const key = "propertyName";
const obj2 = {[key]: "value"};
console.log(obj2.key); // undefined, there isn't any property with the name "key"
console.log(obj2.propertyName); // "value"
console.log(obj2[key]); // "value"
They are not the same.
If you use string literals, then they behave the same.
These are identical:
const example1 = {foo: 'bar'}
const example2 = {'foo': 'bar'}
const example3 = {['foo']: 'bar'}
The difference appears when you try to put an expression in a key:
const keyName = 'foo'
const example1 = {keyName: 'bar'} //{keyName: 'bar'}
const example2 = {[keyName]: 'bar'} //{foo: 'bar'}
The interpolator syntax ([]) allows you to put any expression into the key, for example, this is also valid:
const fn = foo => foo.length
const example = {
[10 - fn('baz') + 'baz']: 'bar'
}
//{7baz: 'bar'}

Is there a way to capture a variable access? [duplicate]

Is there a way to set the default attribute of a Javascript object such that:
let emptyObj = {};
// do some magic
emptyObj.nonExistingAttribute // => defaultValue
Since I asked the question several years ago things have progressed nicely.
Proxies are part of ES6. The following example works in Chrome, Firefox, Safari and Edge:
let handler = {
get: function(target, name) {
return target.hasOwnProperty(name) ? target[name] : 42;
}
};
let emptyObj = {};
let p = new Proxy(emptyObj, handler);
p.answerToTheUltimateQuestionOfLife; //=> 42
Read more in Mozilla's documentation on Proxies.
Use destructuring (new in ES6)
There is great documentation by Mozila as well as a fantastic blog post that explains the syntax better than I can.
To Answer Your Question
var emptyObj = {};
const { nonExistingAttribute = defaultValue } = emptyObj;
console.log(nonExistingAttribute); // defaultValue
Going Further
Can I rename this variable? Sure!
const { nonExistingAttribute: coolerName = 15} = emptyObj;
console.log(coolerName); // 15
What about nested data? Bring it on!
var nestedData = {
name: 'Awesome Programmer',
languages: [
{
name: 'javascript',
proficiency: 4,
}
],
country: 'Canada',
};
var {name: realName, languages: [{name: languageName}]} = nestedData ;
console.log(realName); // Awesome Programmer
console.log(languageName); // javascript
There isn't a way to set this in Javascript - returning undefined for non-existent properties is a part of the core Javascript spec. See the discussion for this similar question. As I suggested there, one approach (though I can't really recommend it) would be to define a global getProperty function:
function getProperty(o, prop) {
if (o[prop] !== undefined) return o[prop];
else return "my default";
}
var o = {
foo: 1
};
getProperty(o, 'foo'); // 1
getProperty(o, 'bar'); // "my default"
But this would lead to a bunch of non-standard code that would be difficult for others to read, and it might have unintended consequences in areas where you'd expect or want an undefined value. Better to just check as you go:
var someVar = o.someVar || "my default";
my code is:
function(s){
s = {
top: s.top || 100, // default value or s.top
left: s.left || 300, // default value or s.left
}
alert(s.top)
}
The way I achieve this is with the object.assign function
const defaultProperties = { 'foo': 'bar', 'bar': 'foo' };
const overwriteProperties = { 'foo': 'foo' };
const newObj = Object.assign({}, defaultProperties, overwriteProperties);
console.log(defaultProperties); // {"foo": "bar", "bar": "foo"}
console.log(overwriteProperties); // { "foo": "foo" };
console.log(newObj); // { "foo": "foo", "bar": "foo" }
This seems to me the most simple and readable way of doing so:
let options = {name:"James"}
const default_options = {name:"John", surname:"Doe"}
options = Object.assign({}, default_options, options)
Object.assign() reference
This sure sounds like the typical use of protoype-based objects:
// define a new type of object
var foo = function() {};
// define a default attribute and value that all objects of this type will have
foo.prototype.attribute1 = "defaultValue1";
// create a new object of my type
var emptyObj = new foo();
console.log(emptyObj.attribute1); // outputs defaultValue1
I think the simplest approach is using Object.assign.
If you have this Class:
class MyHelper {
constructor(options) {
this.options = Object.assign({
name: "John",
surname: "Doe",
birthDate: "1980-08-08"
}, options);
}
}
You can use it like this:
let helper = new MyHelper({ name: "Mark" });
console.log(helper.options.surname); // this will output "Doe"
Documentation (with polyfill):
https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Or you can try this
dict = {
'somekey': 'somevalue'
};
val = dict['anotherkey'] || 'anotherval';
Simplest of all Solutions:
dict = {'first': 1,
'second': 2,
'third': 3}
Now,
dict['last'] || 'Excluded'
will return 'Excluded', which is the default value.
If you only have an object that is a single level deep (nested object properties will not merge as expected since it directly destructures from the first level), you can use the following destructuring syntax:
const options = {
somevar: 1234,
admin: true
};
const defaults = {
test: false,
admin: false,
};
var mergedOptions = {...defaults, ...options};
Of which the output would be:
console.log(options);
// { somevar: 1234, admin: true }
console.log(mergedOptions);
// { test: false, admin: true, somevar: 1234 }
Or even formatted as a single statement (this is slightly unreadable though):
const options = {...{
// Defaults
test: false,
admin: false,
}, ...{
// Overrides
somevar: 1234,
admin: true
}};
I saw an article yesterday that mentions an Object.__noSuchMethod__ property: JavascriptTips I've not had a chance to play around with it, so I don't know about browser support, but maybe you could use that in some way?
I'm surprised nobody has mentioned ternary operator yet.
var emptyObj = {a:'123', b:'234', c:0};
var defaultValue = 'defaultValue';
var attr = 'someNonExistAttribute';
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue;//=> 'defaultValue'
attr = 'c'; // => 'c'
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue; // => 0
In this way, even if the value of 'c' is 0, it will still get the correct value.
var obj = {
a: 2,
b: 4
};
console.log(obj);
--> {a: 2, b: 4}
function applyDefaults(obj) {
obj.a ||= 10;
obj.b ||= 10;
obj.c ||= 10;
}
// do some magic
applyDefaults(obj);
console.log(obj);
--> {a: 2, b: 4, c: 10}
This works because
undefined || "1111111" --> "1111111"
"0000000" || "1111111" --> "0000000"
as null, undefined, NaN, 0, "" (Empty String), false itself, are all considered to be equivalent to false (falsy). Anything else is true (truthy).
Note that this is not uniformly supported across browsers and nodejs versions (confirm for yourself).
So two troublesome cases are the empty String "" and 0 (zero). If it is important not to override those, you might need to rewrite this as:
if (typeof obj.d == "undefined") obj.d = "default"
This will be better supported across browsers also.
Alternatively you could write this as:
obj.d ??= "default"
This is the nullish assignment which applies only to values that are null or undefined (nullish) - of which the empty string is not part. However, this has again a diminished cross-browser support.
See also on the official Mozilla Website - Assigning a default value to a variable.
This is actually possible to do with Object.create. It will not work for "non defined" properties. But for the ones that has been given a default value.
var defaults = {
a: 'test1',
b: 'test2'
};
Then when you create your properties object you do it with Object.create
properties = Object.create(defaults);
Now you will have two object where the first object is empty, but the prototype points to the defaults object. To test:
console.log('Unchanged', properties);
properties.a = 'updated';
console.log('Updated', properties);
console.log('Defaults', Object.getPrototypeOf(properties));
Object.withDefault = (defaultValue,o={}) => {
return new Proxy(o, {
get: (o, k) => (k in o) ? o[k] : defaultValue
});
}
o = Object.withDefault(42);
o.x //=> 42
o.x = 10
o.x //=> 10
o.xx //=> 42
One approach would be to take a defaults object and merge it with the target object. The target object would override values in the defaults object.
jQuery has the .extend() method that does this. jQuery is not needed however as there are vanilla JS implementations such as can be found here:
http://gomakethings.com/vanilla-javascript-version-of-jquery-extend/
With the addition of the Logical nullish assignment operator, you can now do something like this
const obj = {}
obj.a ??= "default";
In the case where you have an empty list as the default value and want to push to it, you could do
const obj = {}
(obj.a ??= []).push("some value")
I came here looking for a solution because the header matched my problem description but it isn't what i was looking for but i got a solution to my problem(I wanted to have a default value for an attribute which would be dynamic something like date).
let Blog = {
title : String,
image : String,
body : String,
created: {type: Date, default: Date.now}
}
The above code was the solution for which i finally settled.

Default property on Javascript object [duplicate]

Is there a way to set the default attribute of a Javascript object such that:
let emptyObj = {};
// do some magic
emptyObj.nonExistingAttribute // => defaultValue
Since I asked the question several years ago things have progressed nicely.
Proxies are part of ES6. The following example works in Chrome, Firefox, Safari and Edge:
let handler = {
get: function(target, name) {
return target.hasOwnProperty(name) ? target[name] : 42;
}
};
let emptyObj = {};
let p = new Proxy(emptyObj, handler);
p.answerToTheUltimateQuestionOfLife; //=> 42
Read more in Mozilla's documentation on Proxies.
Use destructuring (new in ES6)
There is great documentation by Mozila as well as a fantastic blog post that explains the syntax better than I can.
To Answer Your Question
var emptyObj = {};
const { nonExistingAttribute = defaultValue } = emptyObj;
console.log(nonExistingAttribute); // defaultValue
Going Further
Can I rename this variable? Sure!
const { nonExistingAttribute: coolerName = 15} = emptyObj;
console.log(coolerName); // 15
What about nested data? Bring it on!
var nestedData = {
name: 'Awesome Programmer',
languages: [
{
name: 'javascript',
proficiency: 4,
}
],
country: 'Canada',
};
var {name: realName, languages: [{name: languageName}]} = nestedData ;
console.log(realName); // Awesome Programmer
console.log(languageName); // javascript
There isn't a way to set this in Javascript - returning undefined for non-existent properties is a part of the core Javascript spec. See the discussion for this similar question. As I suggested there, one approach (though I can't really recommend it) would be to define a global getProperty function:
function getProperty(o, prop) {
if (o[prop] !== undefined) return o[prop];
else return "my default";
}
var o = {
foo: 1
};
getProperty(o, 'foo'); // 1
getProperty(o, 'bar'); // "my default"
But this would lead to a bunch of non-standard code that would be difficult for others to read, and it might have unintended consequences in areas where you'd expect or want an undefined value. Better to just check as you go:
var someVar = o.someVar || "my default";
my code is:
function(s){
s = {
top: s.top || 100, // default value or s.top
left: s.left || 300, // default value or s.left
}
alert(s.top)
}
The way I achieve this is with the object.assign function
const defaultProperties = { 'foo': 'bar', 'bar': 'foo' };
const overwriteProperties = { 'foo': 'foo' };
const newObj = Object.assign({}, defaultProperties, overwriteProperties);
console.log(defaultProperties); // {"foo": "bar", "bar": "foo"}
console.log(overwriteProperties); // { "foo": "foo" };
console.log(newObj); // { "foo": "foo", "bar": "foo" }
This seems to me the most simple and readable way of doing so:
let options = {name:"James"}
const default_options = {name:"John", surname:"Doe"}
options = Object.assign({}, default_options, options)
Object.assign() reference
This sure sounds like the typical use of protoype-based objects:
// define a new type of object
var foo = function() {};
// define a default attribute and value that all objects of this type will have
foo.prototype.attribute1 = "defaultValue1";
// create a new object of my type
var emptyObj = new foo();
console.log(emptyObj.attribute1); // outputs defaultValue1
I think the simplest approach is using Object.assign.
If you have this Class:
class MyHelper {
constructor(options) {
this.options = Object.assign({
name: "John",
surname: "Doe",
birthDate: "1980-08-08"
}, options);
}
}
You can use it like this:
let helper = new MyHelper({ name: "Mark" });
console.log(helper.options.surname); // this will output "Doe"
Documentation (with polyfill):
https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Or you can try this
dict = {
'somekey': 'somevalue'
};
val = dict['anotherkey'] || 'anotherval';
Simplest of all Solutions:
dict = {'first': 1,
'second': 2,
'third': 3}
Now,
dict['last'] || 'Excluded'
will return 'Excluded', which is the default value.
If you only have an object that is a single level deep (nested object properties will not merge as expected since it directly destructures from the first level), you can use the following destructuring syntax:
const options = {
somevar: 1234,
admin: true
};
const defaults = {
test: false,
admin: false,
};
var mergedOptions = {...defaults, ...options};
Of which the output would be:
console.log(options);
// { somevar: 1234, admin: true }
console.log(mergedOptions);
// { test: false, admin: true, somevar: 1234 }
Or even formatted as a single statement (this is slightly unreadable though):
const options = {...{
// Defaults
test: false,
admin: false,
}, ...{
// Overrides
somevar: 1234,
admin: true
}};
I saw an article yesterday that mentions an Object.__noSuchMethod__ property: JavascriptTips I've not had a chance to play around with it, so I don't know about browser support, but maybe you could use that in some way?
I'm surprised nobody has mentioned ternary operator yet.
var emptyObj = {a:'123', b:'234', c:0};
var defaultValue = 'defaultValue';
var attr = 'someNonExistAttribute';
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue;//=> 'defaultValue'
attr = 'c'; // => 'c'
emptyObj.hasOwnProperty(attr) ? emptyObj[attr] : defaultValue; // => 0
In this way, even if the value of 'c' is 0, it will still get the correct value.
var obj = {
a: 2,
b: 4
};
console.log(obj);
--> {a: 2, b: 4}
function applyDefaults(obj) {
obj.a ||= 10;
obj.b ||= 10;
obj.c ||= 10;
}
// do some magic
applyDefaults(obj);
console.log(obj);
--> {a: 2, b: 4, c: 10}
This works because
undefined || "1111111" --> "1111111"
"0000000" || "1111111" --> "0000000"
as null, undefined, NaN, 0, "" (Empty String), false itself, are all considered to be equivalent to false (falsy). Anything else is true (truthy).
Note that this is not uniformly supported across browsers and nodejs versions (confirm for yourself).
So two troublesome cases are the empty String "" and 0 (zero). If it is important not to override those, you might need to rewrite this as:
if (typeof obj.d == "undefined") obj.d = "default"
This will be better supported across browsers also.
Alternatively you could write this as:
obj.d ??= "default"
This is the nullish assignment which applies only to values that are null or undefined (nullish) - of which the empty string is not part. However, this has again a diminished cross-browser support.
See also on the official Mozilla Website - Assigning a default value to a variable.
This is actually possible to do with Object.create. It will not work for "non defined" properties. But for the ones that has been given a default value.
var defaults = {
a: 'test1',
b: 'test2'
};
Then when you create your properties object you do it with Object.create
properties = Object.create(defaults);
Now you will have two object where the first object is empty, but the prototype points to the defaults object. To test:
console.log('Unchanged', properties);
properties.a = 'updated';
console.log('Updated', properties);
console.log('Defaults', Object.getPrototypeOf(properties));
Object.withDefault = (defaultValue,o={}) => {
return new Proxy(o, {
get: (o, k) => (k in o) ? o[k] : defaultValue
});
}
o = Object.withDefault(42);
o.x //=> 42
o.x = 10
o.x //=> 10
o.xx //=> 42
One approach would be to take a defaults object and merge it with the target object. The target object would override values in the defaults object.
jQuery has the .extend() method that does this. jQuery is not needed however as there are vanilla JS implementations such as can be found here:
http://gomakethings.com/vanilla-javascript-version-of-jquery-extend/
With the addition of the Logical nullish assignment operator, you can now do something like this
const obj = {}
obj.a ??= "default";
In the case where you have an empty list as the default value and want to push to it, you could do
const obj = {}
(obj.a ??= []).push("some value")
I came here looking for a solution because the header matched my problem description but it isn't what i was looking for but i got a solution to my problem(I wanted to have a default value for an attribute which would be dynamic something like date).
let Blog = {
title : String,
image : String,
body : String,
created: {type: Date, default: Date.now}
}
The above code was the solution for which i finally settled.

Is there a purely functional way to merge objects in JavaScript?

Say I have an object with some properties:
const obj = {
key1: 1
key2: 2
}
and I have a function someFunc that takes one object as a parameter. I want to pass obj with some additional parameters, like
someFunc({
key1: 1
key2: 2
otherKey: 3
})
The problem is that I don't want to actually change the state of obj. After someFunc has been called it should not contain otherKey as a property.
This leaves Object.assign out of the question:
let ident = someObj => someObj;
ident(Object.assign(obj, {otherKey: 3}))
console.log(someObj); // -> {key1: 1, key2: 2, otherKey: 3}
Creating a new variable and assigning otherKey = 3 to it does the same thing.
I know I can use some sort of deep duplication - but that seems like an unnecessarily complex way of handling a fairly simple problem.
The most elegant way of doing this would be a pure function that takes some objects obj1, obj2, returns the same output as Object.assign(obj1, obj2), but alters neither of them. Is there some obvious way of doing this in JS that I haven't seen, or do I have to use some other library?
Just reverse the parameters to Object.assign - it will add the properties from obj to a new object:
ident(Object.assign({otherKey: 3}, obj))
Caution
But you must be careful about properties that are already present in obj as they will overwrite the ones in the new array.
You are dealing with immutability. Another way to accomplish thit is to use spread operator of ES6:
const obj1 = {
key1: 1,
key2: 2,
}
const obj2 = {
key1: 1,
key2: 12,
otherKey: 3
};
const merged = { ...obj1, ...obj2 };
console.log('merged: ', merged);
console.log('obj1: ', obj1);
console.log('obj2: ', obj2);
You'll see that neither obj1 nor obj2 is altered

Categories

Resources