I came across this code for stripping Marketo forms of their included stylesheets. Let's assume that the code author is a super senior engineer. Array.from() could have been used instead of defining arrayFrom (functionally at any rate), so why use the latter?
For my part I'm trying to understand the arrayFrom definition (first line of the codeblock):
bind() sets this to the provided value, here [].slice (why?)
call() allows us to call getSelection with the this value bound by bind.
getSelection() returns a Selection object (or string in Firefox) of the selected text. This I'm unsure about.
In its use, arrayFrom gets passed an array (or NodeList) of stylesheets and returns an array of the same stylesheets (a shallow copy thereof?) no differently than if Array.from were used, so the functional bit of bind and call must be to alter the this value in a desirable way. Not sure how that acts on [].slice though.
Anyone? I'm clearly missing something.
const arrayFrom = getSelection.call.bind([].slice);
// remove element styles from <form> and children
const styledEls = arrayFrom(formEl.querySelectorAll("[style]")).concat(
formEl
);
styledEls.forEach(function (el) {
el.removeAttribute("style");
});
// create an array of all stylesheets in document
const styleSheets = arrayFrom(document.styleSheets);
// loop through stylesheets and check for ownerNode properties on each
styleSheets.forEach(function (ss) {
if (
//array of <link/> elements tied to stylesheets
[mktoForms2BaseStyle, mktoForms2ThemeStyle].indexOf(ss.ownerNode) !=
-1 ||
formEl.contains(ss.ownerNode)
) {
ss.disabled = true;
}
});
Nowadays we would just use Array.from. But your questions are about the construct that is used:
const arrayFrom = getSelection.call.bind([].slice);
First of all, this has nothing to do with getSelection, as the expression is not binding that, but the call function. This call function is on the Function prototype, so the above leads to the same result as:
const arrayFrom = Function.prototype.call.bind(Array.prototype.slice);
call is a function that allows one to call another function with the possibility to provide a this-argument to it. Here we define that the function to be called should be slice. The first argument we will provide to arrayFrom will be like the first argument we would provide to call, i.e. the object on which slice should be called. This gives it a similar behaviour as Array.from.
It may help to replace bind by this function that does a similar thing:
function arrayFrom(arrayLike) {
return Function.prototype.call.call(Array.prototype.slice, arrayLike);
}
It is confusing, but we invoke call with call so that we can provide a this argument to it (defining the function we want to call), making the second argument the this-argument that call (the first one) deals with.
A simple .forEach() stop after first iteration depending on the content...
My initial loop looked like this :
document.getElementsByClassName("app-form-to-js").forEach(function(node) {
node.classList.remove("app-form-to-js");
jsForms[node.id]=node.cloneNode(true);
node.remove();
});
While the first loop occur, it doesn't go beyond.
Then, after some tweaking, I managed to get the loop working with this code :
document.getElementsByClassName("app-form-to-js").forEach(function(node) {
jsForms[node.id]=node.cloneNode(true);
jsForms[node.id].classList.remove("app-form-to-js");
});
But, the moment I use node.classList.remove("app-form-to-js"); or node.remove(); in the loop,
it will always stop after first iteration.
Any idea why loop is stopping after first iteration (so it iterate at least once) if I alter the node?
document.getElementsByClassName returns an HTMLCollection, which doesn't have a definition for forEach. You can however turn this into an array, which does have one defined, by using Array.from() like so:
var collection = Array.from(document.getElementsByClassName("app-form-to-js"));
collection.forEach(function(node){...});
EDIT:
As was pointed out to me by #Preciel, the NodeList object does have a forEach() function similar to an array. If you would rather use this, you can replace document.getElementsByClassName("app-form-to-js") with document.querySelectorAll(".app-form-to-js") and this will return a NodeList rather than an HTMLCollection
An alternative way to create an array using ES6 spread operator (this is my preferred method):
const elements = [...document.getElementsByClassName("app-form-to-js")];
elements.forEach((node)=>{...});
//OR:
elements.map((node,i)=>{... return node});
//Etc...
If you use the .map() method, you can make changes directly to the elements and it will be reflected in the DOM etc. - very powerful technique (remember to return the modified node in the function)
Long time I write code in JavaScript, using jQuery, ReactJS and NodeJS server side (ExpressJS). I learned partially MDN and other sources, however there is one question I can not found an answer for.
Why follow code requires prototype object's property? Why I can not use forEach directly from Array object? I mean, in terms of OOP, if Array class extends class contains forEach method, I can call it from object instantiated from Array directly, I don't need, using reflection find base class, instantiate it and call the method from the base class.
Array.prototype.forEach.call(document.querySelectorAll('.klasses'), function(el){
el.addEventListener('click', someFunction);
});
Example taken from here: https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/
Your code:
Array.prototype.forEach.call(document.querySelectorAll('.klasses'), function(el){
el.addEventListener('click', someFunction);
});
could indeed be written as
[].forEach.call(document.querySelectorAll('.klasses'), function(el){
el.addEventListener('click', someFunction);
});
Going through the Array prototype object is just a way to access the functions available to any array instance without having to actually create an array. Using one or the other is mostly a matter of preference, and though the second form does involve the creation of an array object, the performance implications are minimal.
Why I can not use forEach directly from Array object?
The Array object is a constructor function used to create arrays. It doesn't have a forEach property of its own.
You can access the forEach property either from an instance of Array (which you can create with new Array or, more idiomatically, with []), or by accessing the prototype that gets applied to instances of Array.
querySelectorAll doesn't return an Array, returns a NodeList (like array-object) instead. The NodeList has a property called .length that indicates the count of elements inside of it.
Some browsers/engines/backend techs are incompatible because that NodeList no necessarally will provide the function forEach.
So, an alternative is converting that NodeList to an Array using the Array prototype:
Array.prototype.forEach.call(document.querySelectorAll('.klasses'), function(el){...})
Or, you can use the function Array.from that will use the property .length to create the array:
Array.from(document.querySelectorAll('.klasses'));
From MDN docs
Although NodeList is not an Array, it is possible to iterate on it using forEach(). Several older browsers have not implemented this method yet. You can also convert it to an Array using Array.from.
I am using a forEach to loop through a nodeList. My code is as follows
var array = document.querySelectorAll('items');
array.forEach(function (item) {
console.log(item);
});
And this code throws an error as
Uncaught TypeError: array.forEach is not a function
Then after reading few online blog articles i changed the code to this.
[].forEach.call(array, (function (item) {
console.log(item);
}));
Could someone please explain why it is not possible to call forEach on a nodeList and what does the above second code piece do. :)
Edit: 7/25/2017
This question does not valid for modern browsers. You can use forEach on node lists in them
Although NodeList is not an Array, it is possible to iterate on it
using forEach(). It can also be converted to an Array using
Array.from().
However some older browsers have not yet implemented
NodeList.forEach() nor Array.from(). But those limitations can be
circumvented by using Array.prototype.forEach() (more in this
document).
Ref: MDN
This is a fundamental thing in JavaScript: you can take a function from one object and apply to any other object. That is: call it with this set to the object you apply the function to. It is possible, because in JavaScript all property names etc. are (plainly speaking) identified by name. So despite NodeList.length being something different then Array.length the function Array.forEach can be applied to anything that exposes property length (and other stuff that forEach requires).
So what happens in your case is that:
querySelectorAll() returns an object of type NodeList, which happens to expose length property and is enumerable (let's say it is accessible by [] operator); NodeList does not expose forEach function (as you can see i.e here: https://developer.mozilla.org/en-US/docs/Web/API/NodeList) - that's why it's impossible to call forEach directly on the results of querySelectorAll()
[].forEach returns a function - this a not so clever shortcut for Array.prototype.forEach
with [].forEach.call(array, …) this function is applied onto an object referenced by array, an object of type NodeList (that is forEach is invoked with array as this in function body, so when inside forEach there is this.length it refers to length in array despite array being NodeList and not real Array)
this works, because forEach is using properties that Array and NodeList have in common; it would fail if, i.e. forEach wanted to use some property that Array has, but NodeList has not
the NodeList object doesnt contain the method forEach, its a method of the Array object. the below code:
[].forEach.call(array, (function (item) {
console.log(item);
}));
is using the forEach method from array and passing it a NodeList.
Another option you have, and arguabiliy better, is to convert your NodeList into an array, like this:
var myArrayOfNodes = [].slice.call(NodeList);
This uses the Array objects slice method to create an array of nodes from a NodeList. This is a better aproach as you can then use an array rather then hacking an array-like object
querySelectorAll gets the element in array-like object not an Array. So you need to use as you have in second code example.
I'm looking back at the Backbone todo list and have a question about the collection.
Here is the code:
window.TodoList = Bacbone.Collection.extend({
model: Todo,
localStorage: new Store("todos"),
done: function() {
return this.filter(function(todo){return todo.get("done")})
},
remaining: function() {
return this.without.apply(this, this.done());
}
})
I understand everything that is going on here, except for the 'remaining' function.
The return statement: return this.without.apply(this, this.done()); is using a proxy to an underscore method - _.without
According to Underscore docs, here is what that is for:
without_.without(array, [*values]) Returns a copy of the array with
all instances of the values removed. === is used for the equality
test.
_.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
=> [2, 3, 4]
So, I get that it is saying to return everything in the collection without a 'done' attribute with the value of 'true'.
What I don't understand is the 'apply' function that is being chained to it. That doesn't appear in the Backbone docs or the Underscore docs. At least not anywhere I can find it.
Can anyone explain in detail what is going on with those elements in the Return statement?
this is referring to the collection.
apply is a method of javascript functions that allows you to set context of a method and send an array of values to the caller.
apply expects context as the first parameter then an array or array-like (such as arguments) which will be passed in as parameters the function.
You can do the same thing with .call except the 2nd+ params are comma separated.
apply and call are native to javascript.
So...
return this.without.apply(this, this.done());
the method this.done() returns an array, but uses the context of the collection and passes in a series of values to be ignored via the without method. Which in turn returns all todos that aren't done within the collection.
Example:
_.without([1,2,3,4],1,2); === _.without.call([], [1,2,3,4], 1, 2);
My understanding is that, in this case, the use of apply is redundant, remaining could be shortened as follows:
remaining: function() {
return this.without(this.done());
}
As I understand it, the only reason to use apply is if you want (or need) to change the contextual item that without will operate on. In this case, we have no need to do that.
If I'm wrong, I'd really (really!) like to have an explanation of why apply is necessary here.
apply invokes a function and binds this in the context of that function to the first argument passed (in this case, the Collection instance TodoList). The second argument is an array of arguments to be passed to without.
By the way, apply isn't a Backbone thing -- it's native to JavaScript.
The reason for this
this.without.apply(this, this.done())
is that "_.without" does not accept as argument the array of items to be excluded as a single array ([]) argument
See here _.without to accept array as second argument
apply, which is part of the JS Function.prototype, here is a workaround to inject the excluding items in a single array argument
the use of apply in this case is redundant because backbone collections is doing the job correctly cf. http://backbonejs.org/docs/backbone.html#section-122
we can use underscore like this: _.without.apply(this, this.done()) or backbone binding like this: this.without(this.done) by using backbone bind.
Please take a look at underscore doc :
like this :
without_.without(array, [*values])
Returns a copy of the array with all instances of the values removed.
_.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
=> [2, 3, 4]
I changed the function to this and got the exact same result on the TODO list application:
remaining: function () {
return this.filter(function (todo) {
return !todo.get('done');
});
}
I still didn't understand how apply became a method of without, I knew apply was a Javascript function, but then I read the documentation for apply and understood that in this case without was being used in an object-oriented way (see http://underscorejs.org/#chain).
In fact, apply could be passed null instead of this as the context and it wouldn't change the result because it's not being used at all:
return this.without.apply(null, this.done());
Notice the first this is the actual collection of models and without, via the second argument in apply, which is the array of done (completed) tasks, is producing the array of pending todo tasks.
By the way, the latest version of the TODO application renames the function done to completed.
Here this.without() delegates to _.without() function. _.without() needs an array and elements as parameters not as an array. By using apply() , apply calls _.without() in the correct manner.
var obj = {
f1: function(a,b,c){}
};
Now obj.f1(1,2,3) == obj.f1.apply(obj, [1,2,3]).
With this infomation, this.without(this.complete()) passes an array to the without method. But without method needs individual elements to be passed as arguments. That can be done using Function.apply().
The without function needs a list of elements to remove from this.
this.completed() returns an array, therefore it is not what without function is expecting.
apply is a native JavaScript function, which calls a function setting the this context as the first argument and an array as the second argument. The argument is passed to the original function as its list arguments.
In this case apply passes the arguments to without in this.completed(), meeting the expectation of without.
In conclusion, in this case, apply is necessary.
Sorry, I'm a total newb # this stuff, but couldn't fn.remaining (also) be declared as:
return this.filter(function(todo){return !todo.get("done")})
Stating this as a request for clarification, rather than an alternative declaration :)
(edit: couldn't bold the '!' before 'todo.get...')