Invoking .apply on .concat of array - javascript

I'm creating wrappers for common JavaScript types that offer more features. In addition to new methods and properties, they have all of the same methods and properties as the types that they are wrappers for. I'm not completely rewriting the already existing methods even though I might change their logic, so I'm always calling back to the original method on the wrapped value.
Here is what charCodeAt might look like for a wrapper around strings:
this.charCodeAt = function (index) {
return internalValue.charCodeAt(index);
};
I don't need to do much in the above example:
The parameter index in a call to string.charCodeAt is optional, so I don't need to write any logic that would handle the case of the same parameter being omitted in a call to wrapper.charCodeAt.
index is the only parameter string.charCodeAt takes, so there is also no need to handle additional variables passed to wrapper.charCodeAt as they will be ignored.
I've encountered a problem while trying to make calls to a method that accepts a variable number of arguments. The method in question is array.concat. There are other contexts in which I've written code to pass an arbitrary number of arguments to an function in JavaScript and the solution I've used is func.apply(null, argumentArray) where argumentArray is an array of the arguments I want to pass to func. This doesn't work for .concat:
var array = [0, 1];
var error;
try {
array.concat.apply(null, [2, 3]);
} catch (error) {
console.log(error);
}
The error message that gets written to the console says TypeError: Array.prototype.concat called on null or undefined. I researched using apply with concat and it seems like I'd get similar problems with other methods that are defined on a constructor's prototype.
Why am I encountering this problem with array.concat.apply? What is the correct way to pass an arbitrary number of arguments as an array to a method such as .concat?

Calling the prototype method and correctly setting the this argument in the call to apply() should work:
var array = [0, 1];
var error;
try {
array = Array.prototype.concat.apply(array, [2, 3]);
} catch (error) {
console.log(error);
}
console.log(array);

