JavaScript object-oriented programming -- ES6

Posted by vlcinsky on Sun, 16 Feb 2020 05:05:34 +0100

Class 1

In the previous articles, we introduced the object-oriented programming of ES5. We know that before ECMAScript6 specification, there was no concept of class in JavaScript, only constructor was allowed to simulate class, and inheritance was implemented through prototype.

This way of implementing object-oriented is not so friendly in terms of writing method or concept understanding. It is quite different from other object-oriented languages (such as C + + and Java), and it is easy to confuse new programmers learning this language.

In order to solve these problems, ES6 provides a writing method closer to the traditional language, and introduces the concept of class (class). As a template of objects, class can be defined by class keyword.

2, Why object-oriented programming?

At the beginning of JavaScript Design, Class was not introduced, but the concept of Class was introduced in ES6 to facilitate object-oriented programming for developers, so why object-oriented programming?

Process oriented is actually the most practical way of thinking, because we always solve problems step by step. It can be said that process oriented is a basic method, which considers the actual implementation. So process oriented programming is actually more intuitive for novices.

But because of the encapsulation of data and methods in object-oriented programming, classes are highly reusable. So in large projects, object-oriented is almost necessary.

Object oriented programming is very different from the most intuitive process oriented programming. Any programming should consider two elements, one is data, the other is method. Object-oriented programming first considers data, then methods, while process oriented programming is the opposite.

A little digression: just because JavaScript didn't introduce Class at the beginning of design, it was so "unusual" to use JavaScript for object-oriented programming before introducing the concept of Class. But conversely, if the author introduced Class at the beginning, making JavaScript a complete object-oriented language directly, it would make the original The language that I want to use to do some web page interaction is too formal, which increases the difficulty for beginners. Maybe JavaScript will not have the development now. There are two sides to everything.

3, Declaration writing of class

1. Grammar

In ES5, we often use the combination pattern of constructors and prototypes to create objects:

// es5 creating an object
function Person(age, name) {
  this.age = age;
  this.name = name;
}
Person.prototype.toString = function () {
  return `Full name:${this.name},Age:${this.age}`;
};

// Create an instance of Xiaoming
const xiaoMing = new Person(23, 'Xiao Ming');
console.log(xiaoMing.toString()); // Name: Xiaoming, age: 23

In ES6, we can do this:

// es6 create a class (object)
class Person {
  constructor(age, name) {
    this.age = age;
    this.name = name;
  }
  toString() {
    return `Full name:${this.name},Age:${this.age}`;
  }
}

// Create an instance of Xiaoming
const xiaoMing = new Person(23, 'Xiao Ming');
console.log(xiaoMing.toString());

The above code defines a "class". You can see that there is a constructor method in it, which is the constructor method, and this keyword represents the instance object. That is to say, the constructor Person of ES5 corresponds to the constructor of the Person class of ES6.

There is this method in every class. If not, JavaScript will actually create one by default, that is:

class Person {
}

// Equate to
class Person {
  constructor() { }
}

The constructor method returns the instance object (that is, this) by default:

class Foo {
  constructor() { }
}

console.log(new Foo() instanceof Foo); // true

We can also specify to return another object:

class Foo {
  constructor() {
    return Array;
  }
}

console.log(new Foo() instanceof Foo); // false

2. Must be called via new

Class must be called with new, otherwise an error will be reported. This is a major difference between it and ordinary constructors, which can be executed without new.

class Person { }

const person = Person(); // TypeError: Class constructor Person cannot be invoked without 'new'

3. Strict mode

In fact, ES6 upgrades the entire language to strict mode. So we don't need to use use strict; to specify the run mode.

4. No variable promotion

const person = new Person(); // ReferenceError: Person is not defined

class Person { }

5. Direction of this

If there is this inside the method of a class, it points to the instance of the class by default. However, great care must be taken that using this method alone can result in an error.

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

  sayName() {
    this.print(`Hello ${this.name}`);
  }

  print(text) {
    console.log(text);
  }
}

