Article directory
- 1. Define classes in class mode
-
- 1. Understand class definition class
- 2. Similarities and differences between classes and constructors
- 3. Class constructor
- 4. Instance methods of classes
- 5. Accessor methods of classes
- 6. Static methods of classes
- 2. Inheritance
-
- 1.extends implements inheritance
- 2.super keyword
- 3. Inherit built-in classes
- 4. Class mixing into mixin
- 3. ES6 to ES5
-
- 1.class conversion
- 2.extends conversion
- 4. Polymorphism
1. Define classes in class mode
1. Understand class and define classes
We will find that creating a class according to the previous constructor form is not only too similar to writing an ordinary function
, but also the code is not easy to understand
.
- In the new standard of ES6 (ECMAScript2015), the
class
keyword is used to directly define classes; - But classes are still essentially
syntactic sugar
for the constructors and prototype chains mentioned earlier; - Therefore, learning the previous constructors and prototype chains will help us understand the concept and inheritance relationship of
classes
;
So, how to use class to define a class?
- There are two ways to declare a class: class declaration and class expression;
//Method 1 Class declaration class Person {<!-- --> } // Method 2 Class expression var Student = class {<!-- --> }
2. Similarities and differences between classes and constructors
Let’s examine some features of classes:
- You will find that it has the same characteristics as our constructor;
// function definition class function Person1(name, age) {<!-- --> this.name = name this.age = age } Person1.prototype.running = function() {<!-- -->} Person1.prototype.eating = function() {<!-- -->} var p1 = new Person1("why", 18) console.log(p1.__proto__ === Person1.prototype) console.log(Person1.prototype.constructor) console.log(typeof Person1) // function // Difference: Called as a normal function Person1("abc", 100) // class definition class class Person2 {<!-- --> constructor(name, age) {<!-- --> this.name = name this.age = age } running() {<!-- -->} eating() {<!-- -->} } var p2 = new Person2("kobe", 30) console.log(p2.__proto__ === Person2.prototype) console.log(Person2.prototype.constructor) console.log(typeof Person2) // Difference: The class defined by class cannot be called as an ordinary function. Person2("cba", 0)
3. Class constructor
If we want to pass some parameters to the class
when creating an object, what should we do at this time?
- Each class can have its own constructor (method), and the name of this method is fixed
constructor
; - When we operate a class through the new operator, the constructor of this class will be called;
- Each class can only have one constructor. If it contains multiple constructors, an exception will be
thrown
;
When we operate a class through the new keyword, we will call this constructor function
and perform the following operations:
-
Create a new object (empty object) in memory;
-
The
[[prototype]]
attribute inside this object will be assigned the value of the prototype attribute of the class; -
This inside the constructor will point to the new object created;
-
Execute the internal code of the constructor (function body code);
-
If the constructor does not return a non-null object, the new object created is returned;
class Person {<!-- --> constructor(name, age) {<!-- --> this.name = name this.age = age } }
4. Instance methods of classes
The properties we defined above are placed directly on this, which means that they are placed in the new object created:
- We said before that for instance methods, we hope to
place them on the prototype
so that they can beshared
by multiple instances; - At this time we can define it directly in the class;
class Person {<!-- --> constructor(name, age) {<!-- --> this.name = name this.age = age } run(){<!-- --> console.log(this.name + "running~") } }
5. Accessor methods of classes
When we talked about object property descriptors before, we mentioned that objects can add setter
and getter
functions, so classes can also add:
class Student {<!-- --> constructor(name, age) {<!-- --> this._name = name this._age = age } set name(name) {<!-- --> this._name = name } get name() {<!-- --> return this._name } } var s = new Student("abc", 123) console.log(s.name)
Extended Review: Accessor Methods of Objects
// for objects // Method 1: Descriptor // var obj = {<!-- --> // _name: "why" // } // Object.defineProperty(obj, "name", {<!-- --> // configurable: true, // enumerable: true, // set: function() {<!-- --> // }, // get: function() {<!-- --> // } // }) // Method 2: Define the accessor directly on the object // Monitor when _name is accessed and set a new value var obj = {<!-- --> _name: "why", // setter method set name(value) {<!-- --> this._name = value }, // getter method get name() {<!-- --> return this._name } } obj.name = "kobe" console.log(obj.name)
Accessor application scenarios
// 2. Application scenarios of accessor class Rectangle {<!-- --> constructor(x, y, width, height) {<!-- --> this.x = x this.y = y this.width = width this.height = height } get position() {<!-- --> return {<!-- --> x: this.x, y: this.y } } get size() {<!-- --> return {<!-- --> width: this.width, height: this.height } } } var rect1 = new Rectangle(10, 20, 100, 200) console.log(rect1.position) console.log(rect1.size)
6. Static methods of classes
Static methods are usually used to define methods that are executed directly using the class
. There is no need for an instance of the class. Use the static
keyword to define:
// function Person() {} // //Instance methods // Person.prototype.running = function() {} // // Class methods // Person.randomPerson = function() {} // var p1 = new Person() //p1.running() // Person.randomPerson() //Class defined by class var names = ["abc", "cba", "nba", "mba"] class Person {<!-- --> constructor(name, age) {<!-- --> this.name = name this.age = age } //Instance method running() {<!-- --> console.log(this.name + "running~") } eating() {<!-- -->} // Class method (static method) static randomPerson() {<!-- --> console.log(this) var randomName = names[Math.floor(Math.random() * names.length)] return new this(randomName, Math.floor(Math.random() * 100)) } } var p1 = new Person() p1.running() p1.eating() var randomPerson = Person.randomPerson() console.log(randomPerson)
2. Inheritance
1.extends implements inheritance
We have spent a lot of time discussing the solution to implement inheritance in ES5. Although we finally achieved a relatively satisfactory inheritance mechanism, the process is still very cumbersome.
In ES6, the extends
keyword is newly added, which can easily help us implement inheritance:
class Person {<!-- -->} class Student extends Person {<!-- -->}
Sample code
//Define parent class class Person {<!-- --> constructor(name, age) {<!-- --> this.name = name this.age = age } running() {<!-- --> console.log("running~") } eating() {<!-- --> console.log("eating~") } } class Student extends Person {<!-- --> constructor(name, age, sno, score) {<!-- --> // this.name = name // this.age = age super(name, age) this.sno = sno this.score = score } // running() {<!-- --> // console.log("running~") // } // eating() {<!-- --> // console.log("eating~") // } studying() {<!-- --> console.log("studying~") } } var stu1 = new Student("why", 18, 111, 100) stu1.running() stu1.eating() stu1.studying() class Teacher extends Person {<!-- --> constructor(name, age, title) {<!-- --> // this.name = name // this.age = age super(name, age) this.title = title } // running() {<!-- --> // console.log("running~") // } // eating() {<!-- --> // console.log("eating~") // } teaching() {<!-- --> console.log("teaching~") } }
2.super keyword
We will find that in the above code I used a super
keyword. This super keyword can be used in different ways:
- Note: Before using this in the constructor of a child (derived) class or returning the default object, you must call the constructor of the parent class through super first!
- There are three places where super can be used:
constructor
,instance method
, andstatic method
of subclasses;
class Animal {<!-- --> running() {<!-- --> console.log("running") } eating() {<!-- --> console.log("eating") } static sleep() {<!-- --> console.log("static animal sleep") } } class Dog extends Animal {<!-- --> // If the subclass is not satisfied with the method implementation of the parent class (inherited method) // Reimplementation is called overriding (rewriting of parent class methods) running() {<!-- --> console.log("dog has four legs") // Call the parent class method super.running() // console.log("running~") // console.log("dog running with four legs~") } static sleep() {<!-- --> console.log("lying") super.sleep() } } var dog = new Dog() dog.running() dog.eating() Dog.sleep()
3. Inherit built-in classes
We can also have our class inherit from a built-in class, such as Array:
// 1. Create a new class, inherit from Array and extend it class HYArray extends Array {<!-- --> get lastItem() {<!-- --> return this[this.length - 1] } get firstItem() {<!-- --> return this[0] } } var arr = new HYArray(10, 20, 30) console.log(arr) console.log(arr.length) console.log(arr[0]) console.log(arr.lastItem) console.log(arr.firstItem) // 2. Directly extend Array Array.prototype.lastItem = function() {<!-- --> return this[this.length - 1] } var arr = new Array(10, 20, 30) console.log(arr.__proto__ === Array.prototype) console.log(arr.lastItem()) // Function apply/call/bind method -> Function.prototype
4. Class mixing into mixin
JavaScript classes only support single inheritance
: that is, there can only be one parent class
So when we need to add more similar functions to a class during development, how should we do it?
At this time we can use Mixin (mixin);
//JavaScript only supports single inheritance (does not support multiple inheritance) function mixinAnimal(BaseClass) {<!-- --> return class extends BaseClass {<!-- --> running() {<!-- --> console.log("running~") } } } function mixinRunner(BaseClass) {<!-- --> return class extends BaseClass {<!-- --> flying() {<!-- --> console.log("flying~") } } } class Bird {<!-- --> eating() {<!-- --> console.log("eating~") } } // var NewBird = mixinRunner(mixinAnimal(Bird)) class NewBird extends mixinRunner(mixinAnimal(Bird)) {<!-- --> } var bird = new NewBird() bird.flying() bird.running() bird.eating()
3. ES6 to ES5
1.class conversion
ES6 code
class Person {<!-- --> constructor(name, age) {<!-- --> this.name = name this.age = age } running() {<!-- -->} eating() {<!-- -->} static randomPerson() {<!-- -->} } var p1 = new Person()
ES5 code
"use strict"; function _classCallCheck(instance, Constructor) {<!-- --> if (!(instance instanceof Constructor)) {<!-- --> throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) {<!-- --> for (var i = 0; i < props.length; i + + ) {<!-- --> var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) {<!-- --> if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", {<!-- --> writable: false }); return Constructor; } //Pure function: The same input must produce the same output and will not produce side effects var Person = /*#__PURE__*/ (function () {<!-- --> debugger function Person(name, age) {<!-- --> _classCallCheck(this, Person); this.name = name; this.age = age; } _createClass( Person, [ {<!-- --> key: "running", value: function running() {<!-- -->} }, {<!-- --> key: "eating", value: function eating() {<!-- -->} } ], [ {<!-- --> key: "randomPerson", value: function randomPerson() {<!-- -->} } ] ); return Person; })(); var p1 = new Person("why", 18)
2.extends conversion
ES6 code
class Person {<!-- --> constructor(name, age) {<!-- --> this.name = name this.age = age } running() {<!-- -->} eating() {<!-- -->} static randomPerson() {<!-- -->} } class Student extends Person {<!-- --> constructor(name, age, sno, score) {<!-- --> super(name, age) this.sno = sno this.score = score } studying() {<!-- -->} static randomStudent() {<!-- -->} } var stu = newStudent()
ES5 code
"use strict"; function _typeof(obj) {<!-- --> "@babel/helpers - typeof"; return ( (_typeof = "function" == typeof Symbol & amp; & amp; "symbol" == typeof Symbol.iterator ? function (obj) {<!-- --> return typeof obj; } : function (obj) {<!-- --> return obj & amp; & amp; "function" == typeof Symbol & amp; & amp; obj.constructor === Symbol & amp; & amp; obj !== Symbol.prototype ? "symbol" : typeof obj; }), _typeof(obj) ); } function _inherits(subClass, superClass) {<!-- --> if (typeof superClass !== "function" & amp; & amp; superClass !== null) {<!-- --> throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass & amp; & amp; superClass.prototype, {<!-- --> constructor: {<!-- --> value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", {<!-- --> writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) {<!-- --> _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {<!-- --> o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) {<!-- --> var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() {<!-- --> var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) {<!-- --> var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else {<!-- --> result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) {<!-- --> if (call & amp; & amp; (_typeof(call) === "object" || typeof call === "function")) {<!-- --> return call; } else if (call !== void 0) {<!-- --> throw new TypeError( "Derived constructors may only return object or undefined" ); } return _assertThisInitialized(self); } function _assertThisInitialized(self) {<!-- --> if (self === void 0) {<!-- --> throw new ReferenceError( "this hasn't been initialised - super() hasn't been called" ); } return self; } function _isNativeReflectConstruct() {<!-- --> if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try {<!-- --> Boolean.prototype.valueOf.call( Reflect.construct(Boolean, [], function () {<!-- -->}) ); return true; } catch (e) {<!-- --> return false; } } function _getPrototypeOf(o) {<!-- --> _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {<!-- --> return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _classCallCheck(instance, Constructor) {<!-- --> if (!(instance instanceof Constructor)) {<!-- --> throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) {<!-- --> for (var i = 0; i < props.length; i + + ) {<!-- --> var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) {<!-- --> if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", {<!-- --> writable: false }); return Constructor; } var Person = /*#__PURE__*/ (function () {<!-- --> function Person(name, age) {<!-- --> _classCallCheck(this, Person); this.name = name; this.age = age; } _createClass( Person, [ {<!-- --> key: "running", value: function running() {<!-- -->} }, {<!-- --> key: "eating", value: function eating() {<!-- -->} } ], [ {<!-- --> key: "randomPerson", value: function randomPerson() {<!-- -->} } ] ); return Person; })(); function inherit(SubType, SuperType) {<!-- --> SubType.prototype = Object.create(SuperType.prototype) SubType.prototype.constructor = SubType } var Student = /*#__PURE__*/ (function (_Person) {<!-- --> _inherits(Student, _Person); var _super = _createSuper(Student); function Student(name, age, sno, score) {<!-- --> var _this; _classCallCheck(this, Student); _this = _super.call(this, name, age); _this.sno = sno; _this.score = score; return _this; } _createClass( Student, [ {<!-- --> key: "studying", value: function studying() {<!-- -->} } ], [ {<!-- --> key: "randomStudent", value: function randomStudent() {<!-- -->} } ] ); return Student; })(Person); var stu = new Student("why", 18, 111, 100);
4. Polymorphism
The three major characteristics of object-oriented: encapsulation, inheritance, and polymorphism.
Wikipedia’s definition of polymorphism: Polymorphism (English: polymorphism) refers to providing a unified interface
for entities of different data types
, or using a single symbol to represent multiple entities. different types.
Very abstract, personal summary: different data types perform the same operation and show different behaviors, which is the embodiment of polymorphism.
So from the above definition, JavaScript must have polymorphism.
//Performance of polymorphism: JS is full of polymorphism function sum(a1, a2) {<!-- --> return a1 + a2 } sum(20, 30) sum("abc", "cba") //polymorphic expression var foo = 123 foo = "Hello World" console.log(foo.split()) foo = {<!-- --> running: function() {<!-- -->} } foo.running() foo = [] console.log(foo.length)