The following is an attempt to set the default value for an argument, based on the first argument:
function tree(values, ...[[curr] = values]) {
console.log(curr);
}
tree(['foo']); // foo
tree(['foo'], ['bar']); // bar
It appears to work (actually it isn't quite what I want, but I want to understand this first).
What is going on here?
The spread syntax is spreading an anonymous array with the contents of curr…
No. Nothing is being spreaded into an array literal.
The spread syntax is capturing the rest of the arguments into an anonymous array instance which is being destructured…
Yes.
…into the first value of either: the supplied array if present, or values.
Not sure I understand. The captured array is destructured to the target [[curr] = values], which takes out the first element or - if not present - the values default, and assigns that to the target [curr].
And as #FelixKling commented, you really should not do that, but use
function tree(values, [curr] = values) {
console.log(curr);
}
This happens because default parameters can reference previous parameters. In your example you are destructuring values to curr, that's why the console is printing the first element of the array. You could suppress the spread operator and the result would be the same:
function tree(values, [curr] = values) {
console.log(curr);
}
tree(['foo']); // foo
tree(['foo'], ['bar']); // bar
What is happening to the second call tree(['foo'], ['bar']) is that the second argument (['bar']) is overriding [curr] default value.
Please let me know if I wasn't clear enough.
Reference:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters (look for the section titled "Default parameters are available to later default parameters")
Related
I thought that destructuring was performed after all the parameters were retrieved, however I noticed that this code works.
function extract(propertyName, { [propertyName]: value }) {
console.log(propertyName, value);
}
extract('property', { property: 'value' });
While this one doesn't.
function extract({ [propertyName]: value }, propertyName) {
console.log(propertyName, value);
}
extract({ property: 'value' }, 'property');
Which invalidates my initial thought, but I can't find any documentation or specification that explains the precise behavior.
Parameter values are determined in order, very much as though you wrote each parameter as its own let declaration taking the value from the actual arguments received. (That isn't how it actually works, that's just an analogy.)
So your first example conceptually behaves like this:
// ONLY CONCEPTUAL
function extract() {
let propertyName = /*...the first actual argument...*/;
let { [propertyName]: value } = /* ...the second actual argument... */;
console.log(propertyName, value);
}
Whereas your second one behaves like this:
// ONLY CONCEPTUAL
function extract() {
let { [propertyName]: value } = /* ...the first actual argument... */;
let propertyName = /*...the second actual argument...*/;
console.log(propertyName, value);
}
...and so it tries to use the value of propertyName before the "variable" is initialized, getting an error. (Because parameter expressions were added in ES2015, so let rather than var semantics were used for them.)
Another way to look at it is that if you think of the entire parameter list as the contents of iterator destructuring, it works exactly the same way. Thinking of it that way, your first example is:
// v−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−v−−−−− iterable destructuring
let [propertyName, { [propertyName]: value }] = /* an iterable for the actual arguments */;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^−−−−−− parameter list
This is covered in the specification in the FunctionDeclarationInstantiation and IteratorBindingInitialization sections.
Note that this doesn't really have anything to do with destructuring, just how parameter values are assigned to parameters when the function code is run. For instance, if you try to use a later parameter name in the default value expression of an earlier one, it'll fail for the same reason.
I was breaking down some code I found. I got stuck on a specific issue and managed to break it down into a smaller piece. Just keep in mind that this code is part of a much bigger piece of code.
function foo(string, num) {
console.log("calling foo ---", string, num);
}
This following bit looks like it should work. I pass my two arguments into the array that's passed into apply.
But as you can see by the comment I added. The num parameter is undefined. And the string parameter has the second value in the array I passed in, which was intended to be the for num.
foo.bind.apply(foo, ["fdsf", 432])(); // calling foo --- 432 undefined
But if I add an additional value at the beginning of the array (it could be anything), Then everything works smoothly.
foo.bind.apply(foo, [0, "fdsf", 432])(); // calling foo --- fdsf 432
Can anyone please shed some light on what's happening here? If I call bind or apply separately, the array padding is not needed. It only seems to be needed when combining them.
Why is the additional value at the beginning of the array needed?
Also, here is the original code for reference. It's from one of Kyle Simpsons books.
function asyncify(fn) {
var orig_fn = fn,
intv = setTimeout( function(){
intv = null;
if (fn) fn();
}, 0 );
fn = null;
return function() {
if (intv) {
fn = orig_fn.bind.apply(orig_fn, [this].concat([].slice.call(arguments)));
} else {
orig_fn.apply( this, arguments );
}
};
}
There's a lot more going on here. But inside the if statement about 2/3's though the code, you can see it.
The second argument for apply is the array, with the first value (the padding) being the global object (I'm assuming it's the global object, it's not important, it could be anything). followed by any of the arguments that were passed in.
Some of that extra bit is just turning the arguments array-like object into an array.
The first argument that bind accepts is the this value to be used inside the function. So, if you use
fn.bind.apply(fn, arr)
the first item of the arr becomes the first argument of bind, eg, it's equivalent to:
fn.bind(arr[0], arr[1], arr[2] /* ... */ )
So the first item of the array becomes the this value, the second item of the array becomes the first argument, the third item of the array becomes the second argument, etc.
Here's a live example of how "padding" the array with a value at the start becomes the new this value when the function is called:
const obj = { prop: 'val' };
function foo(string, num) {
console.log("calling foo ---", string, num);
console.log('this:', this);
}
const fn = foo.bind.apply(foo, [obj, 'str', 55]);
fn();
As for the repetitive nature of
foo.bind.apply(foo, ...
It's necessary to repeat the function name when calling apply so that when bind is called, it's called with the right calling context - the this value used when .bind is called is how .bind knows which function to bind. (The first argument passed to .apply, just like .call, is the this value to be used in the function being applied)
can some please explain why this function returns its original argument value instead of the altered value.
edit: FYI I know the solution to the problem already i just don't understand what is going on 'under the hood'; what is causing this not to work. i want better understanding of the language
function helper(value){
let hold = value;
hold.replace(/[^\w]/g, '')
hold.split('')
hold.sort()
hold.join('')
hold.toLowerCase()
return hold
}
console.log(helper('hello world')) <--- returns 'hello world'
You need to reassign, the functions does not change the original value and needs to be re-assigned. You can also use dot operator to combine all the operations and shorten the code like following
function helper(value){
return value.replace(/[^\w]/g, '').split('').sort().join('').toLowerCase();
}
console.log(helper('hello world'))
Alternatively, you can correct your code like this
function helper(value){
let hold = value;
hold = hold.replace(/[^\w]/g, ''); // re-assign
hold = hold.split(''); // re-assign
hold.sort(); // sort updates hold - re-assignment not required
hold = hold.join(''); // re-assign
hold = hold.toLowerCase(); // re-assign
return hold;
}
console.log(helper('hello world'))
replace doesn't modify input argument but returns a new string instead.
I have found the solution as to "WHY" the code was acting the way it was.
it is because Arrays and Objects are mutable, while strings and numbers and other primitives are immutable.
for more info read this blog What are immutable and mutable data structures?
and the solution was posted here by "Nikhil Aggarwal"
There was a Function.prototype.arity property purposed for getting number of arguments function expects. Now it is obsolete (since JS 1.4), and the same goal has Function.prototype.length.
But recently I've found an article in the documentation about Array.prototype.reduce method. And it clearly says that the method has property length equal to 1:
The length property of the reduce method is 1.
This exact article has a header with number of arguments, and there are two of them:
Array.prototype.reduce ( callbackfn [ , initialValue ] )
callbackfn and initialValue (optional).
So it is not clear to me, what exactly the purpose of length property.
If it is used to give useful information to developer, then it actually does not.
If it is just a technical automatically-generated property that just indicates number of arguments in function definition, then why don't maintain its consistency?
Array.prototype.reduce's length is 1 because the second parameter is optional.*
So it is not clear to me, what exactly the purpose of length property.
To tell the developer how many declared parameters it has prior to the first parameter with a default value (if any) or the rest parameter (if any), whichever is earliest in the parameter list. Or as the spec puts it:
The value of the length property is an integer that indicates the typical number of arguments expected by the function.
The exact algorithm is in the spec's Static Semantics: ExpectedArgumentCount section.
If it is used to give useful information to developer, then it actually does not.
Well, that's a matter of opinion. :-)
When you have a language like JavaScript where functions can only express an expectation but may be called with fewer or more arguments, and particularly when you add the concepts of default parameter values and rest parameters, it's not surprising that the arity of function is a bit of a soft concept.
Some fun examples:
function ex1(a) { } // length is 1, of course
function ex2(a, b = 42) { } // length is 1, `b` has a default
function ex3(a, b = 42, c) { } // length is 1, `b` has a default and
// `c` is after `b`
function ex4(a, ...rest) { } // length is 1 yet again, rest parameter doesn't count
* In ES5, its declaration in JavaScript would be:
function reduce(callback) {
// ...
}
...and then it would use arguments.length to determine whether you'd supposed an initialValue.
In ES2015+ (aka "ES6"+), it would either still be like that, or be like this:
function reduce(callback, ...args) {
// ...
}
...and use args.length to see if there was an initial value.
Or possibly like this:
const omitted = {};
function reduce(callback, initialValue = omitted) {
// ...
}
...and then use initialValue === omitted to know whether you'd supplied an initial value. (The default value of initialValue can't be undefined or null or similar because the function has to branch based on whether the argument was provided [not what its value is]. But we can do that with object identity.)
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.