8.3 KiB
Class basic syntax
In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).
In practice, we often need to create many objects of the same kind, like users, or goods or whatever.
As we already know from the chapter info:constructor-new, new function
can help with that.
But in the modern JavaScript, there's a more advanced "class" construct, that introduces great new features which are useful for object-oriented programming.
The "class" syntax
The basic syntax is:
class MyClass {
// class methods
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
Then use new MyClass()
to create a new object with all the listed methods.
The constructor()
method is called automatically by new
, so we can initialize the object there.
For example:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// Usage:
let user = new User("John");
user.sayHi();
When new User("John")
is called:
- A new object is created.
- The
constructor
runs with the given argument and assignsthis.name
to it.
...Then we can call object methods, such as user.sayHi()
.
A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error.
The notation here is not to be confused with object literals. Within the class, no commas are required.
What is a class?
So, what exactly is a class
? That's not an entirely new language-level entity, as one might think.
Let's unveil any magic and see what a class really is. That'll help in understanding many complex aspects.
In JavaScript, a class is a kind of function.
Here, take a look:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// proof: User is a function
*!*
alert(typeof User); // function
*/!*
What class User {...}
construct really does is:
- Creates a function named
User
, that becomes the result of the class declaration. The function code is taken from theconstructor
method (assumed empty if we don't write such method). - Stores class methods, such as
sayHi
, inUser.prototype
.
After new User
object is created, when we call its method, it's taken from the prototype, just as described in the chapter info:function-prototype. So the object has access to class methods.
We can illustrate the result of class User
declaration as:
Here's the code to introspect it:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// class is a function
alert(typeof User); // function
// ...or, more precisely, the constructor method
alert(User === User.prototype.constructor); // true
// The methods are in User.prototype, e.g:
alert(User.prototype.sayHi); // alert(this.name);
// there are exactly two methods in the prototype
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
Not just a syntactic sugar
Sometimes people say that class
is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same without class
keyword at all:
// rewriting class User in pure functions
// 1. Create constructor function
function User(name) {
this.name = name;
}
// any function prototype has constructor property by default,
// so we don't need to create it
// 2. Add the method to prototype
User.prototype.sayHi = function() {
alert(this.name);
};
// Usage:
let user = new User("John");
user.sayHi();
The result of this definition is about the same. So, there are indeed reasons why class
can be considered a syntactic sugar to define a constructor together with its prototype methods.
Still, there are important differences.
-
First, a function created by
class
is labelled by a special internal property[[FunctionKind]]:"classConstructor"
. So it's not entirely the same as creating it manually.And unlike a regular function, a class constructor must be called with
new
:class User { constructor() {} } alert(typeof User); // function User(); // Error: Class constructor User cannot be invoked without 'new'
Also, a string representation of a class constructor in most JavaScript engines starts with the "class..."
class User { constructor() {} } alert(User); // class User { ... }
-
Class methods are non-enumerable. A class definition sets
enumerable
flag tofalse
for all methods in the"prototype"
.That's good, because if we
for..in
over an object, we usually don't want its class methods. -
Classes always
use strict
. All code inside the class construct is automatically in strict mode.
Besides, class
syntax brings many other features that we'll explore later.
Class Expression
Just like functions, classes can be defined inside another expression, passed around, returned, assigned, etc.
Here's an example of a class expression:
let User = class {
sayHi() {
alert("Hello");
}
};
Similar to Named Function Expressions, class expressions may have a name.
If a class expression has a name, it's visible inside the class only:
// "Named Class Expression"
// (no such term in the spec, but that's similar to Named Function Expression)
let User = class *!*MyClass*/!* {
sayHi() {
alert(MyClass); // MyClass name is visible only inside the class
}
};
new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass name isn't visible outside of the class
We can even make classes dynamically "on-demand", like this:
function makeClass(phrase) {
// declare a class and return it
return class {
sayHi() {
alert(phrase);
};
};
}
// Create a new class
let User = makeClass("Hello");
new User().sayHi(); // Hello
Getters/setters, other shorthands
Just like literal objects, classes may include getters/setters, computed properties etc.
Here's an example for user.name
implemented using get/set
:
class User {
constructor(name) {
// invokes the setter
this.name = name;
}
*!*
get name() {
*/!*
return this._name;
}
*!*
set name(value) {
*/!*
if (value.length < 4) {
alert("Name is too short.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // Name is too short.
The class declaration creates getters and setters in User.prototype
, like this:
Object.defineProperties(User.prototype, {
name: {
get() {
return this._name
},
set(name) {
// ...
}
}
});
Here's an example with a computed property name in brackets [...]
:
class User {
*!*
['say' + 'Hi']() {
*/!*
alert("Hello");
}
}
new User().sayHi();
Class properties
Class-level properties are a recent addition to the language.
In the example above, User
only had methods. Let's add a property:
class User {
*!*
name = "Anonymous";
*/!*
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi();
alert(User.prototype.sayHi); // placed in User.prototype
alert(User.prototype.name); // undefined, not placed in User.prototype
The property name
is not placed into User.prototype
. Instead, it is created by new
before calling the constructor, it's a property of the object itself.
Summary
The basic class syntax looks like this:
class MyClass {
prop = value; // property
constructor(...) { // constructor
// ...
}
method(...) {} // method
get something(...) {} // getter method
set something(...) {} // setter method
[Symbol.iterator]() {} // method with computed name (symbol here)
// ...
}
MyClass
is technically a function (the one that we provide as constructor
), while methods, getters and setters are written to MyClass.prototype
.
In the next chapters we'll learn more about classes, including inheritance and other features.