Difference between array and object in javascript? or Array Vs Object - javascript

How to recognize array & object in js where typeof doesn’t come in handy?
var arr = [], ob = {};
As everything in js are objects,
if(typeof arr == typeof ob) => returns true
I want a operator or ... that will tell me that the variable is an array. I can then use only the arrayfunctions to objects which are array. How is that possible?

var arr = [], ob = {};
As everything in js are objects, even **Array is an Object but an instance of class Array
if(typeof arr == typeof ob) => returns true as Both are **Objects
So, how will you to identify objects.
This is where instanceof operator comes in handy, to identify whether its an array you can put a additional check cde:
if(arr instanceof Object && arr instanceof Array) => returns true
if(ob instanceof Object && ob instanceof Array) => returns false

You could use Array.isArray() method to check if a variable is array or otherwise.
var myArray = [1,2,3,4,5];
console.log(Array.isArray(myArray));
true

Among numerous simple/sophisticated comparisons, one difference is:
var arr = []; # arr.length => 0
var obj = {}; # obj.length => undefined

There are multiple ways of differentiating between array and object, some on them are already mentioned above i would like to just add on above answers.
First Approach
Differentiation using length, length property exist on Array but doesn't exist on Object
var arr = [1,2,3]; arr.length => 3
var obj = {name: 'name'}; obj.length => undefined
Note: This approach fails when someone declares object like below, we can use this approach only when we are sure we will not get any object having length property
var rectangle = {length: 50, width: 50}; rectangle.length => 50
Second Approach
Using instanceof Array
var arr = [1,2,3]; arr instanceof Array => true
var obj = {name: 'name'}; ojb instanceof Array => false
Third Approach
Using Array.isArray, this is most preferable approach and is supported by most of browser now
Note: Array.isArray is preferred over instanceof because it works
through iframes.
Array.isArray(arr) => true
true
Array.isArray(obj) => false
If you want to support i.e 8 browser the use Object.prototype.toString
We can write polyfill for i.e 8
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
Object.prototype.toString.call(arr); =>"[object Array]"
Object.prototype.toString.call(obj); =>"[object Object]"
Reference: isArray

Related

How to modify an array of object and push in JavaScript?

