Using 'this' in default parameters

# ` Mystery . (9 years ago)

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

# Bergi (9 years ago)

` 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
# ` Mystery . (9 years ago)

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

# Andrea Giammarchi (9 years ago)

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

# Jason Orendorff (9 years ago)

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.)

# ` Mystery . (9 years ago)

@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

# Andrea Giammarchi (9 years ago)

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

# ` Mystery . (9 years ago)

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写道:

# Andrea Giammarchi (9 years ago)

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

# ` Mystery . (9 years ago)

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写道:

# Bergi (9 years ago)

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

# Tab Atkins Jr. (9 years ago)

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.