I have a couple of functions in a javascript project that accept an object as an argument and are expected to return a copy of this object with a few changes. I'm trying to make these functions immutable, but the argument can be either an Object or an Array, so I can't just use Object.assign({}, original) or {...original}.
Instead, I've come up with a couple of options:
let doSomething = function doSomething(original) {
let ret = Object.assign(Array.isArray(original) ? [] : {}, original);
//OR
let ret = Array.isArray(original) ? [...original] : {...original};
//OR
let ret = Object.assign(new (original.constructor), original);
//OR
let ret;
if(Array.isArray(original))
ret = original.slice();
else
ret = Object.assign({}, original);
//make changes
return ret;
}
Out of the four, my preference would be for the third method, just because it supports any type of object (not that I plan on using anything other than Objects or Arrays), but it feels "clever" in a bad way.
Out of the four methods, which is best for readability? Or is there another preferred way to copy any type of Object?
Options #1, #2 and #4 are doing completely the same. As for readability my vote is for #2 - shortest and clearest.
Option #3 also recreates constructor/prototype chain that is not you need in this case. It will allow to run check like copiedObj instanceof OriginalObjectClass. But it also may fail once constructor expects some arguments(and does not get them obviously). So in your case it looks rather dangerous approach than helpful.
Also I completely agree with D Lowther it would be better to extract cloning itself from your recursive function.
But I guess that you don't only want to clone some object but also process clone's nested property recursively. And this way you will also require to check if this object or array, right? So instead of cloning array separately and then cloning its members it would be easier to do that in one line with .map:
function cloneRecursively(originalItem) {
let clonedItem = {...originalItem};
clonedItem.children = (clonedItem.children || []).map(cloneRevursively);
return clonedItem;
}
You can use the constructor method instead of new (original.constructor)!
As your param can be an object or an array, you could simply write it like this:
let ret = Object.assign(original.constructor(), original)
Calling it like that, will return an empty object or array.
Be aware that if your param is not a plain JS object, using constructor would either fail or return something unexpected
Related
I am trying to create a function that mimics Array.prototype.push.
It takes a variable number of arguments and pushes them into a specific array.
I have managed to do this with the following code:
var array=[];
function append(){
for(var i=0;i<arguments.length;i++)
array.push(arguments[i]);
}
Now my question is:Can I rewrite the append function without using "for loop"?
Thanks in advance.
If you need to get arguments array, you should use Array's slice function on an arguments object, and it will convert it into a standard JavaScript array:
var array = Array.prototype.slice.call(arguments);
You could use Array.prototype.push.apply
function append(){
// make arguments an array
var args = Array.prototype.slice.call(arguments);
// return the number of elements pushed in the array
return Array.prototype.push.apply(array, args);
}
So, what's happening here with args? We use Array.prototype.slice.call with arguments, the purpose being to make arguments an array, because it is a special object. Function.prototype.call is used to call a function with a specific context (aka this), and then the arguments to call the function with (comma separated). Conveniently, it appears that slice() looks at the length property of the this context, and arguments has one too, and when not empty, has properties from 0 to length -1, which allows slice to copy arguments in a new array.
You can rewrite this without a for loop, but you have to use a loop of some sort (you're working with multiple items, it's a necessity).
If you have access to ES6 or Babel, I would use something like:
function append(...args) {
return array.concat(args);
}
Without ES6, you need to work around the fact that arguments isn't a real array. You can still apply most of the array methods to it, by accessing them through the Array prototype. Converting arguments into an array is easy enough, then you can concat the two:
function append() {
var args = Array.prototype.map.call(arguments, function (it) {
return it;
});
return array.concat(args);
}
Bear in mind that neither of these will modify the global array, but will return a new array with the combined values that can be used on its own or assigned back to array. This is somewhat easier and more robust than trying to work with push, if you're willing to array = append(...).
Actually i honestly believe that push must be redefined for the functional JS since it's returning value is the length of the resulting array and it's most of the time useless. Such as when it's needed to push a value and pass an array as a parameter to a function you cant do it inline and things get messy. Instead i would like it to return a reference to the array it's called upon or even a new array from where i can get the length information anyway. My new push proposal would be as follows;
Array.prototype.push = function(...args) {
return args.reduce(function(p,c) {
p[p.length] = c;
return p
}, this)
};
It returns a perfect reference to the array it's called upon.
I'm trying to implement a dictionary much like Python. So, I would like to have a keys() method that returns keys added to the subclass Dict, but not properties such as the Object's method "keys"
EDIT AGAIN
Basically, I'm making a class to pass settings to a function like function(arg1, arg2, myObj) where my object is {map: texMap, alphaMap: aTexMap}. It's for Three.js, and I have to wait on images to download before I can create settings on 3D objects. So, interface like one would expect with d in var d = { a: aData b: bData }, but hide the methods etc that are not added by the user.
ie don't return this.prototype.propertyName when own is passedHere's what I have so far:
function Dict(){
this.prototype = {};
var _keys = this.prototype.keys;
this.keys = function(own){
if(typeof own === 'undefined') { return _keys(); }
var ownKeys = [];
for(var key in _keys()){
if(this.hasOwnProperty(key)) {
ownKeys.push(key);
}
}
return ownKeys;
}
}
Will this work as follows? Is there a better or already existent way to do it?
save the overloaded keys() method to a private var
return everything as usual, unless own is something that resolves to true.
if own == true, get the usual keys and filter out those
belonging to the superclass.
On the subject, I'm likely most concerned about saving back the prototype method as a way to get all of the keys and filter out proto keys.
Also, I've read overloading isn't built into Javascript. But, much of what I've found deals with standalone functions such as this Q&A on best practices. I don't need a built in way, but I'll take advantage of whatever's available (Hence, using Object as a Dict).
Any feedback is appreciated!
EDIT
In Python, we get this:
In[2]: d = {}
In[3]: 'has_key' in d.keys()
Out[3]: False
In[7]: 'has_key' in d.__class__.__dict__.keys()
Out[7]: True
In[8]: d.has_key('has_key')
Out[8]: False
In[9]: d['newKey'] = 5
In[10]: d.newKey # ERROR
Python has a dict attribute contained in its class where the functions are accessed via a dot (see In[8]...). So, those standard {} or dict() functions and operators are hidden (not private) while keys/data are added to the user's dict are accessed via []. d['newKey'] = 5 adds a new key or overwrites the old and sets the data to 5.
I don't need all of that to work, though it would be great. keys() returning Python-like keys would be fine for now.
There seem to be multiple issues here.
You seem to want to pass variable arguments to a function:
I'm making a class to pass settings to a function like function(arg1, arg2, myObj) where my object is {map: texMap, alphaMap: aTexMap}.
JS function arguments are very flexible.
You can either set up names for every one of them:
function foo(arg1, arg2, map, alphaMap)
and pass values directly. This style is preferred for functions that work on a fixed set of arguments.
Or you can set up an "options" object that collects keys and values:
function foo(options)
and pass {arg1: val1, arg2: val2, map: valMap, alphaMap: valAlphaMap}. This style often occurs on constructor functions that initialize objects with a certain set configuration options.
Or you can set up an empty function signature
function foo()
and work with the arguments collection inside the function. This is found in functions that work with a variable number of uniform arguments (imagine add(1, 2, 3, 4, 6)) or strictly positional arguments instead of named ones.
In any case, passing arguments to a function is optional in JavaScript, even when there is an argument list in the function signature. You are free to pass none, less or more arguments. Of course all these approaches can be combined if it suits you.
It's for Three.js, and I have to wait on images to download before I can create settings on 3D objects.
This is a problem caused by the asynchronous nature of the web. The solution is to use event handlers. These are either callbacks or - as an abstraction over callbacks - promises.
So, interface like one would expect with d in var d = { a: aData b: bData }, but hide the methods etc that are not added by the user.
This can be solved by not adding methods etc to data objects, or at least not directly. Add them to the prototype if your data objects must have behavior.
The direct equivalent to a Python Dict is a plain object in JavaScript.
var dict = {};
The direct equivalent of Python's keys() method is the Object.keys() static method in JavaScript.
var keys = Object.keys(dict);
To iterate the keys you can either use an imperative approach:
var i, key;
for (i = 0; i < keys.length; i++) {
key = keys[i];
doSomething(key, dict[key]);
}
or a functional one
keys.forEach(function (key) {
doSomething(key, dict[key]);
});
The direct equivalent of Python's in is .hasOwnProperty() in JavaScript:
if ( dict.hasOwnProperty('foo') ) ...
or, if it is a pure data object with no prototype chain, you can use in as well.
if ('foo' in dict)
Using in in for loops is not recommendable because it iterates the prototype properties as well. The way to guard against this is by using Object.keys() instead or by combining it with .hasOwnProperty(), as you did.
var key;
for (key in dict) {
if ( dict.hasOwnProperty(key) ) ...
}
Your question indicates that you are missing basic puzzle pieces about JS and try to substitute them with more familiar Python constructs. I would recommend not doing that.
I also suspect that you try to shoehorn Python's class-based inhertiance pattern into JS' prototype-based inheritance pattern. I strongly recommend that you don't do that, either.
In the source code of serve-static npm library I have seen the following line
options = merge({}, options)
where merge function is from utils-merge library and it has exactly the following body
exports = module.exports = function(a, b){
if (a && b) {
for (var key in b) {
a[key] = b[key];
}
}
return a;
};
What is the sense of options = merge({}, options) statement since it just joins options object with an empty object?
What is the sense of options = merge({}, options) statement since it just joins options object with an empty object?
To do exactly that. It copies all properties into a new object, where they cannot be modified anymore even if the caller of serveStatic still holds a reference to the options object he passed.
However, avoiding that the caller messes with the object is not the only reason for detaching it. If we read on, we see things like
delete options.setHeaders
options.maxage = options.maxage || options.maxAge || 0
options.root = root
so we also want to avoid messing with the object that was passed to us. Mutating your arguments is an antipattern.
merge({}, options) creates the new object which has the same attributes as source object (so it is the simplest way to clone JS object). But if you don't pass an empty object as the destination object to merge function or just skip this line, all changes on options object inside serveStatic() function will affect external object what passed to function serveStatic().
Here is the detailed explanation of this nuance of JavaScript language: https://stackoverflow.com/a/3638034/1806421
I've created a Javscript prototypal object with a range of attributes. And I need to pass some of these attributes to a function. The function processes the attributes and returns some result.
function ObjCt(id,data) {
this.id = id;
this.data = data;
}
So, I'm trying to pass the data of the chart object to a function. Im calling it as:
var obj=new ObjCt(id,data);
process(obj.id,obj.data);
I read from somewhere that passing a value in JS results in a call by value and passing an object results in a call by reference. Here I am trying to call by value, but, seems whatever processing happens in the process() function is reflected in the object obj.
I checked the variable received in the process function using typeof and it comes up as 'object' and not variable. So, what can I do to pass this data as a value?
Edit
I found a rather tiresome workaround. id is primitive and data is a JSON object. So, I tried this in the process() function
JSON.parse(JSON.stringify(data))
Apparently, all references were slashed here. I have a working implementation now, but, Id still love to see if there is an easier way for passing an attribute as a value.
Edit
Solved the problem. As per Paul's answer, tried a normal step by step cloning and another alternative using jquery extend method which goes like this
var object=$.extend(true,{},oldObject);
and passed the newly created object to the process() function.
Please refer this link: What is the most efficient way to deep clone an object in JavaScript?
In JavaScript, primitives (Number, String) are passed ByVal and Objects (including Array, Function) are passed ByRef.
Most of the time this makes no difference, however you can experience problems when using methods which modify an Object without taking into consideration you may want it elsewhere, too.
To get around this, clone your Object before passing it into such a method.
Array has the native Array.prototype.slice to clone, for Objects you need to loop over keys and recurse over the properties.
Custom function to clone, lets you extend it to do more Objects via instanceof testing
function clone(o) {
var o2 = {}, k;
if (typeof o !== 'object')
return o;
if (o instanceof Array)
return o.slice().map(clone);
if (o instanceof Date)
return new Date(o);
for (k in o)
if (Object.prototype.hasOwnProperty.call(o, k))
o2[k] = clone(o[k]);
return o2;
}
Cloning via JSON, only things which can be written in JSON are supported
function clone(o) {
return JSON.parse(JSON.stringify(o));
}
Pretending to clone by using inheritance
This will not protect foo.bar.baz = primitive, but will protect foo.bar = primitive so how good a choice it is for you depends on what the Object looks like or what is being done to it.
function pseudoclone(o) {
return Object.create(o);
}
Don't try to clone functions, it will go horribly wrong. If you need to modify them somehow, wrap them.
Are you attached to that approach only?
var Chart=function(id,data){
this.ObjCt = function () {
this.id = id;
this.data = data;
}
}
then
var obj=new Chart(id,data);
obj.ObjCt();
//process(obj.id,obj.data);
Assuming that id is primitive and data is an object:
function ObjCt(id, data){
this.id = id;
this.data = eval(data.toSource());
}
This will clone the object held in data with simple trick.
I have a couple of these and think (know) that I'm doing something wrong (or could be simpler).
html:
<div class='item-to-select' data-global-id='55'>some</div>
var l=$(this).map(function(){
t=new Object();
t.global_id=$(this).data('global-id');
return t;
}).get();
var list=l[0]; // want to remove this
How would I remove this intermediary object? Or a better way
thx
If you mean that you don't want to have to define the l variable just so you can use it once in setting up your list variable you can do this:
var list = $(this).map(function(){
return {
global_id : $(this).data('global-id')
};
}).get()[0]; // note the [0] directly after .get()
The return from any function that returns an array (or array-like object) doesn't have to be assigned to a variable before you can use it. So:
var temp = someFuncReturnsArray();
console.log(temp[0]);
// can be replaced by
console.log(someFuncReturnsArray()[0]);
Of course if you need to do further processing on the returned array you need to put it in a variable. E.g., if you need to test its length, or if the function could possibly return null in some situations, etc. In the example above if an empty array was returned then obviously [0] will be undefined.
But if you only need the return value once you can just use it directly.
Note that I've removed the t variable from your code too. When creating an empty object it is considered good practice to say obj = {} rather than saying obj = new Object(). But you can create an object with properties in one step if the property values are already known. In the case of your function the t object you create isn't manipulated in any way other than adding a single property to it before you return it, so you can simply return an object literal directly instead of doing it in three steps.
The jQuery .get() method accepts an index.
So, you can write :
var list=$(this).map(function(){
t=new Object();
t.global_id=$(this).data('global-id');
return t;
}).get(0);