If there's anyone who can help me understand this one, I will be impressed. It's #28 from John Resig's advanced JavaScript.
Here's a function for looping, and, if you look at the tutorial, it seems to run 3 times producing the 6 Pass below.
Can you please explain in plain language in as much detail as possible what is happening in this program, explaining along the way:
fn.call(array, array[i], i) Why are there these 3 parameters to the function and how do they all work. Does the function first deal with array, then array[i], and then i? What's happening when all that is going on?
Also, in the function, I understand that i++ goes up every time that it goes through the array.length, but what triggers num++ to increase it's value, and in what way is value == num++
In function(value, i), what is value? Is value alternately 0,1,2 of the loop array? If so, how do those loop array numbers get passed as a parameter in function(value, i)
this instanceof Array what is this trying to show? How?.
Code:
function loop(array, fn){
for ( var i = 0; i < array.length; i++ )
fn.call( array, array[i], i );
}
var num = 0;
loop([0, 1, 2], function(value, i){
assert(value == num++, "Make sure the contents are as we expect it.");
assert(this instanceof Array, "The context should be the full array.");
});
PASS Make sure the contents are as we expect it.
PASS The context should be the full array.
PASS Make sure the contents are as we expect it.
PASS The context should be the full array.
PASS Make sure the contents are as we expect it.
PASS The context should be the full array.
The function is an anonymous function, that is, a function without a name. The full second argument to loop is
function(value, i) {
assert(value == num++, "Make sure the contents are as we expect it.");
assert(this instanceOf Array, "The context should be the full array.");
}
which is a complete anonymous function which takes three arguments: this (the object for which it is a method), the current value, and the loop counter.
loop iterates over the array, and uses fn.call to invoke the anonymous function, passing it three arguments; here, the array object must be explicit, because call can't know what context the function reference it's being invoked on should be invoked in (that is, what to make this in the call).
The anonymous function, as invoked by loop, receives the array as this. The second ASSERT verifies this. It also expects that the array's value is [0, 1, 2] and verifies this by incrementing num on each call and comparing that to the passed array element.
So, following the execution chain:
num is declared and initialized to 0.
loop([0, 1, 2], function ...) is invoked.
loop invokes fn, the anonymous function, with the array (as this), its first element, and i which indicates the element offset. (i is never actually used.)
The anonymous function ASSERTs that it was passed the expected first element, 0, by comparing against num and incrementing num afterward.
The anonymous function ASSERTs that its this is an Array.
loop invokes fn as in #3, but with the second array element.
The anonymous function again performs its ASSERTs, this time comparing the passed second array element, which is expected to be 1, against num (which, because of the post-increment in step 4, is 1).
loop invokes fn as before with the third array element.
The anonymous function does its ASSERTs again, this time comparing the expected array element 2 against num whose value is now 2.
fn is a function pointer to the anonymous function that was created when invoking loop.
a) There are three parameters because the first one is a reference to the object you are going to be acting on, i.e. the array, the 2nd parameter is just current value in the iteration of the array, and the third value is location of that element in that array.
b) when you call num++, it increments the value of num. The post increment returns the value (aka assigns to value) and then increments num.
c) value is the 2nd parameter passed in by fn.call a couple lines up.
d) this refers to the array that you passed into the first parmater in your fn.call() invocation.
I think an easier way to understand what's going on is to remove the usage of call and instead invoke the method directly - the old fashioned way.
function loop(array, fn) {
for(var i = 0; i < array.length; i++)
fn(array[i], i);
}
var num = 0;
loop([0, 1, 2], function(value, i) {
assert(value == num, "Make sure the contents are as we expect it.");
assert(this instanceof Array, "The context should be the full array.");
num++;
});
If you run this code on the source url, you would see the this instanceof Array test fails each time, since this is not pointing to an instance of Array anymore.
To make this point to an instance of Array, we need to invoke the method using call or apply.
fn.call(array, array[i], i);
The first parameter is what this will point to. All other parameters here are passed as arguments to the function being called. Note that we can cheat here to pass the test. Instead of passing the array object that we are looping over, you can pass any Array object to make the second test pass.
fn.call([], array[i], i);
fn.call([1,2,3,4,5], array[i], i);
I don't actually know javascript but I believe I understand whats going on...
The first four lines are defining a function called loop. Loop takes in two variables, an array and a function. The only reason why I believe the second parameter is function is because the coder called call on the variable which probably calls the function. The function loops through the elements of the array and passes them to the function somehow through the call method. You should probably look this part up.
After the definition, num is defined to start at zero.
Then comes the fun part. The function loop is called with the parameters [0, 1, 2] and an anonymous function that was created on the spot. function(value, i) and everything after it is the definition of this anonymous function. It has no name but it just defined for this function call. The function checks to make sure that value equals num and increments num after it and then checks to makes sure the variable is an array.
Related
So, I've already understood the idea of recursion in JavaScript (having a function that loops itself until it reaches a base condition, at which point it stops and returns the final result), but I'm having a bit of a headache when I apply that to the actual syntax of trying to apply it to create arrays.
Let's use this countdown exercise from freeCodeCamp as an example. This is the correct result:
function countdown(n) {
if (n < 1) {
return [];
} else {
const arr = countdown(n - 1);
arr.unshift(n);
return arr;
}
}
The idea here is obviously to have a function to count down from a given n to 1. I understand the idea, but I have a couple of questions:
1 - Why is the array declared as a constant and assigned to the recursion call? Shouldn't the array be better declared outside as a variable (since it changes value)ā and doesn't its current position mean that it is declared (and over-written) every time the recursion takes place?
2 - I get the unshifting of the current value, but why is it declared after the recursive call? Shouldn't it be positioned before (one line above) so as to unshift the value before the function loops itself?
3 - Similarly, why is the full final array returned within the else loop? Shouldn't it be better returned outside, or on the base condition above, so as to not be returned every time the function loops?
As it is, my understanding is that when n reaches 0, the loop should return an empty array, and not the full array that has been created with the recursion.
~ ~ ~
FINAL EDIT / UNDERSTANDING: Thanks to every answer! So, if my current understanding is correct, recursion does not loop the function, as I initially thought ā instead, it sort of "opens" a call of "chained functions" that goes downwards until it finds and resolves the base case.
When that "base" case or function is resolved, the chain goes "up", so to say, resolving each recursive step above, .unshifting each n value, and returning the resulting array "upwards", until we reach the initial n value, at which point the final array is returned, and the function is exited.
I think I (sort of) got the point now! Actually writing recursive functions won't be easier though, but I have a better grasp of how the process actually takes place.
Why is the array declared as a constant and assigned to the recursion call?
It is not. The array is variable. Every call to this function will return a new array. No arrays get "overwritten". const in JavaScript is merely used to declare a constant variable which has lexical scope (that is, the variable arr ceases to exist after return arr) and is constant (can't be mutated / assigned to).
I get the unshifting of the current value, but why is it declared after the recursive call? Shouldn't it be positioned before (one line above) so as to unshift the value before the function loops itself?
You can't position it above since the recursive call is required to obtain arr in the first place. After you have counted down from n-1 to 1, you must prepend n at the first position, which is what unshift does.
Similarly, why is the full final array returned within the else loop? Shouldn't it be better returned outside, or on the base condition above, so as to not be returned every time the function loops?
There is no such thing as an "else loop". The reason for returning the array there as well is that you want countdown(n) to return the array it has just generated, using the array countdown(n-1).
As it is, my understanding is that when n reaches 0, the loop should return an empty array, and not the full array that has been created with the recursion.
That's exactly what if (n < 1) return []; does.
That said, for the task at hand the recursive implementation given is highly suboptimal. First of all, you don't need the else since return will exit the function anyways:
function countdown(n) {
if (n < 1) return [];
const arr = countdown(n - 1);
arr.unshift(n);
return arr;
}
second, this is most readable (and performant) as a simple for loop:
function countdown(n) {
const arr = Array(n)
for (let i = 0; i < n; i++) arr[i] = n - i;
return arr;
}
(bonus: the array size being known ahead of time can be leveraged using the Array constructor to prevent JS having to resize the Array as you push elements).
third, the current implementation has a quadratic time complexity of O(nĀ²) because arr.unshift is a linear-time operation which is applied n times to arrays of length 0 to n-1.
Recursion is extremely useful when you need the stack (f.E. in a depth-first traversal). Creating a countdown doesn't require a stack. This is not using but rather abusing recursion to implement a highly suboptimal algorithm. If you - for whatever reason (perhaps you are forced to use a purely functional language that has no loops, only recursion?) - wanted to replace the perfectly fine for loop with a recursive call at all cost, simply use tail recursion and lexical scope (closures):
function countdown(n) {
const arr = [];
function loop(n) {
if (n < 1) return;
arr.push(n);
loop(n-1);
}
loop(n);
return arr;
}
or if you don't like closures, what about an optional arr parameter? This allows turning the recursion around since the caller now has the arr before the callee and can thus append its number before subsequent recursive calls do the same:
function countdown(n, arr = []) {
if (n < 1) return arr;
arr.push(n);
countdown(n - 1, arr);
return arr;
}
same result, but (IMO) cleaner, and most importantly, significantly faster code. Try running all these functions on n = 1e6 and you will see that the freeCodeCamp one fails to complete in a reasonable timeframe whereas the other ones finish almost instantly.
It might help to clarify how the code is working a little. Hopefully, the explanation clears up your questions a bit. Let's start with the most basic case:
countdown(0) - When you call countdown(0) your if-block runs and returns an empty array []. So we see that countdown(0) outputs []. This is the base case.
Next, let's imagine we call the function again, this time as:
countdown(1) - In this scenario the else block runs. We see that it calls countdown(0), which means the interpreter executes the function again, passing n as 0. Above we saw what happens when countdown(0) is called, it returns an empty array [], so that's what gets assigned to arr within the else block. We then unshift n (which is 1 here) onto this empty array, which adds 1 to the start of the array stored in arr. This is then returned. So we say that countdown(1) outputs [1].
Now let's see what happens if we were to call the function again with 2:
countdown(2) - Again, the else block runs, triggering a call to countdown(1). We saw above what occurs when this is called, we get back an array with [1], which is stored in the local variable arr, and we then .unshift(2) so that 2 is added to the beginning of the array which we then return. So we see that countdown(2) returns [2, 1].
When thinking about recursion, it can be helpful to think of it as breaking down your input until it's at a small enough value that it can't be broken down anymore (your base case). Once you hit that, you work your way back up, stitching together your previous calls now that you have "solved" the sub-problems.
To answer your questions more directly:
arr is assigned to the recursive call so that we can build upon the results it returned. If countdown(1) returns [1], then we can build onto that result by storing it in arr and unshift 2 onto it to calculate countdown(2). It could have been made let, but const is commonly used if the variable doesn't need to be reassigned. We don't want to move arr outside of the function as then calling countdown() multiple times will modify the same global array. You would also need to change your code so that we can gradually "build-up" the result by further recursive calls if you did that also, which is more of a "loop mindset" than a "recursive mindset".
Moving the .unshift() component before the recursive call wouldn't work as you wouldn't have anything to unshift to. The recursive call is what provides you with the array result of countdown(n-1) so that you can build onto that later by using .unshift().
Returning your array in the else block is what allows you to obtain results when n is greater than 0. In the countdown(1) example above, we need to return arr so that it outputs [1]. This needs to be in the else block because we're returning arr which is defined in that else-block (and hence can't be accessed outside of it).
I would just add to the first part of LMD's answer (I can't comment because of no reputation):
When you define constant array, you are not saying that the array is constant and can't change. You are creating constant reference to that array. So you still can edit its elements, but can not overwrite it with new array.
More info here:
https://www.w3schools.com/jS/js_array_const.asp
1 - Why is the array declared as a constant and assigned to the recursion call? Shouldn't the array be better declared outside as a variable (since it changes value)ā and doesn't its current position mean that it is declared (and over-written) every time the recursion takes place?
The const declaration is block-scoped. The reference only exists inside the else-block. There is not one generic arr that is shared by each call. Instead, inside of each call to countdown, we get an if-condition. If that fails, we create the else block and assign a brand-new arr variable. When this is returned, and the function ends, that reference is gone, and only the function return value remains.
2 - I get the unshifting of the current value, but why is it declared after the recursive call? Shouldn't it be positioned before (one line above) so as to unshift the value before the function loops itself?
First of all, be careful of "the function loops itself". Recursion is a different mechanism from looping; and although they have equal power, they are not at all the same.
But recall that the recursive call returns the result of calling this with n - 1. So
const arr = countdown(n - 1);
when called with n of 4 sets arr to [3, 2, 1]. Now that we have this value, we can do the unshift operation on it.
3 - Similarly, why is the full final array returned within the else loop? Shouldn't it be better returned outside, or on the base condition above, so as to not be returned every time the function loops?
Again, you need to stop thinking in terms of looping.
Here's an alternative way of thinking about it: For any smaller value of n than our current value, we assume that countdown will magically provide us the correct answer, and then, when we call it, we will prepend our n value to the result.
Recursion is simply the trick of making that magic work. If we know the answer to our simpler problem(s) and we can use these to build the answers to our more complex ones, then all we need is a way to stop breaking down into smaller problems: our base cases.
So, for countdown (3), we call our magical countdown (2) function, which returns [2, 1] and use unshift to prepend 3 and get [3, 2, 1]. Of course if we wanted to dig into how that magical countdown (2) actually worked, we could and we'd see that it called the magical countdown (1), getting [1] and prepended 2 to get [2, 1], and so on. The magic, it turns out, is rather mundane.
But that's what recursion is, at its core: the notion that there is a sequence of simpler and simpler problems, bottoming out in some base case(s), and that we can build our more complex result based on those simpler ones. But while writing the logic to do this, it's easiest to think of those simpler ones as magical.
I'm wanting to write a function to filter objects of an array on some condition. I know that when you pass an array to a function, the changes you make to the array inside the function also change the array outside the function (if you are able to make the distinction in the first place anyway). For example,
a=[1,2,3]
function addItem(list){
list.push(4)
}
addItem(a)
// now a = [1,2,3,4]
Also, even though the filter method only returns a new array and doesn't change the array it was called it, it can be used to change that array by writing a = a.filter(someFunction). I imagine there is some subtlety I'm missing here because when I try to combine these two facts to write a function filtering an array on some condition, the original array isn't changed at all. Here is an example:
function isEven(n){
return (n%2 == 0)
}
function filterOdds(list){
list = list.filter(isEven)
}
a=[1,2,3,4]
filterOdds(a)
// a is still [1,2,3,4]
I suppose I really have two questions:
Why doesn't the above code change a to [2,4]? and
How can I write a function to filter lists as I'm trying to do above? Of course, you could get the job done without the function, but what I really want is to filter multiple arrays in a single function. This just seemed like the relevant simplification.
In your second code, you are reassigning the argument only. Reassigning a variable will, in almost all cases, have no side-effects; all it'll do is change what further references to that variable in that scope will refer to. If there happens to be an outer variable which gets passed as an argument, that outer variable will not get reassigned regardless of what happens inside the function, unless the function deliberately reassigns the outer references.
You could implement this by not passing the list argument - have filterOdds reassign the outer variable:
function isEven(n) {
return (n % 2 == 0)
}
function filterOdds() {
list = list.filter(isEven)
}
list = [1, 2, 3, 4]
filterOdds()
console.log(list);
If you want a more generic solution that'll work for any array passed, since dynamically reassigning an outer variable inside a function isn't possible, your only decent remaining option would be to return the new array and, when calling the function, assign the result:
function isEven(n) {
return (n % 2 == 0)
}
function filterOdds(list) {
return list.filter(isEven)
}
a = [1, 2, 3, 4]
a = filterOdds(a)
console.log(a);
Can anybody explain the flow of the code? I need to know how the function 'isEven' gets the 'x' value
$(document).ready(function(){
var array = [1,2,3,4,5];
function isEven(x){ //checks if a value is even
console.log(x)
return x % 2 == 0;
}
var newArray = array.filter(isEven); //uses a callback to check if an element is even
console.log(newArray);
});
As stated in the comment in your code, you are passing a callback, so here the current item processed in .filter() will be automatically passed to this callback function, or in other words isEven function will be called with the current item from .find() call.
As you can see in the MDN Reference for .find():
callback
Function to execute on each value in the array, taking three
arguments:
element The current element being processed in the array.
So writing:
array.filter(isEven);
Is equivalent to writing:
array.filter(function(item){
isEven(item);
});
The solution is in this line
var newArray = array.filter(isEven); //uses a callback to check if an element is even
Here you are calling the method "filter" on the array. Filter takes a method that returns true or false, and calls it on each of the array element, passing the element itself. That line could be implemented like this
let newArray;
for(let x: array){
if(isEven(x)){
newArray.push(x);
}
}
The filter() function on an array takes a function as it's input. In this case that input function is the isEven function. The filter function then iterates over the array and runs the isEven function over each of the elements. It then filters out any elements in the array for which the function returned false.
Note that in the parentheses of the filter function you do not specify any arguments to the isEven function. That is because filter does this for you.
Your code is equivalent to:
var newArray = [1, 2, 3, 4, 5].filter(x => x % 2 == 0);
The x value is taken in your first array. See the doc.
filter is a function defined in the Array API, it receives as a parameter a "this" which in your case is the variable "array" and a callback that would be "isEven", inside that "this" is the values of your array, it just need to go through it and call each one to your function.
Read this
array.filter will automatically take this as its argument if not passed, when it is getting looped. Check here for more details.
So, in case of your array this values corresponds to the elements 1,2,3,4,5
If a thisArg parameter is provided to filter, it will be used as the
callback's this value. Otherwise, the value undefined will be used as
its this value. The this value ultimately observable by callback is
determined according to the usual rules for determining the this seen
by a function.
This question already has answers here:
Understanding Javascript callback parameters
(3 answers)
Closed 5 years ago.
var animals = ["cat","dog","fish"];
var lengths = animals.map(function(c) {
return c.length;
});
console.log(lengths);//[3, 3, 4]
Here is the code. I don't understand where this 'c' argument comes from.
I tried to change this argument to another one (any word, actually, in both places), and the console.log result is always the same!
But this 'c' is not defined anywhere! Where does 'the engine' get the value of this 'c'?
You've asked two slightly different questions. First to the question body:
I don't understand where this 'c' argument comes from. I tried to change this argument to another (any word, actually, in both places), and the console.log result is always the same!
But this 'c' is not defined anywhere!
Where does 'the engine' gets the value of this 'c'?
You define the parameter name (as you've noticed, you can choose any name for it you like). The value comes from the array, because map calls your callback and determines what argument to pass for that parameter.
Here's a conceptual implementaton of Array.prototype.map, which make make this clearer:
// CONCEPTUAL ONLY, NOT AN ACTUAL VERSION OF IT
function maplike(array, callback) {
var result = [];
for (var i = 0; i < array.length; ++i) {
result[i] = callback(array[i]);
// ^^^^^^^^--- where 'c' comes from
}
return result;
}
var animals = ["cat","dog","fish"];
var lengths = maplike(animals, function(c) {
return c.length;
});
console.log(lengths);//[3, 3, 4]
Do array elements have names by default in JavaScript?
Sort of, but not in the way you're thinking. The name of the element is its index, 0, 1, etc. In fact, JavaScript arrays aren't really arrays at all* and those indexes are converted to string property names (in theory; in practice, JavaScript engines optimize it).
* (disclosure: that's a post on my anemic little blog)
You're telling the interpreter how the parameter is called, here:
function(c) {
^
Array.prototype.map() requires a callback that accepts up to 3 parameters. The first parameter is always the "current item", which you happen to have named c.
For a more in-depth explanation, have a look at T.J. Crowders answer, as well.
In javascript, functions are first class object, which means they can be assigned to variables, passed as function parameters and returned from values. The Array.prototype.map function takes a function with it's first parameter denoting an item of the array. When invoked, the map function executes the given function for each of the items and creates a new array from the outputs of the given function.
In your case, you are defining the input function on the fly, inside the map function.
You can actually define the function outside and pass the function by reference inside map like below.
function getLength(item) {
return item.length;
}
var animals = ["cat","dog","fish"];
var lengths = animals.map(getLength);
console.log(lengths);//[3, 3, 4]
Here, you can see it outputs the same result.
The code does not know what is the parameter named. You map an array. map function creates a new array in the lengths variable (variable being assigned to). How? It provides to the function parameter inside it, each element in the current array one-by-one by value.
Here the value is actual string name ("cat" or "dog" or "fish").
In javascript, parameters can be optional. This map function can take three parameters, currentValue, index, array. In your case, c provides currentvalue.
If you would add one more parameter c,idx. Map function will get currentvalue and index inside it.
var animals = ["cat","dog","fish"];
var lengths = animals.map(function(c, idx, arr, test) {
console.log(c); // currentvalue being processed in the array.
console.log(idx); // index of currentvalue in the array
console.log(arr); // original array being operated on.
console.log(test); // undefined always. not available in map.
return c.length;
});
console.log(lengths);//[3, 3, 4]
I know this is a silly question so please go easy.
I'm having trouble understanding documentation in general such as this page:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Can someone explain the terms 'callback' 'currentValue' 'index' 'array' (in that context) and 'thisArg' in very basic layman's? I'm sure it's something simple but it's just not clicking in my brain, and it's making learning the language on my own very difficult so I would greatly appreciate the help.
I'll try to make it very simple and understandable.
A callback function is passed as an argument to a function (like age is passed to foo in the example below) and is executed after a certain event. It is called callback because it is executed not before the parent function (to which the callback function was passed to as an argument) itself is executed.
For example:
function foo(age, function callBack()) {
print("I am " + age + " years old");
}
function gender() {
print(" and male");
}
function certainEvent() {
foo(99, gender);
}
If you now call certainEvent(), the result would be:
I am 99 years old and male
callback is a function that you pass to map. It will be called with the arguments currentValue, index and array.
For instance using a callback that logs:
[1, 2, 3].map(function(currentValue, index, array) {
console.log("currentValue", currentValue);
console.log("index", index);
console.log("array", array);
});
Logs:
currentValue 1
index 0
array [1, 2, 3]
currentValue 2
index 1
array [1, 2, 3]
currentValue 3
index 2
array [1, 2, 3]
Note that the function doesn't have to inline, this is equal:
function mapper(currentValue, index, array) {
console.log("currentValue", currentValue);
console.log("index", index);
console.log("array", array);
}
[1, 2, 3].map(mapper);
.map is a function. Function usually accept arguments. The part you are referring to describes the arguments and their purpose in more detail. Lets have a look at the signature of the function:
arr.map(callback[, thisArg])
This tells you that the function takes two arguments, where the second argument is optional (indicated by the [...]).
The documentation chose to name the first argument "callback" and the second argument "thisArg". It could have chosen different names ore no names at all and simply referred to the "first" and "second" argument.
Of course the chosen names carry some meaning, but this is only secondary. Naming them at all is done to be able to easily refer to those arguments later in the documentation. Whenever you see callback (i.e. formatted as code) in the documentation for .map, such as
Value to use as this when executing callback.
you know that it refers to the first argument.
Similarly "currentValue", "index" and "array" are labels for the arguments that are passed to the first argument of .map ("callback"), since that argument is supposed to be a function as well.
currentValue by itself doesn't really mean anything. However, in the context of .map it refers to the first argument that is passes to the first argument of .map. It's meaning is described in the documentation:
The current element being processed in the array.
This follows the typical way we write functions. When you declare a function, you usually give the parameters names. For example:
function first(arr) {
return arr[0];
}
Here I am giving the first parameter the name arr so I can refer to it more easily later. The same happens in the documentation: The parameter/argument gets a name so it can easily be referred to later.
The map method has two arguments. The callback and the scoping parameter.
The callback is the function that you set that does the processing. It has three arguments that give you the state of the current iteration as map loops over the array.
var myArray = [1,2,3,4,5];
function callback ( currentValue, index, array) {
console.group("callback called");
console.log("currentValue:", currentValue); //Current value of the index aka `array[index]`
console.log("index:", index); //current index of the loop (think of it as a for loop (var i=0; i<array.lenght; i++) It would be i.
console.log("array:", array); //The array you are looping over..aka a reference to myArray
console.groupEnd("callback called");
return currentValue * 2;
}
var result = myArray.map(callback);
console.log(result);
And the [] in the method declaration [, thisArg] states that that parameter/argument is optional.