preface

Because it is spring recruitment recently, I read the interviews of the big guys, and found that class and inheritance were mentioned, which happened to be partly related to TS and React that I recently learned, so I would like to make a summary of this part (the content of the article is relatively simple). Many references are made to the article, which is marked at the end. If you find any errors or misinterpretations in this article, please kindly point them out.

This article (with the test code) is available at github: github.com/OnlyWick/Fu…

Welcome to star ~

Review that ES5 constructors create objects

Before we dive into ES6 classes, let’s review how constructors create objects in ES5

Create an object

function Person(name, sex, hobby) {
    this.name = name;
    this.sex = sex;
    this.hobby = hobby;
}

Person.prototype.say = function () {
    console.log("My name is" + this.name + ", a" + this.sex + "Live, I love it." + this.hobby);
}
Copy the code

Characteristics of the

  1. The function names use the big camel name method
  2. Inside the constructor, an object (this) is implicitly created and returned
  3. Can have a return value, if the return value is a primitive type, then ignored, reference type, then directly returned

disadvantages

  1. It is just named according to the naming convention and is essentially a normal function, so it is not usednewKeyword, can also be used
  2. All properties (including methods) are enumerable, and in general we don’t want methods to be enumerable
  3. Property definitions are separated from stereotype methods, which do not conform to the pattern of object-oriented programming languages (such as Java)
  4. Attribute or method sharing is not possible without a prototype

new.target

You can use the new.target attribute inside the function. If the current function is called with the new command, new.target points to the current function, otherwise undefined.

function Demo() {
    console.log(new.target)
}
new Demo();
Demo();
Copy the code

The class of ES6

Introduction to the

Although JavaScript archetypal mechanisms don’t work as well as traditional classes, that hasn’t stopped a strong trend for the language to stretch its syntactic sugar to express “classes” more like real classes, hence the ES6 class keyword and its associated mechanisms.

Declaration of a class

class Person {
    // Equivalent to the Person constructor, note: there can only be one constructor!!
    constructor(name, sex, hobby) {
        this.name = name;
    	this.sex = sex;
        this.hobby = hobby;
    }
    // Equivalent to person.prototype.say
    say() {
        console.log("My name is" + this.name + ", a" + this.sex + "Live, I love it." + this.hobby); }}// The above and below codes can be considered equivalent

function Person(name, sex, hobby) {
    this.name = name;
    this.sex = sex;
    this.hobby = hobby;
}

Person.prototype.say = function () {
    console.log("My name is" + this.name + ", a" + this.sex + "Live, I love it." + this.hobby);
}
Copy the code

Note: the (normal function) methods in class are automatically placed on the prototype and are not enumerable!

Immutability of class attributes

Unlike functions, class attributes (attributes of a class, not an object) cannot be assigned new values.

There are a lot of attributes, so just change a few.

Constant class name

The name of the class is constant only within the class, so changes to alignment within the class will give an error, but can be changed externally.

An internal Person, as declared by const, cannot be modified, while an external Person, as declared by let, can be modified at will

class Person {
    construtor() {
        Person = "Animal";	Assignment to constant variable Assignment to constant variable}}Copy the code

Note: It is necessary to distinguish the immutability of the class attribute above, which is merely a reassignment of the variable.

class Person {
}
Person = "Animal";
Copy the code

Accessors (getters and setters)

This syntax is very simple, much like the accessor for Object.defineProperty, so just look at it

class Person {
    constructor(name, sex, hobby) {
        this.name = name;
        this.sex = sex;
        this.hobby = hobby;
    }
    // The value function takes no arguments
    get name() {
        console.log("The evaluated function is running.");
        // Note the recursion problem
        return this._name;
    }
    // Save a value function to accept a parameter
    set name(value) {
        console.log("The save function is running.");
        // Note the recursion problem
        this._name = value;
    }
    say() {
        console.log("My name is" + this.name + ", a" + this.sex + "Live, I love it." + this.hobby); }}Copy the code

This accessor is relatively simple, the need to be careful to prevent recursion

Static members

Static members belong to the class itself, and instance objects of the class are not accessible.

class Person {
    constructor(name, sex, hobby) {
        this.name = name;
        this.sex = sex;
        this.hobby = hobby;
    }
    static isTall = true;
    
    static eat() {
        console.log("Humans can eat.");
    }
	
    say() {
        console.log("My name is" + this.name + ", a" + this.sex + "Live, I love it." + this.hobby); }}Copy the code

Static members reside on the implicit prototype (__proto__) of the instance object of the class, that is, the class object itself.

For those of you who don’t understand static members or instance objects, I’m going to give you the terms.

Properties: In addition to representing normal properties (states), methods (actions) are also called properties, but they are essentially the same, because methods are also properties

Member: Property or method of an object

Static member: A member belonging to a class

Instance member: A member belonging to an object

Instance objects: objects created by (constructor) functions, function objects (in this case, built-in function objects provided by JS, such as Array) and custom function objects (self-defined functions). This includes class, which is just a syntactic sugar.)

Static member prior to ES6

Prior to ES6, class members were added directly to constructors to simulate static members

function Person(name) {
    this.name = name;
}
Person.study = function() {
	console.log("Start learning");
}
Copy the code

Private methods and private properties

Private methods and properties, which can only be accessed inside a class but not outside it, are common requirements that facilitate code encapsulation, but ES6 does not provide such operations.

Method one: Naming differences

Prefix the name with a character such as _ or __ to indicate that it is a private property, although this is just a convention, as Vue has a similar $for a private property or method.