apply expects a this value of an array (or array-like) object. When you do
array.concat.apply(null, [2, 3]);
the this value is null; it's effectively the same thing as
Array.prototype.concat.apply(null, [2, 3]);
Any reference to the original array is disregarded.
If you wanted to output the concat of the original array with [2, 3], you would need to specify that the this value should be array:
var array = [0, 1];
var arrayToConcat = [2, 3];
const result = array.concat.apply(array, arrayToConcat);
console.log(result);
// or, use array spread:
const result2 = [...array, ...arrayToConcat];
console.log(result2);
// or, use concat without anything special at all:
const result3 = array.concat(arrayToConcat);
console.log(result3);
(technically, you can use a non-array-like object as the this for concat, but it's an odd thing to do)

Related

Is there syntactic sugar for calling a function on each element of a list in JavaScript?

Let's say I had the following code:
let array = [1, 2, 3]
array.forEach(x => someFunc(x))
In Java, I know the second line could be simpler using Streams (at least for static methods), like so:
array.stream().map(ClassName::someFunc) ...
Essentially I'm asking is there an analog for the ClassName::someFunc part in JavaScript, instead of having to write x => someFunc(x)?
In the simplest case, you can replace
array.forEach(x => someFunc(x))
with just
array.forEach(someFunc)
but there's some fine print you should be aware of:
this generally doesn't work with object methods: .forEach(someObj.someMeth) won't work
the callback should accept exactly one argument or conform to the forEach calling convention callback(element, index, this). For example, array.map(parseInt) won't work either, because parseInt has its own idea of what the second argument means.
Yes there is no need to wrap "someFunc" in an anonymous () => "arrow function"
As long as someFunc's first argument is configured to accept the currently iterated item from the array and do something with it then you can pass in a reference to that function as the argument in .forEach (but do not call it).
So you would do:
let array = [1, 2, 3]
array.forEach(someFunc)
Make sure not to do this:
let array = [1, 2, 3]
array.forEach(someFunc())
(note the calling parenthesis, this is bad)

How to filter Object using Array.prototype.filter?

Given
var arr = [1,2,true,4,{"abc":123},6,7,{"def":456},9,[10]]
we can filter number items within array arr using Number constructor
var res = arr.filter(Number); // [1, 2, true, 4, 6, 7, 9, Array[1]]
are true and [10] expected in resulting array ? If we substitute false for true at arr
var arr = [1,2,false,4,{"abc":123},6,7,{"def":456},9,[10]]
var res = arr.filter(Number) // [1, 2, 4, 6, 7, 9, Array[1]]
using Array.isArray
var res = arr.filter(Array.isArray) // [Array[1]]
String
var res = arr.filter(String) // [1, 2, true, 4, Object, 6, 7, Object, 9, Array[1]]
If we want to filter items within arr that are object, at indexes 4 , 7 and we try
var res = arr.filter(Object) // [1, 2, true, 4, Object, 6, 7, Object, 9, Array[1]]
Although we would prefer to simply call arr.filter(Object), we could pass a function call; trying different properties of Object so that we can eventually find a property or method that we could use as a function or constructor to pass to as the pattern arr.filter(/* method, constructor, other approach */) to return the filtered results matching the object, or even property name or value of the object within the input array.
We start, innocently enough, by checking if the item in the array has a constructor having name equal to "Object"
var res = arr.filter(function(prop) {
return prop.constructor.name === "Object"
}) // [Object, Object]
though when we add an object to arr; e.g.;
var c = Object.create(null); arr.push(c);
var res = arr.filter(function(prop) {
return prop.constructor.name === "Object"
}) // `Uncaught TypeError: Cannot read property 'name' of undefined`
as c prototype and constructor are undefined. Although we are certain that this will not return expected results
var n = arr.filter(Object.hasOwnProperty, "abc"); // [1, 2]
at least an error was not returned; let us continue
var n = arr.filter(function(prop, val) {
return prop.hasOwnProperty(this.valueOf())
}, "abc"); // [Object abc: 123__proto__: Object]
the expected results are returned; though we are trying to use
var n = arr.filter(/* function reference */, this /* optional parameters passed */)
to
filter an array for Object : {} objects; even if the object does not have a defined prototype or constructor; optionally converting JSON string "{"abc":123}" to object; though we have not reached this far, yet;
pass a property name to .filter(callback, this) pattern where this serves as property name, or value of object; or utilize an approach using filter.bind , .call or .apply or other method to filter an object from the input array - without using full
.filter(function(prop, value) {})
pattern. How can we coerce the Object.hasOwnProperty() call into a pattern similar to
.filter(Object.hasOwnProperty, "abc")
?
Mentioning .call, .bind and .apply after searching for a similar Question and finding JS Array.prototype.filter on prototype method . Though not certain how to implement approaches described in filtering both objects and objects having specific properties as described above.
Note, Question can also be resolved by a destructuring , or other es-6, es-7 approach, providing comparable or, even stricter results, when compared to .filter(). That is, use .filter() without
function(prop, value) {
}
pattern. Returning objects; that is Object , {} ; and objects filtered by property ; objects filtered by property value.
Questions:
How to filter objects with or without Object prototype or constructor within in an array passed to Array.prototype.filter() without using an anonymous function callbackpattern ?
How to filter specific objects within an array passed to Array.prototype.filter() by passing property name or value to match object without using anonymous function callback pattern ?
How to filter objects with or without Object prototype or constructor
within in an array passed to Array.prototype.filter() without using an
anonymous function callbackpattern ?
As per spec
callbackfn should be a function that accepts three arguments and
returns a value that is coercible to the Boolean value true or false
Number object (function's constructor) does return NaN for bad Number conversion but String and Object constructors don't return a false value (yes, filter(Number) also filters out 0)
var arr = [0,1,2,true,4,{"abc":123},6,7,{"def":456},9,[10]];
arr.filter(Number); //outputs [1, 2, true, 4, 6, 7, 9, Array[1]]
You can create a customer function OBJ,
function OBJ(value,index,arr){ return typeof value === "object" && !Array.isArray(value) }
or Arrays are also welcome in the resultset then remove the Array.isArray check
function OBJ(value,index,arr){ return typeof value === "object" }
when used with
arr.filter(OBJ); //outputs [{"abc":123},{"def":456}]
There is no real way to do it safely without creating your own function. Additionally it is very complicated because the definition of Object is too broad.
Let's start with the following:
var types = ['1', 2, true, null, undefined, [], {}, new Date()];
and run the following:
types.map((e) => typeof e);
// ["string", "number", "boolean", "object", "undefined", "object", "object", "object"]
Do you think of null of as an Object? I don't think so. Do you think of an Array as of an Object, because the Array is an instance of Object? I am not sure as well.
What you can try is the following:
types.map(Object.isExtensible);
// [false, false, false, false, false, true, true, true]
This excludes the null from the result but still the array is present here. The Date Object is here as well as any other Object with any prototype, e.g. new Boolean() will also be an Object. Additionally the object could be frozen and this won't be returned as an Object here as well.
So the both examples here successfully demonstrate that the definition of Object is too broad and it cannot be really handled in a useful way.
You seem to want to filter an array for elements with a certain type. Pass an appropriate function to filter:
array.filter(istype("String"))
You just need to write istype now:
function istype(type) {
return function(x) {
return Object.prototype.toString.call(x) === '[object ' + type + ']';
}
}
You seem to have thought you could filter for numbers by saying filter(Number) etc. But that will not work. Number is just another function, which tries to turn something into a number (not check if it's a number). Then, filter filters the array depending on whether the result is truthy or falsy. Number will produce a truthy value for any non-zero number obviously, and true. For a string, or an object, or pretty much anything else, it will return NaN, which is falsy, with odd exceptions such as returning 0 for [] or an all-blank string.
Same with string. String is just another function, which tries to turn something into a string. Then, filter filters the array depending on whether the result is truthy or falsy. String will produce a truthy value for almost anything other than a non-empty string.
This has nothing whatsoever to do with destructuring; why would you think it does? You might want to remove that unfortunate part of your post. Nor is it clear what you mean by "calling filter without a callback"--using a callback to determine which elements to filter in and out is the entire DNA of filter. It is also unclear what pattern you are referring to when you say function(prop, value) { } pattern.
At the end of your question, you ask two specific questions:
How to filter objects with or without Object prototype or constructor within in an array passed to Array.prototype.filter() without using an anonymous function callbackpattern ?
You filter objects from an input array by providing a function which determines if a particular element is an object. That is not what the object prototype or constructor Object is, so that won't help you. You have to write a little function to pass to filter, that's how it works. It could be anonymous, or it could be defined elsewhere and passed in
How to filter specific objects within an array passed to Array.prototype.filter() by passing property name or value to match object without using anonymous function callback pattern ?
What do you mean by "passing property name or value to match object"? Do you mean, filter out elements which are missing a particular property name or value? Then write a function to do that. There is no built-in function for this purpose, if that is what are looking for.
Without passing a callback function, you can instead pass in a regex by using the RegExp.prototype.test method and binding a regex
var arr = [1,2,true,4,{"abc":123},6,7,{"def":456},9,[10]]
var res = arr.filter(RegExp.prototype.test.bind(/\[object Object\]/));
console.log(res)
This would match any string containing [object Object] as well, but it seems highly unlikely that a string would contain those exact words, unless you have made a mistake and included stringified objects in your array.
In ES6 the following would do it for the example values you have listed:
arr.filter(Object.isExtensible)
Obviously, this will exclude objects that have been marked non-extensible, by a call to Object.freeze, Object.seal, or Object.preventExtensions. Unless you plan to use those, I believe this does the job.
var arr = [
/* primitives: */
2, true, "str", null, undefined, NaN,
/* objects */
new Number(2), {a:1}, Object.create(null), [10], x=>x, new Date(), new Set()
];
var objects = arr.filter(Object.isExtensible);
console.log(objects);
The closest have been able to reach to requirement so far, also matches Object.create(null)
var res = []; for (let p of arr) /^\{?.+\}$/.test(JSON.stringify(p)) && res.push(p)

Overwrite the this value in array.prototype

I've found that the native js sort function screws up from time to time so I wanted to implement my own. Lets say i have the following :
Array.prototype.customSort = function(sortFunction, updated) {...}
var array = [5, 2, 3, 6]
array.customSort(function(a,b) {return a - b})
console.log(array)
Array should be [2, 3, 5, 6]
Updated is the array that has been sorted.
No matter what I return in customSort, the order of array is still in the original order. How do I overwrite the 'this' value / get it to point to the array with the correct order?
If you consider the actual code you gave above, you have to make sure that your customSort function updates this.
One case is that customSort only uses this as "read-only" input, that is - only puts the sorted array in updated, rather than changing this.
In such case, considering the code above (which you might have performed tests with), no updated parameter is sent to the function, to receive the sorted values.
Another case is that customSort returns the sorted array, in which case you have to collect it:
array = array.customSort(function(a,b) {return a - b});
console.log(array);
I just ended up iterating over the updated array and replacing each value in this with the value in updated. In code, that looks like...
function customSort(cb) {
...//updated is the sorted array that has been built
var that = this;
_.each(updated, function (ele, index) {
that[index] = ele;
})
}
I wanted the function to operate in the exact same way the native array.sort function does - it overwrites the array provided instead of returning a new, sorted array.
I find it odd that this works... you cannot overwrite the entire this value in one clean sweep but you can in steps. I couldn't do this for in the customSort function :
this = updated;

function executed by .apply() works with array, but does not the original function

if Math.max([1,3,9]) returns error (needs a list of numbers, not an array), why calling it via apply like below works?
function getMaxOfArray(numArray) {
return Math.max.apply(null, numArray);
}
getMaxOfArray([1,3,9]) //9
getMaxOfArray(1,3,9) //error
I understand .apply passes an array, but why should max function work with them only when called via apply? is there some internal transformation array => list ?
apply expects the parameters to be in an array. if you just have the parameter list how you do in the second case, use call instead
Math.max.apply(null,[1,3,9])
Math.max.call(null,1,3,9)
What is the difference between call and apply? goes into a good amount of detail on the difference between call and apply
Your function only accepts one argument (numArray), not three—this is why your call to getMaxOfArray is failing. If you are writing a one-liner, you should use call instead of apply for a series of parameters rather than an array, as so:
Math.max.apply(null, [1, 3, 9]);
Math.max.call(null, 1, 3, 9);
For a function, you can use the arguments object for a variable number of parameters, if you do not want the user to pass an array. Here's how you would go about doing it this way. (Note that I still call apply here, because I store all of the arguments called by the user into an array.)
function getMaxOfArguments() {
var parameters = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
return Math.max.apply(null, parameters);
}
Now, your second function call should work:
getMaxOfArguments(1, 3, 9); // 9
The function signature of Math.max is function(arg1, arg2, arg3, ...) not function(Array). Apply is converting the input into a more palatable form for Math.max.

javascript: apply two-dimensional array

Is there a way to apply a two-dimensional array to an object?
Like this:
var myArray = [[0,1],[2,3]];
someObject.apply(null,myArray);
It seems to apply only the first inner array :-/
Why is that?
Okay, since your question was so uninformative I'm going to assume a lot of stuff. Firstly, I'm going to assume that someObject is a function. Next I'm going to assume that it has only one formal parameter like #Adam pointed out. So this is what I assume your code looks like:
function someObject(a) {
alert(a); // were you expecting [[0,1],[2,3]]?
}
var myArray = [[0,1],[2,3]];
someObject.apply(null,myArray);
This is what I think you want instead:
function someObject() {
alert(arguments); // now it alerts [[0,1],[2,3]]
}
var myArray = [[0,1],[2,3]];
someObject.apply(null,myArray);
Remember, when you apply arguments to a function you pass it the arguments as an array. It's kind of like calling the function as follows:
function someObject() {
alert(arguments);
}
var myArray = [[0,1],[2,3]];
someObject(myArray[0], myArray[1]);
Of course, it also assigns the function a custom this pointer.
Edit: Looking back at your code I think you might have intended to use call instead of apply. The method call allows you to pass the arguments to the function as separate arguments instead of an array of arguments. So your code would look:
function someObject(a, b) {
alert(a); // now a is [[0,1],[2,3]]
alert(b); // b is 5 and so on
}
var myArray = [[0,1],[2,3]];
someObject.call(null,myArray,5);
You code works..
With that code you pass two arguments to the someObject function, [0,1] and [2,3]
According to this fiddle that is exactly what happens.
http://jsfiddle.net/BgVxQ/
Edit: If you have an unknown number of arguments, use the arguments variable available inside the function to get hold of them. If you have a fixed number of arguments, then it's often easier to declare them
function someObject(parameter1, parameter2){
//Do stuff
}
That way you don't need to manually extract them from arguments
var myArray = [[0,1],[2,3]];
someObject.apply(null,[myArray]);
According to the docs, apply takes an array of arguments and passes them to your function. So, you need to place myArray inside an array that will be unpacked to form the argument to someObject:
var myArray = [[0, 1],[2, 3]];
someObject.apply(null, [myArray]);
In the code you posted, the function someObject was receiving two arguments: [0, 1] and [2, 3]. This is legal because JavaScript allows functions to be called with a number of arguments that differs from the number of formal parameters. But because there were more arguments than formal parameters, the second argument ([2, 3]) was lost and you only saw the first ([0, 1]).

Categories