Class Basic grammar of

<>Class Basic grammar of

<> brief introduction

JavaScript In language , The traditional way to generate instance objects is through constructors . Here is an example .
function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString =
function () { return '(' + this.x + ', ' + this.y + ')'; }; var p = new Point(1,
2);
The above method is similar to the traditional object-oriented language ( such as C++ and Java) Very different , It's easy to confuse new programmers learning the language .

ES6 Provides a more traditional approach , Introduced Class( class ) This concept , Template as object . adopt class keyword , Classes can be defined .

Basically ,ES6 Of class Can be seen as just a grammar sugar , Most of its functions ,ES5 Can do it , new class
The writing method is just to make the writing method of the object prototype clearer , It's more like the syntax of object-oriented programming . For the above code ES6 Of class rewrite , It's like this .
// Defining classes class Point { constructor(x, y) { this.x = x; this.y = y; } toString() {
return '(' + this.x + ', ' + this.y + ')'; } }
The above code defines a “ class ”, You can see that there's a constructor method , This is the construction method , and this Keyword represents instance object . in other words ,ES5 Constructor for Point
, corresponding ES6 Of Point Construction method of class .

Point Class except construction method , It also defines a toString method . be careful , definition “ class ” When , No need to add function
This keyword , Just put in the function definition . in addition , Methods do not need to be separated by commas , If you add it, you will report an error .