const person = new Person('Deepspace');
person.sayName(); // Hello Deepspace

const { sayName } = person;
sayName(); // TypeError: Cannot read property 'print' of undefined

A relatively simple solution is to bind this in the construction method:

class Person {
  constructor(name) {
    this.name = name;
    this.sayName = this.sayName.bind(this);
  }

  sayName() {
    this.print(`Hello ${this.name}`);
  }

  print(text) {
    console.log(text);
  }
}

const person = new Person('Deepspace');
person.sayName(); // Hello Deepspace

const { sayName } = person;
sayName(); // Hello Deepspace

Another solution is to use the arrow function:

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

  sayName = () => {
    this.print(`Hello ${this.name}`);
  }

  print(text) {
    console.log(text);
  }
}

const person = new Person('Deepspace');
person.sayName();

const { sayName } = person;
sayName(); // Hello Deepspace

Please run the above code in Chrome, because there is no such writing method in the official proposal of ES6 at present, but it is very common in practical application. Generally, we will configure the corresponding babel plugin.

This within the arrow function always points to the object at the time of definition. In the above code, the arrow function is located inside the constructor, and its definition takes effect when the constructor executes. At this time, the running environment of the arrow function is the instance object, so this will always point to the instance object.

4, Instance of class

Write the instance of the generated Class exactly like ES5, using the new command. As mentioned before, if you forget to add new and call Class like a function, an error will be reported.

1. Properties and methods on classes and instances

Like ES5, an instance's properties are defined on the prototype (that is, class) unless they are explicitly defined on itself (that is, on this object).

Use the hasOwnProperty() method to detect whether a property exists in the instance or in the prototype. When the given attribute exists in the object instance, it will return true; when it exists in the prototype, it will return false:

class Person {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    console.log(`(${this.x}, ${this.y})`);
  }
}

const person = new Person(2, 3);

person.toString(); // (2, 3)

console.log(person.hasOwnProperty('x')); // true
console.log(person.hasOwnProperty('y')); // true
console.log(person.hasOwnProperty('toString')); // false
console.log(person.__proto__.hasOwnProperty('toString')); // true

Both x and y are the properties of the instance object person itself (because they are defined on the this variable), so the hasOwnProperty method returns true, while toString is the property of the prototype object (because it is defined on the Person class), so the hasOwnProperty method returns false.

If we add an attribute to the instance and the attribute is an existing attribute on the class, the newly added attribute will mask the attribute on the class:

class Person {
  sayName() {
    console.log('Deepspace');
  }
}

const person1 = new Person();
const person2 = new Person();

person1.sayName = function () {
  console.log('chenxingxing');
};

person1.sayName(); // chenxingxing
person2.sayName(); // Deepspace

delete person1.sayName;
person1.sayName(); // Deepspace

Use the delete operator to have the instance recover access to properties on the class.

2. Instance sharing prototype object

Like ES5, all instances of a class share a prototype object.

class Person { }

const person1 = new Person();
const person2 = new Person();

console.log(person1.__proto__ === person2.__proto__); // true

This also means that you can add methods to the class through the \

class Person { }

const person1 = new Person();
const person2 = new Person();

person1.__proto__.sayHello = function () {
  console.log('Hello');
};

person1.sayHello(); // Hello
person2.sayHello(); // Hello

Because instances share prototypes, person2 can also access the sayHello method.

3. A new way to write instance attribute

Instance properties can be defined at the top level of a class as well as this in the constructor() method.

class Person {
  name = 'Deepspace';
  // constructor() {
  //   this.name = 'Deepspace';
  // }

  sayName() {
    console.log(this.name);
  }
}

const person = new Person();
person.sayName(); // Deepspace

This method is clearer than before, but the disadvantage is that it can't pass parameters.

5, Static properties / static methods

Class is the prototype of an instance. All methods defined in the class will be inherited by the instance.