class Person {
    length = 10;
    constructor(name, weight) {
        this.name = name;
        this.weight = weight;
    }
    loseWeight() {
        this._exercise();
        console.log('Oh, I didn'tThe ${this.weight + this.length}Jin to thinThe ${this.weight}The jin `);
    }
	// Private attributes
    _exercise() {
        for(let i = 1; i <= this.length; i ++) {
            this.weight --; }}}Copy the code

Method two: Extract methods that need to be private

Because the methods in a class are visible to the outside world. So you can pull out methods that need to be private, and in the case of this, you can unbind them using methods like call.

function exercise() {
    for(let i = 0; i < this.length; i ++) {
        this.weight --; }}class Person {
    constructor(name, weight) {
        this.name = name;
        this.weight = weight;
    }
    loseWeight() {
        exercise.call(this); }}Copy the code

Method 3: Symbol

Take advantage of the uniqueness of Symbol

Mention a small point that can be skipped

By the way, some of the above methods might be thought **(yes, me)**, you can do this, I can see, I can modify the code, or I can call directly, so properties and methods are not private. Reflect.ownkeys () can also be used to retrieve symbols.

Lol, this is my idea of learning ES6 a long time ago. The above methods to make properties and methods private are just a few of our implementation methods, if you want to exploit the loophole is not easy? The purpose of proprietary is just something that we don’t want others to use directly when we’re building a wheel or encapsulating something.

Method 4: Use the proposed syntax

Use the # character to private the member.

Note: Since this syntax is being proposed, it is incompatible and needs to be usedbabelTo convert

Babel is what?

Babel is a toolchain for converting ECMAScript 2015+ version code into backwardly compatible JavaScript syntax so it can run in current and older versions of browsers or other environments.

Here is a brief introduction to the configuration items. For details, please go to Babel official website (attached address at the end of this article).

@babel/ Preset -env: An intelligent preset that lets you use ES2015+(preset) syntax

UseBuiltIns: Configure preset-env How to handle polyfill

Corejs: contains the syntax and API implementation of the latest ES release

@babel/plugin-proposal-class-properties: compatible with properties being proposed

@babel/plugin-proposal-private-method: a plugin for a private method in a proposal that is compatible with #method

//. Babelrc configuration {"presets": [["@babel/preset-env", {"useBuiltIns": "Corejs ": 3}]]," preset ": // Preset -env, preset is version 2 of Corejs, but downloaded version 3, path is inconsistent, set "corejs": 3}]], "preset ": [ "@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-private-methods"] }Copy the code
Class Person {// Private attribute #length = 10; constructor(name, weight) { this.name = name; this.weight = weight; } loseWeight() {// Use this.#exercise(); Console. log(' Oh, I lost weight from ${this.weight + this.#length} to ${this.weight}); } // private method #exercise() {console.log(" start exercise ") for (let I = 0; i < this.#length; i++) { this.weight--; } } } const p1 = new Person("Wick", 135); p1.loseWeight();Copy the code

Unable to access private property, error reported

Unable to access private method, error reported

Features:

  1. # is part of the member name and needs to be used together

  2. Private members can use getters and setters. Note that getters and setters cannot have the same name as private members

    With getters and setters, private members can be evaluated and assigned by them.

    class Counter { #xValue = 0; add() { console.log(++ this.#x); } print() {console.log(' ${this.#x} '); } get #x() { return this.#xValue; } set #x(value) { this.#xValue = value; } } const c1 = new Counter(); c1.add(); c1.add(); c1.add(); c1.print();Copy the code

Duplicate names are not allowed

Class Counter {// echo #xValue = 0; add() { console.log(++ this.#x); } print() {console.log(' ${this.#x} '); } get #xValue() {return this.#xValue; } set #xValue(value) {this.#xValue = value; } } const c1 = new Counter(); c1.add(); c1.add(); c1.add(); c1.print();Copy the code

  1. Not limited to this; instances of this class can be used as long as they are inside the class itself

    class Counter {
        #myName = "Counter";
    
        static getMyName(c) {
            return c.#myName;
        }
    }
    
    console.log(Counter.getMyName(new Counter()))
    Copy the code

Not an instance object of a class

class Counter { #myName = "Counter"; static getMyName(c) { return c.#myName; }} // [] is not an instance of the class console.log(counter.getMyName ([]))Copy the code

  1. Let’s add the static keyword and make it a static private member that can only be used in a class. Static is called by the name of the class, so it’s a static private member that can only be used in a class

    class Counter {
        static #Demo() {
            return "Counter";
        }
        static getMyName() {
            return Counter.#Demo();
        }
    }
    console.log(Counter.getMyName());
    Copy the code

The external call failed

class Counter {
    static #Demo() {
        return "Counter";
    }
    static getMyName() {
        return Counter.#Demo();
    }
}
Counter.#Demo();
Copy the code

The above code is just for the convenience of example, so there will be a lot of deficiencies, understand the meaning of good.

Property initializer

Property initializer, is part of ES7. If you are concerned about compatibility, use Babel for conversion

class Person {
    static age = 18;
	isTall = true;
}

// The upper and lower codes are equivalent
class Person {
    constructor() {
        // write it in the constructor
        this.isTall = true; }}Copy the code

Note the small difference between arrow functions and normal functions

When writing code, you can accidentally cause this to point to an error (due to strict schema).

Come kangkang, the normal function writing method, what will be the problem.

class Pig {

    constructor(name) {
        this.name = name;
    }

    eat() {
        console.log(this);
        console.log(`The ${this.name}Start the meal); }}const pig = new Pig(Fat Pig Number one);
const temp = pig.eat;
temp();
Copy the code

If you use this in a method, you are bound to, but there is a problem with this. If you accidentally change the direction of this, as in the case above, you will get an error.

To prevent the above error, many people use the arrow function to point this to the current instance object.

class Pig {
    constructor(name) {
        this.name = name;
    }