I have any array of objects like this
let myObj=[{a:'CR',showMe: true},{a:'PY'}];
Now I'm trying to find the object which has a as CR and showMe as true and want to change the a value.
let findObj = myObj.filter(i=> i.a == 'CR' && i.showMe);
findObj.map(ele => ele['a'] = "PS");
When I'm trying to console myObj,value of a in myObj is changed along with findObj.
I don't want myObj to be changed.
What is causing the issue, could someone help?
You need to (shallow) clone the objects in findObj so that modifying them doesn't modify the objects in myObj
let myObj=[{a:'CR',showMe: true},{a:'PY'}];
let findObj = myObj.filter(i=> i.a == 'CR' && i.showMe);
findObj = findObj.map(obj => ({...obj, a: 'PS'}));
console.log(myObj);
console.log(findObj);
Other comments & answers suggest suggest using JSON.parse(JSON.strinfigy(obj)) to deep clone objects, but this is lossy; e.g. it loses types and methods. In your case, your objects are 1 level deep (i.e. don't contain nested arrays or objects), so a shallow clone is sufficient. The spread operator {...obj} is the simplest way to shallow clone objects. Object.assign({}, obj) is another more verbose alternative.
let myObj = [{
a: 'CR',
showMe: true
}, {
a: "FF",
showMe: true
}];
let result = [];
myObj.filter(i=> {
let item = Object.assign({}, i);
return item.a == 'CR' && item.showMe && result.push(item)
});
result.map(ele => { ele['a'] = "PS"});
console.log({myObj, result});

Javascript deepcopy without JSON or variable assignment

I have a javascript question:
I was doing some challenge exercises from a JS book I'm learning from and I have no idea how to go about it and I don't have the sample answers. The aim is to write a deep copy function using *only ES6*'s arrow functions without using any JSON methods or any variable assignments. The author also suggests no scopes using the {} brackets. Any hints or tips on how I can do this?
Create a recursive function which accepts a value and will return a copy of it. Check the type of the value. If it's an Array, return an new Array which maps every element to a recursive call of that function. If it's an Object, create a new Object with copies of its properties. If it's primitive (string, number, boolean...), just return it.
On a side note, writing this without any variable assignment & curly brackets may be fun for learning, but it makes it a lot less readable. I would never write it this way at work:
const deepCopy = v =>
// Is it an Array?
v instanceof Array ?
// If it is, map every value to its copy
v.map(deepCopy) :
// Otherwise, is it a non-null Object?
(typeof v === "object" && v !== null) ?
// If it is, create a new Object with copies of its property values
Object.entries(v)
.reduce((acc, [k, v]) => ({ ...acc, [k]: deepCopy(v) }), {}) :
// Otherwise, just return the value
v;
console.log(deepCopy({foo: 'bar', arr: [{baz: 42}, true]}));
console.log(deepCopy("Hello world"));
console.log(deepCopy(["a", "b", null]));
You can get arrays of keys and values with Object.keys() and Object.values(), then iterates and assigns those items to a new object, try this:
let obj = {key1: 'value1', key2: 'value2'};
let keys = Object.keys(obj);
let values = Object.values(obj)
console.log(keys, values)
let newObj = {};
for( let i = 0; i < keys.length; i++ ){
newObj[keys[i]] = values[i]
}
console.log(newObj);
newObj.key1 = 'asd';
console.log(newObj);

clone js array w/ objects and primitives

Given the array:
['1', {type:'2'}, ['3', {number: '4'}], '5']
I need to make a clone without using the slice, json.parse and other methods.
At the moment, the code is working, but it will not clone objects:
var myArr =['1',{type:'2'},['3',{number:'4'}],'5'];
var arrClone=[];
for(var i=0;i<myArr.length;i++){
if(typeof(myArr[i])==='object')
{
var arr=[];
for(var j=0;j<myArr[i].length;j++){
arr[j]=myArr[i][j];
}
arrClone[i]=arr;
}else { arrClone[i]=myArr[i]}
}
You could check if an object is supplied and if so, another check is made for an array. Then retrn either the cloned array or the object, or the value itself.
function getClone(value) {
if (value && typeof value === 'object') {
return Array.isArray(value)
? value.map(getClone)
: Object.assign(
...Object.entries(value).map(([k, v]) => ({ [k]: getClone(v) }))
);
}
return value;
}
var data = ['1', { type: '2' }, ['3', { number: '4' }], '5'],
clone = getClone(data);
console.log(getClone(data));
Here is a simple implementation without Array methods:
function deepClone(obj) {
if (typeof obj !== "object" || obj === null) return obj; // primitives
// It could be an array or plain object
const result = obj.constructor.name == "Array" ? [] : {};
for (const key in obj) {
result[key] = deepClone(obj[key]); // recursive call
}
return result;
}
// Demo
var myArr =['1',{type:'2'},['3',{number:'4'}],'5'];
var arrClone = deepClone(myArr);
console.log(arrClone);
Note that this only works for simple data types. As soon as you start to work with Dates, regex objects, Sets, Maps, ...etc, you need much more logic. Also think of self references and functions, and how they should be dealt with.
For more advanced cloning see How to Deep Clone in JavaScript, but expect the use of several methods.

deleting a object of an Array based on property of the object

I have an Array like this: var obj = [{x:4, y:5}, {x:6, y:2}, ...] and I'm trying to delete one of the inside objects (properties) based on the x.
this is How I'm trying to do this:
obj.forEach(function (child){
if(child.x === 4){
obj.destroy(child)
}
});
But it's not working and i get
obj.destroy is not a funtion
I also tried obj.splice(child) but it just mess up the array. so what am doing wrong here?
Also is there a better way to do this by not having to loop through all of Array property every time?
You can just use filter on the array: e.g.
let arrayToFilter = [ {x:4, y:5}, {x:6, y:2}];
const valueToFilter = 4;
var filteredArray = arrayToFilter .filter((o) => {
return o.x !== valueToFilter;
});
console.log(filteredArray);
forEach() works on array.
If obj is an array, you can simply use filter() to remove the unwanted object from the array:
var obj = [{x:4, y:5}, {x:6, y:2}]
obj = obj.filter(c => c.x !== 4)
console.log(obj);
You perhaps, have an array as obj because the one you posted in the question is simply invalid syntax.
Moreover, you can use Array#findIndex to get the index of the matching element first, and then splice that index from the array.
var obj = [{x:4, y:5}, {x:6, y:2}];
var index = obj.findIndex(item => item.x === 4);
obj.splice(index, 1);
console.log(obj);
i'm assuming your trying to filter out objects in an array which have an x that matches a given value. If thats the case, you should probably use the filter method.
So assuming thats what you mean you could do the following
obj = obj.filter(function (child){
if(child.x !== 4){
return obj
}
});
// shorter
obj = obj.filter( child => child.x !== 4 );
In this case, only the objects which do not have the value of 4 will be available to you in the obj variable. And all other objects (assuming there are no other references in your code) will be garbage collected.

Exclude some properties in comparison using isEqual() of lodash

I am using _.isEqual that compares 2 array of objects (ex:10 properties each object), and it is working fine.
Now there are 2 properties (creation and deletion) that i need not to be a part of comparison.
Example:
var obj1 = {name: "James", age: 17, creation: "13-02-2016", deletion: "13-04-2016"}
var obj2 = {name: "Maria", age: 17, creation: "13-02-2016", deletion: "13-04-2016"}
// lodash method...
_.isEqual(firstArray, secondArray)
You can use omit() to remove specific properties in an object.
var result = _.isEqual(
_.omit(obj1, ['creation', 'deletion']),
_.omit(obj2, ['creation', 'deletion'])
);
var obj1 = {
name: "James",
age: 17,
creation: "13-02-2016",
deletion: "13-04-2016"
};
var obj2 = {
name: "Maria",
age: 17,
creation: "13-02-2016",
deletion: "13-04-2016"
};
var result = _.isEqual(
_.omit(obj1, ['creation', 'deletion']),
_.omit(obj2, ['creation', 'deletion'])
);
console.log(result);
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js"></script>
#ryeballar's answer is not great for large objects because you are creating a deep copy of each object every time you do the comparison.
It's better to use isEqualWith. For example, to ignore differences in the "creation" and "deletion" properties:
var result = _.isEqualWith(obj1, obj2, (value1, value2, key) => {
return key === "creation" || key === "deletion" ? true : undefined;
});
EDIT (important caveat pointed out in the comments): if objects have different numbers of keys, then isEqualWith considers them to be different, regadless of what your customizer does. Therefore do not use this approach if you want to ignore an optional property. Instead, consider using _.isMatch(), _.isMatchWith(), or #ryeballar's _.omit() approach.
Note that if you're writing for ES5 and earlier, you'll have to replace the arrow syntax (() => {) with function syntax (function() {)
_.omit creates deep copy of the object. If you need to exclude only root props it is better to create shallow copy using, for example, destructuring assignment:
const x = { a: 4, b: [1, 2], c: 'foo' }
const y = { a: 4, b: [1, 2], c: 'bar' }
const { c: xC, ...xWithoutC } = x
const { c: yC, ...yWithoutC } = y
_.isEqual(xWithoutC, yWithoutC) // true
xWithoutC.b === x.b // true, would be false if you use _.omit
Best way is not to create copies at all (TypeScript):
function deepEqual(
x?: object | null,
y?: object | null,
ignoreRootProps?: Set<string>
) {
if (x == null || y == null) return x === y
const keys = Object.keys(x)
if (!_.isEqual(keys, Object.keys(y)) return false
for (let key of keys) {
if (ignoreRootProps && ignoreRootProps.has(key)) continue
if (!_.isEqual(x[key], y[key])) return false
}
return true
}
You could map your array into a "cleaned" array, then compare those.
// Create a function, to do some cleaning of the objects.
var clean = function(obj) {
return {name: obj.name, age: obj.age};
};
// Create two new arrays, which are mapped, 'cleaned' copies of the original arrays.
var array1 = firstArray.map(clean);
var array2 = secondArray.map(clean);
// Compare the new arrays.
_.isEqual(array1, array2);
This has the downside that the clean function will need to be updated if the objects are expecting any new properties. It is possible to edit it so that it removes the two unwanted properties instead.
I see two options.
1) Make a second copy of each object that doesn't contain the creation or date.
2) Loop through all the properties and, assuming you know for certain that they both have the same properties, try something like this.
var x ={}
var y ={}
for (var property in x) {
if(property!="creation" || property!="deletion"){
if (x.hasOwnProperty(property)) {
compare(x[property], y[property])
}
}
}
Where compare() is some simple string or object comparison. If you are certain of the properties on one or both the objects, you can simplify this code a bit further, but this should work in most cases.
My final solution required a full comparison ignoring an optional property so the above solutions did not work.
I used a shallow clone to remove the keys I wanted to ignore from each object before comparing with isEqual:
const equalIgnoring = (newItems, originalItems) => newItems.length === originalItems.length
&& newItems.every((newItem, index) => {
const rest1 = { ...newItem };
delete rest1.creation;
delete rest1.deletion;
const rest2 = { ...originalItems[index] };
delete rest2.creation;
delete rest2.deletion;
return isEqual(rest1, rest2);
});
If you want to check a subset for each item in the array this works:
const equalIgnoringExtraKeys = (fullObjs, partialObjs) =>
fullObjs.length === partialObjs.length
&& fullObjs.every((fullObj, index) => isMatch(fullObj, partialObjs[index]));
If you also want to ignore a specific property and check subset:
const subsetIgnoringKeys = (fullObjs, partialObjs) =>
fullObjs.length === partialObjs.length
&& fullObjs.every((fullObj, index) => isMatchWith(
fullObj,
partialObjs[index],
(objValue, srcValue, key, object, source) => {
if (["creation", "deletion"].includes(key)) {
return true;
}
return undefined;
}
));

Categories