If the static keyword is added before a method, it means that the method belongs to the class, not to the object or instance created by the class, and can only be called through the class, which is called "static method".

class Person {
  static classMethod() {
    console.log('hello');
  }
}

Person.classMethod(); // 'hello'

const person = new Person();
person.classMethod(); // TypeError: person.classMethod is not a function

1. this in static method

Therefore, if a static method contains this keyword, this refers to a class, not an instance:

class Foo {
  static bar() {
    this.baz();
  }
  static baz() {
    console.log('hello');
  }
  baz() {
    console.log('world');
  }
}

Foo.bar(); // hello

Because this refers to a class rather than an instance, only static methods in the class can be accessed in the bar method. Here, this.baz(); is equivalent to Foo.baz(). You can also see from the above example that static methods can have the same name as non-static methods.

2. Subclass can inherit the static method of the parent class

The static method of the parent class can be inherited by the child class.

class Foo {
  static classMethod() {
    console.log('hello');
  }
}

class Bar extends Foo {
}

Bar.classMethod(); // 'hello'

Static methods can also be called from super objects.

class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
  static classMethod() {
    return `${super.classMethod()}, too`;
  }
}

console.log(Bar.classMethod()); // 'hello, too'

3. Static properties

Static attribute refers to the attribute of Class itself, namely Class.propName, rather than the attribute defined on the instance object (this).

In ES6, static can only be used on methods. Currently, ES6 only supports static methods and does not support static attributes. However, the following writing methods can be supported:

class Point {
  constructor() {
    this.x = 0;
  }
}

Point.y = 2;
const p = new Point();

console.log(p.x); // 0
console.log(p.y); // Undefined, class property instead of instance property, so instance call printing is undefined

It's just that it doesn't look that good. Now there's a proposal The static attribute of the class is provided: before the instance attribute, add the static keyword:

// Old writing
class Foo {
  // ...
}
Foo.prop = 1;

// neographism
class Foo {
  static prop = 1;
}

The new writing method is explicit declaration rather than assignment processing, so the semantics is better.

6, Private properties / private methods

Private methods and properties refer to methods and properties that can only be accessed inside a class, but not outside. This is a very common requirement that facilitates code encapsulation. However, it is a pity that ES6 does not provide it, and it can only be realized through flexible simulation.

There is a weak way (to cheat yourself) to underline the method and distinguish its name (because private methods in other languages are written in this way), which means that it is a private method limited to internal use.

class Point {
  constructor() {
    this.x = 0;
  }
  // I lied to myself. It's a private method. It depends on whether we obey it or not
  _fun() {
    console.log('pravite');
  }
}

const point = new Point();
point._fun(); // pravite

This method can still be called outside the class.

There is another way to meet the function: put the private method outside the class (because all the methods inside the class are visible to the outside), use call to point, but finally only export the class, not the method, so you can't call this method outside the class.

// Private methods can't be exported or used by others
function praviteFun() { }

// Export class only
export default class Point {
  constructor() {
    this.x = 0
  }
  // Private method
  fun() {
    praviteFun.call(this)
  }
}

Seven, inheritance

1. Parent and child classes

In ES6 syntax, inheritance is implemented through the extends keyword.

To demonstrate these effects, use the following code:

  • The properties in the parent class can be used in the child class;
  • Methods in the parent class can be called in the child class;
  • Subclasses can extend their own properties and methods.
class Father {
  constructor() {
    this.gender = 'male';
  }
  getFamilyName() {
    console.log('The family name is Chen');
  }
}

class Son extends Father {
  constructor() {
    super();
    this.height = 160;
  }
  getSchoolName() {
    console.log('NO.2 Middle School');
  }
}

const tom = new Son();
console.log(tom.gender); // male
console.log(tom.height); // 160
tom.getFamilyName(); // The family name is Chen
tom.getSchoolName(); // NO.2 Middle School

