Why do Arrow functions exist and how could you use them in objects if you wanted to?

Question

If arrow functions do not depend on where the function is called, why do they exist and how could you use them in objects if you wanted to?

Answer

Arrow functions have been adopted by JavaScript to provide an easier, shorter syntax, and a clearer definition of the keyword this which will always be bound to the enclosing lexical context (aka the global scope where it exists). An example of how they provide an easier syntax would be:

//As a callback

const duplicates = [1,2,3,4].map(num =>  num * 2 );

There, we can see that an arrow function as a one-liner does not need curly braces, parenthesis around the parameter, or a return keyword. In that form, they all become optional. When we have to provide two or more arguments, we need to have parenthesis, and when we need more than one line of operations in the function, we need curly braces and the return keyword. Ie.:

const duplicatePairs = [1,2,3,4].map((num, index) =>  {
  
  let newNum = num*2;
  const pair = {};

  pair[index] = newNum;

  return pair;
  } )

console.log(duplicatePairs) //[ { 0: 2 }, { 1: 4 }, { 2: 6 }, { 3: 8 } ]

It is not recommended to use arrow functions in objects for the limiting use of the keyword this, yet if we really wanted to write an arrow function in an object and we wanted to refer to the calling object, we have to make a direct reference by using the object’s name. Ie.:

const robot = {
  energyLevel: 100,
  checkEnergy: () => {
    console.log(`Energy is currently at ${robot.energyLevel}%.`)
  }
}

robot.checkEnergy(); //Energy is currently at 100%.

const newRobot = robot; //to demonstrate that referring to robot in the main 
                        //object, but that it becomes an inherent value that
                        //that refers back to the object.
newRobot.energyLevel = 50;
newRobot.checkEnergy() // Energy is currently at 50%.
robot.checkEnergy() // Energy is currently at 50%.

Again, this is a case where we could say an arrow function might not be as efficient as a method.

7 Likes

@axelkaban

So, if we can access the calling object’s property (from within the same object’s method) by using the object’s name instead of this, why use this at all?

I’ve checked and confirmed that by using the object’s name instead of this we can use either arrow function or function expression syntax in our object’s method, so at the moment I can’t see any benefit to using this.

12 Likes

@axelkaban @mtf

I think I’ve answered my own question, and thought of a couple of good reasons:

When the calling object doesn’t have a name e.g. in a factory function. It seems that, in this case, the only way is to use this.

So, if there are cases where using this is the only way, I guess it makes sense to be consistent and always use this rather than the calling object’s name.

Am I also right in thinking that in @axelkaban’s example above it would also be preferable to use this (without the arrow function) because if we assign an object (robot in the example) to a new variable (e.g. newRobot) the code for the object’s method(s) (e.g. checkEnergy) is clearer, because this refers to the calling object whatever name it is assigned to? i.e.

const robot = {
  energyLevel: 100,
  checkEnergy () {
    console.log(`Energy is currently at ${this.energyLevel}%.`)
  }
}

robot.checkEnergy()        // Energy is currently at 100%.
const newRobot = robot
newRobot.energyLevel = 50
newRobot.checkEnergy()     // Energy is currently at 50%.
robot.checkEnergy()        // Energy is currently at 50%. 

Do you agree? Are there any other reasons?

6 Likes

Without getting into answering your question(s) first consider that when we equate (assign) a data structure to a variable we only give it the address, not the data.

const newRobot = robot

is not a copy. newRobot and robot are one and the same. Both names reference the same object. That’s why when you changed one it was reflected in the other. The only difference is the names.

Same applies when data structures are used as arguments in functions. We pass in a data structure by name only, not the actual object. The function can name it locally as anything that makes sense to the function. Still the same object.

24 Likes

I tried to write out the following code using the longhand “old-fashioned” way- except that it results in a console error.

checkEnergy() {
    console.log(`Energy is currently at ${this.energyLevel}%.`)
  }

Because the above is a function, wouldn’t the longhand just involve adding a function at the beginning, and checkEnergy would be the function name?

We cannot declare a function inside an object, though we can define one if we use object syntax…

checkEnergy: function () {

}

Clearly, the new syntax for methods is simpler and more concise.

2 Likes

Ohh, right!! Thank you for showing me, I get it now! :+1:

Agreed. I was hoping to get some familiarity with both before just going with the shorthand haha

1 Like

What is the reason behind arrow functions working (or being broken) this way? Why not have them work as one would expect? Is there some advantage that I am not aware of?

