follow [front end Xiaoao] , read more original technical articles
class
- The newly introduced class keyword in ES6 has the ability to formally define classes, and the concepts of prototype and constructor are still used behind it
Class definition
- Similar to function types, there are two main ways to define classes: class declaration and class expression. Both methods use the class keyword to enlarge parentheses
class Person {} // Class declaration var animal = class {} // Class expression
- Class expressions cannot be referenced before being evaluated (the same as function expressions), and class definitions cannot declare promotion (different from function expressions)
console.log(FunctionDeclaration) // [Function: FunctionDeclaration], function declaration in advance function FunctionDeclaration() {} console.log(ClassDeclaration) // Referenceerror: cannot access' classdeclaration 'before initialization, the class is not declared in advance class ClassDeclaration {}
- Class is limited by block level scope (function is limited by function scope)
{ function FunctionDeclaration2() {} class ClassDeclaration2 {} } console.log(FunctionDeclaration2) // [Function: FunctionDeclaration2] console.log(ClassDeclaration2) // ReferenceError: ClassDeclaration2 is not defined
Composition of classes
- Classes can contain constructor methods, instance methods, get functions, set functions and static class methods, or empty class definitions
- By default, the code in the class definition is executed in strict mode
- Class names are recommended to be capitalized (like constructors) to distinguish between classes and instances
class Foo {} // Empty class definition class Bar { constructor() {} // Constructor get myBaz() {} // Get function static myQux() {} // Static method }
- After assigning a class expression to a variable, you can access the class expression within the scope of the class expression and obtain the name of the class expression through the name attribute
var Person2 = class PersonName { identify() { console.log(PersonName) // Within the scope of class expression, access class expression console.log(Person2.name, PersonName.name) // Inside the scope of class expression, access the name of class expression /* [class PersonName] PersonName PersonName */ } } var p = new Person2() p.identify() console.log(Person2.name) // PersonName console.log(PersonName) // ReferenceError: PersonName is not defined, the scope of class expression is external, and the class expression cannot be accessed
class constructor
- Create the constructor of the class with the constructor keyword inside the class definition block:
- When you use the new operator to create an instance of a class, you call the constructor method
- The definition of the constructor is not required. Not defining the constructor is equivalent to defining the constructor as an empty function
instantiation
- Using the new operator to call the constructor of the class will perform the following operations (the same as the constructor):
- A new object (instance) was created
- The [[prototype]] attribute inside the new object is assigned as the prototype attribute of the constructor (pointing to the prototype together)
- Assign the scope of the constructor (i.e. this) to the new object
- Execute the code in the constructor (that is, add a new attribute to this object)
- Returns a new or non empty object
class Animal2 {} class Person3 { constructor() { console.log('person ctor') } } class Vegetable { constructor() { this.color = 'orange' } } var a = new Animal2() var p = new Person3() // 'person ctor' var v = new Vegetable() console.log(v.color) // 'orange'
- When a class is instantiated, the parameters passed in will be used as the parameters of the constructor (if there is no parameter, the class name can be left without parentheses)
class Person4 { constructor(name) { console.log(arguments.length) this.name = name || null } } var p1 = new Person4() // 0, no parameter, parentheses after Person4 can also be omitted console.log(p1.name) // null var p2 = new Person4('Jake') // 1 console.log(p2.name) // 'Jake'
- By default, the class constructor will return this object (class instance) after execution. If the returned object is not this object, the object is not associated with the class when detected with the instanceof operator
class Person5 { constructor() { this.foo = 'foo' } } var p3 = new Person5() console.log(p3) // Person5 {foo: 'foo'}, class instance console.log(p3.__proto__) // {}, class prototype console.log(p3.constructor) // [class Person5], the class itself acts as a constructor console.log(Person5 === p3.constructor) // true console.log(Person5.prototype === p3.__proto__) // true console.log(p3 instanceof Person5) // true, p3 is an instance of Person5 (Person5.prototype is on the prototype chain of p3) class Person6 { constructor() { return { bar: 'bar', // Returns a completely new object (not an instance of this class) } } } var p4 = new Person6() console.log(p4) // {bar: 'bar'} is not a class instance of Person6 console.log(p4.__proto__) // {}, Object prototype console.log(p4.constructor) // [Function: Object], Object constructor console.log(Object === p4.constructor) // true console.log(Object.prototype === p4.__proto__) // true console.log(p4 instanceof Person6) // false, p4 is not an instance of Person6 (Person6.prototype is not on the prototype chain of p4)
- The main difference between class constructors and constructors is that class constructors must use the new operator, and ordinary constructors can not use the new operator (as ordinary function calls)
function Person7() {} // Ordinary constructor class Animal3 {} // class constructor var p5 = Person7() // The constructor does not use the new operator and is called as an ordinary function var a1 = Animal3() // TypeError: Class constructor Animal3 cannot be invoked without 'new', class constructor must be instantiated with new operator var a2 = new Animal3()
- After the class constructor is instantiated, it will become an ordinary instance method. You can use the new operator to reference it on the instance
class Person8 { constructor() { console.log('foo') } } var p6 = new Person8() // 'foo' // p6.constructor() // TypeError: Class constructor Person8 cannot be invoked without 'new' new p6.constructor() // 'foo'
Treat classes as special functions
- Class is a special function, which can be detected by typeof operator
console.log(typeof Person8) // function
- The class ID has a prototype attribute (pointing to the prototype), and the constructor attribute of the prototype (default) points to the class itself
console.log(Person8.prototype) // {}, prototype console.log(Person8.prototype.constructor) // [class Person8], class itself console.log(Person8.prototype.constructor === Person8) // true
- You can use the instanceof operator to check whether the object pointed to by the class prototype exists in the prototype chain of the class instance
console.log(p6 instanceof Person8) // true, p6 is an instance of Person8
- When using the new operator to call the class itself, the class itself is treated as a constructor, and the constructor of the class instance points to the class itself
- When using the new operator to call the class constructor, * * class constructor (constructor()) * * is regarded as the constructor, and the constructor of the class instance points to the Function constructor
class Person9 {} console.log(Person9.constructor) // [Function: Function], pointing to the constructor of the Function prototype, that is, the Function constructor var p7 = new Person9() // new calls the class itself, which is treated as a constructor console.log(p7.constructor) // [class Person9], the constructor points to the constructor, that is, the class itself console.log(p7.constructor === Person9) // true console.log(p7 instanceof Person9) // true, p7 is an instance of Person9 var p8 = new Person9.constructor() // new calls the class constructor, and the class constructor (constructor()) is used as the constructor console.log(p8.constructor) // [Function: Function], constructor points to the constructor, that is, the Function constructor console.log(p8.constructor === Function) // true console.log(p8 instanceof Person9.constructor) // true, p8 is person9 Instance of constructor
- You can pass classes as parameters
let classList = [ class { constructor(id) { this._id = id console.log(`instance ${this._id}`) } }, ] function createInstance(classDefinition, id) { return new classDefinition(id) } var foo = new createInstance(classList[0], 3141) // 'instance 3141'
- Class can be instantiated immediately
var p9 = new (class Foo2 { constructor(x) { console.log(x) // 'bar' } })('bar') console.log(p9) // Foo2 {}, class instance console.log(p9.constructor) // [class Foo2], class itself
Instances, prototypes, and class members
- The syntax of a class is very convenient to define members that should exist on instances, prototypes, and the class itself
Instance member
- Inside the constructor() of a class, you can add its own properties to the instance
- Each instance corresponds to a unique member object, and all members will not be shared on the prototype
class Person10 { constructor() { this.name = new String('Jack') this.sayName = function () { console.log(this.name) } this.nickNames = ['Jake', 'J-Dog'] } } var p10 = new Person10() var p11 = new Person10() console.log(p10.name) // [String: 'Jack'], string wrapping object console.log(p11.name) // [String: 'Jack'], string wrapping object console.log(p10.name === p11.name) // false, not the same object (not shared) console.log(p10.sayName) // ƒ () {console.log(this.name)}, function object console.log(p11.sayName) // ƒ () {console.log(this.name)}, function object console.log(p10.sayName === p11.sayName) // false, not the same object (similarly, not shared) console.log(p10.nickNames === p11.nickNames) // false, similarly ↑ p10.name = p10.nickNames[0] p11.name = p10.nickNames[1] p10.sayName() // 'Jake', instance members do not affect each other p11.sayName() // 'J-Dog', instance members do not affect each other
Prototype method and accessor
- The method defined in the class block ({}) is used as the prototype method
class Person11 { constructor() { // Example method this.locate = () => { console.log('instance') } } locate() { // Prototype method console.log('prototype') } locate2() { // Prototype method console.log('prototype2') } } var p12 = new Person11() p12.locate() // 'instance', the instance method covers the prototype method p12.__proto__.locate() // 'prototype' p12.locate2() // 'prototype2'
- Methods can be defined in class constructors or class blocks, and attributes cannot be defined in class blocks
class Person12 { name: 'jack' // Uncaught SyntaxError: Unexpected identifier }
- You can use strings, symbols, or calculated values as keys for class methods
const symbolKey = Symbol('symbolKey') class Person13 { stringKey() { // String as key console.log('stringKey') } [symbolKey]() { // Symbol as key console.log('symbolKey') } ['computed' + 'Key']() { // Calculated value as built console.log('computedKey') } }
- Define get and set accessors in the class block ({})
class Person14 { set setName(newName) { this._name = newName } get getName() { return this._name } } var p13 = new Person14() p13.setName = 'Jake' console.log(p13.getName) // 'Jake'
Static class method
- Define static class methods in the class block ({}), which exist on the class itself
- Each class can have only one static class member
class Person15 { constructor() { // The content added to this exists on different instances this.locate = () => { console.log('instance', this) // this here is a class instance } } locate() { // Defined on the prototype object of the class console.log('prototype', this) // this here is a class prototype } static locate() { // Defined on the class itself console.log('class', this) // this here is the class itself } } var p14 = new Person15() p14.locate() // 'instance' Person15 { locate: [Function (anonymous)] } p14.__proto__.locate() // 'prototype' {} Person15.locate() // 'class' [class Person15]
- Static class methods are well suited as instance factories
class Person16 { constructor(age) { this._age = age } sayAge() { console.log(this._age) } static create() { return new Person16(Math.floor(Math.random() * 100)) } } console.log(Person16.create()) // Person16 { _age: ... }
Non functional prototypes and class members
- You can manually add member data on prototypes and classes outside the class definition
- The class definition does not show the method that supports adding data members. The instance should own the data referenced through this alone
class Person17 { sayName() { console.log(`${Person17.greeting} ${this.name}`) } } var p15 = new Person17() Person17.greeting = 'My name is' // Define data on a class Person17.prototype.name = 'Jake' // Define data on Prototype p15.sayName() // 'My name is Jake'
Iterator and generator method
- Generator methods can be defined on the prototype and the class itself
class Person18 { *createNicknameIterator() { // Define generator methods on prototypes yield 'Jack' yield 'Jake' yield 'J-Dog' } static *createJobIterator() { // Define generator methods in the class itself yield 'Butcher' yield 'Baker' yield 'Candlestick maker' } } var jobIter = Person18.createJobIterator() // Call the generator function to generate the generator object console.log(jobIter.next().value) // 'Butcher' console.log(jobIter.next().value) // 'Baker' console.log(jobIter.next().value) // 'Candlestick maker' var p16 = new Person18() var nicknameIter = p16.createNicknameIterator() // Call the generator function to generate the generator object console.log(nicknameIter.next().value) // 'Jack' console.log(nicknameIter.next().value) // 'Jake' console.log(nicknameIter.next().value) // 'J-Dog'
- The generator method can be used as the default iterator to turn the class instance into an iteratable object
class Person19 { constructor() { this.nickNames = ['Jack', 'Jake', 'J-Dog'] } *[Symbol.iterator]() { // Generator function as default iterator yield* this.nickNames.entries() } } var p17 = new Person19() for (let [i, n] of p17) { console.log(i, n) /* 0 'Jack' 1 'Jake' 2 'J-Dog' */ }
- Iterator instances can be returned directly
class Person20 { constructor() { this.nickNames = ['Jack', 'Jake', 'J-Dog'] } [Symbol.iterator]() { // Returns an iterator instance return this.nickNames.entries() } } var p18 = new Person20() for (let [i, n] of p18) { console.log(i, n) /* 0 'Jack' 1 'Jake' 2 'J-Dog' */ }
inherit
- ES6 supports class inheritance mechanism and still uses prototype chain
Inheritance basis
- ES6 class supports single inheritance. You can inherit any object with [[Construct]] and prototype (inheritable class or ordinary constructor) by using the extends keyword
class Vehicle {} class Bus extends Vehicle {} // Inheritance class var b = new Bus() console.log(b instanceof Bus) // true console.log(b instanceof Vehicle) // true function Person21() {} class Engineer extends Person21 {} // Inheritance constructor var p19 = new Engineer() console.log(p19 instanceof Engineer) // true console.log(p19 instanceof Person21) // true
- The extends keyword can be used in class expressions
var Bus2 = class extends Vehicle {}
- The subclass accesses the parent class and the methods defined on the parent class prototype through the prototype chain. The value of this reflects the instance or class that calls the corresponding method
class Vehicle2 { identifyPrototype(id) { // Methods defined on the parent class prototype console.log(id, this) } static identifyClass(id) { // Methods defined by the parent class itself console.log(id, this) } } class Bus3 extends Vehicle2 {} var v = new Vehicle2() var b2 = new Bus3() v.identifyPrototype('v') // 'v' Vehicle2 {}, this is the parent class instance b2.identifyPrototype('b') // 'b' Bus3 {}, this is a subclass instance v.__proto__.identifyPrototype('v') // 'v' {}, this is the parent class prototype b2.__proto__.identifyPrototype('b') // 'b' Vehicle2 {}, this is the subclass prototype, that is, the parent class instance Vehicle2.identifyClass('v') // v [class Vehicle2], this is the parent class itself Bus3.identifyClass('b') // b [class Bus3 extends Vehicle2], this is the subclass itself
Constructors, HomeObject, and super()
- In the subclass constructor, you can call the parent constructor through super()
class Vehicle3 { constructor() { this.hasEngine = true } } class Bus4 extends Vehicle3 { constructor() { super() // Call the constructor of the parent class console.log(this) // Bus4 {hasengine: true}, subclass instance, parent class constructor called console.log(this instanceof Vehicle3) // true console.log(this instanceof Bus4) // true } } new Bus4()
- In the subclass static method, you can call the parent class static method through super()
class Vehicle4 { static identifyV() { // Parent class static method console.log('vehicle4') } } class Bus5 extends Vehicle4 { static identifyB() { // Subclass static method super.identifyV() // Call the static method of the parent class } } Bus5.identifyB() // 'vehicle4'
-
ES6 adds an internal feature [[HomeObject]] to class constructors and static methods, pointing to the object that defines the method, and super will always be defined as the prototype of [[HomeObject]]
-
Several problems needing attention when using super:
- super can only be used in subclass constructors and subclass static methods
- You cannot reference the super keyword alone. You can either call the constructor or reference a static method
- Calling super() will call the parent constructor and assign the returned instance to this
- When calling super(), if you need to pass parameters to the constructor of the parent class, you need to pass them in manually
- If the subclass does not define a constructor, when instantiating it * * automatically calls super() * * and passes in all the parameters passed to the parent class
- You cannot reference this in a subclass constructor or static method before calling super()
- If the subclass explicitly defines the constructor, it can either call super() in it, or return a new object in it.
class Vehicle5 { constructor(id) { // Super() / / syntax error: 'super' keyword unexpected here, super can only be used in subclass constructors and subclass static methods this.id = id } } class Bus6 extends Vehicle5 { constructor(id) { // console. Log (super) / / syntax error: 'super' keyword unexpected here. Super cannot be referenced separately // console.log(this) // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor. This cannot be referenced before calling super() super(id) // Call the parent constructor, manually pass parameters to the parent constructor, and assign the returned instance to this (subclass instance) console.log(this) // Bus6 {ID: 5}, subclass instance console.log(this instanceof Vehicle5) // true console.log(this instanceof Bus6) // true } } new Bus6(5) class Bus7 extends Vehicle5 {} // Subclass does not define constructor console.log(new Bus7(6)) // Bus7 {ID: 6}. When instantiating, super() is automatically called and parameters are passed class Bus8 extends Vehicle5 { // constructor() {} // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor constructor(id) { super(id) // Subclasses explicitly define constructors or call super() } } class Bus9 extends Vehicle5 { // constructor() {} // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor constructor(id) { return {} // Subclasses explicitly define constructors or return other objects } } console.log(new Bus8(7)) // Bus8 {ID: 7}, subclass instance console.log(new Bus9(8)) // {}, return new object
Abstract base class
- You can set that the parent class is only inherited by the child class and will not be instantiated. You can use new Target detects whether it is an abstract base class
- A method must be defined for the subclass of the parent class. Check whether the corresponding method exists through this
class Vehicle6 { constructor() { console.log(new.target) if (new.target === Vehicle6) { // Prevent abstract base classes from being instantiated throw new Error('Vehicle6 cannot be directly instantiated') } if (!this.foo) { // Subclasses are required to define foo() methods throw new Error('Inheriting class must define foo()') } } } class Bus10 extends Vehicle6 {} // Subclass does not define foo() method class Bus11 extends Vehicle6 { // Subclasses define foo() methods foo() {} } // new Vehicle6() // [class Vehicle6],Error: Vehicle6 cannot be directly instantiated // new Bus10() // [class Bus10 extends Vehicle6],Error: Inheriting class must define foo() new Bus11() // [class Bus11 extends Vehicle6]
Inherit built-in types
- You can extend built-in types using class inheritance
class SuperArray extends Array { // Add method to subclass prototype: shuffle arbitrarily shuffle() { for (let i = this.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) ;[this[i], this[j]] = [this[j], this[i]] } } } var a = new SuperArray(1, 2, 3, 4, 5) console.log(a instanceof Array) // true, a is an instance of Array console.log(a instanceof SuperArray) // true, a is an instance of SuperArray console.log(a) // SuperArray(5) [ 1, 2, 3, 4, 5 ] a.shuffle() console.log(a) // SuperArray(5) [ 3, 1, 2, 5, 4 ]
- Some methods of built-in types return new instances. By default, the returned instance type is the same as the original instance type
var a1 = new SuperArray(1, 2, 3, 4, 5) var a2 = a1.filter((x) => !!(x % 2)) // The filter method returns a new instance, and the instance type is consistent with that of a1 console.log(a1) // SuperArray(5) [ 1, 2, 3, 4, 5 ] console.log(a2) // SuperArray(3) [ 1, 3, 5 ] console.log(a1 instanceof SuperArray) // true console.log(a2 instanceof SuperArray) // true
- You can use symbol Categories defines the getter method of the static getter, which overrides the class returned by the method of the built-in type when creating a new instance
class SuperArray2 extends Array { // Symbol. Categories defines a static getter method that overrides the class returned when a new instance is created static get [Symbol.species]() { return Array // When a method of built-in type creates a new instance, it returns the Array type } } var a3 = new SuperArray2(1, 2, 3, 4, 5) var a4 = a3.filter((x) => !!(x % 2)) // The filter method returns a new instance, and the instance type has been overwritten (Array) console.log(a3) // SuperArray(5) [ 1, 2, 3, 4, 5 ] console.log(a4) // [ 1, 3, 5 ] console.log(a3 instanceof SuperArray2) // true console.log(a4 instanceof SuperArray2) // false
Class blending
- The extends keyword can be followed by any expression that can be parsed into a class or constructor in addition to the parent class
class Vehicle7 {} function getParentClass() { console.log('evaluated expression') return Vehicle7 // The expression is resolved to the Vehicle7 class } class Bus12 extends getParentClass {} new Bus12() // 'evaluated expression'
- Multiple mixed in elements can be concatenated in an expression through the mixed in mode, and the expression is finally resolved to a class that can be inherited
class Vehicle8 {} let FooMixin = (SuperClass) => // The expression receives a superclass as a parameter and returns a subclass class extends SuperClass { // Subclass prototype addition method foo() { console.log('foo') } } let BarMixin = (SuperClass) => // The expression receives a superclass as a parameter and returns a subclass class extends SuperClass { // Subclass prototype addition method bar() { console.log('bar') } } class Bus13 extends BarMixin(FooMixin(Vehicle8)) {} // Nested level by level inheritance: FooMixin inherits Vehicle8, BarMixin inherits FooMixin, and Bus13 inherits BarMixin var b3 = new Bus13() console.log(b3) // Bus13 {}, subclass instance b3.foo() // 'foo' inherits the methods on the superclass prototype b3.bar() // 'bar' inherits the method on the superclass prototype
- Expand the nested calls by writing an auxiliary function
function mix(BaseClass, ...Mixins) { /* reduce Receive 2 parameters: merge function that will run for each item and initial value of merge starting point (not required) The merge function receives four parameters: the previous merge value, the current item, the current index, and the array itself */ return Mixins.reduce( (pre, cur) => cur(pre), // Merge method: executes the current method, and the parameter is the previous merge value BaseClass // Merge initial value ) } class Bus14 extends mix(Vehicle7, FooMixin, BarMixin) {} var b4 = new Bus14() console.log(b4) // Bus14 {} b4.foo() // 'foo' b4.bar() // 'bar'
Summary & ask questions
- How to define JS classes? What are the similarities and differences between classes and functions?
- What can a class contain? How do I access class expressions and their names?
- What are the internal steps of class instantiation? What does the class constructor return by default? What is the impression of returning a new object?
- What are the main differences between class constructors and ordinary constructors?
- What data type does the class belong to? What does its prototype and constructor point to respectively? What is the difference between using the new operator to call the class itself and the class constructor?
- Write two pieces of code to express "class as parameter" and "class instantiation immediately"
- How to define instance members, prototype methods, accessor methods and static class methods of a class? Where are they defined in the class (instance / prototype / class itself)?
- Write a piece of code and use the static class method as the instance factory
- How to manually add class member data? Why does the class definition not show support for adding data members?
- Where can a generator method be defined in a class? Write a piece of code, add the generator method when defining the class, and use it as the default iterator
- Which objects can a class inherit through the extends keyword? What elements can be followed by extensions? The subclass can access the methods of the parent class through the prototype chain?
- What is the function and usage of super keyword? What are the precautions when using it?
- How to define an abstract base class? What is its function?
- Write a piece of code to extend the built-in object Array with class inheritance, and the subclass returns the Array instance type after calling the concat() method
- Write a piece of code, use the nested call of mixed mode to realize the level by level inheritance of multiple classes, and then use the auxiliary function to realize the nested expansion