It seems to me that, in ES6, the following two functions are very nearly identical:
function () {
return this;
}.bind(this);
() => {
return this;
};
The end result seems the same: arrow functions produce a JavaScript function object with their this context bound to the same value as the this where they are created.
Obviously, in the general sense, Function.prototype.bind is more flexible than arrow functions: it can bind to values other than the local this, and it can bind any function's this at any point in time, potentially long after it is initially created. However, I'm not asking how bind itself is different from arrow functions, I'm asking how arrow functions differ from immediately calling bind with this.
Are there any differences between the two constructs in ES6?
There are no (significant) differences.
Well, okay, that's a little premature. There are three tiny differences unique to arrow functions.
Arrow functions cannot be used with new.
This means, of course, that they do not have a prototype property and cannot be used to create an object with the classically-inspired syntax.
new (() => {}) // TypeError: () => {} is not a constructor
This is probably for the best, though—the way new works would not make much sense with bound functions.
Arrow functions do not have access to the special arguments object that ordinary JavaScript functions have access to.
(() => arguments)(1, 2, 3) // ReferenceError: arguments is not defined
This one is probably a little bit more of a gotcha. Presumably this is to remove one of JavaScript's other oddities. The arguments object is its own special beast, and it has strange behavior, so it's not surprising that it was tossed.
Instead, ES6 has splats that can accomplish the same thing without any magic hidden variables:
((...args) => args)(1, 2, 3) // [1, 2, 3]
Arrow functions do not have their own new.target property, they use the new.target of their enclosing function, if it exists.
This is consistent with the other changes to remove "magically" introduced values for arrow functions. This particular change is especially obvious, considering arrow functions can't be used with new anyway, as mentioned above.
Otherwise, arrows are just like bound functions, semantically. It's possible for arrows to be more performant, since they don't have to carry around the extra baggage and since they don't need to be converted from ordinary functions first, but they're behaviorally exactly the same.
There are a few differences:
Arrow functions cannot be constructed. While both arrow functions and bound functions both don't have a .prototype property, the former do throw an exception when called with new while the latter just ignore the bound value and call their target function as a constructor (with the partially applied bound arguments, though) on the new instance.
function F() {}
var f = () => {},
boundF = F.bind({});
console.log(new boundF(), new boundF instanceof F) // {}, true
console.log(new f) // TypeError
Arrow functions do have lexical arguments, new.target and super as well (not only lexical this). A call to an arrow function does not initialise any of those, they are just inherited from the function the arrow function was defined in. In a bound function, they just refer to the respective values of the target function.
Arrow functions don't actually bind a this value. Rather, they don't have one, and when you use this it is looked up like a variable name in the lexical scope. This does allow you to lazily define an arrow function while this is not yet available:
class X extends Object {
constructor() {
var f = () => this, // works
boundF = function(){ return this; }.bind(this);
// ^^^^ ReferenceError
super(); // initialises `this`
console.log(f(), f() == this); // {}, true
}
}
new X;
Arrow functions cannot be generator functions (though they can return generators). You can use .bind() on a generator function, yet there is no way to express this using an arrow function.
Here is one more subtle difference:
Arrow functions can return a value without using the 'return' keyword, by omitting the {} braces following the => immediately.
var f=x=>x; console.log(f(3)); // 3
var g=x=>{x}; console.log(g(3)); // undefined
var h=function(x){x}; console.log(h(3)); // undefined
var i=x=>{a:1}; console.log(i(3)); // undefined
var j=x=>({a:1}); console.log(j(3)); // {a:1}
Related
As far as I know, the arrow function is similar to a normal function. There aren’t any problem when I use it like this:
let X = () => {};
let Y = function() {};
X();
Y();
However, the error occurred when I used them with new:
let X = () => {};
let Y = function() {};
x = new X();
y = new Y();
Uncaught TypeError: X is not a constructor
Why is that?
Q. What did I do wrong?
A. You used new with an arrow function, and that's not allowed.
Q. Can I turn an arrow function into a constructor?
A. Only by wrapping it in a normal function, which would be silly.
You can't turn an arrow function itself into a constructor.
Q. Can you explain how the specification disallows new with arrow functions?
A. To be a constructor, a function object must have
a [[Construct]]
internal method.
Functions created with the function
keyword are constructors, as are some built-in functions
such as Date. These are the functions you can use with new.
Other function objects do not have a [[Construct]]
internal method. These include arrow functions. So you can't
use new with these. This makes sense since you can't set
the this value of an arrow function.
Some built-in functions are also not constructors. E.g. you
can't do new parseInt().
Q. Can you explain the rationale behind disallowing new
with arrow functions in the specification?
A. Use common sense, or search the es-discuss archives.
Arrow functions are not synonymous with normal functions. arguments and this inside arrow functions reference their outer function.
When the code new Foo(...) is executed, the following things happen:
A new object is created, inheriting from Foo.prototype.
The constructor function Foo is called with the specified arguments, and
with this bound to the newly created object. new Foo is equivalent
to new Foo(), i.e. if no argument list is specified, Foo is called
without arguments.
The object returned by the constructor function
becomes the result of the whole new expression. If the constructor
function doesn't explicitly return an object, the object created in
step 1 is used instead. (Normally constructors don't return a value,
but they can choose to do so if they want to override the normal
object creation process.)
Since this inside an arrow function references its outer function (arrow functions inherit this from their declaration context, as #Iven is saying), using new keyword with an arrow function does not really make sense.
First, I create a ES5 Function and then create it's prototype:
var Person = function() {};
Person.prototype.city = function() {return 'New York'}
I get no error in here. But when I create the same with ES6 fat arrow function, I get Cannot set property 'city' of undefined:
let Person = () => {};
Person.prototype.city = () => {return 'New York'}
Why is this?
Because by definition, arrow functions don't have prototypes. They're designed to be lightweight, without some of the baggage that old-style functions have.
Another likely reason for this is that arrow functions capture the surrounding this rather than having it determined dynamically. So they would serve poorly as constructor functions because the this within them would be referring to the this from the surrounding scope instead of the object being created. (In fact, you can't even use them as constructor functions. JavaScript will throw an error if you try to.)
From MDN:
Use of prototype property
Arrow functions do not have a prototype property.
var Foo = () => {};
console.log(Foo.prototype); // undefined
I have been trying to understand es6 arrow function. I read some articles introducing it. But I'am still not getting it fully.
For example I have this code:
sortedArticles(): Article[] {
return this.articles.sort((a: Article, b: Article) => b.votes - a.votes);
}
It sorts the below array:
[
new Article('Angular 2', 'http://angular.io', 3),
new Article('Fullstack', 'http://fullstack.io', 2),
new Article('Angular Homepage', 'http://angular.io', 1),
];
How would the same code look in plain old js? I am not able to fully get it.
It would look like this if you just converted the arrow function to a function function:
sortedArticles(): Article[] {
return this.articles.sort(function(a: Article, b: Article) { return b.votes - a.votes;});
// ---------------------------^^^^^^^^------------------------^^^-------------------------^^
}
...but note that there's more going on there than ES2015 ("ES6"). The : Article[] part is saying that sortedArticles returns an array of Article. (And similarly the : Article qualifiers on a and b.) That's not JavaScript at all. It looks like TypeScript.
The pure JavaScript version would just drop those type annotations:
sortedArticles() {
return this.articles.sort(function(a, b) { return b.votes - a.votes;});
}
But TypeScript's "fat arrow" functions work largely the same way ES2015's arrow functions do, so let's continue on the basis that we're talking about ES2015 arrow functions:
There are four fundamental differences1 between arrow functions and function functions:
They close over this, super, and several other things,2 they don't have their own versions of those like function functions do. (A consequence of this is that they can use super if they're defined in a context that can use super, which function functions cannot.)
They can have a concise body rather than a verbose one (but they can have a verbose body as well).
They cannot be used as constructors. E.g., you can't use new with an arrow function. A consequence of this is that arrow functions don't have a prototype property on them (since it's only used if the function is used with new).
There is no generator syntax for arrow functions. E.g., there is no arrow equivalent to function *foo() { ... }.
These three functions are all effectively the same, since they don't use this or arguments:
// A `function` function
var f1 = function(x) {
return x * 2;
};
// An arrow function with a verbose body
var f2 = x => {
return x * 2;
};
// An arrow function with a concise body
var f3 = x => x * 2;
(If they used this or arguments, they would not be the same.)
Note that the concise body doesn't have a { after the => and must contain a single top-level expression (which can of course have subexpressions), which is used as the return value.
1 You'll find people telling you there's a fifth: That arrow functions cannot have a name. That's a myth. Arrow functions can have names; the arrow functions above have true names, f2 and f3 respectively. It's not just the variables that have names, the functions do as well.
2 Specifically, they close over this, super, arguments (the automatic pseudo-array of runtime arguments), and new.target.
A huge thank you to CodingIntrigue for pointing out several errors and omissions in the earlier versions of this answer.
If you're not familiar with arrow function, or if it's complicated, you can use JS Refactor, it's a Visual Studio Code extension. It can convert arrow function to normal function. Hope it helps someone.
As far as I know, the arrow function is similar to a normal function. There aren’t any problem when I use it like this:
let X = () => {};
let Y = function() {};
X();
Y();
However, the error occurred when I used them with new:
let X = () => {};
let Y = function() {};
x = new X();
y = new Y();
Uncaught TypeError: X is not a constructor
Why is that?
Q. What did I do wrong?
A. You used new with an arrow function, and that's not allowed.
Q. Can I turn an arrow function into a constructor?
A. Only by wrapping it in a normal function, which would be silly.
You can't turn an arrow function itself into a constructor.
Q. Can you explain how the specification disallows new with arrow functions?
A. To be a constructor, a function object must have
a [[Construct]]
internal method.
Functions created with the function
keyword are constructors, as are some built-in functions
such as Date. These are the functions you can use with new.
Other function objects do not have a [[Construct]]
internal method. These include arrow functions. So you can't
use new with these. This makes sense since you can't set
the this value of an arrow function.
Some built-in functions are also not constructors. E.g. you
can't do new parseInt().
Q. Can you explain the rationale behind disallowing new
with arrow functions in the specification?
A. Use common sense, or search the es-discuss archives.
Arrow functions are not synonymous with normal functions. arguments and this inside arrow functions reference their outer function.
When the code new Foo(...) is executed, the following things happen:
A new object is created, inheriting from Foo.prototype.
The constructor function Foo is called with the specified arguments, and
with this bound to the newly created object. new Foo is equivalent
to new Foo(), i.e. if no argument list is specified, Foo is called
without arguments.
The object returned by the constructor function
becomes the result of the whole new expression. If the constructor
function doesn't explicitly return an object, the object created in
step 1 is used instead. (Normally constructors don't return a value,
but they can choose to do so if they want to override the normal
object creation process.)
Since this inside an arrow function references its outer function (arrow functions inherit this from their declaration context, as #Iven is saying), using new keyword with an arrow function does not really make sense.
This question already has answers here:
Are 'Arrow Functions' and 'Functions' equivalent / interchangeable?
(4 answers)
Closed 6 years ago.
I am curious about ES6 arrow functions (fat arrow functions). Are they simply syntactic sugar derived from CoffeeScript, or is there more to them than meets the eye?
ES6 Arrow functions in depth
One of the prettiest features of ES6, it could easily win a beauty contest, if such a contest would be held. What many people don’t know is that the arrow function is not simply a form of syntactic sugar that we can use instead of the regular callback.
As I like to explain it to the people who attend my trainings/workshops, arrow functions are this-less, arguments-less, new.target-less and super-less.
Let us now get past the shorter syntax and dive deeper into the specifics of the arrow function.
Lexical-bound this
Previously, regular functions would have their this value set to the global object if they were used as callbacks, to a new object in case they were called with the new operator or, in the case of libraries like jQuery, they would be set to the object that triggered an event in case of event handlers, or the current element in a $.each iteration.This situation proved very confusing even for experienced developers.
Let’s say you have a piece of code like the one below.
var obj = {
nameValue: 'default',
initializeHandlers: function() {
var nameInput = document.querySelector('#name');
nameInput.addEventListener('blur', function(event) {
this.nameValue = event.target.value;
});
}
};
obj.initializeHandlers();
The problem is that this inside the blur event handler is set to the global object rather than obj. In strict mode — ‘use strict’; — you risk breaking your application because this is set to undefined. In order to side-step this issue we have two options:
Convert the event handler to a function bound to the outer scope, using Function.prototype.bind
Use the dirty var self = this; expression in the initializeHandlers function (I see this as a hack)
Both options are illustrated below.
[...]
initializeHandlers: function() {
var nameInput = document.querySelector('#name');
// more elegant but we can do better
var blurHandler = function(event) {
this.nameValue = event.target.value;
}.bind(this)
nameInput.addEventListener('blur', blurHandler);
}
[...]
[...]
initializeHandlers: function() {
var nameInput = document.querySelector('#name');
// ugly and error-prone
var self = this;
nameInput.addEventListener('blur', function(event) {
self.nameValue = event.target.value;
});
}
[...]
On the other hand, arrow functions have no internal context. They inherit their context from the outer scope. Let’s take a look at how arrow functions solve this problem.
const obj = {
nameValue: 'default',
initializeHandlers: function() {
const nameInput = document.querySelector('#name');
nameInput.addEventListener('blur', (event) => {
// this references obj instead of the global object
this.nameValue = event.target.value;
});
}
};
In our new implementation this is a hard reference to the obj object and doesn’t get lost due to nesting.
Lexical arguments
Have you ever tried to access the arguments object inside an arrow function? I have, and I wasted 3 solid hours trying to figure out why do I get the arguments of the outer function instead of those of the arrow functions.
Thankfully, MDN exists, and as good practice dictates, you check the documentation at the end, when you sit in a corner, knees tucked to your chest, rocking and repeating to yourself: “I should have been a carpenter!”
Fun aside, arrow functions do not expose an arguments object. If you try to access it, you will get the arguments of the surrounding function. In our case, given the fact that the outer function is an arrow function as well, and we have no more functions further up the chain, we will get a ReferenceError.
const variadicAdder = (x) => {
return () => {
let args = Array.prototype.slice.call(arguments, 0);
return args.reduce((accumulator, current) => {
return accumulator + current;
}, x);
}
}
const variadicAdderOf5 = variadicAdder(5);
console.log(variadicAdderOf5(10, 11, 12));
// ReferenceError: arguments is not defined
There is no fix here, as there is nothing broken. What we can do is to return a plain function, rather than an arrow, from our variadicAdder().
This will give us the opportunity to access the arguments object without an issue. The updated code will look like the one below with the only difference
that it will actually work and not throw an error.
const variadicAdder = (x) => {
return function() {
let args = Array.prototype.slice.call(arguments, 0);
return args.reduce((accumulator, current) => {
return accumulator + current;
}, x);
}
}
const variadicAdderOf5 = variadicAdder(5);
console.log(variadicAdderOf5(10, 11, 12));
// 38
To find out more about Array.prototype.reduce, head to the Mozilla Developer Network.
Other characteristics
As I mentioned in the introductory section of this article, arrow functions have several more characteristics besides the context and the arguments.
The first thing I would like to mention is that you are unable to use the new operator with arrow functions. As a direct implication, arrow functions also don’t have super(). Snippets like the one below would simply throw a TypeError.
const Person = (name) => {
this.name = name;
};
let p = new Person('John');
// TypeError: Person is not a constructor
The third characteristic, which is as well, a direct implication of the inability to use the new operator, is the fact that arrow functions don’t have new.target. In a nutshell, new.target allows you to detect whether or not a function has been called as a constructor.
Arrow functions, inherit new.target from their surrounding scope. If the outer scope is a function, and it is called like a constructor (e.g. new Person('Adrian');), then new.target will point to the outer function.
The Mozilla Developer Network hosts a detailed explanation on new.target and I encourage you to check it out.
This article is also published on my blog, here: /es6-arrow-functions-in-depth/