I don’t really know why arrow functions act like this but I’m pretty sure that’s one reason why they were introduced: to simplify this. When using arrow functions this now refers to the object enclosing the object it was called on - that’s what I understood, not 100% sure

The main reason arrow functions were introduced is because of the more concise and more readable syntax

Can someone please check that I grasped the concept well:

  • The this keyword refers to the object is being called / accessed on
  • When using the arrow function syntax, does the this keyword refer to the object that encloses the object that it was called / accessed on?

One reason arrow functions were introduced was to alleviate scope ( this ) complexities and make executing functions much more intuitive.
Arrow function expressions - MDN

this - MDN

In the quoted text of the code snippet bellow saying that the value of this is the global object which in this case is this goat object. But the part that I don’t fully understasnd they’re saying that this global object doesn’t have a dietType property and therefore returns undefined . But we clearly see that this object has a property of dietType. And i’m completely lost! Could someone explain this to me please?

“Arrow functions inherently bind , or tie, an already defined this value to the function itself that is NOT the calling object. In the code snippet below, the value of this is the global object , or an object that exists in the global scope, which doesn’t have a dietType property and therefore returns undefined .”

const goat = {
  dietType: 'herbivore',
  makeSound() {
    console.log('baaa');
  },
  diet: () => {
    console.log(this.dietType);
  }
};
 
goat.diet(); // Prints undefined

Outside of the code above, add the following…

var dietType = 'HERBIVORE'

goat.diet()    //  HERBIVORE

var declarations in global scope are bound to the window object in the same way as writing them as properties of that object…

window.dietType = 'HERBIVORE'

goat.diet()    //  HERBIVORE

The binding for this in arrow functions is to the window object, not the context in which it is written. That is why we do not use arrow functions as methods.

diet () {
    console.log(this.dietType);
 }

goat.diet()    //  herbivore
1 Like

I just tried it but I got undefined like so:

const goat = {
  dietType: 'herbivore',
  makeSound() {
    console.log('baaa');
  },
  diet: () => {
    console.log(this.dietType);
  }
};
 var dietType = 'HERBIVORE'
goat.diet(); // Prints undefined

But when I append console.log(goat.dietType) with both expression and arrow functions I get herbivore and I’m really asking myself if they both work well without adding this keyword why don’t we use they?

const goat = {
  dietType: 'herbivore',
  makeSound() {
    console.log('baaa');
  },
  diet: () => {
    console.log(goat.dietType);
  }
};
 //var dietType = 'HERBIVORE'
goat.diet(); // Prints herbivore

Resisting recommendations is only going to add friction to the learning process. We are advised to not use arrow functions in methods for the very reason mentioned above… this does not bind to the context object. It is not something we need to work around, but simply accept the facts and move on. Use ES6 methods and this is properly bound to the context.

diet () {
    console.log(this.dietType)
}

What could be simpler?

What happens if we ever try to clone the goat object? The above will always be looking for a goat object. this frees us up to clone any object and have the methods work correctly for the clone.

I simply wanted a better understanding the behavior of this keyword with arrow functions as methods. Thank you for your explanation & recommendation

1 Like

Arrow functions inherently bind , or tie, an already defined this value to the function itself that is NOT the calling object. In the code snippet above, the value of this is the global object , or an object that exists in the global scope, which doesn’t have a dietType property and therefore returns undefined .

Does this mean that the arrow function becomes an independent function ?

Thank you for further explaining the difference using Arrow functions vs function expressions!

It seems like the example @mtf gave is correct. It just doesn’t work in Codecademy’s editor. This example gives you an idea that something is off:

var testingStuff = 'Does variable become a property in the window object?';

const goat = {
  printWindowObject: () => {
    console.log(this);
  },
  addSomethingToWindowObject: () => {
    this.whereDoesThisAppear = 'New content';
  },
  normalFunction: function() {
    console.log(this);
  }

};

goat.normalFunction();
// { printWindowObject: [Function: printWindowObject],
//   addSomethingToWindowObject: [Function: addSomethingToWindowObject],
//   normalFunction: [Function: normalFunction] }

goat.printWindowObject();
// Prints empty object:
// {}
goat.addSomethingToWindowObject();

goat.printWindowObject();
// { whereDoesThisAppear: 'New content' }

It seems safe to say that the window object is not supposed to be an empty object. If you paste your example code in a normal JS editor, it will work as predicted by @mtf.

1 Like

I have a small question. Why did the example add a colon after diet? Previous examples have been diet( ). It seems odd to me.

That’s the way the user wrote it, in the old way, but with an arrow function instead of the old way. We can forget about that. Stick to the new way and there will be no problems.

diet () {

}
2 Likes