    eat = (a)= > {
        console.log(`The ${this.name}Start the meal); }}const pig = new Pig(Pig Number two.);
const temp = pig.eat;
temp();
Copy the code

If you are careful, you will notice that the eat method is no longer in the prototype, but in the instance object. This conflicts with our previous statement that function methods are placed on prototypes and are not enumerable.

That’s a no-brainer. It’s definitely taking up memory space. So we need to weigh which functions to use depending on the scenario, for example, an object won’t be created many times, that’s fine, we’ll let it take over, but some objects need to be created so many times that they can’t be used.

Such expressions

Anonymous expression

const Person = class {
    constructor(name) {
        this.name = name; }}console.log(new Person("Wick").name);
Copy the code

Named expression

const Person = class Person2 {
    constructor(name) {
        this.name = name; }}console.log(Person);
console.log(Person2);
Copy the code

An alternative use of class expressions that creates singletons and does not expose a reference to the class in scope.

const bugEngineer = new class {
    constructor(name) {
        this.name = name;
    }
    writeBug() {
        console.log(`The ${this.name}Start writing bugs);
    }
}("Wick");
Copy the code

Class of first class citizens

In a program, a first-class citizen is a value that can be passed into a function, returned from a function, and assigned to a variable. JavaScript functions are first-class citizens (also known as first-class functions), which is what makes JavaScript unique.

ES6 continues this tradition by making classes first-class citizens as well, allowing classes to use their features in a variety of ways.

Pass the class as an argument to the function

function createObject(classDef) {
    return new classDef();
}
const obj = createObject(class {
    sayHi() {
        console.log("Hi~")
    }
})
obj.sayHi();
Copy the code

Decorator function

Ha, ha, ha, ha, ha, ha, ha, ha.

This function was introduced in ES7 to modify the behavior of classes, and is in the second phase of the proposal, subject to major changes in future releases.

Decorators are class decorators, property decorators, method decorators, and for a variety of reasons, method decorators are just a little fun to play with here.

Note: Compatibility is very poor, not supported by browsers or NodeJs. So use Babel or TypeScript if you want to

I’m going to stick with Babel here (don’t ask why I don’t use TS, I haven’t learned TS yet, and at this point, I’m in technical tears again).

Install plug-in:

@babel/plugin-proposal-class-properties: compatible with properties being proposed

@babel/plugin-proposal-decorators: Used to support decorators

For more information, please visit our official website (address is at the end of this article)

{
    "presets": [["@babel/preset-env",
            {
                "useBuiltIns": "entry"."corejs": 3}]],"plugins": [["@babel/plugin-proposal-decorators",
            {
                "legacy": true}], ["@babel/plugin-proposal-class-properties",
            {
                "loose": true}}]]Copy the code

Go straight to the code. The information is in the comments

/** ** @param {*} target: used Object * @param {*} prop: used member name * @param {*} Descriptor: attribute descriptor (object.defineProperty) */
function writeBug(target, prop, descriptor) {
    console.log(target)
    console.log(prop)
    console.log(descriptor)
}
class Person {

