Advice on JS script argument parameters - javascript

I am working through 'Javascript: the definitive guide' by Flannagan. There is a section which explains how Javascript classes can be augmented by adding new methods. The example script shows how a new method 'Times' can be added to the number prototype ( this is how I interpret it).
I am struggling to know what the argument parameters are in the following script, particularly 'context'.
// Invoke the function f this many times, passing the iteration number
// For example, to print "hello" 3 times:
// var n = 3;
// n.times(function(n) { console.log("hello"); });
Number.prototype.times = function(f, context) {
var n = this.valueOf();
console.log(n);
for(var i = 0; i < n; i++) f.call(context, i);
};
var n = 3;
n.times(function(n) { console.log("hello"); });
I think the value of f becomes:
function(n) { console.log("hello"); })
I'm not sure what 'context' is?
Any help gratefully received...

times accepts a function; the context argument allows you to specify the value this refers to inside that function, if needed.
In your example, the callback passed to times doesn't use this at all, so it's not needed, but imagine if the callback depended on this referring to an object:
const obj = {
count: 3,
increment: function() {
this.count++;
console.log(this.count);
}
};
obj.increment();
obj.increment();
The obj.increment function depends on obj.count. For this to work in your times function, you'd need the this to refer to obj, so pass it as the second argument to times, so that obj is passed as the first parameter to .call:
Number.prototype.times = function(f, context) {
var n = this.valueOf();
console.log('repeating ' + n + ' times:');
for(var i = 0; i < n; i++) f.call(context, i);
};
const obj = {
count: 3,
increment: function() {
this.count++;
console.log(this.count);
}
};
(3).times(obj.increment, obj);
A custom this isn't necessary if you pass a different function to .times, one which invokes obj.increment itself:
Number.prototype.times = function(f, context) {
var n = this.valueOf();
console.log('repeating ' + n + ' times:');
for(var i = 0; i < n; i++) f.call(context, i);
};
const obj = {
count: 3,
increment: function() {
this.count++;
console.log(this.count);
}
};
(3).times(() => obj.increment());

I think the value of f becomes:
function(n) { console.log("hello"); })
Right!
I'm not sure what 'context' is?
"Context" is a word people sometimes use (incorrectly, IMHO) to refer to the this value for a function call.
In that call to times:
n.times(function(n) { console.log("hello"); });
...there's no argument for the context parameter being passed to times, so times will get undefined for the value of its context parameter. Then it uses that value in f.call(...). When you use undefined (or null) with Function.prototype.call, in loose mode the function is called with this set to the global object; in strict mode, the function sees this ase undefined or null instead.
So in that example, the callback will be called with either the global object as this, or undefined as this.
It's analogous to the thisArg parameter of Array.prototype.forEach and related.

context is the second value passed to times, and becomes the this special variable value inside the function, due to the f.call(context, i) call (the first value is the this value).
Number.prototype.times = function(f, context) {
var n = this.valueOf();
console.log(n);
for(var i = 0; i < n; i++) f.call(context, i);
};
var n = 3;
n.times(function(n) { console.log(this, n); }, "hello");
The reason that this appears to behave strangely is because this must be an object, so the string is converted into a String object (which is similar to an Array object, hence the appearance of an array).
This is more useful in situations where you're using a prototype function:
Number.prototype.times = function(f, context) {
var n = this.valueOf();
console.log(n);
for(var i = 0; i < n; i++) f.call(context, i);
};
var arr = [1, 2, 3, 4];
(3).times(Array.prototype.reverse, arr);
console.log(arr);

Related

When I use for loop in JavaScript, Is exist all value in memory?

