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) Great difference, It's easy to confuse new programmers learning the language.

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

Basically,ES6 Ofclass Can be seen as just a grammar sugar, Most of its functions,ES5 Can do it, Newclass
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 Ofclass rewrite, It's like this.
// Definition class 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 aconstructor Method, This is the construction method, andthis Keyword represents instance object. In other words,ES5 Constructor forPoint
, Corresponding ES6 OfPoint Construction method of class.

Point Class except construction method, It also defines atoString Method. Be careful, Definition“ class” When, No need to addfunction
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, 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 classesnew command, Exactly the same as the constructor.
class Bar { doStuff() { console.log('stuff'); } } var b = new Bar();
b.doStuff()// "stuff"
Constructor'sprototype attribute, stay ES6 Of“ class” It continues to exist. In fact, All methods of a class are defined in theprototype Attribute above.
class Point { constructor() { // ... } toString() { // ... } toValue() { // ...
} }// Equate 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 yesB Class instances, Itsconstructor The way isB Quasi prototypicalconstructor Method.

Because the methods of the class are defined inprototype Above object, So new methods of the class can be added to theprototype 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 Objectconstructor attribute, Direct pointing“ class” Itself, This and 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 method isPoint Methods defined within a class, It can't be enumerated. This point and 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 Writing,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 classgetArea, It's from an expression.

<> Strict mode

Inside classes and modules, The default is strict mode, So you don't need to useuse 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, adoptnew When the command generates an object instance, Call this method automatically. A class must haveconstructor Method, If not explicitly defined, An empty one.
constructor Method will be added by default.
class Point { } // Equate to class Point { constructor() {} }
In the above code, Defined an empty classPoint,JavaScript The engine will automatically add an emptyconstructor Method.

constructor Method returns the instance object by default( Namelythis), 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, As a result, the instance object is notFoo Class instances.

Class must usenew call, Otherwise, an error will be reported. This is a major difference between it and ordinary constructors, The latter need not.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 Exactly the same, Also usednew command. Said earlier, If you forget to addnew, Call like a functionClass, It will be reported wrong..
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( That is defined inthis On object), Otherwise, they are all defined on the prototype( That is defined inclass upper).
// Definition class 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 andy Are instance objectspoint Own attributes( Because it's defined inthis Variable), thereforehasOwnProperty Method returnstrue, andtoString
Is a property of a prototype object( Because it's defined inPoint Class), thereforehasOwnProperty Method returnsfalse. 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 andp2 All arePoint Example, Their prototypes are allPoint.prototype, therefore__proto__ Property is equal.

It also means, You can use the__proto__ Attribute is“ class” Adding 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 inp1 Added aprintName Method, Becausep1 The prototype ofp2 Archetype, thereforep2 You can also call this method. And, New instances created afterp3
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 isMyClass Instead ofMe,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, It 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 point and ES5 totally different.
new Foo(); // ReferenceError class Foo {}
In the above code,Foo Class before, After definition, 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, becauseBar inheritFoo When,Foo It's already defined. however, If existclass Promotion, The above code will report an error, becauseclass Will be promoted to the code header, and
let Orders are not promoted, So causeBar inheritFoo When,Foo Not defined yet.

<> Private method

Private method is a common requirement, but ES6 Do not provide, It can only be realized through flexible 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 calledbar.call(this, baz). This makesbar Actually becomes the private method of the current module.

Another way is to useSymbol Uniqueness of values, Name the private method aSymbol 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 andsnaf All areSymbol value, Which makes them unavailable to third parties, So it achieves the effect of private methods and private properties.

<> Private attributes

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>, byclass
Private attribute added. Method is before the property name, Use# Express.
class Point { #x; constructor(x = 0) { #x = +x; // finish writing sth. this.#x Also can } get x() {
return #x } set x(value) { #x = +value } }
In the above code,#x Private propertyx, stayPoint 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 Andget 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 adoptedprivate 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 [email protected] Represents a private property,ES6 Not using this symbol#
, [email protected] 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

Class if it containsthis, 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 inthis, Default directionLogger Class instances. 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 itprint Method results in an error.

A simpler solution is, Bind in constructorthis, So that we don't lose itprint 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 useProxy, When getting methods, Automatic bindingthis.
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 essentially,ES6 Class is just ES5 A layer of wrappers for the constructor of, So many of the features of a function areClass inherit, Includename attribute.
class Point {} Point.name // "Point"
name Property always returns immediatelyclass Class name after keyword.

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

And ES5 equally, stay“ class” Can be used insideget andset Keyword, Set the store value function and value taking function for a property, Block access to 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 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 inhtml Above the description object of the property, This and ES5 Completely consistent.

<>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 ClassSymbol.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, Addstatic
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 ClassclassMethod Before methodstatic Keyword, It shows that this method is a static method, Can be directly inFoo Class call(Foo.classMethod()
), Not inFoo 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 containsthis Keyword, thisthis Refers to the category. 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 methodbar Calledthis.baz, Therethis Refer toFoo class, Instead ofFoo Example, Equivalent to callFoo.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, Parent classFoo There is a static method, SubclassBar You can call this method.

Static methods are also available fromsuper 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, NamelyClass.propName, Not defined in the instance object(this) Properties on.
class Foo { } Foo.prop = 1; Foo.prop // 1
The above is written asFoo Class defines a static propertyprop.

at present, It's the only way to write it, because ES6 Clearly stipulate,Class There are only static methods inside, No static properties.
// Neither of the following is valid class Foo { // Write a way prop: 2 // Writing two 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 NamelyMyClass Instance properties for. stayMyClass Examples, This property can be read.

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

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

For readability purposes, For those inconstructor 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会报错.