Using 'this' in default parameters
` Mystery . wrote:
During the development of an ES6 web application, I came across a situation where I wanted to bind 'this' of the outer function to a parameter of an inner function
Use an arrow function for that:
function outer() {
const inner = (_this = this) => {
console.log(_this.a); // 1
}
inner.call({});
}
outer.call({a: 1});
> In the latest Firefox, the above script prints 'undefined'. This seems
> pretty counter-intuitive, and it also fails when I try to pass 'this.a' as
> default value for the parameter of the inner function. I wonder if this is
> an intended behavior? Thanks in advance.
Yes, it is intended, and pretty intuitive - default initialisers are
evaluated in the scope of the called function, at the call - they're not
values that are computed at the time of the definition of the function.
,
Bergi
Thanks for the replies. I'm aware of the arrow functions, but the reason I don't use them here is that I wish to bind a custom context to the inner function (i.e. preserve the "this" passed while calling the inner function, like a custom context for promise callbacks for example) and use some variables in the outer function's context as well (might be a class method). Guess I should fallback to old "saving this to a variable" technique in this situation.
IMHO I don't think the default parameters should be evaluated within the context of the function being called, at least not while it's outside of function body's region...... Is there a specific reason to do that, or it's just a design preference?
Yours, Charles Weng
I think this discussion is not really appropriate for this ML but I'd like to point out you have already all possible solutions.
For instance, you don't need to bond at all for your case since you use
.call
already. Please be sure you understand what .call
does because
it's kinda pointless to use both .call
and .bind
or bound variables.
Example:
function outer() {
function inner() {
console.log(this.a);
}
inner.call({});
}
outer.call({a: 1});
If you need to bring in the default this
arrow function does exactly that
and it will be pointless to .call
an arrow function with a different
context.
function outer() {
const inner = () => {
console.log(this.a);
};
inner.call({}); // or just inner();
}
outer.call({a: 1});
If you'd like to create an inner function with a different context you can
use .bind
which has been in core for quite a while and its purpose is
exactly that one.
function outer() {
const inner = function () {
console.log(this.a);
}.bind({});
inner();
}
outer.call({a: 1});
As you can see the last thing we need is yet another way to bind something
in the language ... but of course, if you really want to use the var self = this
pattern you are free to do that, no need to change specifications
though.
Eventually, what you might want is a hsortcut for function like ->
instead of =>
could be ... but that's a whole different story.
Best
On Fri, Jan 29, 2016 at 8:14 AM, ` Mystery . <mystery.wd at gmail.com> wrote:
IMHO I don't think the default parameters should be evaluated within the context of the function being called, at least not while it's outside of function body's region...... Is there a specific reason to do that, or it's just a design preference?
Sure, there is a reason: it's about how defaults are used.
Most defaults are probably constants or empty objects. Those don't need to refer to any variables at all, so we can set them aside.
Of the rest, I suspect most refer to previous parameters:
function send(sender, recip, message, onBehalfOf=sender) { ... }
or the this
for the current method call:
class MyArray {
...
slice(start=0, stop=this.length) { ... }
}
The only way to support these is to put the arguments in scope.
(Another reason is consistency. This is how var
initializers already
work, and have always worked: initializers are in the scope of the
bindings being initialized, and can use the values of previously
initialized bindings on the same line of code.)
@Andrea Giammarchi Sorry, perhaps I didn't express myself clearly. I use .call() to manually bind the context "this" to a function, and yes, the last one is exactly what I need, accessing both contexts (inner and outer) which I couldn't manage without using an additional variable, as described in the following snippet:
function outer() {
const inner = function (_this = this) {
console.log(this.b, _this.a);
};
inner.call({b: 1});
}
outer.call({a: 2});
@Jason Orendorff I see. If JavaScript is going to support calling class functions with class members as default parameters, it has to parse the default values with the given "this" context at calling. Perhaps that's also the reason why the most language does not support this feature, like C++ and Python..... but it's JavaScript :)
Thanks guys for the patient explanation.
Yours, Charles Weng
Sorry Charles, I'm not sure I understand. Would this work?
function outer() {
const inner = (_this) => {
console.log(this.b, _this.a);
};
inner({b: 1});
}
outer.call({a: 2});
that should produce what you expect already, right? I think it's misleading
to use .call
with a bound/arrow function, as it's misleading to have two
different this
in params or body.
Just my pov
Yes, the example you show is sufficient for most scenarios. However the situation I came across is more complex: The custom Promise class I implemented uses .call() to bind the same "this" scope object across different .then() tasks, so they can exchange variables just simply by storing them in "this". Of course it will work if I just pass that environment object as a parameter, but it would require a lot of work to modify other callbacks that does not use "this" of the outer function. Perhaps I should stick to storing a "self" variable instead of introducing so many changes. ;)
Andrea Giammarchi <andrea.giammarchi at gmail.com>于2016年1月30日周六 00:07写道:
Agreed there should be no changes in specs.
However, you can solve with both utilities, one overwriting call
(yak)
const boundCall = (f) => {f.call=(...a)=>f.apply(null,a);return f};
function outer() {
const inner = boundCall((_this) => {
console.log(this.a, _this.b);
});
inner.call({b: 1});
}
outer.call({a: 2});
and one creating Python like callbacks that will receive the self
as
first argument.
const snake = (f) => function snake(...a) {
return f.apply(this, [this].concat(a));
};
const outer = snake((outerSelf) => {
const inner = snake((innerSelf) => {
console.log(outerSelf.a, innerSelf.b);
});
inner.call({b: 1});
});
outer.call({a: 2});
As you can see there are plenty of different tricks you can use to make
your custom Promise do whatever you but if you want a hint do not ever call
a method call
or apply
... it's the most misleading name you could
think of, no matter what's the purpose, IMHO
Worth mentioning that D3.js, a popular visualization and SVG manipulating library that I'm using heavily in my project, uses .call() to bind DOM elements and invoke callbacks. Difficult to change that behavior without overriding built-in function as you said, although I'm expecting something cooler like: function (self = this) {}...... But never mind, thanks~
Yours, Charles Weng
Andrea Giammarchi <andrea.giammarchi at gmail.com>于2016年1月30日周六 00:56写道:
Jason Orendorff wrote:
On Fri, Jan 29, 2016 at 8:14 AM, ` Mystery . <mystery.wd at gmail.com> wrote:
IMHO I don't think the default parameters should be evaluated within the context of the function being called, at least not while it's outside of function body's region...... Is there a specific reason to do that, or it's just a design preference?
Sure, there is a reason: it's about how defaults are used.
Most defaults are probably constants or empty objects. Those don't need to refer to any variables at all, so we can set them aside.
Well, primitive-valued constants are fine. But if you have an empty object constant, which is statically attached to the function instance instead of being re-evaluated at every call, that would surely cause enough havoc already :-)
, Bergi
On Sun, Jan 31, 2016 at 3:59 PM, Bergi <a.d.bergi at web.de> wrote:
Jason Orendorff wrote:
On Fri, Jan 29, 2016 at 8:14 AM, ` Mystery . <mystery.wd at gmail.com> wrote:
IMHO I don't think the default parameters should be evaluated within the context of the function being called, at least not while it's outside of function body's region...... Is there a specific reason to do that, or it's just a design preference?
Sure, there is a reason: it's about how defaults are used.
Most defaults are probably constants or empty objects. Those don't need to refer to any variables at all, so we can set them aside.
Well, primitive-valued constants are fine. But if you have an empty object constant, which is statically attached to the function instance instead of being re-evaluated at every call, that would surely cause enough havoc already :-)
I was about to send almost this exact reply. ^_^ This is actually very important, and can't be set aside - Python got it wrong, which is why if you want an argument to default to an empty list, for example, you have to do:
def foo(bar=None):
or any sentinel value, but None is almost always usable
if bar is None: bar = [] ...
Otherwise you get a single array created at function definition time and shared by all calls that default that parameter. ^_^ JS made the right ergonomic decision for this, but of course some use-cases are negatively affected in the trade-off, but none of them are made impossible, or even difficult, as several people in this thread have shown.
During the development of an ES6 web application, I came across a situation where I wanted to bind 'this' of the outer function to a parameter of an inner function, here is a quick snippet demonstrated what I'm trying to do:
function outer() { function inner(_this = this) { console.log(_this.a); } inner.call({}); } outer.call({a: 1});
In the latest Firefox, the above script prints 'undefined'. This seems pretty counter-intuitive, and it also fails when I try to pass 'this.a' as default value for the parameter of the inner function. I wonder if this is an intended behavior? Thanks in advance.
Yours, Charles Weng