0

I was trying to understand the lexical scoping of 'this' in arrow functions. I tried the following code -

const obj1 = {
  name: "obj1name",
  method: function(){
    return () => {return this.name};
  }
};

const arrowFunc1 = obj1.method();

console.log(arrowFunc1());

const obj2 = {
  name: "obj2name",
  method: function(){
    return obj1.method();
  }
};

const arrowFunc2 = obj2.method();

console.log(arrowFunc2());

This gives the output -

obj1name
obj1name

As expected, 'this' inside the arrow function was lexically scoped to obj1 and hence it prints 'obj1name' no matter how we call it. All fine till here. Then instead of defining obj2.method I decided to directly copy obj1.method into it -

const obj1 = {
  name: "obj1name",
  method: function(){
    return () => {return this.name};
  }
};

const arrowFunc1 = obj1.method();

console.log(arrowFunc1());

const obj2 = {
  name: "obj2name",
  method: obj1.method
};

const arrowFunc2 = obj2.method();

console.log(arrowFunc2());

This gives the output -

obj1name
obj2name

I don't understand why it prints 'obj2name'.

My understanding is that the arrow function is defined in obj1.method and so it is lexically scoped in it. Hence it should take the value of 'this' from obj1.method which is obj1.

What am i getting wrong?

chetan
  • 402
  • 3
  • 12
  • 1
    `this` in arrow functions is bind to `this` of the outer scope. If you define an arrow function inside a method of an object, the outer scope `this` is what ever it is when you call that method. – Teemu May 13 '20 at 18:31
  • 1
    The value of `this` is bound to what-ever the execution context is when the arrow function is defined. In your example the call of `.method()` defines the content of `this`. `obj1.method()` -> `obj1`, `obj2.method()` -> `obj2` - just the "normal" `this` rules -> [How does the “this” keyword work?](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) – Andreas May 13 '20 at 18:35
  • @Teemu But isn't lexical scoping static (as mentioned in this [answer](https://stackoverflow.com/a/22395580/7730507))? If the scope depends on how the function is called then won't that be dynamic scoping? – chetan May 13 '20 at 18:44
  • What is static? In this case **static** means, that the scope is the same as its parent scope - the parent is **obj1** in the first case and **obj2** in the second case. – muka.gergely May 13 '20 at 18:52
  • @muka.gergely in lexical scoping the scope is determined at compile time based on the lexical structure of the code. As you rightly say, the parent scope is changing at run time. Since parent scope changes at run time, the scope of 'this' also changes at run time. Then why do we call it lexical scoping? – chetan May 13 '20 at 19:18
  • The case you presented does not mean anything at compile time - you just copy a function from here to there - the lexical structure of your code exactly says that it should respect the fact that it's placed in **obj2**. Just look at the snippets in my answer - I logged the objects that are created, and the logs show exactly the lexical structure: nothing refers to **obj1** in **obj2**'s method. It's just a plain function placed in **obj2**. – muka.gergely May 13 '20 at 19:23
  • That is: the structure of your code is not always as you imagine - JS (during compile time) sees the function in **obj2** as a function in **obj2** (no relations to **obj1**). – muka.gergely May 13 '20 at 19:25
  • Yes, _lexical scope_ is static, but scope is for variables only. _Lexical environment record_ is a different thing, and that's what is applied to arrow functions. – Teemu May 13 '20 at 19:45

2 Answers2

1

It is lexically scoped: you defined a function (arrow) in obj1, but called it in obj2 - the lexical scope of obj2 is obj2 - so the method you invoked there works as supposed to.

The console.logs in the snippet show the problem:

const obj1 = {
  name: "obj1name",
  method: function(){
    return () => {return this.name};
  }
};

const arrowFunc1 = obj1.method();

console.log(arrowFunc1());

const obj2 = {
  name: "obj2name",
  method: obj1.method
};

const arrowFunc2 = obj2.method();

console.log(arrowFunc2());

// these console.logs show your problem:
console.log('obj1:', obj1)
console.log('obj2:', obj2)

In your case, the function() creates the scope for this - and that function() is in obj2.

Maybe this snippet helps to understand a little more:

// extracted the function, but left the "this"
// just calling the function in itself would throw an
// error, but inside an object (that has a name property)
// it works nicely
const fn = function() {
  return () => {
    return this.name
  }
}

const obj1 = {
  name: "obj1name",
  method: fn
};

const arrowFunc1 = obj1.method();

console.log(arrowFunc1());

const obj2 = {
  name: "obj2name",
  method: fn
};

const arrowFunc2 = obj2.method();

console.log(arrowFunc2());

// these console.logs show your problem:
console.log('obj1:', obj1)
console.log('obj2:', obj2)

And my last snippet:

const obj1 = {
  name: "obj1name",
  method: function() {
    return () => {
      return this.name
    };
  }
};

const arrowFunc1 = obj1.method();

console.log(arrowFunc1());

// if you want to call with obj1, then it's not enough to add the
// function - you have to CALL (invoke) it
const obj2 = {
  name: "obj2name",
  method: obj1.method()
};

// see that there're no parantheses after obj2.method - its
// value is resolved when creating obj2 by INVOKING the function
const arrowFunc2 = obj2.method;

console.log(arrowFunc2());

// these console.logs show your problem:
console.log('obj1:', obj1)
console.log('obj2:', obj2)
muka.gergely
  • 5,852
  • 2
  • 14
  • 31
  • I read this [answer](https://stackoverflow.com/a/22395580/7730507) to understand lexical scoping. According to this, lexical scoping is static and determined by the structure of the source code. So why is the lexical scope in this case determined by how i am calling the arrow function instead of the code structure. Won't that be dynamic? – chetan May 13 '20 at 18:38
  • @chetanraina I added a snippet to my answer: you can see that the functions are called on the object they contain, so the scope is changing (just logged the objects themselves as they appear to JavaScript) – muka.gergely May 13 '20 at 18:43
  • 1
    @chetanraina and third (last) snippet: if you invoke the function in obj2, then it has the scope of obj1 - as it's defined there. Originally you just passed the function, but this way the **value of the function** is passed (in JS functions are first-class citizens: http://ryanchristiani.com/functions-as-first-class-citizens-in-javascript/ - that means a lot when you pass them around) – muka.gergely May 13 '20 at 18:59
0

This:

const obj1 = {
  name: "obj1name",
  method: function(){
    return () => {return this.name};
  }
};

Should be this:


const obj1 = {
  name: "obj1name",
  method: () => {return this.name}
};

By wrapping the arrow function in a regular function the scope once again becomes the scope of the caller.

phuzi
  • 8,111
  • 3
  • 24
  • 43