Is there an ESLint rule to enforce that arrow functions are used in classes, whenever this is referenced.
Javascript famously has strange semantics on how this is bound to a function.
Sometimes it's desirable to have this bound by the caller, however with class methods, it's almost always not what the intention of the programmer was.
It can often lead to unexpected behavior when a class method is used as a higher order function.
I would like an ESLint rule that will warn me when a class method is NOT defined as an arrow function.
Contrived example of why this behavior is undesired:
type CelebrateBirthday = () => number
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
celebrateBirthday(): number {
return ++this.age
}
celebratBirthdayArrow = (): number => {
return ++this.age
}
}
function celebrateBirthdayAndPrintAge(birthdayCelebrationFn: CelebrateBirthday) {
console.log("Happy birthday, you are " + birthdayCelebrationFn() + " years old")
}
const person = new Person("bob", 10)
// works
celebrateBirthdayAndPrintAge(person.celebratBirthdayArrow)
// throws error "this is undefined"
celebrateBirthdayAndPrintAge(person.celebrateBirthday)
TS Playground Link of the above code
Arrow function by design don't bind anything to this. So trying to write arrow functions inside of a Class definition means that you can't access this which is one of the major reasons for using a class... accessing local data and methods.
An eslint rule to look for what you're asking would effectively break what your Class is trying to do.
Related
When I printed the object I did't get the conventional style functions but only the arrow style functions. Are the conventional functions hidden part of the created object or they do not belong to the class object?
class Person {
pName = "Hasnain";
pAge = 22;
displayNormalFunc1()
{
return this;
}
displayNormalFunc2()
{
return this;
}
displayArrowFunc1 = ()=>
{
return this;
}
displayArrowFunc2 = ()=>
{
return this;
}
}
objp = new Person();
console.log(objp)
displayArrowFunc1, displayArrowFunc2 is like a variable it will be initialized every time Person is initialized
displayNormalFunc1, displayNormalFunc2 are methods that belong to prototype which is a design principle of prototypical programming languages they are created only once when declaring Person and directly written to your Person.prototype it is possible to access them through some javscript APIs but it is not in this question
More info:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#constructors
See example:
const p1 = new Person()
const p2 = new Person()
console.log(p1.displayNormalFunc1 === p2.displayNormalFunc1) // true
consele.log(p1.displayArrowFunc1 === p2.displayArrowFunc1) // false
and
class Foo {
pLog() {
console.log('p a')
}
log = () => console.log('a')
}
const bar = new Foo()
bar.pLog() // 'p a'
Foo.prototype.pLog = () => console.log('p b')
bar.pLog() // 'p b'
bar.log() // 'a'
Foo.prototype.log = () => console.log('b')
bar.log() // 'a'
This actually is nothing to do with whether the functions are arrow functions or not. I can rewrite your example with the displayArrowFunc properties defined in the same way but as "regular" function expressions, and the result is the same:
class Person {
pName = "Hasnain";
pAge = 22;
displayNormalFunc1()
{
return this;
}
displayNormalFunc2()
{
return this;
}
displayArrowFunc1 = function()
{
return this;
}
displayArrowFunc2 = function()
{
return this;
}
}
objp = new Person();
console.log(objp)
// see comments on answer
console.log(Person.prototype);
console.log(Person.__proto__);
console.log(Person.__proto__ === Function.prototype);
The result is actually down to a few different things about Javascript objects and "classes". I'll try not to get too deep into this but give a quick summary of the key facts, with links.
Javascript has a version of "inheritance" for objects - for objects directly, not "classes" - which is quite different from that found in languages like Java and C#. Basically, each object is linked to a "prototype object" which is used to look up properties (including function properties, or "methods") that don't exist on the original object.
When you console.log an object, you'll only be shown the properties that "directly exist" on that object - not those that exist on its prototype, or its prototype's prototype and so on. Even though accessing those properties that are in the prototype chain will still work.
That's the root of what you observe - it turns out the "arrow style functions" in your original example (as well as the non-arrow ones in my modified example) are direct properties of the class instance objp, but the other ones are not.
The "normal functions" are not because this is how Javascript "classes" work. As I've tried to imply by the use of quotation marks, JS doesn't really have "clases" - they're syntactic sugar for a regular Javascript function. JS has the feature that any function can be called using the new operator, which will then construct a new object, whatever the function body itself actually does. This used to be the way, before ES6 introduced the class keyword (in around 2014/5), that people used "classes" in JS.
And the way to add "methods" to such a "class" would be like this:
function SomeClass(a) {
this.a = a;
}
SomeClass.prototype.someMethod = function() {
// do something here
}
Notice how the method is actually a property of the object SomeClass.prototype - which then (due to how JS works internally) becomes the "prototype object" (in the sense mentioned above) of any instance you construct via const someInstance = new SomeClass(2);.
And that's exactly what your "class" code gets transformed into - it's just a syntactic sugar. This is why displayNormalFunc and so on aren't logged - they're not on the actual instance object, but on its prototype.
As for why displayArrowFunc1 and friends are logged, that's because you've defined these in a different way inside your class - a way that is a more recent JS feature than "classes" themselves. These, where you put someProperty = something inside the class body, are known as class fields. Notice this sentence in the docs I linked to:
Public instance fields exist on every created instance of a class.
So in short, that's why they are logged - because they're on the instance, not its prorotype. This applies not only to "regular" values like your pName and pAge, but also the functions/methods you defined this way - functions in Javascript are just values like any other. And this is why as I said it's nothing to do with whether you defined those function expressions as arrow functions or not - it's the syntax you use to add them to the class.
In short, someProperty = someValue inside the class body puts the property directly on each constructed instance, including when someValue is a function. Whereas "standard" method definitions are a special syntax and they end up added to the prototype of all such instances - therefore they don't appear when an instance is logged.
With the release of ECMAScript 6 on June 2015, Javascript classes syntax was introduced.
This syntax:
class Polygon {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
is basically same as:
function Polygon(width, height) {
this.width = width;
this.height = height;
}
So what is the benefit of using class instead of traditional function?
And in what condition I should use class instead of function?
There are some differences between Class and Function - most people will start by saying that the Class is "just syntax sugar", but that sugar does matter quite a bit. When the JS parser is processing the JavaScript code the parser will save them in different AST nodes, like shown here the ClassDeclaration and ClassExpression are different node types in the resulting AST tree:
https://github.com/estree/estree/blob/master/es2015.md#classes
You can see that for this parser, the new ES6 Classes spec introduces a number of new AST elements to the syntax:
ClassBody
MethodDefinition
ClassDeclaration
ClassExpression
MetaProperty
Since the AST syntax is not standard, there can be more or less types depending on the parser, but what is important to notice that when the code enters the class declaration or class expression it will be interpreted differently by the JavaScript engine.
This means, that Class and Function declarations can not be exchanged. You can see this if you try to write
class notWorking {
return 1; // <-- creates a parser error
};
This is because when the parser encounters the class -keyword, it will start treating the following code as ClassBody of either ClassDeclaration or ClassExpression and then it expects to find MethodDefinitions.
This is a small problem, because creating private variables becomes a bit more challenging. The function declaration could define a private variable neatly like this:
function myClass() {
var privateVar;
}
The class declaration can not have this:
class myClass {
var privateVar; // ERROR: should be a method
}
This is because the syntax of class allows only methods to be declared inside the class body. At least right now.
However, there exists a proposal for creating private fields:
https://github.com/zenparsing/es-private-fields
Thus, in the future you might be able to say
class myClass {
#privateVar; // maybe this works in the future?
}
There is a separate answer considering the private properties in ES6 Classes, which is suggesting some workarounds, like the use of Symbols:
Private properties in JavaScript ES6 classes
var property = Symbol(); // private property workaround example
class Something {
constructor(){
this[property] = "test";
}
}
Naturally there are more differences between classes and functions. One of them is Hoisting 1 - unlike Functions, you can't declare the Class anywhere in the scope:
An important difference between function declarations and class
declarations is that function declarations are hoisted and class
declarations are not. You first need to declare your class and then
access it
The Class declarations and Function declarations are quite similar;
function foo1() {} // can be used before declaration
class foo2{} // new foo2(); works only after this declaration
The class expressions work quite similarly to function expressions, for example they can be assigned to a variable:
var myClass = class foobar {};
More differences are 1
The Class expression / declaration body is always executed in Strict mode - no need to specify that manually
Classes have special keyword constructor - there can be only one of them, or error is thrown. Functions could have multiple definitions of variable of function named "constructor"
Classes have special keyword super which relates to the parent classes constructor. If you are inside the constructor you can call super(x, y); to call the parent class constructor but inside the Method you can call super.foobar() to create call to any parent class function. This kind of functionality is not available for standard Functions although you might emulate it with some custom hacking.
Inside class body you can define function with static keyword so it can be called using only ClassName.FunctionName() -syntax.
Both class declarations and expressions can use extends keyword like class Dog extends Animal
MethodDeclaration does not need function -prefix, thus you can define function "ok" inside the class "m" like this: class m { ok() { } }. Actually it is not even allowed to define function as class m { function ok() { } }
However, after the parser has completed it's job, the class instance is essentially running the same way as any other object.
The new ES6 Class syntax is essentially more clear way of expressing objects in a traditional OOP way and if you like it, then you should use it.
EDIT: also, the ES6 Class syntax has also another limitation: it does not allow the member functions to use lexically binded using fat arrow. ES7 seems to have experimental feature allowing it. That can be useful for example when binding methods to event handlers, the related question is here.
1 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
class its nothing but a syntactical sugar over javascript logic class creation using function. if you are using a function as class the entire function is act as a constructor, if you want to put other member functions you need to do that in constructor like this.something = ..., or var something = ... in case of private members (if you are not injecting from outside, assume you are creating object with other methods / properties), but in case of class the entire function is not actually act a constructor you can explicitly separate it with other member functions and data.
I know that ES6 solved a lot of problems that existed with the this keyword in ES5, such as arrow functions and classes.
My question relates to the usage of this in the context of an ES6 class and why it has to be written explicitly. I was originally a Java developer and I come from a world where the following lines of code were perfectly natural.
class Person {
private String myName;
public Person() {
myName = "Heisenberg";
}
public void sayMyName() {
System.out.println("My name is " + myName);
}
}
The compiler will always refer to the value of the field myName, unless it has a local variable with the name myName defined in the scope of the method.
However, once we convert this code to ES6:
class Person {
constructor() {
this.myName = "Heisenberg";
}
sayMyName() {
console.log(`My name is ${myName}`);
}
}
This won't work and it will throw an Uncaught ReferenceError: myName is not defined. The only way to fix this is to throw in an explicit this reference:
console.log(`My name is ${this.myName}`)
I understand the need for this in the constructor since ES6 classes don't allow your fields to be defined outside of the constructor, but I don't understand why the Javascript engines can't (or won't, because of the standard) do the same as Java compilers can in the case of sayMyName
Maybe I will not directly answer your question, but I'll try to direct the way you should think about JS class keyword.
Under the cover there is no magic about it. Basically it's a syntatic sugar over prototypal inheritance that was since the beginning of JavaScript.
To read more about classes in JS click here and here.
As of why you need explicitly write this is that because in JS it's always context sensitive, so you should refer to exact object. Read more.
// bad
class Listing extends React.Component {
render() {
return <div>{this.props.hello}</div>;
}
}
// bad (relying on function name inference is discouraged)
const Listing = ({ hello }) => (
<div>{hello}</div>
);
// good
function Listing({ hello }) {
return <div>{hello}</div>;
}
This is taken from the Airbnb react style guide. Can someone please explain why "relying on function name inference is discouraged"? Is it just a style concern?
I think this could also have something to do with the unexpected behaviour that you might run into from implicitly giving a lexical name to what you may expect to be an anonymous function.
Say for example someone understood the arrow function:
(x) => x+2;
To have the regular function equivalent:
function(x) {
return x+2;
}
It would be pretty easy to expect this code:
let foo = (x) => x+2;
To then be the equivalent of:
let foo = function(x) {
return x+2;
}
Where the function remains anonymous and would be incapable of referencing itself to do things like recursion.
So if then, in our blissful ignorance, we had something like this happening:
let foo = (x) => (x<2) ? foo(2) : "foo(1)? I should be a reference error";
console.log(foo(1));
It would successfully run because that function obviously wasn't anonymous:
let foo = function foo(x) {
return (x<2) ? foo(2) : "foo(1)? I should be a reference error";
}
This could potentially be exacerbated by the fact that in other situations where Babel implicitly adds a name to anonymous functions, (which I think is actually a bit of a side-effect of supporting implicit function names in the first place, though I could be wrong on that), they correctly handle any edge cases and throw reference errors where you would expect.
For example:
let foo = {
bar: function() {}
}
// Will surprisingly transpile to..
var foo = {
bar: function bar() {}
};
// But doing something like:
var foo = {
bar: function(x) {
return (x<2) ? bar(2) : 'Whats happening!?';
}
}
console.log(foo.bar(1));
// Will correctly cause a ReferenceError: bar is not defined
You can check 'view compiled' on this quick DEMO to see how Babel is actually transpiling that to maintain the behaviour of an anonymous function.
In short, being explicit with what you are doing is typically a good idea because you know exactly what to expect from your code. Discouraging the use of implicit function naming is likely a stylistic choice in support of this while also remaining concise and straightforward.
And probably hoisting. But hey, fun side trip.
EDIT #2: Found AirBnbs reason in their Javascript style guide
Don’t forget to name the expression - anonymous functions can make it harder to locate the problem in an Error's call stack (Discussion)
Original answer below
MDN has a good run-down on how function name inference works, including two warnings:
Observations
There is non-standard <function>.name inference behaviour in the following two scenarios:
when using script interpreters
The script interpreter will set a function's name property only if a function does not have an own property called name...
when using js tooling
Be careful when using Function.name and source code transformations such as those carried out by JavaScript compressors (minifiers) or obfuscators
....
In the uncompressed version the program runs into the truthy-branch and logs 'foo' is an instance of 'Foo' whereas in the compressed version it behaves differently and runs into the else-branch. Therefore, if you rely on Function.name like in the example above, make sure your build pipeline doesn't change function names or don't assume a function to have a particular name.
What is function name inference?
The name property returns the name of a function, or (before ES6 implementations) an empty string for anonymous functions
function doSomething() {}
console.log(doSomething.name); // logs "doSomething"
Functions created with the syntax new Function(...) or just Function(...) have their name property set to an empty string. In the following examples anonymous functions are created, so name returns an empty string
var f = function() {};
var object = {
someMethod: function() {}
};
console.log(f.name == ''); // true
console.log(object.someMethod.name == ''); // also true
Browsers that implement ES6 functions can infer the name of an anonymous function from its syntactic position. For example:
var f = function() {};
console.log(f.name); // "f"
Opinion
Personally I prefer (arrow) functions assigned to a variable for three basic reasons:
Firstly, I don't ever use function.name
Secondly, mixing lexical scope of named functions with assignment feels a little loose:
// This...
function Blah() {
//...
}
Blah.propTypes = {
thing: PropTypes.string
}
// ...is the same as...
Blah.propTypes = {
thing: PropTypes.string
}
function Blah() {
//...
}
// ALTERNATIVELY, here lexical-order is enforced
const Blah = () => {
//...
}
Blah.propTypes = {
thing: PropTypes.string
}
And thirdly, all things being equal, I prefer arrow functions:
communicate to reader that there is no this, no arguments etc
looks better (imho)
performance (last time I looked, arrow functions were marginally faster)
EDIT: Memory snapshots
I was listening to a Podcast and guest told of a situation were he had to deal with the limitations of using arrow functions with memory profiling, I have been in the exact same situation before.
Currently, memory snapshots will not include a variable name - so you might find yourself converting arrow functions to named functions just to hook up the memory profiler. My experience was quite straightforward, and I'm still happy with arrow functions.
Plus I've only used memory snapshots once, so I feel comfortable forgoing some "instrumention" for (subjective) clarity by default.
As any other style guide, Airbnb's is opinionated and isn't always well reasoned.
Function name property isn't supposed to be used for anything but debugging in client-side application because function original name is lost during minification. As for debugging, it becomes less efficient if a function doesn't have a meaningful name in call stack, so it's beneficial to preserve it in some cases.
A function gets name with both function definition like function Foo = () => {} and function named expression like an arrow in const Foo = () => {}. This it results in Foo function having a given name, Foo.name === 'Foo'.
Some transpilers follow the specification. Babel transpiles this code to ES5:
var Foo = function Foo() {};
And TypeScript breaks the specification:
var Foo = function () {};
This doesn't mean that named function expression is bad and should be discouraged. As long as a transpiler is spec-compliant or function name doesn't matter, this concern can be discarded.
The problem is applicable to transpiled applications. It depends on a transpiler in use and a necessity to keep function name property. The problem doesn't exist in native ES6.
This is because:
const Listing = ({ hello }) => (
<div>{hello}</div>
);
has an inferred name of Listing, while it looks like you are naming it, you actually aren't:
Example
// we know the first three ways already...
let func1 = function () {};
console.log(func1.name); // func1
const func2 = function () {};
console.log(func2.name); // func2
var func3 = function () {};
console.log(func3.name); // func3
what about this?
const bar = function baz() {
console.log(bar.name); // baz
console.log(baz.name); // baz
};
function qux() {
console.log(qux.name); // qux
}
This question already has answers here:
Private properties in JavaScript ES6 classes
(41 answers)
Closed 6 years ago.
In ES5, you could emulate a class with private and public variables like this:
car.js
function Car() {
// using var causes speed to be only available inside Car (private)
var speed = 10;
// public variable - still accessible outside Car
this.model = "Batmobile";
// public method
this.init = function(){
}
}
But in ES6, you can no longer declare vars outside the constructor, making it actually HARDER to work with classes in a OOP way!?
You can declare variables in the constructor using this, but that makes them public by default. This is very weird since ES6 DOES have a get / set keyword!
class Vehicle {
constructor(make, year) {
// the underscore is nice, but these are still public!
this._make = make;
this._year = year;
}
// get and set can be handy, but would make more sense
// if _make and _year were not accessible any other way!
get make() {
return this._make;
}
get year() {
return this._year;
}
}
ES6 standard does not offer a new way for defining private variables.
It's a fact, that new ES6 class is simply syntactic sugar around regular prototype-based constructors.
get and set keywords are offering a way for simplified definition of ES5 custom getters and setters that were previously defined with a descriptor of Object.defineProperty()
The best you could do is to use those techniques with Symbols or WeakMaps
The example below features the use of a WeakMap for storing private properties.
// myModule.js
const first_name = new WeakMap();
class myClass {
constructor (firstName) {
first_name.set(this, firstName);
}
get name() {
return first_name.get(this);
}
}
export default myClass;
I'm referring to article, written by David Vujic What? Wait. Really? Oh no! (a post about ES6 classes and privacy) with the idea of using WeakMaps.
The same way than in ES5: define the methods that must access the private variables in the constructor instead of the prototype, thus making them privileged methods.
Otherwise there in no good way to allow prototypical methods to access private data, but still hide it from the outside. You can try symbols, weakmaps or handshakes, but IMO none is perfect. See accessing private member variables from prototype-defined functions for some ideas.
But in ES6, you can no longer declare vars outside the constructor
And you don't need to. You didn't do it in your ES5 constructor either. You can translate your code literally to
class Car {
constructor() {
// local variable
var speed = 10;
// public property
this.model = "Batmobile";
// public method
this.init = () => {
…
}; // using an arrow function here simplifies things
}
}
Update January 2016 - whilst I found the approach given in the accepted answer correct, I would like to state that using modules and symbols is an effective information hiding technique in ES2015+ (but Class attributes using Symbols will be hidden, not strictly private).
An effective, lightweight information hiding can be achieved through a combination of ES2015 modules (which would only export what you declare as exported) and ES2015 symbols. Symbol is a new built-in type. Every new Symbol value is unique. Hence can be used as a key on an object.
If the client calling code doesn't know the symbol used to access that key, they can't get hold of it since the symbol is not exported.
Quick example using your code:
vehicle.js
const s_make = Symbol();
const s_year = Symbol();
export class Vehicle {
constructor(make, year) {
this[s_make] = make;
this[s_year] = year;
}
get make() {
return this[s_make];
}
get year() {
return this[s_year];
}
}
and to use the module vehicle.js
client.js
import {Vehicle} from './vehicle';
const vehicle1 = new Vehicle('Ford', 2015);
console.log(vehicle1.make); //Ford
console.log(vehicle1.year); // 2015
However, symbols although unique, are not actually private since they are exposed via reflection features like Object.getOwnPropertySymbols...
const vals = Object.getOwnPropertySymbols(vehicle1);
vehicle1[vals[0]] = 'Volkswagon';
vehicle1[vals[1]] = 2013;
console.log(vehicle1.make); // Volkswagon
console.log(vehicle1.year); // 2013
Takeaway message - Modules in general are a great way to hide something because if not exported then not available for use outside the module, and used with privately stored Symbols to act as the keys, then class attributes too can become hidden (but not necessarily private). Class declarations are particularly well suited when considered as part of well constructed modules (amd, commonjs, or es6/2015).