ES6 Class of , Another way to write a constructor .
class Point { // ... } typeof Point // "function" Point ===
Point.prototype.constructor// true
The above code indicates , The data type of a class is a function , Class itself points to a constructor .

When using , It is also used directly for classes new command , Exactly the same as the constructor .
class Bar { doStuff() { console.log('stuff'); } } var b = new Bar();
b.doStuff()// "stuff"
Constructor's prototype attribute , stay ES6 Of “ class ” It continues to exist . in fact , All methods of a class are defined in the prototype Attribute above .
class Point { constructor() { // ... } toString() { // ... } toValue() { // ...
} }// Equivalent to Point.prototype = { constructor() {}, toString() {}, toValue() {}, };
Call a method on an instance of a class , In fact, it is to call the methods on the prototype .
class B {} let b = new B(); b.constructor === B.prototype.constructor // true
In the above code ,b yes B Instance of class , its constructor The way is B Archetypal constructor method .

Because the methods of the class are defined in prototype Above object , So new methods of the class can be added to the prototype Above object .Object.assign
Method can easily add more than one method to a class at a time .
class Point { constructor(){ // ... } } Object.assign(Point.prototype, {
toString(){}, toValue(){} });
prototype Object's constructor attribute , Direct to “ class ” Of itself , It's about ES5 The behavior of is consistent .
Point.prototype.constructor === Point // true
in addition , All defined methods within a class , It's all inestimable (non-enumerable).
class Point { constructor(x, y) { // ... } toString() { // ... } } Object
.keys(Point.prototype)// [] Object.getOwnPropertyNames(Point.prototype) //
["constructor","toString"]
In the above code ,toString The way is Point Methods defined within a class , It can't be enumerated . This is related to ES5 Inconsistent behavior of .
var Point = function (x, y) { // ... }; Point.prototype.toString = function() {
// ... }; Object.keys(Point.prototype) // ["toString"] Object
.getOwnPropertyNames(Point.prototype)// ["constructor","toString"]
The above code adopts ES5 How to write ,toString Methods are enumerable .

Property name of class , You can use expressions .
let methodName = 'getArea'; class Square { constructor(length) { // ... }
[methodName]() {// ... } }
In the above code ,Square Method name of class getArea, It's from an expression .

<> Strict mode

Inside classes and modules , The default is strict mode , So you don't need to use use strict Specify operation mode . As long as your code is written in a class or module , Only strict mode is available .

Considering all the code in the future , In fact, they are all running in modules , therefore ES6 In fact, the whole language has been upgraded to strict mode .

<>constructor method

constructor Method is the default method of a class , adopt new When the command generates an object instance , Call this method automatically . A class must have constructor method , If not explicitly defined , An empty one
constructor Method will be added by default .
class Point { } // Equivalent to class Point { constructor() {} }
In the above code , Defined an empty class Point,JavaScript The engine will automatically add an empty constructor method .

constructor Method returns the instance object by default ( Namely this), You can specify to return another object .
class Foo { constructor() { return Object.create(null); } } new Foo()
instanceof Foo // false
In the above code ,constructor Function returns a new object , The result is that the instance object is not Foo Instance of class .

Class must use new call , Otherwise, an error will be reported . This is a major difference between it and ordinary constructors , The latter is not used new Can also be executed .
class Foo { constructor() { return Object.create(null); } } Foo() //
TypeError: Class constructor Foo cannot be invoked without 'new'
<> Instance object of class

How to write instance objects of generated classes , And ES5 It's exactly the same , Also used new command . As I said before , If you forget to add new, Call like a function Class, Will report an error .
class Point { // ... } // report errors var point = Point(2, 3); // correct var point = new
Point(2, 3);
And ES5 equally , The properties of an instance unless explicitly defined in itself ( Defined in this On object ), Otherwise, they are all defined on the prototype ( Defined in class upper ).
// Defining classes class Point { constructor(x, y) { this.x = x; this.y = y; } toString() {
return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3);
point.toString()// (2, 3) point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
In the above code ,x and y Are instance objects point Own attributes ( Because it's defined in this On variables ), therefore hasOwnProperty Method return true, and toString
Is a property of a prototype object ( Because it's defined in Point On class ), therefore hasOwnProperty Method return false. These are all related to ES5 Consistent behavior .

And ES5 equally , All instances of a class share a prototype object .
var p1 = new Point(2,3); var p2 = new Point(3,2); p1.__proto__ === p2.__proto__
//true
In the above code ,p1 and p2 It's all Point Examples of , Their prototypes are all Point.prototype, therefore __proto__ Property is equal .

It also means , You can use the __proto__ Property is “ class ” Add method .

__proto__  It's not the language itself , This is a private attribute added by major manufacturers when they implement it , Although many modern browsers JS
This private property is provided in the engine , However, it is still not recommended to use this attribute in production , Avoid dependence on the environment . In production environment , We can use  Object.getPrototypeOf 
Method to get the prototype of the instance object , Then add a method for the prototype / attribute .
var p1 = new Point(2,3); var p2 = new Point(3,2); p1.__proto__.printName =
function () { return 'Oops' }; p1.printName() // "Oops" p2.printName() // "Oops"
var p3 = new Point(4,2); p3.printName() // "Oops"
The above code is in p1 Added a printName method , because p1 The prototype of p2 Prototype of , therefore p2 You can also call this method . and , New instances created after p3
You can also call this method . This means , Using instance's __proto__ Property override prototype , You have to be very careful , Not recommended , Because it's going to change “ class ” Original definition of , Affect all instances .

<>Class expression

Same as function , Classes can also be defined in the form of expressions .
const MyClass = class Me { getClassName() { return Me.name; } };
The above code defines a class with an expression . It should be noted that , The name of this class is MyClass instead of Me,Me Only in Class Internal code for is available , Refers to the current class .
let inst = new MyClass(); inst.getClassName() // Me Me.name // ReferenceError:
Me is not defined
The above code indicates ,Me Only in Class Internally defined .

If the inner part of the class is not used , Can be omitted Me, It can be written in the following form .
const MyClass = class { /* ... */ };
use Class expression , Can write immediate Class.
let person = new class { constructor(name) { this.name = name; } sayName() {
console.log(this.name); } }(' Zhang San '); person.sayName(); // " Zhang San "
In the above code ,person Is an instance of an immediate class .

<> No variable promotion

Class does not have variable promotion (hoist), This is related to ES5 totally different .
new Foo(); // ReferenceError class Foo {}
In the above code ,Foo Class before , Defined after , It's a mistake , because ES6
The declaration of the class will not be promoted to the code header . The reasons for this provision relate to the succession to be mentioned below , You must ensure that the subclass is defined after the parent .
{ let Foo = class {}; class Bar extends Foo { } }
The above code will not report an error , because Bar inherit Foo When ,Foo It's already defined . however , If present class Promotion of , The above code will report an error , because class Will be promoted to the code header , and
let Orders are not promoted , So it led to Bar inherit Foo When ,Foo Not defined yet .

<> Private method

Private method is a common requirement , but ES6 No , It can only be realized through flexible method simulation .

One way is to distinguish names .
class Widget { // public Method foo (baz) { this._bar(baz); } // Private method _bar(baz) { return
this.snaf = baz; } // ... }
In the above code ,_bar Underline before method , Indicates that this is a private method for internal use only . however , This kind of naming is not safe , Outside the class , You can still call this method .

Another way is to simply remove the private method from the module , Because all the methods inside the module are visible .
class Widget { foo (baz) { bar.call(this, baz); } // ... } function bar(baz) {
return this.snaf = baz; }
In the above code ,foo It's a public method , Internally called bar.call(this, baz). This makes bar Actually becomes the private method of the current module .

Another way is to use Symbol Uniqueness of values , Name the private method a Symbol value .
const bar = Symbol('bar'); const snaf = Symbol('snaf'); export default class
myClass{ // public Method foo(baz) { this[bar](baz); } // Private method [bar](baz) { return this
[snaf] = baz; }// ... };
In the above code ,bar and snaf It's all Symbol value , Which makes them unavailable to third parties , So it achieves the effect of private methods and private properties .

<> Private property

Same as private method ,ES6 Private property is not supported . at present , There's a proposal
<https://github.com/tc39/proposal-class-fields#private-fields>, by class
Private attribute added . Method is before the property name , use # express .
class Point { #x; constructor(x = 0) { #x = +x; // finish writing sth. this.#x Yes } get x() {
return #x } set x(value) { #x = +value } }
In the above code ,#x Private property x, stay Point This property cannot be read outside the class . You can also see , Private property and instance property can have the same name ( such as ,#x And get x()).

Private properties can specify initial values , Initialize at constructor execution time .
class Point { #x = 0; constructor() { #x; // 0 } }
Why introduce a new prefix # Represents a private property , Not adopted private keyword , Because JavaScript
Is a dynamic language , Using independent symbols seems to be the only reliable way , It can accurately distinguish whether an attribute is private or not . in addition ,Ruby Language use @ Represents a private property ,ES6 Not using this symbol #
, Because @ Has been left Decorator.

The proposal only specifies the writing of private property . however , Naturally , It can also be used to write private methods .
class Foo { #a; #b; #sum() { return #a + #b; } printSum() { console
.log(#sum()); }constructor(a, b) { #a = a; #b = b; } }
<>this Direction of

Class if it contains this, It points to an instance of the class by default . however , You have to be very careful , Once used alone , It's likely to be wrong .
class Logger { printName(name = 'there') { this.print(`Hello ${name}`); }
print(text) {console.log(text); } } const logger = new Logger(); const {
printName } = logger; printName();// TypeError: Cannot read property 'print' of
undefined
In the above code ,printName Method this, Default point Logger Instance of class . however , If you extract this method and use it alone ,this
Points to the environment in which the method runs , Because we couldn't find it print Method results in an error .

A simpler solution is , Bind in constructor this, So that we don't lose it print Method .
class Logger { constructor() { this.printName = this.printName.bind(this); }
// ... }
Another solution is to use the arrow function .
class Logger { constructor() { this.printName = (name = 'there') => { this
.print(`Hello ${name}`); }; } // ... }
Another solution is to use Proxy, When getting methods , Auto bind this.
function selfish (target) { const cache = new WeakMap(); const handler = { get
(target, key) {const value = Reflect.get(target, key); if (typeof value !==
'function') { return value; } if (!cache.has(value)) { cache.set(value,
value.bind(target)); }return cache.get(value); } }; const proxy = new Proxy
(target, handler);return proxy; } const logger = selfish(new Logger());
<>name attribute

Because in essence ,ES6 The class of ES5 A layer of wrappers for the constructor of , So many of the features of a function are Class inherit , include name attribute .
class Point {} Point.name // "Point"
name Property always returns immediately class Class name after keyword .

<>Class Value function of (getter) Sum store function (setter)

And ES5 equally , stay “ class ” Can be used inside get and set keyword , Set store value function and value function for a property , Block access behavior of this property .
class MyClass { constructor() { // ... } get prop() { return 'getter'; } set
prop(value) {console.log('setter: '+value); } } let inst = new MyClass();
inst.prop =123; // setter: 123 inst.prop // 'getter'
In the above code ,prop Attribute has corresponding store value function and value function , So both the assignment and the read behavior are customized .

Store value function and value function are set in the attribute Descriptor On object .
class CustomHTMLElement { constructor(element) { this.element = element; } get
html() {return this.element.innerHTML; } set html(value) { this
.element.innerHTML = value; } }var descriptor = Object
.getOwnPropertyDescriptor( CustomHTMLElement.prototype,"html" ); "get" in
descriptor// true "set" in descriptor // true
In the above code , Stored value function and value function are defined in html Above the description object of the property , It's about ES5 Exactly the same .

<>Class Of Generator method

If a method is preceded by an asterisk (*), That means the method is a Generator function .
class Foo { constructor(...args) { this.args = args; } * [Symbol.iterator]() {
for (let arg of this.args) { yield arg; } } } for (let x of new Foo('hello',
'world')) { console.log(x); } // hello // world
In the above code ,Foo Class Symbol.iterator Method has an asterisk before it , Indicates that the method is a Generator function .Symbol.iterator Method returns a
Foo Class's default traverser ,for...of The loop will automatically call the traverser .

<>Class Static method of

Class is the prototype of an instance , All methods defined in the class , Will be inherited by the instance . If before a method , add static
keyword , It means that the method will not be inherited by the instance , It is called directly through the class , This is called “ Static method ”.
class Foo { static classMethod() { return 'hello'; } } Foo.classMethod() //
'hello' var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is
not a function
In the above code ,Foo Class classMethod Before the method static keyword , It shows that this method is a static method , Can be directly in Foo Call on class (Foo.classMethod()
), Not in Foo Called on an instance of class . If you call a static method on an instance , An error will be thrown , Indicates that the method does not exist .

be careful , If the static method contains this keyword , this this Refers to class , Not an instance .
class Foo { static bar () { this.baz(); } static baz () { console.log('hello'
); } baz () {console.log('world'); } } Foo.bar() // hello
In the above code , Static method bar Called this.baz, there this refer to Foo class , instead of Foo Examples of , Equivalent to call Foo.baz
. in addition , From this example, we can see that , Static methods can have the same name as non-static methods .

Static method of parent class , Can be inherited by subclass .
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo {
} Bar.classMethod()// 'hello'
In the above code , Superclass Foo There is a static method , Subclass Bar You can call this method .

Static methods are also available from super Object called .
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo {
static classMethod() { return super.classMethod() + ', too'; } }
Bar.classMethod()// "hello, too"
<>Class Static and instance properties for

Static attributes refer to Class Its own properties , Namely Class.propName, Not defined in the instance object (this) Properties on .
class Foo { } Foo.prop = 1; Foo.prop // 1
The above is written as Foo Class defines a static property prop.

at present , It's the only way to write it , because ES6 Clearly defined ,Class There are only static methods inside , No static properties .
// Neither of the following is valid class Foo { // Style 1 prop: 2 // Writing method 2 static prop: 2 } Foo.prop //
undefined
There is currently a static proposal <https://github.com/tc39/proposal-class-fields>
, A new writing method is provided for instance attribute and static attribute .

(1) Instance properties of class

The instance properties of a class can be represented by an equation , Write to the definition of the class .
class MyClass { myProp = 42; constructor() { console.log(this.myProp); // 42 }
}
In the above code ,myProp namely MyClass Instance properties for . stay MyClass On the instance of , This property can be read .

before , We define instance properties , Can only be written in class constructor Inside the method .
class ReactCounter extends React.Component { constructor(props) { super(props);
this.state = { count: 0 }; } }
In the above code , Construction method constructor inside , Defined this.state attribute .

With a new way of writing , Can not be in constructor Method definition .
class ReactCounter extends React.Component { state = { count: 0 }; }
This style of writing is clearer than before .

For readability purposes , For those in constructor Instance properties defined in , New writing allows direct listing .
class ReactCounter extends React.Component { state; constructor(props) { super
(props);this.state = { count: 0 }; } }
(2) Static properties of class

Class 静态属性只要在上面的实例属性写法前面,加上static关键字就可以了.
class MyClass { static myStaticProp = 42; constructor() { console
.log(MyClass.myStaticProp);// 42 } }
同样的,这个新写法大大方便了静态属性的表达.
// 老写法 class Foo { // ... } Foo.prop = 1; // 新写法 class Foo { static prop = 1; }

上面代码中,老写法的静态属性定义在类的外部.整个类生成以后,再生成静态属性.这样让人很容易忽略这个静态属性,也不符合相关代码应该放在一起的代码组织原则.另外,新写法是显式声明(declarative),而不是赋值处理,语义更好.

<>new.target 属性

new是从构造函数生成实例对象的命令.ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new
命令作用于的那个构造函数.如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的.
function Person(name) { if (new.target !== undefined) { this.name = name; }
else { throw new Error('必须使用 new 命令生成实例'); } } // 另一种写法 function Person(name) {
if (new.target === Person) { this.name = name; } else { throw new Error('必须使用
new 命令生成实例'); } } var person = new Person('张三'); // 正确 var notAPerson =
Person.call(person,'张三'); // 报错
上面代码确保构造函数只能通过new命令调用.

Class 内部调用new.target,返回当前 Class.
class Rectangle { constructor(length, width) { console.log(new.target ===
Rectangle);this.length = length; this.width = width; } } var obj = new
Rectangle(3, 4); // 输出 true
需要注意的是,子类继承父类时,new.target会返回子类.
class Rectangle { constructor(length, width) { console.log(new.target ===
Rectangle);// ... } } class Square extends Rectangle { constructor(length) {
super(length, length); } } var obj = new Square(3); // 输出 false
上面代码中,new.target会返回子类.

利用这个特点,可以写出不能独立使用,必须继承后才能使用的类.
class Shape { constructor() { if (new.target === Shape) { throw new Error(
'本类不能实例化'); } } } class Rectangle extends Shape { constructor(length, width) {
super(); // ... } } var x = new Shape(); // 报错 var y = new Rectangle(3, 4); //
正确
上面代码中,Shape类不能被实例化,只能用于继承.

注意,在函数外部,使用new.target会报错.