I just noticed this in some code and I've been trying to understand what it is doing.
this.rows[rowIndex][cell] = event.target.value;
this.rows = [...this.rows];
It appears to me that it's simply assigning this.rows to itself. Is there some other use of the spread operator in which this makes sense? Or is it simply a bug?
The spread syntax will give a shallow copy of the original array.
There are at least two reasons why this may be useful:
Any references that exist to the original rows property will not be affected by later assignments made to the direct properties of the copied array.
If the original value of rows was not a true array, but iterable (array-like), then the result of the spread syntax assignment will still be a true array.
Here is an artificially made object to illustrate these two points:
class Foo {
constructor(n) { // Define array-like object
this.rows = {
// Define iterator
[Symbol.iterator]: function* () {
for (let i = 0; i < n; i++) yield this[i];
},
}
// Create "index" properties
for (let i = 0; i < n; i++) this.rows[i] = [];
}
bar(rowIndex, cell, value) {
this.rows[rowIndex][cell] = value;
console.log(this.rows);
// Take shallow copy
this.rows = [...this.rows];
// Now it is a true array
console.log(this.rows);
// We add a value to that array, which other copies will not see
this.rows.push("added");
console.log(this.rows);
}
}
var foo = new Foo(2); // Create array-like object
var remember = foo.rows; // get its rows
foo.bar(1, 0, 15); // set one particular value to 15
// Demonstrate that the value that was added inside the method is not in our copy
console.log(remember);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Note how the first output has the { } notation: it is not recognised as a true array. The second output is with [ ]: a true array. The third output shows an additional value that was pushed on that array. The final output shows these manipulations (making it an array and adding a value to it) are not reflected in the original reference we had to the rows property.
Related
I must reimplement a list and the method forEach with the following instructions:
// Do not construct any array literal ([]) in your solution.
// Do not construct any arrays through new Array in your solution.
// DO not use any of the Array.prototype methods in your solution.
// You may use the destructuring and spreading (...) syntax from Iterable.
the result should look like:
const list = List.create(1, 2)
list.forEach((item) => console.log(item))
Here is my incomplete solution:
export class List {
constuctor(){
}
public static create(...values: number[]): List {
// Do *not* construct any array literal ([]) in your solution.
// Do *not* construct any arrays through new Array in your solution.
// DO *not* use any of the Array.prototype methods in your solution.
// You may use the destructuring and spreading (...) syntax from Iterable.
List list = new List();
values.forEach(function(item){
list.push(item);
});
return list;
}
public forEach(callback: any){
for (let i = 0; i<this.length ; i++){
callback(this[i], i, this);
}
}
}
in the create loop, but the problem, as a static method, the this is not recognized
EDITED thanks to comments
...this is not recognised
It is. But you have not given this any property. And this is because:
constuctor should be written as constructor
You need to define a push method (since you call it in create)
You need to define a length property (since you reference it in forEach)
Furthermore, there some other issues:
you write that Array.prototype functions cannot be used, but your code has values.forEach(), ... so that is a violation of that rule. Use a for..of loop instead.
Here is your code with those remarks taken on board:
class List {
constructor() {
this.length = 0;
}
push(value) {
this[this.length++] = value;
}
static create(...values) {
let list = new List();
for (let item of values) {
list.push(item);
}
return list;
}
forEach(callback) {
for (let i = 0; i < this.length ; i++) {
callback(this[i], i, this);
}
}
}
const list = List.create(1, 2)
list.forEach((item) => console.log(item))
Remarks
The above "test" will be fine, but when also assignments to properties are to work correctly, like list[2] = 3, then there are more things to take care of. Take for instance this program:
const list = List.create(1, 2);
list[5] = 42; // should update length
list.check = true; // should not update length
console.log("length = " + list.length);
console.log("enumerable keys are " + Object.keys(list));
list.forEach((item) => console.log(item)); // should not output empty slots
list.length = 1; // should delete some index properties
list.forEach((item) => console.log(item)); // should not output deleted items
...then the output should be:
length = 6
enumerable keys are 0,1,5,check
1
2
42
1
You can make this happen by trapping the access to properties, and making length a getter/setter. But you'd also need to distinguish between properties that are array indices, and which are not, so all-in-all that would make the code less trivial.
Array.prototype.myUcase = function() {
for (i = 0; i < this.length; i++) {
this[i] = this[i].toUpperCase();
}
};
but strings are immutable. Why does reassigning value to the string works in this case?
Strings are immutable, but the array you're processing is not. If you need your prototype method to return a new array, like map or reduce does, you need to first make a copy of the original array, and then work on that.
Something along the lines of this (pseudo-code):
Array.prototype.something = function() {
var copy = [...this];
// do operations on copy
return copy;
}
Note, that the spread operator (...) only makes a new reference, not a "deep clone".
I would like to write a function that will search the fields of objects in an array for a specific string, adding this object to a new array if the string is found in any of the object's fields. I've gotten my function to work, but was having the issue of the new list containing multiple copies of the objects which contain multiple copies of the string being searched for. I know this is because I've looped it so that each field it finds the string in, it will add the object once more to the new array. However, I wasn't sure of what alternative way I could go about writing the function to add the object only once, regardless of how many of its fields have matched with the string being searched for. This is my function:
function search (keyword, array){
var newArray = [];
for(let i = 0; i < array.length; i++) {
for (key in array[i]) {
if(array[i][key].includes(keyword)){
newArray.push(array[i]);
}
}
}
return newArray;
}
An example of where the output is problematic is if I do:
console.log(search('yes', myArray))
And if myArray contains an object in which 'yes' appears in 3 different fields, it will add this object to newArray 3 times.
Improved version of Danny Buonocore code.
No accidental global variables.
Uses forEach to iterate over the array.
Uses for...of and Object.values() to iterate over the values of the object (using for..in iterates over all non-Symbol, enumerable properties of an object itself and those the object inherits from its constructor's prototype and are cause for many bugs)
"short circuit" the test for adding an object: if a value has matched, there is no need to check the other values. This alone would probably solved your problem, but using a set will prevent duplicates if you have the same object multiple times in your array.
function search (keyword, array){
var result = new Set();
array.forEach( object => {
for (const value of Object.values(object)) {
if ( value.includes(keyword) ) {
result.add(object);
continue; // Don't need to check more on this item.
}
}
});
return Array.from(result);
}
console.log(search("yes", [
{ key1 :"yes", key2:"yes" },
{ key1 :"no", key2:"no" }
]));
You could use a Set, this will prevent duplicates.
function search (keyword, array){
var result = new Set();
for(let i = 0; i < array.length; i++) {
for (key in array[i]) {
if(array[i][key].includes(keyword)){
result.add(array[i]);
}
}
}
return Array.from(result);
}
function removeDupes() {
var out = [],
obj = {};
for (x = 0; x < intArray.length; x++) {
obj[intArray[x]] = 1;
}
for (x in obj) {
out.push(x);
}
return out;
}
hi all, obj { } is supposed to have been declared as an object, but why putting [ ] and using obj as array works? thanks
someObject["somePropertyName"] is how you access the property of an object. (You can also use someObject.somePropertyName but only if the property name is a valid identifier).
The syntax has nothing specifically to do with arrays.
Arrays are just a type of object with a bunch of methods that treat property names that are integer numbers in special ways.
(Numbers are not valid identifiers so you must use the square bracket syntax to access properties with names that are numbers).
First of all, let's fix your removedDupes function by adding intArray as an argument and declaring the loop iterators as local variables var x:
function removeDupes(intArray) {
var out = [],
obj = {};
for (var x = 0; x < intArray.length; x++) {
obj[intArray[x]] = 1;
}
for (var x in obj) {
out.push(x);
}
return out;
}
The first loop iterates over all intArray elements. For each element it creates a new property on obj with key intArray[x] and value 1 via obj[intArray[x]] = 1. This is called bracket notation. Now, objects can't have duplicate keys. So adding the same property key twice actually overrides the previously added property. Thus, when the loop completes, your obj has exactly one key per unique array element.
The second loop then pushes these keys into the resulting out array.
There are some issues with your removeDupes function. Firstly, it returns an array of string elements even though the input is an array of number elements. This is because obj keys can't be numbers and are always converted to strings. You can fix it by storing the numeric values along with the keys as follows:
function removeDupes(intArray) {
var out = [],
obj = {};
for (var x = 0; x < intArray.length; x++) {
obj[intArray[x]] = intArray[x];
}
for (var x in obj) {
out.push(obj[x]);
}
return out;
}
Now, this works, but it is very verbose. You can make it shorter by replacing the second loop with return Object.values(obj);. Or even shorter by using a Set:
function removeDupes(intArray) {
return [...new Set(intArray)];
}
Note: I'm only a novice coder, so there might be a glaring error or misconception at the heart of this question.
Essentially, I need to deep copy multidimensional arrays 'by value' in JavaScript to an unknown depth. I thought this would require some complex recursion, but it seems that in JavaScript you only need to copy one level deep in order to copy the whole array by value.
As an example, here is my test code, using a deliberately convoluted array.
function test() {
var arr = [ ['ok1'],[],[ [],[],[ [], [ [ ['ok2'], [] ] ] ] ] ];
var cloned = cloneArray(arr);
arr = ''; // Delete the original
alert ( cloned );
}
function cloneArray(arr) {
// Deep copy arrays. Going one level deep seems to be enough.
var clone = [];
for (i=0; i<arr.length; i++) {
clone.push( arr[i].slice(0) )
}
return clone;
}
In my running of this test (latest stable Chrome and Firefox on Ubuntu), even the deepest parts of the array seem to be successfully copied by value in the clone, even after the original is deleted, despite the fact that the slice() "copying" only went one layer deep. Is this the standard behaviour in JavaScript? Can I depend on this to work for older browsers?
Your test is flawed for whether a true copy is being made which makes your conclusion incorrect that you are getting a full copy of all the data in the nested arrays. You are only doing a two level copy, not an N level copy.
Javascript is a garbage collected language so you don't actually delete variables or objects and, even if you tried that doesn't affect the same variable if it's being referenced somewhere else in your code. To see if you truly have a completely independent copy, try nesting an object two levels deep and then change a property on the object in the original array. You will find that the same object changes in the cloned array because you aren't doing a deep clone. Both arrays have a reference to the exact same object.
Here's an example.
function cloneArray(arr) {
// Deep copy arrays. Going one level deep seems to be enough.
var clone = [];
for (i=0; i<arr.length; i++) {
clone.push( arr[i].slice(0) )
}
return clone;
}
var x = [[{foo: 1}]];
var y = cloneArray(x);
x[0][0].foo = 2;
// now see what the value is in `y`
// if it's 2, then it's been changed and is not a true copy
// both arrays have a reference to the same object
console.log(y[0][0].foo); // logs 2
The same result would happen if the third level was another array too. You will have to recursively traverse every element that is an object type and then clone that object itself to get a complete clone of everything in the nested arrays.
If you want code that will do a deep copy (to an arbitrary level) and work for all data types, see here.
FYI, your cloneArray() function assumes that all first level members of your array are arrays themselves and thus doesn't work if it contains any other type of value.
Array.prototype.slice is not suitable for cloning arrays
This should work well for you
function deepClone(arr) {
var len = arr.length;
var newArr = new Array(len);
for (var i=0; i<len; i++) {
if (Array.isArray(arr[i])) {
newArr[i] = deepClone(arr[i]);
}
else {
newArr[i] = arr[i];
}
}
return newArr;
}
If you need to support older browser, make sure to use this polyfill (via MDN)
if(!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
Your code does not work:
If your array contains other variables such as numbers or strings (not only arrays), it will fail, because it will call arr[i].slice(), but since that arr[i] is a number, it hasn't got any .slice property and this will throw an error.
Your function will, in any case, leave all the references to objects and other stuff inside the array alive. So you will not actually obtain a copy of your array.
Example:
var a = [1,2, [11,13], ['ok']];
var b = cloneArray(a);
> TypeError: undefined is not a function // because numbers have no .slice method
Solution:
To copy an array you will need to make a deep copy of it. Since that creating a deep copy will need a function that uses recursion to deep copy any object or array inside of the main one, the easiest way to do it is by using jQuery and its .extend method, which performs a deep copy of the array, see here for more info.
var a =[[1], [2], [3]];
var b = $.extend(true, [], a);
b[0][0] = 99;
a[0][0] // still 1
Here's my recursive approach to "cloning" a multidimensional array. It runs all the way down to the deepest levels:
if ( !Array.clone )
{
Array.prototype.clone = function()
{
var _arr = ( arguments[0] == null ) ? [] : arguments[0] ;
for( var _p = 0 ; _p < this.length ; _p++ )
{
if ( this[_p] instanceof Array )
{
var _sub = [] ;
this[_p].clone( _sub ) ;
_arr.push( _sub.slice() );
}
else _arr.push( this[_p] );
}
return _arr ;
}
}
Now try this code:
var _a = [ "a", "b", [ "c", "d", [ "e", "f" ] ] ];
var _b = _a.clone();
console.log( _b );
Vars _a and _b are two distinct objects: if you remove an element from var _b, then var _a would not be affected.