2. super calls the parent method

In the process of using class inheritance, it is necessary to understand the role of super().

What is super?

You can simply think that super represents the parent class. There are two main uses:

  • Use super() to call constructor() of the parent class
  • Use super.functionName() to call the static method in the parent class

Function of super():

The subclass must call the super method in the constructor method, otherwise the new instance will be reported wrong. This is because the subclass does not have its own this object, but inherits the parent's this object and processes it. If the super method is not called, the subclass will not get this object.

In essence, the inheritance mechanism of ES6 is to create the instance object this of the parent class (so the super method must be called first), and then modify this with the constructor of the child class.

class Father {
  constructor(familyName) {
    this.familyName = familyName;
  }

  getFamilyName() {
    return `The family name is ${this.familyName}`;
  }
  static sayHello() {
    return 'hello';
  }
}

class Son extends Father {
  constructor(familyName, height) {
    super(familyName);
    this.height = height; // Without the super() in the previous line, this is not allowed here
  }

  getSchool() {
    return 'NO.2 Middle School!';
  }
  static hello() {
    return super.sayHello(); // Call the static method of the parent class
  }
}

const tom = new Son('Chen', 180);
console.log(tom.height); // 180
console.log(tom.getFamilyName()); // The family name is Chen
console.log(Son.hello()); // hello

8, Just grammar sugar

Although the concept of class is added in ES6, it is only a syntactic sugar. Why do you say that?

1. Type is still function

Let's look at the following code:

class Person { }

console.log(typeof Person); // function

Use the typeof operator to check the type of Person, and find that it is a function type. In a disguised way, there is no Class type in JavaScript, just make it similar to other object-oriented languages through the form of "makeup".

The class of ES6 can be regarded as another way to write a constructor. The class itself points to the constructor.

When it is used, the new command is also used directly for the class, which is exactly the same as the constructor.

class Person {
  sayName() {
    console.log('Deepspace');
  }
}

const person = new Person();
person.sayName(); // Deepspace

2. prototype property still exists

The prototype property of the constructor continues to exist on the class of ES6. In fact, all methods of a class are defined on the prototype property of the class:

class Person {
  constructor() {
    // ...
  }

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// Equate to

Person.prototype = {
  constructor() { },
  toString() { },
  toValue() { },
};

To call a method on an instance of a class is to call a method on the prototype:

class Person { }
let person = new Person();

console.log(person.constructor === Person.prototype.constructor); // true

Person is an instance of the person class. Its constructor method is the constructor method of the person class prototype.

Because the methods of the class are all defined on the prototype object, the new methods of the class can be added on the prototype object. Using the Object.assign method, you can add multiple methods to a class at a time:

class Person {
  constructor() {
    // ...
  }
}

Object.assign(Person.prototype, {
  toString() { },
  toValue() { }
});

The constructor property of the prototype object points directly to the "class" itself (this is consistent with the behavior of ES5):

class Person { }

console.log(Person === Person.prototype.constructor); // true

3. Methods in non enumerable classes

But in ES6, classes are different: all the methods defined within a class are non enumerable.

class Person {
  constructor(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

console.log(Object.keys(Person.prototype)); // []
console.log(Object.getOwnPropertyNames(Person.prototype)); // [ 'constructor', 'toString' ]

However, in ES5, attributes on prototypes can be enumerated:

const Person = function (x, y) {
  // ...
};

Person.prototype.toString = function () {
  // ...
};

console.log(Object.keys(Person.prototype)); // [ 'toString' ]
console.log(Object.getOwnPropertyNames(Person.prototype)); // [ 'constructor', 'toString' ]

Nine, summary

Through the above description, the "class" in ES6 is not so perfect, there are still many places to be improved. But this does not affect us to use classes to organize our code, so that the reusability of the code becomes better and easier to maintain.

Published 31 original articles, won praise 11, visited 2667
Private letter follow

Topics: Programming Attribute Javascript Java