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.
Related
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)
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")
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.)
In javascript, it is possible to define an object's property as a getter/setter rather than just a "plain" value:
Object.defineProperty( obj, 'complex_property', {
get: function getter() { return this.protected; },
set: function setter( value ) { this.protected = value; }
} );
Is there any way to return a non-value property of an object without first having its getter function evaluated so that this (or the like) is possible?
obj.property = function( name ) { return this.[ name ]; };
// Doesn't work:
// ReferenceError: invalid assignment left-hand side
obj.property( 'complex_property' ) = 5;
The last line effectively reads 6 = 5 - obviously an error - since this.complex_property is evaluated first, then returned. Combining closures with getter-setter properties is an effective way to simulate "private" properties and validate assignment values without having to use actual get/set functions: one of the nicer features of modern javascript. It'd be even nicer if there was a way to return an unevaluated getter/setter property: is there a way I've missed or is it just not possible? Are we stuck using set( prop, value ) functions?
Unfortunately, this isn't possible with JavaScript functions. All JavaScript functions created by user code can only return values, not references (and this won't be possible by code within the browser too, starting from ES6).
A ReturnStatement is evaluated as follows (bold my emphasis):
If the Expression is not present, return (return, undefined, empty).
Let exprRef be the result of evaluating Expression.
Return (return, GetValue(exprRef), empty).
The only way to do so is either using a set(property, value) function or creating an actual setter.
You can get a property. Well, a property descriptor at least. And it ain't pretty.
var prop = Object.getOwnPropertyDescriptor(obj /* from question */, 'complex_property');
prop.set.call(obj, 5);
edit:
obj.property = function (name) { return Object.getOwnPropertyDescriptor(this, name); }
obj.property('complex_property').set.call(obj, 5);
let say I've got this kind of code:
var obj1 = {test: false};
function testCondition(condition){
if (!condition){
testCondition(condition);
}
}
testCondition(obj1.test);
above code will pass false as argument to testCondition. How can I do to pass reference to obj1.test instead of passing it's value?
EDIT
wow, thanks for quick responses!! :) But I would like to add, that I cannot pass the whole object, because I would like to build one generic function/method which would just check parameter and do onComplete callback or onError callback. Above code is only example of situation where I am right now.
You have two choices, from what I can see:
Pass the object itself, instead of its member. You can then access and modify the member:
function testCondition(object) {
if (!object.test) {
testCondition(object);
}
}
testCondition(obj1)
Alternatively, since you're changing a single value, you can have that value be returned by the function:
function testCondition(condition) {
if (!condition){
return testCondition(condition);
}
}
obj1.test = testCondition(obj1.test);
FYI, your code as you've displayed it right now will cause an infinite recursion if condition is false.
What's wrong with return values?
Alternatively you can wrap the argument in an object:
function foo(arg) {
var val = arg.val;
// do something with val
arg.val = val;
}
var arg = {val:"bar"};
foo(arg);
// do something with arg.val
You can't.
Pass obj1 instead, then examine condition.test inside the function.
You can't. JavaScript passes objects and arrays by reference, primitives (integers, strings, booleans) by value. What you're asking for is impossible, except by bad work-arounds:
function ugly(result) {
result.success = true;
}
var result = {};
ugly(result);
Instead, just return your value. It's how JavaScript is meant to work.
pass the whole object instead of its property:
testCondition(obj1);
and then
if(!passedObj.test){
etc...