Class basic syntax
Wikipedia
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 Constructor, operator "new",
new function
can help with that.正如我们从“构造函数”运算符“ new”一章中已经知道的那样,新功能可以帮助实现这一点。
But in the modern JavaScript, there’s a more advanced “class” construct, that introduces great new features which are useful for object-oriented programming.
但是在现代JavaScript中,有一个更高级的“类”构造,其中引入了许多很棒的新功能,这些功能对于面向对象的编程很有用。
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.然后使用new MyClass()与所有列出的方法创建一个新对象。
The
constructor()
method is called automatically bynew
, so we can initialize the object there.constructor()方法由new自动调用,因此我们可以在那里初始化对象
For example:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(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.构造函数使用给定的参数运行,并将this.name分配给它。
Then we can call object methods, such as
user.sayHi()
.No comma between class methods
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.
在JavaScript中,类是一种函数。
Here, take a look:
class User { construct(name) { this.name = name; } sayHi() { console.log(this.name); } } // proof: User is a function // 证明: User是一个函数 console.log(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).创建一个名为User的函数,该函数成为类声明的结果。函数代码取自构造函数方法(如果我们不编写此类方法,则假定为空)。
Stores class methods, such as
sayHi
, inUser.prototype
.将类方法(例如sayHi)存储在User.prototype中
After
new User
object is created, when we call its method, it’s taken from the prototype, just as described in the chapter F.prototype. So the object has access to class methods.创建新的User对象后,当我们调用它的方法时,它会从原型中获取,如F.prototype一章中所述。因此,对象可以访问类方法。
We can illustrate the result of
class User
declaration as:我们可以将类User声明的结果说明为:
![ClassDeclaration](C:UsersUnityPicturesSaved PicturesClassDeclaration.png)
Here’s the code to introspect it:
// proof: User is a function
// 证明: User是一个函数
console.log(typeof User); // function
class User {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(this.name);
}
}
// class is a function
console.log(typeof User);
// ...or, more precisely, the constructor method
// more precisely 更确切地说
console.log(User === User.prototype.constructor); // true
// The methods are in User.prototype, e.g:
// 方法在User.prototype中
console.log(User.prototype.sayHi); // console.log(this.name);
// there are exactly two methods in the prototype
// 原型中有两种方法
console.log(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 withoutclass
keyword at all:有时人们会说类是一种“语法糖”(语法旨在使内容更易于阅读,但没有引入任何新内容),因为实际上我们完全可以在没有class关键字的情况下声明相同的内容:
// rewriting class User in pure functions
// 用纯函数重写类User
// 1. Create constructor function
function User(name) {
this.name = name;
}
// a 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() {
console.log(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 labeled by a special internal property[[FunctionKind]]:"classConstructor"
. So it’s not entirely the same as creating it manually.首先,由类创建的函数由特殊的内部属性[[FunctionKind]]:“ classConstructor”标记。因此,它与手动创建并不完全相同。
The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with
new
:语言会在许多地方检查该属性。例如,与常规函数不同,必须使用new来调用它:
class User { constructor() { } } console.log(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…”
另外,大多数JavaScript引擎中类构造函数的字符串表示形式都以“ class…”开头
class User { constructor() { } } console.log(User); // class User { ... }
There are other differences, we’ll see them soon.
还有其他差异,我们会尽快看到。
Besides,
class
syntax brings many other features that we’ll explore later.此外,类语法还带来了许多其他功能,我们将在以后进行探讨。
Class methods are non-enumerable. A class definition sets
enumerable
flag tofalse
for all methods in the"prototype"
.类方法是不可枚举的。对于“原型”中的所有方法,类定义将可枚举标志设置为false。
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.类始终使用严格。类构造中的所有代码都自动进入严格模式。
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() { console.log("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() { console.log(MyClass); // MyClass name is visible only inside the class } }; new User().sayHi(); // works, shows MyClass definition console.log(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() { console.log(phrase); } }; } // Create a new class let User = makeClass("Hello"); new User().sayHi(); // Hello
Getters/Setters
Just like literal objects, classes may include getters/setters, computed properties etc.
就像文字对象一样,类可能包含getters/setters,计算属性等
Here’s an example for
user.name
implemented usingget/set
:这是使用get / set实现的user.name的示例:
class User { constructor(name) { // invokes the setter this.name = name; } get name() { return this._name; } set name(value) { if (value.length < 4) { console.log("Name is too short."); return; } this._name = value; } } let user = new User("John"); console.log(user.name); // John user = new User(""); // Name is too short.
Technically, such class declaration works by creating getters and setters in
User.prototype
.从技术上讲,此类声明通过在User.prototype中创建getters和setters来起作用。
Computed names [...]
Here’s an example with a computed method name using brackets
[...]
:这是一个使用方括号[...]计算出的方法名称的示例:
class User { ['say' + 'Hi']() { console.log("Hello"); } } new User().sayHi();
Such features are easy to remember, as they resemble that of literal objects.
这些特征很容易记住,因为它们类似于文字对象的特征。
Class Fields
Old browsers may need a polyfill
Class fields are a recent addition to the language.
Previously, our classes only had methods.
以前,我们的类只有方法。
“Class fields” is a syntax that allows to add any properties.
“类字段”是一种允许添加任何属性的语法。
For instance, let’s add
name
property toclass User
:例如,让我们将name属性添加到User类:
class User { name = "John"; sayHi() { console.log(`Hello, ${this.name}!`); } } new User().sayHi(); // Hello, John!
So, we just write " = " in the declaration, and that’s it.
因此,我们在声明中只写“ =”即可。
The important difference of class fields is that they are set on individual objects, not
User.prototype
:类字段的重要区别在于它们是在单个对象上设置的,而不是在User.prototype上设置的:
class User { name = "John"; } let user = new User(); console.log(user.name); // John console.log(User.prototype.name); // undefined
Technically, they are processed after the constructor has done it’s job, and we can use for them complex expressions and function calls:
从技术上讲,它们是在构造函数完成工作后进行处理的,我们可以为它们使用复杂的表达式和函数调用:
class User { name = prompt("Name, please?", "John"); } let user = new User(); console.log(user.name); // John
Making bound methods with class fields
As demonstrated in the chapter Function binding functions in JavaScript have a dynamic
this
. It depends on the context of the call.如本章所述,JavaScript中的函数绑定函数具有动态的功能。这取决于调用的上下文。
So if an object method is passed around and called in another context,
this
won’t be a reference to its object any more.因此,如果传递一个对象方法并在另一个上下文中调用它,那么它将不再是对其对象的引用。
For instance, this code will show
undefined
:例如,此代码将显示未定义:
class Button { constructor(value) { this.value = value; } click() { console.log(this.value); } } let button = new Button("hello"); setTimeout(button.click, 1000); // undefined
The problem is called "losing
this
".There are two approaches to fixing it, as discussed in the chapter Function binding:
有两种解决方法,如功能绑定一章中所述:
Pass a wrapper-function, such as
setTimeout(() => button.click(), 1000)
.传递包装函数,例如
setTimeout(() => button.click(), 1000)
Bind the method to object, e.g. in the constructor:
将方法绑定到对象,例如在构造函数中:
class Button { constructor(value) { this.value = value; this.click = this.click.bind(this); } click() { console.log(this.value); } } let button = new Button("hello"); setTimeout(button.click, 1000); // hello
Class fields provide a more elegant syntax for the latter solution:
类字段为后一种解决方案提供了更优雅的语法:
class Button { constructor(value) { this.value = value; } click = () => { console.log(this.value); } } let button = new Button("hello"); setTimeout(button.click, 1000); // hello
The class field
click = () => {...}
creates an independent function on eachButton
object, withthis
bound to the object. Then we can passbutton.click
around anywhere, and it will be called with the rightthis
.类字段click =()=> {...}在每个Button对象上创建一个独立的函数,并将此函数绑定到该对象。然后我们可以传递button.click在任何地方,它会被正确地调用。
That’s especially useful in browser environment, when we need to setup a method as an event listener.
当我们需要将方法设置为事件监听器时,这在浏览器环境中尤其有用。
Summary
The basics 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 asconstructor
), while methods, getters and setters are written toMyClass.prototype
.从技术上讲MyClass是一个函数(我们作为构造函数提供的函数),而方法,getter和setter则写入MyClass.prototype。
In the next chapters we’ll learn more about classes, including inheritance and other features.
在下一章中,我们将学习有关类的更多信息,包括继承和其他功能。
Tasks
Rewrite to class
importance 5
The
Clock
class is written in functional style. Rewrite it the “class” syntax.Clock类以函数样式编写。用“类”语法重写它。
function Clock({ template }) {
let timer;
function render() {
let date = new Date();
let hours = date.getHours();
if (hours < 10) hours = "0" + hours;
let mins = date.getMinutes();
if (mins < 10) mins = "0" + mins;
let secs = date.getSeconds();
if (secs < 10) secs = "0" + secs;
let output = template
.replace("h", hours)
.replace("m", mins)
.replace("s", secs);
console.log(output);
}
this.stop = function() {
clearInterval(timer);
};
this.start = function() {
render();
timer = setInterval(render, 1000);
};
}
let clock = new Clock({ template: "h:m:s" });
clock.start();
class Clock {
constructor({ template }) {
this.template = template;
}
render() {
let date = new Date();
let hours = date.getHours();
if (hours < 10) hours = "0" + hours;
let mins = date.getMinutes();
if (mins < 10) mins = "0" + mins;
let secs = date.getSeconds();
if (secs < 10) secs = "0" + secs;
let output = this.template
.replace("h", hours)
.replace("m", mins)
.replace("s", secs);
console.log(output);
}
stop() {
clearInterval(this.timer);
}
start() {
this.render();
this.timer = setInterval(() => this.render(), 1000);
}
}
let clock = new Clock({ template: "h:m:s" });
clock.start();