As I was studying closures and local variables,
I was curious to see the example below.
function myLoop() {
var result = [], i, n = 4;
for (i = 0; i < n; i++) {
result[i] = function () {
return i;
}
}
return result;
}
var l = myLoop();
var f = l[0];
console.log(f()); // undefined
I recently knew how variable allocate.
Point to a value, not a allocate.
if I declare variable,
I allocated 'string'.
and I again allocated another string
var str = 'hello';
str = 'world';
and
str -> hello,
str -> world
Because 'String' is immutable.
Result
In conclusion, I wonder what is the status in memory?
i of 'for loop' exist all value in memory?
i -> 0,
i -> 1,
i -> 2,
i -> 3
or
i = 1(..3,4) allocate?
In this case, you only have a single binding for i, one which is declared at the beginning of myLoop. Every array item in result has a function which points to that singular i. So, when myLoop returns, you've created an array of functions, all of which reference a single value, the i whose value is 4:
function myLoop() {
var result = [], i, n = 4;
for (i = 0; i < n; i++) {
result[i] = function () {
return i;
}
}
return result;
}
const arr = myLoop();
console.log(
arr[0](),
arr[1]()
);
This value will eventually be garbage collected (the variable name's link to the value it contains) once nothing can reference the arr or any of its functions anymore.
If you had used let in the for loop instead of var, you would have a separate binding for i on each iteration:
function myLoop() {
var result = [], n = 4;
for (let i = 0; i < n; i++) {
result[i] = function () {
return i;
}
}
return result;
}
const arr = myLoop();
console.log(
arr[0](),
arr[1]()
);
in which case, every iteration of the for loop would have its own i value, so there'd be 4 separate values for i in memory at the time myLoop returns. Just like above, those 4 values will continue to exist until nothing can reference them anymore, and they get garbage collected.
(Technically, there'd also be another temporary binding or two for i for the duration of the loop, created by the for loop due to the mechanics of let loop declarations, but it's near invisible to scripts and is more confusing than useful to think about in this situation)

Why calling a function inside another function doesn't work?

var mult = (function(){
var cache = {};
var calculate = function(){
var a = 1;
for(var i = 0, l = arguments.length; i < l; i++){
a = a * arguments[i];
}
return a;
}
return function(){
return calculate(arguments);
}
})();
console.log(mult(1, 2));
Above is my code, I expect the mult function will give me value 2, but instead it outputs NaN. I changed the line calculate(arguments) to caculate.apply(null, arguments) and it worked. I don't know why the old code doesn't work? Why do I need to use apply in this case? What does null represent here?
Your calculate function wants separate arguments, but you passed in an array1. Using .apply spreads the content of the array for you.
1 Technically an array-like arguments object that does not inherit from Array.

How to pop unknown argument, then call another function with the rest of unknown arguments

I'm trying to make a function "a" that will take minimum 1 argument in JavaScript.
This function will extract the 1st argument out, then call another function "b" with the rest of the arguments.
Something like this:
var a = function() {
var name = arguments[0]; // get argument1
console.log("Running ", name);
b(<<argument2, argument3... to rest>>>);
}
var b = function() {
for (var i=0; i<arguments.length; i++){
console.log(arguments[i]);
}
}
However I'm really unsure how to write the line:
b(<<argument2, argument3... to rest>>>);
The "arguments" can be converted into array, and hence can pop out the first argument. However I'm really unsure how to call the function b() with the rest of arguments dynamically.
Is there such function call like b(arguments=myArr); in JS ?
Thank you very much!
Try this.
var a = function() {
var name = arguments[0]; // get argument1
console.log("Running ", name);
var argsForB = Array.prototype.slice.call(arguments, 1); // converts arguments to an array and removes the first param
b.apply(null, argsForB); // calls b sending all the other params. Note that `this` inside b will be `null`. You can replace it with any other value.
}
var b = function() {
for (var i=0; i<arguments.length; i++) {
console.log(arguments[i]);
}
}
Perhaps pass an array to function "a", retrieve the first value as you had planned, use shift() on the array, and then pass the remaining array to function "b".
var a = function(arguments) {
var name = arguments[0]; // get argument1
console.log("Running ", name);
arguments.shift();
b(arguments);
}
var b = function(arguments) {
for (var i=0; i<arguments.length; i++){
console.log(arguments[i]);
}
}
a([1,2,3,4]);
Results in the console logging:
Running 1
2
3
4
with ES6, you can use ... to extract array
var a = function() {
var name = arguments[0]; // get argument1
console.log("Running ", name);
var args = Array.prototype.slice.call(arguments);
args.shift()
b(...args);
}
var b = function() {
for (var i=0; i<arguments.length; i++){
console.log(arguments[i]);
}
}

Use 3 arrays as function parameter in js

i want use some arrays as function parameters.
i write this code but doesn't work.
please help me
and i have no idea about number of array
function func2(x,y,z)
{
alert(x[1]);
alert(y[1]);
alert(z[1]);
}
function func1()
{
a = [1,2,3]
b = [3,4,5]
c = [5,6,7]
func2(a,b,c);
}
Your func2 function is calling itself. Also, using var is a good idea here to avoid accidentally creating global variables:
function func2()
{
var a = [1,2,3],
b = [3,4,5],
c = [5,6,7];
func1(a,b,c);
}
Update given your updated question, if you want to create a function that accepts a variable number of parameters, you'll need to access the arguments object:
function func2()
{
alert("There are " + arguments.length + " arguments");
}
The arguments object is can be accessed just like an array (although it is not actually an array). So to print the second element from each array (remember array indexes start at 0), you'd use something like this:
function func2()
{
for (var i = 0; i < arguments.length; i++)
alert(arguments[i][1]);
}
An alternate strategy would be to just accept an array of arrays (also called a multidimensional array, though technically multidimensional arrays aren't supported in JavaScript) like this:
function func2(a)
{
for (var i = 0; i < a.length; i++)
alert(a[i][1]);
}
function func1()
{
var a = [1,2,3],
b = [3,4,5],
c = [5,6,7];
func2([a,b,c]); // notice the […] around the parameters
}
function func1(x,y,z)
{
alert(x[1]);
alert(y[1]);
alert(z[1]);
}
function func2()
{
var a = [1,2,3];
var b = [3,4,5];
var c = [5,6,7];
func1(a,b,c);
}
If I read you minds well, you have to use arguments in order to iterate over a dynamic number of arguments. Assuming you pass only arrays to func2:
function func2() {
for (var i = 0; i < arguments.length; i++) {
alert(arguments[i][1]);
}
}

Closure confusion

I am having some confusion regarding this Closure thing. I have two separate codes below that look similar but their output are different.
function setup(x) {
var array = [];
for(var i=0;i<arguments.length;i++){
array[i]= arguments[i];
}
return array;
}
console.log(setup('a','b')); // will output ["a","b"]
--------------
function f() {
var i, array = [];
for(i = 0; i < 3; i++) {
array[i] = function(){
return i;
}
}
return array;
}
var a = f();
console.log(a()); //output: [function(),function(),function()]
console.log(a[0]()); //output: 3 //same output in a[1]() and a[2]() calls as well
Now my question is, how come the output is different? both of the codes above return an array. in the first code, it prints all elements in array correctly whereas in the second code, why doesn't it print [1,2,3]???
In your second example, you are creating 3 function in a loop, but all the functions are created in the same variable scope, so they all reference and return the value of the same i variable.
Therefore the value of i returned from the functions represents the value at the time when the functions are invoked. Because you're invoking them after the loop, the value of i is 3, so that's the value returned.
This is what is meant by closure. The functions "close over" the variables that existed in the variable scope where they were created. They do not close over the value of the variables, but rather then variables themselves, so they always get the current state of the variable.
For each function to reference a different value of i, each function would need to need to be created in a separate variable scope that has its own unique i.
Because the only way to create a new variable scope in JavaScript is to invoke a function, you'll need to create each function within a new function invocation.
function makeFunction(j) {
return function(){
return j;
};
}
function f() {
var i, array = [];
for(i = 0; i < 3; i++) {
array[i] = makeFunction(i);
}
return array;
}
So here I made a new function called makeFunction. It receives a single parameter, and returns a new function that references and returns that parameter.
Because each invocation of makeFunction creates a new and unique variable scope, each function returned will be referencing its own unique j variable, and will therefore return the value of j that existed when makeFunction was invoked (unless your function modifies j, which it could do if you wanted).
Note that I used the variable name j for clarity. You could use i as well, or some other name.
In JavaScript, you have function statements and function expressions. The first declare named functions, the latter evaluate to a named or anonymous function. You're using a function expression.
What I think you want to do is a call expression. All you have to do is to immediately call your function that returns i.
function f() {
var i, array = [];
for (i = 0; i < 3; i++) {
// The extra parentheses around the function are unnecessary here.
// But this is more idiomatic, as it shares syntax with how function
// expressions are introduced in statements.
// I.e. you could just copy-paste it anywhere.
array[i] = (function () {
return i;
})(); // don't rely on automatic semicolon insertion
}
return array;
}
Also note that, in your problematic example, all closures return 3 because all of them capture the same variable i.
EDIT: To make the previous paragraph more clear, if you really wanted to have 3 distinct closures, you'd have to create a new scope for each one. Unlike other languages, Javascript doesn't create scope just by opening a block. It only creates scope in functions. Here are two ways of doing this:
function f() {
var i, array = [];
for (i = 0; i < 3; i++) {
// With a parameter in an inner function
array[i] = (function (n) {
return function () {
// A new n is captured every time
return n;
};
})(i);
}
return array;
}
function f() {
var i, array = [];
for (i = 0; i < 3; i++) {
array[i] = (function () {
// With a variable in an inner function
var n = i;
return function () {
// A new n is captured every time
return n;
};
})();
}
return array;
}
The next example, however, is wrong, because even though n is declared in the for block, it will be as if it has been declared at the top of the function and only initialized in the for block. Remember, this is JavaScript, not Java, not C, not C#, not <whatever bracketed language>:
function f() {
var i, array = [];
for (i = 0; i < 3; i++) {
var n = i;
array[i] = function () {
return n;
};
}
return array;
}
Equivalent code:
function f() {
var i, array = [];
var n;
for (i = 0; i < 3; i++) {
n = i;
array[i] = function () {
return n;
};
}
return array;
}

Categories