    constructor(name) {
        this.name = name;
    }
    @writeBug
    writeCode() {
        console.log(`The ${this.name}Can write good code); }}Copy the code

Ha ha, you see, you’ve got the property descriptor, it’s not easy to do something else

/** ** @param {*} target: used object * @param {*} prop: used member name * @param {*} descriptor: property descriptor */
function writeBug(target, prop, descriptor) {
    console.log(target);
    console.log(prop);
    console.log(descriptor);
    // Store the original function
    let value = descriptor.value;
    // Override value to allow the function to accept arguments
    descriptor.value = function(args) {
        console.error(Don't listen to ` left left leftThe ${this.name}He is a Bug engineer);
        value.call(this); }}class Person {

    constructor(name) {
        this.name = name;
    }
    @writeBug
    writeCode() {
        console.log(`The ${this.name}Can write good code); }}const wick = new Person("Wick");
wick.writeCode();
Copy the code

Ha ha, just kidding, decorators are actually quite powerful, like you build a wheel with lots of methods in it, and then lots of people use it, and then one day, you realize, gee, this method is not very time and space complex, there’s a better way to implement it. But there are so many people using this method that you can’t just delete it because it will cause a lot of people’s projects to crash and they may not be able to figure out what the problem is. So one way is to update it in the next version, the other way is to borrow a decorator to indicate that the method is obsolete, please replace it with XXXX method (there are other methods of course, this is just an example).

Recently the React of learning, for one, it’s the old part of the life cycle of a hook function, can cause the Bug, but in the new version of the life cycle, they still exist, but you at the time of use, to make yellow, so the operation is a decorator to implement (does not read the source code, is here, for example).

Well, there are a couple of things about Taget

Case 1: when applied to a class

Target is the class function

Case two: On a member of a class

What comes to mind when you see constructor? Yes, target is the stereotype

inheritance

ES6 prior to inheritance implementation

Take a look at how classic inheritance was implemented prior to ES6.

Note: The following two code snippets with their pros and cons were taken from the article of Hu Yuba Da,Click direct to the article address

function Parent () {
    this.names = ['kevin'.'daisy'];
}

function Child () {
    Parent.call(this);
}

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy"]
Copy the code

Advantages:

1. Avoid attributions of reference types being shared by all instances (initialize all attributes to subclass instances)

2. You can pass parameters to Parent in Child

function Parent (name) {
    this.name = name;
}

function Child (name) {
    Parent.call(this, name);
}

var child1 = new Child('kevin');

console.log(child1.name); // kevin

var child2 = new Child('daisy');

console.log(child2.name); // daisy
Copy the code

Disadvantages:

Methods are defined in the constructor and are created each time an instance is created.


I had some ideas, so I made some changes.

/ / parent class
function BugEngineer(name) {
    // Abstract oriented programming
    if (new.target === BugEngineer) {
        throw new Error("This class is abstract and you cannot call it directly.");
    }
    this.name = name;
}

BugEngineer.prototype.writeBug = function () {
    console.log(`The ${this.name}Start writing bugs);
}
/ / subclass
function Engineer(name) {
    BugEngineer.call(this, name);
}

// Change the subclass prototype
Engineer.prototype = Object.create(BugEngineer.prototype);
// Change the subclass constructor
Engineer.prototype.constructor = BugEngineer;
Copy the code

To change the Object prototype (without considering direct assignment), there are two methods Object.setPrototypeof () and Object.create

Object. SetPrototypeOf can change Object prototypes, but I looked at the MDN documentation and it gave the following explanation.

Therefore, because of performance problems, it is recommended that we use Object.create to change the prototype. For more details, please go to MDN.

The text start

Extends and super

You’ve also looked at one of the ways inheritance was implemented prior to ES6, and you can see that it’s a bit of a hassle to implement inheritance. But in ES6, the extends and super keywords save us the hassle.

/ / parent class
class BugEngineer {
    constructor(name) {
        if(new.target === BugEngineer) {
           throw new Error("This class is abstract and you cannot call it directly.");
        }
        this.name = name;
    }
    writeBug() {
        console.log(`The ${this.name}Start writing bugs); }}/ / subclass
class Engineer extends BugEngineer {
    constructor(name) {
        // Equivalent to bugengineer.call (...)
        super(name);	// super represents the constructor of the parent class}}Copy the code

The stereotype will automatically adjust for us, and super will access the parent constructor ~

Precautions for using super:

  1. The constructor is specified and the super keyword must be used.

    If you have a constructor and you don’t use super, who is going to create a parent class for you to inherit from?

    class Engineer extends BugEngineer {}/ / equivalent to the
    class Engineer extends BugEngineer {
        constructor(name) {
            super(name); }}Copy the code

    Note: Class does not prevent you from doing anything else before super (except this), but I recommend putting all statements after super to avoid unnecessary complications

  1. onlyIn the derived classtheIn the constructorusesuper()If you try to use it in a non-derived class (one that is not declared with extends) or in a function, an error is reported.

Note: Derived classes and subclasses are the same thing

  1. In the constructorYou must call super before this, which initializes this, and if you try to access this before calling super, an error will be reported.

From the code below, you can see that when super() is executed, it points to the constructor of the subclass, so you know which subclass instance this in super() refers to

class A {
    constructor() {
        console.log(new.target.name, this); }}class B extends A {
    constructor() {
        super();
    }
}
new A();
new B();
Copy the code

  1. When super is an object, it points to the prototype object of the parent class in normal methods, and to the parent class in static methods. Note that!!!! is called as an object, not as a method

    / / parent class
    class BugEngineer {
        demo() {
            console.log("BugEngineer的demo"); }}/ / subclass
    class Engineer extends BugEngineer {
        constructor(name) {
            super(name);
            // as an object
            super.demo();
        }
        demo() {
            console.log('the demo"); }}const wick = new Engineer("Wick");
    Copy the code

    If super.demo() calls the method of the parent class, you can see that super refers to the bugengineer.prototype of the parent class. So super. Demo () is equivalent to the BugEngineer. Prototype. The demo ().

Now that you know it’s pointing to the parent’s prototype, you can’t call anything on the parent instance object

/ / parent class
class BugEngineer {
 constructor() {
        this.didadida = 123;
        this.dududu = function () {
            console.log(13)}console.log(this)}}/ / subclass
class Engineer extends BugEngineer {
    constructor(name) {
        super(name);
        // as an object
        console.log(super.didadida);
        console.log(super.dududu)
    }
}
Copy the code

If you define the property on the stereotype, super can be fetched

   / / parent class
   class BugEngineer {
   }
   BugEngineer.prototype.dududu = 123;

   / / subclass
   class Engineer extends BugEngineer {
       demo() {
           console.log(super.dududu); }}Copy the code

The super object is a static method that refers to the parent class, so it can only be called by a static member of the parent class. .

/ / parent class
class BugEngineer {
    // The method is on the class
    static biubiubiu() {
        console.log("biubiubiu");
    }
    // This method is on the prototype of the class
    boomboomboom() {
        console.log("boomboomboom"); }}/ / subclass
class Engineer extends BugEngineer {
    static demo() {
        super.biubiubiu();
        super.boomboomboom(); }}console.dir(BugEngineer)
Engineer.demo();
Copy the code

  1. ES6 states that inSubclass common methodBy * *Super objectCall the method of the parent classIs within the methodthisPoints to the current subclass instance **.
/ / parent class
class BugEngineer {
    constructor() {
        this.A = 2;
    }
    print() {
        console.log(this.A)
    }
}
/ / subclass
class Engineer extends BugEngineer {
    constructor() {
        super(a);this.A = 1;
        super.print(); }}new Engineer();
Copy the code

Super. Although print () call is BugEngineer. The prototype. The print (), but BugEngineer. Prototype. The print () within this instance to subclasses Engineer, so I print out 1, This is essentially executing super.print.call(this)

Again: super is used as an object in a normal function and refers to a prototype object of the parent class!!

Based on the above, you can see that if calling a superclass method using a super object in a normal method causes this to point to the subclass instance, then if you assign something using a super object, it will also end up assigning to the subclass instance

Note: the above bold characters emphasize that using the super object to assign to something, to something, to something, does not mean to value or call a method!!

/ / parent class
class BugEngineer {
    constructor() {
        this.x = 456; }}/ / subclass
class Engineer extends BugEngineer {
    constructor() {
        super(a);this.x = 10086; // Assign to the Engineer instance
        console.log(this.x);
        // Assign using the super object, which will eventually be assigned to the Engineer instance
        super.x = 123;
     console.log(this.x);    // Read the value of the Engineer instance
        // Read the property on the parent BugEngineer prototype object, undefined because it doesn't exist
     console.log(super.x)
    }
}
Copy the code

Add attributes to the parent stereotype

/ / parent class
class BugEngineer {
 constructor() {
        this.x = 456; }}// Add attributes
BugEngineer.prototype.x = "Nothing but writing bugs."
/ / subclass
class Engineer extends BugEngineer {
    constructor() {
        super(a);this.x = 10086; // Assign to the Engineer instance
        console.log(this.x);
        // Assign using the super object, which will eventually be assigned to the Engineer instance
        super.x = 123;
        console.log(this.x);    // Read the value of the Engineer instance
        // Read the property on the parent BugEngineer prototype object, undefined because it doesn't exist
        console.log(super.x)
    }
}
Copy the code

  1. When a method of a parent class is called through super in a static method of a subclass, the this inside the method refers to the current subclass, not the instance of the subclass.

     / / parent class
     class BugEngineer {
       constructor() {
           this.A = 2;
       }
       static biubiubiu() {
           console.dir(this)
           console.log(this.x)
       }
     }
     / / subclass
     class Engineer extends BugEngineer {
       constructor() {
           super(a);this.x = 10086;
       }
       static son() {
           super.biubiubiu(); }}Copy the code

  1. When using super, you must explicitly specify whether to use it as a function or as an object, otherwise an error will be reported.

    / / parent class
    class BugEngineer {
        constructor() {}}/ / subclass
    class Engineer extends BugEngineer {
        constructor() {
            super(a);console.log(super); }}Copy the code

In the code above, the super in console.log(super) is not clear if it is used as a function or as an object, so the JavaScript engine will report an error when parsing the code. At this point, if the data type of super is clearly stated, no errors will be reported.

/ / parent class
class BugEngineer {}/ / subclass
class Engineer extends BugEngineer {
    constructor() {
        super(a);console.log(super.valueOf() instanceof Engineer)
    }
}
Copy the code

The above code super calls the parent method valueOf, causing this to point to the current instance.

Since objects always inherit from other objects, the super keyword can be used in any object

const obj = {
    toString() {
        return `MyObject: + The ${super.toString()}`}}Copy the code

Finally, summarize the matters needing attention of super

  1. The constructor is specified and the super keyword must be used.
  2. onlyIn the derived classtheIn the constructorusesuper()If you try to use it in a non-derived class (one that is not declared with extends) or in a function, an error is reported.
  3. Always call super before calling this in the constructor, which initializes this. If you try to access this before calling super, an error will be reported.
  4. superAs an objectIn theCommon methods, pointing to the parent classA prototype objectIn theA static methodThe point toThe parent class.
  5. ES6 states that inSubclass common methodThrough theSuper objectCall the method of the parent class, the ** inside the methodthisPoints to the current subclass instance **.
  6. inA subclasstheA static methodThrough thesuperCall the method of the parent classIs within the methodthisPoints to the current subclassAnd theIs not an instance of a subclass.
  7. usesuperYou must explicitly specify whether to use it as a function or as an object, otherwise an error will be reported.

Static members can also be inherited

class BugEngineer {
    static writeBug() {
        console.log(10 hours of programming, 9 hours of Bug fixing, oh~yeah!)}}class Engineer extends BugEngineer {
}
Engineer.writeBug();
Copy the code

Masking of class methods

A method in a derived class (subclass) always overrides a method with the same name in its parent class.

/ / parent class
class BugEngineer {
    writeBug() {
        console.log("I only write bugs.")}}/ / subclass
class Engineer extends BugEngineer {
    // Override (mask) the parent method
    writeBug() {
        console.log("Study hard and write fewer bugs.")}}Copy the code

According to the search rules of prototype chain, a graph is drawn

The Engineer instance object keeps looking up the implicit prototype __proto__ for the writeBug method, because the subclass and the parent class have the same prototypeheader, causing the shadowing.

Vernacular: if have the same name, first find who use who of.

Of course, it’s not just class methods that get Shadowed, but attributes as well

/ / parent class
class BugEngineer {

}
BugEngineer.prototype.x = "BugEngineer";
/ / subclass
class Engineer extends BugEngineer {
}
Engineer.prototype.x = "Engineer";

console.log(new Engineer().x);
Copy the code

Static methods are the same.

Why does the superclass constructor return an instance of a subclass

Because of super quite so BugEngineer. Prototype. Constructor. Call (this)

class BugEngineer {}class Engineer extends BugEngineer {}Copy the code

ES5 constructors compared to ES6 classes

This transfer is bound

In ES5, you can change this directly through call, apply, etc

function Master() {}function Test() {
    console.log(this)}const master = new Master();
Test.call(master);
Copy the code

In CLASS in ES6, this is not allowed because the new keyword is required

class Test {}
Test.call({});  // TypeError: Class constructor Test cannot be invoked without 'new'
// new Test.call({});
Copy the code

Variable declaration promotion and temporary Dead zones (TDZ)

ES5 constructor:

  1. There is variable declaration promotion
  2. There is no TDZ
  3. Causes global variable contamination
  4. Allow duplicate declarations
console.log(Test);
function Test(name) {
    this.name = name;
}
console.log(window.Test);
function Test(name) {
    this.doubi = name;
}
console.log(new Test("oooo"))
Copy the code

A class of ES6

  1. There is no variable declaration promotion
  2. Contains TDZ
  3. Does not pollute global variables
  4. Duplicate declarations are not allowed
console.log(Test);  // ReferenceError: Cannot access 'Test' before initialization
class Test {
    constructor(name) {
        this.name = name; }}console.log(window.Test);	// undefined
class Test {    // SyntaxError: Identifier 'Test' has already been declared

}
Copy the code

Strict mode

ES5 does not have these restrictions, only ES6 does

Take a simple example, variable is not declared to use, or delete a variable, strict mode restrictions, here is not a display, please test ~

Use variables undeclared

class Test {
    constructor() {
        x = 123; }}new Test();
Copy the code

Delete a variable

class Test {
    constructor() {
        let ddd = 123;
        deleteddd; }}Copy the code

Methods are not enumerable

In ES5, object methods and properties are enumerable

function Demo() {
    this.xx = 123;
    this.say = function() {
        console.log("say")
    }
}
Demo.prototype.ss = function() {
    console.log("ss")}const obj = new Demo();
for (const key in obj) {
    console.log(key)
}
Copy the code

In ES6, methods are not allowed to be traversed unless you manually add them to the prototype

class Demo {
    xx = 123;
say() {
    console.log("say")
}
}
Demo.prototype.ss = (a)= > {
    console.log("ss")}const obj = new Demo();
for (const key in obj) {
    console.log(key)
}
Copy the code

The new keyword cannot be used on methods of objects

In ES5, these methods are just ordinary functions, and new is definitely ok

function Test(name) {
    this.name = name;
    this.say = function(d) {
        this.d = d; }}const obj1 = new Test("Wick");
console.log(obj1);
// Save the say method
const Say = obj1.say;
const obj2 = new Say("10086");
console.log(obj2)
Copy the code

Classes in ES do not work this way; they all have no constructor, and manual additions to prototypes do not count

The new keyword must be used

ES5 constructor, just a normal function

The class in ES must use the new keyword

class Demo {

}
Demo();
Copy the code

Class name cannot be modified in class

ES5 constructor

function Demo() {
    Demo = 123;
}
const obj = new Demo();
console.log(Demo)
Copy the code

In ES6, class names cannot be changed internally

class Demo {
    constructor() {
        Demo = 123; }}const objb = new Demo();
Copy the code

Simple simulation of ES6 class

After comparing the ES5 constructor with the ES6 class, let’s simulate the ES6 class with ES5.

So let’s think about, what are the requirements?

  1. Strict mode
  2. Methods are not enumerable
  3. Class must usenewKeyword, class instance object method cannot be usednewThe keyword
  4. You need to return an object after use
  5. Class name cannot be modified in class

Well, that’s pretty much it.

Strict mode: Nothing to say, just say “use strict”.

Methods are not enumerable: What do you think? Well, the Object. DefineProperty

The new keyword: The easiest way is to use new.target instead of instanceof

Return object: return, that must be a function, right? To be clear, there is a constructor missing.

Class name cannot be changed in a class: const is used and cannot be repeated

let BugEngineer = (function () {
    "use strict";
    // Use const to prevent modification and repeated declarations
    const BugEngineer = function (name) {
        / / must be new
        if (typeof new.target === "undefined") {
            throw new Error("Please use the new keyword");
        }
        this.name = name;
    }
    Object.defineProperties(BugEngineer.prototype, {
        writeBug: {
            value: function () {
                / / not new
                if (typeof new.target ! = ="undefined") {
                    throw new Error("This method cannot be called using the new keyword");
                }
                console.log(`The ${this.name}Started writing Bug `)},// Not enumerable
            enumerable: false.writable: true.configurable: true,}})// return the function
    returnBugEngineer; }) ();Copy the code

Object-oriented three characteristics and code implementation

Since ES6 classes automatically optimize for me, most of the code below is ES5 implementation

encapsulation

Encapsulation refers to the hiding and exposing of properties and methods, such as private properties, private methods, public properties, public methods, and so on.

The idea of encapsulation doesn’t exist explicitly in JavaScript, but it can be implemented with clever tricks.

Create private variables and private methods

Create private variables and private methods using JavaScript’s function-level scope (variables and methods declared inside a function are not accessible outside the function).

Let’s take a look at a couple of terms, and the code will be marked with comments

Object public properties and methods: The properties and methods that an instance object created with this inside a function has. These properties and methods are externally accessible and are considered the object’s public properties and methods.

Privileged methods: Because this creates a method that can access not only the public but also the private properties and methods of the object, it is considered a privileged method.

Static methods and static properties: Properties and methods class.xxx are added to classes (functions) using dot syntax. Because the properties and methods added to the class are not executed when new creates the new object, the newly created object cannot get them.

Constructor: At object creation time, some properties of the instance object can be initialized through privileged methods, so these privileged methods that are called when the object is created can be thought of as class constructors

The test code is as follows:

var BugEngineer = function (name, age) {
    // Private attributes
    var studyHours = 0;
    // Private methods
    function study() {
        console.log(`The ${this.name}Start learning to write bugs);
        this.study ++;
    }
    // Privileged methods
    this.setName = function (name) {
        this.name = name;
    }
    this.getName = function () {
        return this.name;
    }
    this.setAge = function (age) {
        this.age = age;
    }
    this.getAge = function () {
        return this.age;
    }
    this.getHours = function() {
        return studyHours;
    }
    // Object public method
    this.writeBug = function () {
        console.log(`The ${this.name}Started writing Bug `)
        this.Bugs++;
    }
    // Object public attributes
    this.Bugs = 0;
    / / the constructor
    this.setName(name);
    this.setAge(age);
}
// Class static public properties (objects cannot be accessed)
BugEngineer.position = "Bug Engineer";
// Class static public methods (objects cannot be accessed)
BugEngineer.sleep = function () {
    console.log("Sleep");
}
BugEngineer.prototype = {
    // Public attributes
    bugLanguage: "JavaScript".// Public method
    lookGirl: function () {
        console.log("Look at little Sister."); }}var wick = new BugEngineer("Wick".20);
console.log(wick.studyHours);   // undefined
console.log(wick.study);    // undefined
console.log(wick.getHours());   / / 0
console.log(wick.Bugs); / / 0
console.log(wick.position); // undefined
console.log(BugEngineer.position);  // Bug Engineer
console.log(wick.bugLanguage); // JavaScript
Copy the code

Closure implementation

var BugEngineer = (function () {
    / / create the class
    function _BugEngineer(name, age) {
        // Private attributes
        var studyHours = 0;
        // Private methods
        function study() {
            console.log(`The ${this.name}Start learning to write bugs);
            this.study++;
        }
        // Privileged methods
        this.setName = function (name) {
            this.name = name;
        }
        this.getName = function () {
            return this.name;
        }
        this.setAge = function (age) {
            this.age = age;
        }
        this.getAge = function () {
            return this.age;
        }
        this.getHours = function () {
            return studyHours;
        }
        // Object public method
        this.writeBug = function () {
            console.log(`The ${this.name}Started writing Bug `)
            this.Bugs++;
        }
        // Object public attributes
        this.Bugs = 0;
        / / the constructor
        this.setName(name);
        this.setAge(age);
    }
    // Class static public properties (objects cannot be accessed)
    _BugEngineer.position = "Bug Engineer";
    // Class static public methods (objects cannot be accessed)
    _BugEngineer.sleep = function () {
        console.log("Sleep");
    }
    // Build a prototype
    _BugEngineer.prototype = {
        // Public attributes
        bugLanguage: "JavaScript".// Public method
        lookGirl: function () {
            console.log("Look at little Sister."); }}return_BugEngineer; }) ();var wick = new BugEngineer("Wick".20);
console.log(wick.studyHours);   // undefined
console.log(wick.study);    // undefined
console.log(wick.getHours());   / / 0
console.log(wick.Bugs); / / 0
console.log(wick.position); // undefined
console.log(BugEngineer.position);  // Bug Engineer
console.log(wick.bugLanguage); // JavaScript
Copy the code

inheritance

Prototype chain inheritance

function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function() {
    return this.property;
}
function SubType() {
    this.subproperty = false;
}
// Create a SuperType instance
var superType = new SuperType();
console.log("SuperType instance", superType);
// Change the prototype pointing of SubType
SubType.prototype = superType;
// Add the getSubValue method, which is equivalent to adding a method to the SuperType instance
SubType.prototype.getSubValue = function() {
    return this.subproperty;
}
// Create a SubType instance
var subType = new SubType();
console.log("SubType instance", subType);
Copy the code

The flowchart for the code above looks like this. The subType instance will find what it wants all the way down __proto__.

You cannot create a prototype method using literals

function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function () {
    return this.property;
}
function SubType() {
    this.subproperty = false;
}
var superType = new SuperType();
console.log("SuperType instance", superType);

SubType.prototype = superType;
// To add methods in this form is to rewrite the prototype
SubType.prototype = {
    getSubValue: function () {
        return this.subproperty;
    },
    someMethod: function () {
        // somecode}}var subType = new SubType();
console.log("SubType instance", subType);
Copy the code

The prototype chain problem

Problem one: Attributes of a reference type are shared by all subinstances

Both inherit from the same instance, and reference types are shared.

function SuperType() {
    this.colors = ["red"."blue"."yellow"];
}
function SubType() {
}
SubType.prototype = new SuperType();
var sub1 = new SubType();
sub1.colors.push("black");
console.log(sub1.colors);
var sub2 = new SubType();
console.log(sub2.colors);
Copy the code

On a smaller note, one might wonder, aren’t reference types the same as primitive type calls? Why should reference types be shared?

So sharing means that you’re going to operate on the same thing, but that’s not the same thing as assignment, because you’re writing to the current instance, you’re not going to assign to the prototype, you’re going to have to operate on the prototype of the current instance.

function SuperType() {
    // Basic type
    this.demo = 123;
}
function SubType() {
}
SubType.prototype = new SuperType();
var sub1 = new SubType();
console.log(sub1.demo); // Find the demo on the prototype
sub1.demo = 456; // Write directly to sub1
console.log(sub1.demo);
var sub2 = new SubType();
console.log(sub2.demo); // The same time to find the prototype demo
sub1.__proto__.demo = 10086;  // Modify the demo on the prototype
console.log(sub1);  // Since the demo attribute was added to sub1, the demo on the prototype will not be found
console.log(sub2.demo);
Copy the code

Problem two: Cannot pass arguments to the constructor of the superclass

Borrowing constructors (classical inheritance)

The supertype constructor is called inside the subtype constructor by call or apply

function SuperType(name) {
    this.name = name;
}
function SubType(name) {
    SuperType.call(this, name);
}
var sub = new SubType("Wick");
console.log(sub)
Copy the code

Borrowing constructor problems

The advantages have been mentioned above, but only the disadvantages are mentioned here

Disadvantage 1: poor function reuse

All methods must be defined in the superclass constructor

function SuperType(name) {
    this.name = name;
    this.sayHi = function() {
        console.log("Hi"); }}function SubType(name) {
    SuperType.call(this, name);
}
var sub1 = new SubType("Wick");
var sub2 = new SubType("xiaoming");
console.log(sub1.sayHi === sub2.sayHi);
Copy the code

Disadvantage 2: Methods on superclass prototypes are masked

function SuperType(name) {
    this.name = name;
}
SuperType.prototype.sayHi = function() {
    console.log("Hi");
}
function SubType(name) {
    SuperType.call(this, name);
}
var sub1 = new SubType("Wick");
// The method cannot be found
console.log(sub1.sayHi);
Copy the code

Call the superclass constructor on the first line to prevent subclass properties from being overridden by the superclass

function SuperType(name, age) {
    this.name = name;
    this.age = age;
}
function SubType(name, age) {
    this.age = 20;  // This property is overridden by the parent class
    // Try to place the call on the first line
    SuperType.call(this, name, age);
}
var sub = new SubType("Wick".18);
console.log(sub)
Copy the code

Combinatorial inheritance (pseudo-classical inheritance)

An inheritance pattern that combines a stereotype chain with the techniques of borrowing constructors to take advantage of the best of both.

Implementation idea:

  1. Inheritance of stereotype properties and methods is implemented using stereotype chains
  2. Instance attributes are inherited by borrowing constructors
function SuperType(name) {
    this.name = name;
    this.colors = ["red"."blue"."green"];
}
// Prototype method
SuperType.prototype.sayName = function() {
    console.log(this.name);
}

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}
// Inheritance method
SubType.prototype = new SuperType();

SubType.prototype.sayAge = function() {
    console.log(this.age);
}
var sub1 = new SubType("Wick".20);
sub1.colors.push("black");
console.log(sub1.colors);
sub1.sayName();
sub1.sayAge();

var sub2 = new SubType("xiaoming".18);
console.log(sub2.colors);
sub2.sayName();
sub2.sayAge();
Copy the code

Composite inheritance avoids the defects of stereotype chains and borrowed constructors, and combines their advantages to become the most common inheritance pattern

Although it is the most commonly used inheritance pattern, it does not mean that there are no drawbacks

Cons: In any case, the superclass constructor is called twice

function SuperType(name) {
    console.log("The method is executed.")
    this.name = name;
    this.colors = ["red"."blue"."green"];
}
// Prototype method
SuperType.prototype.sayName = function() {
    console.log(this.name);
}

function SubType(name, age) {
    SuperType.call(this, name); // Second call, inside the subtype constructor
    this.age = age;
}
// Inheritance method
SubType.prototype = new SuperType();    // The first call to create a subtype prototype

SubType.prototype.sayAge = function() {
    console.log(this.age);
}
var sub1 = new SubType("Wick".20);
sub1.colors.push("black");
console.log(sub1.colors);
sub1.sayName();
sub1.sayAge();
Copy the code

The first time the SuperType constructor is called, subtype. prototype gets two properties: name and colors, which are both instance properties of SuperType, but in the prototype of SubType.

The second call to the SuperType constructor is when the SubType constructor is called, this time creating two instance attributes on the new object: name and colors.

Since the subclass instance (new object) and the subclass stereotype have methods with the same name, the two attributes on the subclass instance override the two attributes on the stereotype

Primary inheritance

Using the incoming object as a prototype for the new object is very similar to prototype chain inheritance

var person = {
    name: "Wick".friends: ["doubi"."xiaoming"."xiaohong"]}function createObject(o) {
    function F() {}
    // Create a prototype based on o objects
    F.prototype = o;	// Same as prototype chain inheritance
    return new F();
}
var p1 = createObject(person);
console.log(p1.name);
Copy the code

This inheritance method makes a shallow copy of the object passed in

var person = {
    name: "Wick".friends: ["doubi"."xiaoming"."xiaohong"]}function createObject(o) {
    function F() {}
    F.prototype = o;
    return new F();
}
var p1 = createObject(person);
var p2 = createObject(person);
p1.friends.push("you");
console.log(p2.friends)
Copy the code

In ES5, we added object.createAPI, which behaves the same as createObject with only one argument, so primitive inheritance can also be written as follows

var person = {
    name: "Wick".friends: ["doubi"."xiaoming"."xiaohong"]}var p1 = Object.create(person);
var p2 = Object.create(person);
p1.friends.push("you");
console.log(p2.friends);
Copy the code

Parasitic inheritance

Create a function that encapsulates inheritance only, internally enhancing the object in some way. This method is similar to the old-style inheritance and borrowing constructors

function createObject(origin) {
    var clone = Object.create(origin);
    clone.sayHi = function() {
        console.log("Hi~");
    }
    return clone;
}
Copy the code

Disadvantages: poor function reuse, reference type data is shared

var person = {
    name: "Wick".friends: ["xiaoming"."xiaobai"."xiaohong"]}function createObject(origin) {
    var clone = Object.create(origin);
    clone.sayHi = function () {
        console.log("Hi~");
    }
    return clone;
}
var p1 = createObject(person);
var p2 = createObject(person);
p1.friends.push("you");
console.log(p1.sayHi === p2.sayHi);
console.log(p2.friends);
Copy the code

Parasitic combinatorial inheritance

Parasitic combinatorial inheritance is used to solve the problem of combinatorial inheritance calling the superclass constructor twice

Basic idea:

  1. You do not need to call the constructor of a supertype to specify the stereotype of a subtype
  2. All you need is a copy of the supertype prototype

Implementation code:

// subType: subType constructor
// superType: superType constructor
function inhertPrototype(subType, superType) {
    var prototype = Object.create(superType.prototype);	// Copy the superclass prototype
    prototype.constructor = subType;	// Add the Contructor attribute to the replica (to compensate for the loss of the default constructor attribute when rewriting the prototype)
    subType.prototype = prototype;	// Assign the copy to the prototype of the subtype
}
Copy the code

The test code

function inhertPrototype(subType, superType) {
    var prototype = Object.create(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}
function SuperType(name) {
    console.log("The method is executed.")
    this.name = name;
    this.colors = ["red"."blue"."green"];
}
SuperType.prototype.sayName = function () {
    console.log(this.name);
}
function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}
inhertPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
    console.log(this.age)
}
var p1 = new SubType("Wick".18);
p1.sayName();
p1.sayAge();
Copy the code

polymorphism

Multiple calls to the same method.

Implementation idea: according to the incoming parameters to make a judgment to achieve a variety of call.

The chestnut below is just a chestnut, no other thoughts.

The switch to realize

function liaomei() {
    var length = arguments.length;
    switch (length) {
        case 0:
            return "Straight";
        case 1:
            return "Not bad.";
        case 2:
            return "Okay.";
        case 3:
            return "A bit of a mess.";
        case 4:
            return "Men who cheat on women's affections."; }}console.log(liaomei());
console.log(liaomei(1));
console.log(liaomei(1.2));
console.log(liaomei(1.2.3));
console.log(liaomei(1.2.3.4));
Copy the code

Methods encapsulate

function Liaomei() {
    function zero() {
        return "Straight";
    }
    function one() {
        return "Not bad.";
    }
    function two() {
        return "Okay.";
    }
    function three() {
        return "A bit of a mess.";
    }
    function four() {
        return "Men who cheat on women's affections.";
    }
    var length = arguments.length;
    switch (length) {
        case 0:
            return zero();
        case 1:
            return one();
        case 2:
            return two();
        case 3:
            return three();
        case 4:
            returnfour(); }}console.log(Liaomei());
console.log(Liaomei(1));
console.log(Liaomei(1.2));
console.log(Liaomei(1.2.3));
console.log(Liaomei(1.2.3.4));
Copy the code

The last

Welcome to join me in WX, wX: Onlywick

Refer to the article

JavaScript You Didn’t Know, Volume 2

“JavaScript Advanced Programming — 3rd Edition”

Deep Understanding of ES6

Ruan Yifeng ES6

The various ways in which JavaScript extends inheritance and the pros and cons

Object.setPrototypeOf

Object.create

@babel/plugin-proposal-private-methods

@babel/plugin-proposal-class-properties

@ babel / plugin-proposal-decorators