I want to map an array, returning the same array except .created property, that I'm transforming from milliseconds to Date Object.
const posts = data.map((post) => {
post.created = new Date(post.created); //milliseconds to Date Object
return post;
})
This function do the result that I expected, but I don't like because the function inside is not pure, is mutating the object. How can be an alternative to do the same with pure function?
Thank you so much.
You can achieve this using Object.assign (docs) inside the map:
const posts = data.map((post) => {
return Object.assign({}, post, { created: new Date(post.created) });
})
This essentially clones the original post and then overrides the created property.
I would provide both solutions.
Clone the array and its objects
data.map(post => Object.assign({created: new Date(post.created)},post));
The first argument of Object.assign is the target object. It's also the object that Object.assign returns.
Modify the existing array
for(let post of data){
post.created = new Date(post.created);
}
This method is, of course, faster and costs less resources since it doesn't need to initialize a new array and its objects then copy all objects' properties like the other does.
The choice is yours. Just make sure you know what you're doing.
You can make copy without copying all of the attributes from the old object:
const posts = data.map((post) => {
const obj = Object.create(post);
obj.created = new Date(post.created); // shadow created with the new value
return obj;
});
In practice it uses JavaScript inheritance scheme so that the new object only has created as it's own attribute and the rest is inherited from the original object.
Related
In my component i have declarated some data like this:
data() {
return {
defaultValue: {json object with some structure},
activeValue: {}
...
And in component methods a make copy this value:
this.activeValue = this.defaultValue
But problem is, after change this.activeValue value a have changes in this.defaultValue too.
If i use Object.freeze(this.defaultValue) and trying change this.activeValue i have get error - object is not writable.
How i can make copy of data but without reference?
If you have simple object, quickest and easiest way is to just use JSON.parse and JSON.stringify;
const obj = {};
const objNoReference = JSON.parse(JSON.stringify(obj));
this.activeValue = { ...this.defaultValue }
Using an ES6 spread operator will help you to do a copy if you do not have a nested object. If you equate using equal = sign, it will not create a new object, it will just create a variable with the reference to the current object (like a shallow copy).
To do a complete deep copy, even it is nested object, go for this:
const objNoReference = JSON.parse(JSON.stringify(obj));
as suggested by Owl.
Click to read more for better understanding of the concept
A nicer way rather than using JSON.parse, JSON.stringify is:
this.activeValue = {...this.defaultValue}
but this is not natively supported by some browser (IE), unless used with a transpiler (babel)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
Update
Considering your originial question is about a way in Vue, there is also a native method in vue:
this.activeValue = Vue.util.extend({}, this.defaultValue)
as for this answer.
Hope this helps!
Objects are assigned and copied by reference.
All operations via copied references (like adding/removing properties) are performed on the same single object.
To make a “real copy” (a clone) we can use Object.assign for the so-called “shallow copy” (nested objects are copied by reference).
For “deep cloning” use _.cloneDeep(obj) from loadash library.
JSON stringify&parse method have some issues like converting date objects to strings. It also cannot handle special data types like Map,Set,function etc... This is prone to future bugs.
I use the following method to deep copy an object.
REMEMBER! this is not a complete application of cloning. There are more data types to handle like Blob, RegExp etc...
const deepClone = (inObject) => {
let outObject, value, key
if (typeof inObject !== "object" || inObject === null)
return inObject
if (inObject instanceof Map) {
outObject = new Map(inObject);
for ([key, value] of outObject)
outObject.set(key, deepClone(value))
} else if (inObject instanceof Set) {
outObject = new Set();
for (value of inObject)
outObject.add(deepClone(value))
} else if (inObject instanceof Date) {
outObject = new Date(+inObject)
} else {
outObject = Array.isArray(inObject) ? [] : {}
for (key in inObject) {
value = inObject[key]
outObject[key] = deepClone(value)
}
}
return outObject
}
You can use 'JSON.parse and stringify' or using some clone function in the libs like lodash (underscore, ramda...)
Also a simple solution is to store defaultValue: {json object with some structure} with JSON.stringify(defaultValue) in a string variable:
var x = JSON.stringify(this.defaultValue);
If you need it as JSON object again you can get it with JSON.parse():
var newObject = JSON.parse(x);
The object reference is also broken doing it this way, x will stay unchanged if the content of the object defaultValue is altered.
I have this small function (within my Angular 7 application) which uses JavaScript reduce(), and locates an object within a nested array of objects. I can then proceed to update certain properties on the fly.
Now, in addition to this find logic, I would like to also insert/delete an object into/from the nested array.
Question is: once I do locate my object, can I push() and/or delete an object ?
const input={UID:2,GUID:"",LocationName:"USA",ParentLocation:null,subs:[{UID:42,GUID:"",LocationName:"New Jersey",Description:"",subs:[{UID:3,GUID:"",LocationName:"Essex County",ParentLocation:null,"subs":[{UID:4,LocationName:"Newark",ParentLocation:3,"subs":[{"UID":49,"GUID":"","LocationName":"Doctor Smith's Office","LocationType":{"UID":2,"LocationTypeName":"Practice","Description":"other location"},"subs":[{"HostID":38,"HostName":"Ocean Host",}]}]}]}]}]};
const findUIDObj = (uid, parent) => {
const { UID, subs } = parent;
if (UID === uid) {
const { subs, ...rest } = parent;
return rest;
}
if (subs) return subs.reduce((found, child) => found || findUIDObj(uid, child), null);
};
console.log(findUIDObj(49, input));
var obj = findUIDObj(49, input);
delete obj;
For example, in my Angular 7 app, it complains if I attempt to delete the found object:
ex/
var obj = findUIDObj(49, input);
delete obj;
'delete' cannot be called on an identifier in strict mode.
Looking briefly at your code, I see you are using a const identifier to declare your data collection. We only use const for static data that does not change, and that’s the purpose of it. So, first of all matters, that seems to be the problem. To test it change it to let. Now, as for methods for data management, immutability is worthy of your consideration for many reasons, but namely Angular will rerender the entire object regardless of altering the existing object, or receiveing a new object. You can look up Immutable JavaScript to understand more. In many cases, creating immutable data management is done with a library, you can do it yourself. Basically, create a function called copy( data ), or something so that you pass in the original object, but you get a copy of it in return with no reference to the original object. That way one does not accidentally change the original object. To do this you can do this inside of your copy function: return JSON.parse(JSON.stringify( data )) ;
The only problem you might run into here is deep nested objects, or objects with circular references can cause problems. I have an overriding stringify method to mange this in little libraries I’ve written.
delete obj would never do what you want: first of all, it is not even an object from your input, since the function created a new object from the found object, excluding the subs property, and returned that. But more importantly, delete is used for deleting properties, not objects.
It seems you want to remove a matching object from its parent subs property. For that you would need to mutate the subs array, so it would exclude the matching object. For that to work in a generic way, your input should be an array. Otherwise that root object could not be removed from anything.
With that in mind, your lookup function should return the array in which the match was found and at which index. With those pieces of information you can decide to remove that element from the array, or to insert another object at that index.
Here is how it could work with removal:
const input=[{UID:2,GUID:"",LocationName:"USA",ParentLocation:null,subs:[{UID:42,GUID:"",LocationName:"New Jersey",Description:"",subs:[{UID:3,GUID:"",LocationName:"Essex County",ParentLocation:null,"subs":[{UID:4,LocationName:"Newark",ParentLocation:3,"subs":[{"UID":49,"GUID":"","LocationName":"Doctor Smith's Office","LocationType":{"UID":2,"LocationTypeName":"Practice","Description":"other location"},"subs":[{"HostID":38,"HostName":"Ocean Host",}]}]}]}]}]}];
const findUIDObj = (uid, arr) => {
if (!arr) return;
const idx = arr.findIndex(obj => obj.UID === uid);
if (idx > -1) return [arr, idx];
for (const obj of arr) {
const result = findUIDObj(uid, obj.subs);
if (result) return result;
}
};
console.log(findUIDObj(49, input));
const [arr, idx] = findUIDObj(49, input) || [];
if (arr) {
arr.splice(idx, 1); // Remove object from its parent array
}
I am puzzled why this following bit of code will return mutations of both the local and global array:
var globalarray = [1,2,3];
function test(){
let localarray = globalarray;
localarray.push(4);
console.log(localarray);
console.log(globalarray);
}
setInterval(test, 2000);
Returns:
[1,2,3,4] for both
My impression was that localarray would be a copy of globalarray. I saw another answer that said in order to make a copy of an array you need to use .slice().reverse(), which seems like a workaround. Why does it not just create a new local copy? Is there a simple and efficient way to make a local copy of a global array? Otherwise it seems like making multiple mutations to a global array is terrible for performance.
The reason for this in your code is because you are simply telling your test function to point to the globalarray with the = operator. This is because in JavaScript, variable assignments do not inherently "copy" objects into the new variables; this might seem confusing, so just think of the = operator as a sign that points your code to the location of an object.
The only times that the = operator is making new copies is when you are working with primitive types. In those cases, you cannot inherently change what those objects are, so = is sufficient to make a copy like you would expect.
The reason for the .slice().reverse() is to work around the problem you are seeing. Another way you could do this is by using let localarray = globalarray.map(e => e), or as samee suggested, by using let localarray = [...globalarray]. The .map operator takes the function given to it as the first argument and applies it to every element, and then it stores the result in a different array at another location that is not the same as globalarray.
Keep in mind that these methods, and any others that might be suggested to you, are shorthand ways of doing
let localarray = new Array(globalarray.length);
for (let i = 0; i < globalarray.length; i++) {
localarray[i] = globalarray[i];
}
// localarray can now be freely modified because it does not point to the same array as globalarray
Also keep in mind that if you need to also create copies of the elements inside of the arrays, then you will have to use more comprehensive copying code. There are libraries that can do this sort of heavy-duty copying if you really need it.
In JavaScript (as in many other languages), objects are passed by reference. Arrays are also passed by reference (because an array is actually a type of object). So when you say: let localarrray = globalarray, you are actually setting localarray to a pointer that resolves to globalarray.
There are several strategies for obtaining a true copy. If you're trying to get a fresh copy of the original array, the Array prototype function of .map() is one of the most targeted tools for the job. It would look like this:
let localarray = globalarray.map(element => element);
Simple way to clone an array is just
let localarray = globalarray.slice();
I do it a different way to deep cloning:
clone: function() {
var clone = undefined;
var instance = this;
if ( XScript.is.xobject(instance) ) {
clone = {};
for ( var prop in instance ) {
if ( instance.hasOwnProperty(prop) ) {
var p = instance[prop];
if ( XScript.is.xobject(p) ) p = p.clone();
clone[prop] = p;
}//END IF this
}//END FOR prop
return clone;
}//END IF xobject
if ( XScript.is.xarray(instance) ) {
clone = instance.slice(0);
return clone;
}
return clone;
}//END FUNCTION clone
This clone will require you attaching the clone object to the object prototype and check to see if its an array, object, or other. I am not changing the function to fit all your needs because one should learn to somewhat how to change it and what to do instead of copy pasta. Plus it is just an example.
So I have been looking at Object.freeze() and Object.seal().
Object.freeze() - will make all existing properties non-writable, and will not allow any new properties to be added.
Object.seal() - "Sealing an object prevents new properties from being added and marks all existing properties as non-configurable."
I am looking for a way to make all existing properties "frozen" (non-writable), but allow new properties to be added.
Is there shorthand for doing that?
The manually way of doing what I want is:
let freezeExistingProps = obj => {
Object.keys(obj).forEach(k => {
Object.defineProperty(obj, k, {
writable: false
});
});
};
The above function works surprisingly well to freeze existing top-level properties on an object (it doesn't overwrite them, just changes them to non-writable), but I am hoping there might be a more official/quicker way to do the above.
You might do the following:
instance -> frozen static proto -> dynamic proto
Some sample:
function freeze(stat,dyn){
Object.setPrototypeOf(stat,dyn);
Object.freeze(stat);
}
var a={unchangeable:1};
var b={changeable:2}
freeze(a,b);
Now have a look at a and change some b props.
Well, if you want to do it in the manner of freeze, then freezing it immediately, and setting up to a prototype of another object might help, but it will return a copy (pointing to the original object as prototype), exactly in the form how you want. there are obviously some pros and cons, as the properties will not be the immediate properties, but we can find it out by its __proto__ if we need all the keys (assuming you have a dedicated use case)
So, just another try
function freezeExistingProps (obj){
var OBJECT = function(){};
Object.freeze(obj)
OBJECT.prototype = obj;
return new OBJECT();
}
You may want to consider cloning your object into a new one with extra attribute. It's also a very good practice (look for immutability).
An example:
const setAge = (person, age) => ({ ...person, age });
const person = {
firstName: 'Luke',
lastName: 'Skywalker',
};
const personWithAge = setAge(person, 24);
How can I manipulate the prototype of a predefined object (for example an Array) so that it does something when creating instances of that object?
Simply I want to alert('an array was created!') whenever an Array is instantiated.
You can set a new method on an array by adding it to the Array.prototype object:
Array.prototype.fizz = function () {
alert('works');
};
var arr = [];
arr.fizz();
However, this only allows you to create new methods, this does not allow you to extend existing methods*, nor does it allow you to override the Array constructor.
Be careful adding new methods to existing types. This can adversely affect the entire scripting environment, and cause unexpected behavior in some libraries. Although some might call it "bad practice", it's quite common to use polyfills for cross-browser compatibility, such as by creating Array.prototype.indexOf.
There is no such thing as a "newed" array, the word is "instantiated":
var a1, a2;
a1 = []; //a1 was instantiated with a new Array
a2 = new Array(); //a2 was also instantiated with a new Array
There is no cross-browser means of overriding the Array constructor.
* it's possible to wrap an existing method in a function so that, when called, the existing method performs its original functionality, in addition to the new functionality. Although this might be referred to as extending an existing method, it is in fact creating a new method.
You can try to override Array with your own function. It seems to work when doing new Array, but not when doing [].
(function() {
var _ac = Array;
Array = function() {
alert('an array was newed!');
return _ac.apply(this, arguments);
};
}());
DEMO: http://jsfiddle.net/DAg9A/
I would suggest the you just create an array namespace for it:
array = {};
array.create = function() {
alert("Created an array");
return [];
}
So whenever you create an array you use: array.create(); .
You should not, and in this case can not, change native functionality. You have to be in charge of every array creation.