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
- The function names use the big camel name method
- Inside the constructor, an object (this) is implicitly created and returned
- Can have a return value, if the return value is a primitive type, then ignored, reference type, then directly returned
disadvantages
- It is just named according to the naming convention and is essentially a normal function, so it is not used
new
Keyword, can also be used - All properties (including methods) are enumerable, and in general we don’t want methods to be enumerable
- Property definitions are separated from stereotype methods, which do not conform to the pattern of object-oriented programming languages (such as Java)
- 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 usedbabel
To 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:
-
# is part of the member name and needs to be used together
-
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
-
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
-
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:
-
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
- onlyIn the derived classtheIn the constructoruse
super()
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
- 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
-
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
- ES6 states that inSubclass common methodBy * *
Super object
Call the method of the parent classIs within the methodthis
Points 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
-
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
-
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
- The constructor is specified and the super keyword must be used.
- onlyIn the derived classtheIn the constructoruse
super()
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. - 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.
super
As an objectIn theCommon methods, pointing to the parent classA prototype objectIn theA static methodThe point toThe parent class.- ES6 states that inSubclass common methodThrough the
Super object
Call the method of the parent class, the ** inside the methodthis
Points to the current subclass instance **. - inA subclasstheA static methodThrough the
super
Call the method of the parent classIs within the methodthis
Points to the current subclassAnd theIs not an instance of a subclass. - use
super
You 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:
- There is variable declaration promotion
- There is no TDZ
- Causes global variable contamination
- 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
- There is no variable declaration promotion
- Contains TDZ
- Does not pollute global variables
- 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?
- Strict mode
- Methods are not enumerable
- Class must use
new
Keyword, class instance object method cannot be usednew
The keyword - You need to return an object after use
- 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:
- Inheritance of stereotype properties and methods is implemented using stereotype chains
- 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:
- You do not need to call the constructor of a supertype to specify the stereotype of a subtype
- 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