Changing JavaScript function's parameter value using arguments array not working - javascript

I am learning JavaScript and am pretty confused about the arguments property array.
I have a function that takes a single argument and returns it. When I pass the parameter and reassign it using arguments[0] = value, it's updating the value.
function a(b) {
arguments[0] = 2;
return b;
}
console.log(a(1)); //returns 2
But when I call the same function with no parameters it returns undefined.
function a(b) {
arguments[0] = 2;
return b;
}
console.log(a()); //returns undefined
But even if I pass undefined, the value will update as well.
function a(b) {
arguments[0] = 2;
return b;
}
console.log(a(undefined)); //returns 2
I thought that if you do not pass a parameter to a JavaScript function, it automatically creates it and assigns the value to undefined and after updating it should reflect the updated value, right?
Also a() and a(undefined) are the same thing, right?

Assigning to arguments indicies will only change the associated argument value (let's call it the n-th argument) if the function was called with at least n arguments. The arguments object's numeric-indexed properties are essentially setters (and getters):
http://es5.github.io/#x10.6
Italics in the below are my comments on how the process relates to the question:
(Let) args (be) the actual arguments passed to the [[Call]] internal method
Let len be the number of elements in args.
Let indx = len - 1.
Repeat while indx >= 0, (so, the below loop will not run when no arguments are passed to the function:)
(assign to the arguments object being created, here called map:)
Add name as an element of the list mappedNames.
Let g be the result of calling the MakeArgGetter abstract operation with arguments name and env.
Let p be the result of calling the MakeArgSetter abstract operation with arguments name and env.
Call the [[DefineOwnProperty]] internal method of map passing ToString(indx), the Property Descriptor {[[Set]]: p, [[Get]]: g, [[Configurable]]: true}, and false as arguments.
So, if the function is invoked with no arguments, there will not be a setter on arguments[0], so reassigning it won't change the parameter at index 0.
The same sort of thing occurs for other indicies as well - if you invoke a function with 1 parameter, but the function accepts two parameters, assigning to arguments[1] will not change the second parameter, because arguments[1] does not have a setter:
function fn(a, b) {
arguments[1] = 'bar';
console.log(b);
}
fn('foo');
So
a() and a(undefined) are the same thing right?
is not the case, because the second results in an arguments object with a setter and a getter on index 0, while the first doesn't.
Note that this odd interaction between the arguments and the function parameters is only present in sloppy mode. In strict mode, changes to arguments won't have any effect on the value an individual argument identifier contains:
'use strict';
function a(b) {
arguments[0] = 2;
return b;
}
console.log(a(1)); //returns 1

ECMA 262 9.0 2018 describes this behaviour in 9.4.4 Arguments Exotic Objects with
NOTE 1:
The integer-indexed data properties of an arguments exotic object whose numeric name values are less than the number of formal parameters of the corresponding function object initially share their values with the corresponding argument bindings in the function's execution context. This means that changing the property changes the corresponding value of the argument binding and vice-versa. This correspondence is broken if such a property is deleted and then redefined or if the property is changed into an accessor property. If the arguments object is an ordinary object, the values of its properties are simply a copy of the arguments passed to the function and there is no dynamic linkage between the property values and the formal parameter values.
In short,
if in 'sloppy mode', then all arguments are mapped to their named variables, if the length correspond to the given parameter, or
if in 'strict mode', then the binding is lost after handing over the arguments.
This is only readable in an older version of ECMA 262 7.0 2016. It describes this behaviour in 9.4.4 Arguments Exotic Objects with
Note 1:
For non-strict functions the integer indexed data properties of an arguments object whose numeric name values are less than the number of formal parameters of the corresponding function object initially share their values with the corresponding argument bindings in the function's execution context. This means that changing the property changes the corresponding value of the argument binding and vice-versa. This correspondence is broken if such a property is deleted and then redefined or if the property is changed into an accessor property. For strict mode functions, the values of the arguments object's properties are simply a copy of the arguments passed to the function and there is no dynamic linkage between the property values and the formal parameter values.

it's because arguments it's not like a Array, it's a object with integer indexed data keys, and property length, And if length equal zero it's mean you don't have a arguments
function a(b) {
arguments[0] = 2;
console.log(arguments.length)
return b;
}
a(1); // length 1 returns 2
console.log(a()); // length 0 returns undefined

When you are not providing any parameter then arguments array has length equal to 0. Then you are trying to set the non existing element of array to 2 which causes returning undefined
You can simply test this with this snippet:
function a(b){
alert(arguments.length) // It will prompt 0 when calling a() and 1 when calling a(undefined)
arguments[0] = 2;
return b;
}

This is the undefined value definition from javascript spec :
primitive value used when a variable has not been assigned a value.
so if you do not specify the function return type it will return undefined.
so a() and a(undefined) it is not same thing. returning undefined is based on return type is defined or not.
for more clarification similar_problem

My understanding is that the arguments object only tracks what is passed into the function. Since you've not initially passed anything, b is not bound and at that point arguments is not 'tracking' b. Next, you assign a value to the initialised but empty Array-like object arguments and finally return b, which is undefined.
To delve into this further:
If a non-strict function does not contain rest, default, or destructured parameters, then the values in the arguments object do change in sync with the values of the argument variables. See the code below:
function func(a) {
arguments[0] = 99; // updating arguments[0] also updates a
console.log(a);
}
func(10); // 99
and
function func(a) {
a = 99; // updating a also updates arguments[0]
console.log(arguments[0]);
}
func(10); // 99
When a non-strict function does contain rest, default, or destructured parameters, then the values in the arguments object do not track the values of the arguments. Instead, they reflect the arguments provided when the function was called:
function func(a = 55) {
arguments[0] = 99; // updating arguments[0] does not also update a
console.log(a);
}
func(10); // 10
and
function func(a = 55) {
a = 99; // updating a does not also update arguments[0]
console.log(arguments[0]);
}
func(10); // 10
and
// An untracked default parameter
function func(a = 55) {
console.log(arguments[0]);
}
func(); // undefined
Source: MDN Web docs

Related

Arguments Object is Empty Despite Setting Default Argument Value – is there a Workaround?

Given this function (e.g. in node.js)
> function f (a = 1) { return arguments; }
undefined
> f(1)
[Arguments] { '0': 1 } // arguments object contains value for argument a
> f() // invoke function f without argument
[Arguments] {} // arguments object is empty despite default value
I need to be able to obtain default values if no argument is given.
Can anybody help me with this?
Straight from the docs:
arguments is an Array-like object accessible inside functions that contains the values of the arguments passed to that function.
This means that arguments will only hold the values that are actually passed to the function when a call is made.

How can I describe the difference between these two value passing styles?

This is what I understand to be pass by reference in C (note: I am not a C programmer):
void foo(int* x) { // The value of the memory address passed into `foo` is copied into parameter `x`
*x = 1; // The memory address x is dereferenced(?) and the value at that location overwritten
}
int main() {
int a = 0;
foo(&a); // The value of the (starting?) memory address associated with `a` is found and supplied as an argument to the function call
// `a` is now `1`
}
In this way, regardless of the type of x (even if it is an object (a struct in C?)), changes made to x inside foo will be reflected in the scope of main, even if the entire object is replaced.
In JavaScript, primitives are simply copied around and this is simple enough to reason about.
But for objects, IIUC, the value of their memory address (or equivalent) is supplied to function foo, which superficially sounds similar to the C code above. However, if we assign a value to the identifier created by parameter x, this is not reflected in the scope of main.
function foo(x) {
x = { bam: 'this is bam' }; // The value of the memory address passed into `foo` is copied into parameter `x`
}
function main() {
let a = { bar: 'this is bar' };
foo(a); // The value of the memory address associated with `a` is found and supplied as an argument to the function call (?)
// `a` is unchaged
}
JavaScript is pass by value. For objects it is sometimes called pass-by-value-of-the-reference - fair enough - but in the C code above, the value of the reference (OK pointer) is also copied into the function. So how can I describe the differences between pass-by-reference and pass by value? Is the passing of values actually the same, but only the referencing/dereferencing behavior of the two languages different?
C passes everything by value1. Sometimes those values are pointer values; this is not the same thing as pass by reference.
In a true pass by reference system the formal parameter in the function definition designates the same object as the actual parameter in the function call (assuming the actual parameter isn’t a numeric literal, anyway). Whether that’s accomplished by using pointers or under the hood or through a different mechanism is a function of the implementation. This is never the case in C.
The expression *x in foo designates the same object as the expression a in main, but the formal parameter x is a separate object from a. The result of the expression &a is passed by value and stored in the unique object x.
x == &a // int * == int *
*x == a // int == int
Arrays are weird. Except when it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array. When you pass an array expression as an argument to the function, the function receives a pointer to the first element, not a copy of the entire array. So unlike other argument types, changes to the contents of an array argument in a function are reflected in the caller. This is still not true pass by reference, however. This is just fallout from C’s somewhat unique array semantics.

Function.call taking array-like object , doesnt require the first parameter?

So, I couldn t find out how Function.call doesnt have to take the first argument as an object as I couldn t find such an information anywhere. Although I have seen hundreds of usage of Function.call like the next line of code, taking only this array-like object, NOT REQUIRING THE FIRST PARAMETER AS AN OBJECT, SINCE IT TAKES ARGUMENTS OF FUNCTION WHICH IS AN ARRAY-LIKE OBJECT. It works of course.
argsSliced = Array.prototype.slice.call((function(){return arguments;})(1,2,3,4))
Although next lines of codes behaves as we expect, requiring the first parameter to be an object to set this of function to that object and requires parameters to pass to function after that object.
var a = function(){return arguments[0] + arguments[1] ; }
console.log(a.call(1,2)); // returns NaN
console.log(a.call(null,1,2)); // behaves as we expect, returns 3
So my question, what is the situation with the array-like object ? How does it work with the Function.call as it doesn t give it an object as a first parameter but only gives an array-like object.
This function, when called, returns an array-like Arguments object with the arguments
var f = function(){return arguments;};
Then, when you call it with arguments 1,2,3 and 4,
f(1,2,3,4) // Arguments [1,2,3,4]
Then, you call Function.prototype.call on Array.prototype.slice, passing that Arguments object as the argument.
So Function.prototype.call will call Array.prototype.slice with the this value set to the Arguments object (instead of Array.prototype), and no arguments.
When Array.prototype.slice is called on an array-like object, it builds a real array from that object. So you get
Array.prototype.slice.call(f(1,2,3,4)); // Array [1,2,3,4]
Note there are better ways to achieve this:
Array.of(1,2,3,4)
Array(1,2,3,4)
[1,2,3,4]
MDN for call() explains it clearly: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
The value of this provided for the call to function. Note that this may not
be the actual value seen by the method: if the method is a function in
non-strict mode code, null and undefined will be replaced with the
global object and primitive values will be converted to objects.
var a = function(){return arguments[0] + arguments[1] ; }
console.log(a.call(1,2));
arguments[1] will be undefined ,and 2 will be arguments[0] because 1 will be boxed to an object.
remember f.call(ob,4); is like saying ob.f(4)

Is it not possible to set arguments that have not been provided, using arguments property

I was testing things. And discovered that i could not set arguments if i have not provided it.
Using arguments[1] etc.
Example:
function abc (a,b) {
arguments[1] = 'new value';
console.log(b);
}
abc('a');
It won't work.
I know i could set value like if (b=='undefined') b='default'; but why i can't like this. In other words this behavior is unexpected, isn't it?
On the other hand, if you do provide argument it will get changed!
calling function like this will output new value
abc('a','b');
is there a solution, if you wanted to set value using arguments[2] and pass argument when calling function.
after more testing it seems: b doesnt get connected with arguments[1] if not called.
My understanding is argument is dynamic whose length is set by number of arguments / parameters provided to the function.
In first case, you have provided only one parameter so size of argument is 1 and hence argument[1] should be out of bound, while in second case, you have argument of length 2 so you can change either parameter value by using its index.
After posting a terribly wrong answer, I took a look at the ECMA Spec and here is in code, what JavaScript does when the arguments object will be created:
function abc () {
var names = ["a", "b"]; //as normally provided in the function declaration: function (a, b)
var a, b;
var args = {};
var mappedNames = [];
args.length = arguments.length;
var index = args.length - 1;
//this is the crucial part. This loop adds properties to the arguments object
//and creates getter and setter functions to bind the arguments properties to the local variables
//BUT: the loop only runs for the arguments that are actually passed.
while (index >= 0) {
var val = arguments[index];
if (index < names.length) {
var name = names[index];
if (mappedNames.indexOf(name) === -1) {
mappedNames.push(name);
var g = MakeArgGetter(name);
var p = MakeArgSetter(name);
Object.defineProperty(args, index.toString(), {get : g, set : p, configurable : true});
}
}
args[index.toString()] = val;
index--;
}
function MakeArgGetter(name) {
return function zz(){
return eval(name);
};
}
function MakeArgSetter(name) {
return function tt(value) {
eval(name + " = value;");
};
}
console.log(args[0]); //ab
args[0] = "hello";
args[1] = "hello";
console.log(a); //hello
console.log(b); //undefined
}
abc('ab');
Note, that there is some more going on in the Spec and I simplyfied it here and there, but this is essentially what is happening.
What are you trying to achieve by this?
If you want to have "dynamic arguments" like abc('a) and abc('a', 'b') are both valid, but the first will set the missing argument to a default value, you either have to check for the arguments length and/or for all arguments values.
Suppose the following: abc('a', null) is given, what then? - depends on logic, can be good but can also be bad.
Given this, checking the argument vector isn't smarter but more cryptic and having something like if (b=='undefined') b='default' much straighter and better to understand.
TL;DR: Not possible per ECMA spec since arguments is binded to only formal parameters that match the provided arguments. Its recommended to only reference arguments and not alter it.
Examples: http://jsfiddle.net/MattLo/73WfA/2/
About Arguments
The non-strict arguments object is a bidirectional and mapped object to FormalParameterList, which is a list generated based on the number of provided arguments that map to defined parameters in the target function defintion/expression.
When control enters an execution context for function code, an arguments object is created unless (as specified in 10.5) the identifier arguments occurs as an Identifier in the function’s FormalParameterList or occurs as the Identifier of a VariableDeclaration or FunctionDeclaration contained in the function code. ECMA 5.1 spec (as of 03/26/2014)
arguments to variables / variables to arguments
Argument-to-variable mappings exist only internally when the method is invoked. Mappings are immutable and are created when a target function is invoked, per instance. The update bindings between the two is also disabled when the use strict flag is available within the scope of the function. arguments becomes strictly a reference and no longer a way to update parameters.
For non-strict mode functions the array index (defined in 15.4) named data properties of an arguments object whose numeric name values are less than the number of formal parameters of the corresponding function object initially share their values with the corresponding argument bindings in the function’s execution context. This means that changing the property changes the corresponding value of the argument binding and vice-versa. This correspondence is broken if such a property is deleted and then redefined or if the property is changed into an accessor property. For strict mode functions, the values of the arguments object‘s properties are simply a copy of the arguments passed to the function and there is no dynamic linkage between the property values and the formal parameter values. ECMA 5.1 spec (as of 03/26/2014)
What about length?
arguments.length can be an indicator initially to determine how many formal parameters were met but it's unsafe since arguments can be changed without length ever updating automatically. length doesn't update because it's not a real array and therefore doesn't adhere to the Array spec.

Is it safe to pass 'arguments' to 'apply()'

Suppose I have the following code (completely useless, I know)
function add( a, b, c, d ) {
alert(a+b+c+d);
}
function proxy() {
add.apply(window, arguments);
}
proxy(1,2,3,4);
Basically, we know that apply expects an array as the second parameter, but we also know that arguments is not a proper array. The code works as expected, so is it safe to say that I can pass any array-like object as the second parameter in apply()?
The following will also work (in Chrome at least):
function proxy() {
add.apply(window, {
0: arguments[0],
1: arguments[1],
2: arguments[2],
3: arguments[3],
length: 4
});
}
Update: It seems that my second code block fails in IE<9 while the first one (passing arguments) works.
The error is Array or arguments object expected, so we shall conclude that it's always safe to pass arguments, while it's not safe to pass an array-like object in oldIE.
From definition of Function.prototype.apply in MDN:
fun.apply(thisArg[, argsArray])
You can also use arguments for the argsArray parameter. arguments is a
local variable of a function. It can be used for all unspecified
arguments of the called object. Thus, you do not have to know the
arguments of the called object when you use the apply method. You can
use arguments to pass all the arguments to the called object. The
called object is then responsible for handling the arguments.
REF: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
As the second argument apply accepts "an array like object, specifying the arguments with which function should be called". To my understanding, this array-like object should have the length property for internal iteration, and numerically defined properties (zero-indexed) to access the values.
And the same is confirmed my the spec: http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.3, as was kindly pointed out by #Pointy.
Assuming ECMAScript 5.1: Yes. As per ECMA-262, 10.6, the arguments object has the length and index properties that 15.3.4.3 (Function.prototype.apply) requires.
MDN can only speak for Mozilla implementations. The actual spec to which all implementations should comply says the following:
15.3.4.3 Function.prototype.apply (thisArg, argArray)
When the apply method is called on an object func with arguments thisArg and
argArray, the following steps are taken:
1. If IsCallable(func) is false, then throw a TypeError exception.
2. If argArray is null or undefined, then
Return the result of calling the [[Call]] internal method of func,
providing thisArg as the this value and an empty list of arguments.
3. If Type(argArray) is not Object, then throw a TypeError exception.
4. Let len be the result of calling the [[Get]] internal method of argArray
with argument "length".
5. If len is null or undefined, then throw a TypeError exception.
6. Let n be ToUint32(len).
7. If n is not equal to ToNumber(len), then throw a TypeError exception.
8. Let argList be an empty List.
9. Let index be 0.
10. Repeat while index < n
a. Let indexName be ToString(index).
b. Let nextArg be the result of calling the [[Get]] internal method of
argArray with indexName as the argument.
c. Append nextArg as the last element of argList.
d. Set index to index + 1.
11. Return the result of calling the [[Call]] internal method of func,
providing thisArg as the this value and argList as the list of arguments.
The length property of the apply method is 2.
NOTE The thisArg value is passed without modification as the this value. This
is a change from Edition 3, where an undefined or null thisArg is replaced
with the global object and ToObject is applied to all other values and that
result is passed as the this value.
It seems that property length and numeric index values are the only prerequisites.

Categories

Resources