0

This is probably a basic question, and I am aware that there have been some similar questions around here, but still I did not find an answer. Please consider the following code:

function Outer(inner2) {
  var x = 5;
  this.inner1 = function() {return (x);}
  this.inner2 = inner2.bind(this);
}
var outer = new Outer(function() {return (x);});
alert(outer.inner1()); // works
alert(outer.inner2()); // does not work

If I would replace the second line by this.x = 5;, the second alert would work. So I guess the problem is that x when declared with var is not part of this, so that bind will have no effect.

Is there any way to make this work without using this.x?

Remirror
  • 692
  • 5
  • 14
  • `x when declared with var is not part of this` - correct. – Jaromanda X Feb 09 '18 at 00:13
  • 1
    You can't. There's no way JavaScript can know you're referring to a local variable inside the constructor while outside it unless you attach it to `this` or lift it out somehow. – Andrew Li Feb 09 '18 at 00:13
  • 1
    If you do not want to expose your local variable with `this.x`, you might want to pass it as an argument to your inner2 method : `var outer = new Outer(function (x) { return (x); });` and `this.inner2 = inner2.bind(this, x)` – Flo Schild Feb 09 '18 at 00:20
  • @Li357 True at declaration time, but note that I never call the anonymous function given as parameter directly but only `outer.inner2()`. So, the compiler could infer the context. – Remirror Feb 09 '18 at 00:21
  • @Flo-Schield-Bobby But then I had to change the signature of the anonymous function (add a parameter). Further, in reality, there are a lot of variables such as `x`, and I do not want to pass each of them... – Remirror Feb 09 '18 at 00:23
  • x is essentially a private field, you are trying to return a private field from outside the object without using a getter, that can't be done. – nick zoum Feb 09 '18 at 00:23
  • @Remirror but that's because the *JavaScript engine* (not compiler) maintains a record of `this`, and the fact you're calling it with `outer` gives the interpreter the context. The engine can't see that you defined an essentially private (local) variable and access it in the constructor after you've constructed an object—hence the name, *local* variable. There is no way to do it you'd like because that's not how the language, or any language I know of, works. – Andrew Li Feb 09 '18 at 00:27
  • @nickzoum I would agree if I called the anonymous function outside of `Outer`, but I don't. – Remirror Feb 09 '18 at 00:28
  • If you want to know why `inner1` works but not `inner2`, I suggest some [reading on closures](https://stackoverflow.com/q/111102/5647260). Essentially `inner2` can access `x` after the constructor is executed because x is "kept alive" for `inner1` to access. But since the engine has no way of knowing that `inner2` contains a reference to a local variable in the constructor, it can't access it. – Andrew Li Feb 09 '18 at 00:35
  • Thanks, but maybe we talk past each other. Please check my answer below. – Remirror Feb 09 '18 at 00:46
  • `this.inner2 = inner2.bind(this);` is redundant. You may simply do like `this.inner2 = inner2;` Yet don't forget that `inner2` might have already been bound to some other object previously but that's another problem. – Redu Apr 23 '18 at 23:05

3 Answers3

1

Is there any way to make this work without using this.x?

No.

Btw, you probably don't even need to use bind, just calling inner2 as a method on the object would suffice when both the constructor and the method use this.x.

If you don't want to make x a property of the object, but keep it as a local variable, the usual strategy would be to pass it to the callback as an argument, not trying to somehow make it available in its scope implicitly:

function Outer(callback) {
  var x = 5;
  this.inner1 = function() { return x; };
  this.inner2 = function() { return callback(x); };
//                                          ^^^
}
var outer = new Outer(function(y) { return y; });
//                             ^           ^
alert(outer.inner1()); // works
alert(outer.inner2()); // works
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

I think you need clarification on what the word "this" is referring to.

"this" is not pointing to the function "Outer."

When you invoke a constructor function with the "new" keyword, a few things happen.

  1. The constructor function returns an object.
  2. The "this" variable is changed, so that it is set to point to that object that is returned.
  3. (Also, the .proto of the object returned is set to the .prototype of the constructor function, but that step is not relevant here).

So, you are binding the callback function to the object that you are returning from the constructor function, not the constructor function itself.

Thus, the callback function is bound to outer (with a lower-case), not outer (with an upper-case).

Also, when you bind, you are not binding to the scope of a function. X is not assigned to any property. I think you only can bind to an object and access its properties with this.a etc.

The x in the first function worked because its value was assigned in the scope of the function.

Maiya
  • 932
  • 2
  • 10
  • 26
0

I found an, albeit ugly, solution:

function Outer(inner2) {
  var x = 5;
  this.inner1 = function() {return (x);}
  eval('this.inner2 = ' + inner2.toString());
}

This works and shows my point: the parameter inner2 is just a prescription of how this.inner2 should look like; it is never invoked itself.

Let me know, if you have a neater solution than this.

Angel Politis
  • 10,955
  • 14
  • 48
  • 66
Remirror
  • 692
  • 5
  • 14