preface

Object-oriented programming languages have three basic characteristics: encapsulation 1, inheritance 2 and polymorphism 3, with the coordination of these three characteristics, can the advantages of object-oriented thought as far as possible to show. Although JavaScript is OOP (Object-oriented Programming) language, because of its dynamic language characteristics, the “class” support is not very perfect.

Classes in JavaScript – constructors

In terms of object orientation alone, JavaScript does a great job because it can create objects directly without using classes. The biggest advantage of this approach is that it allows beginners to develop directly without having to understand the concepts involved in object-oriented thinking. The downside is that you can’t understand the core of object orientation, so you can write code that doesn’t balance efficiency and performance.

In order to accommodate more developers, JavaScript still comes up with its own “class”, which is the constructor, as shown in the following example:

function Article(title, content) {
    this.title = title
   	this.content = content
}

Article.prototype.Author = "wolfberry"
Article.prototype.getAuthor = function() {
    return this.Author
}

/ / instantiate
const article = new Article("Constructor"."AAAA")
Copy the code

Note that the JavaScript compiler does not distinguish between a function that is a constructor and an ordinary function 4, which means that any JavaScript function is both a normal function and a constructor:

console.log(Article("Constructor"."AAAA"), window.title, window.content)  // undefined "constructor" "AAAA"
Copy the code

Because of the lexical scope of the constructor, when called directly, its this refers to the global object window, so the input parameter is assigned to all properties of the global object. The only way to distinguish a constructor from a normal function is to call a new constructor.

The effect of MDN on the new operator is explained as follows:

The new operator creates an instance of a user-defined object type or of a built-in object with a constructor.

One of the imperfections is encapsulation

Effective encapsulation in JavaScript is one of the first challenges faced by many JavaScript library developers. JavaScript’s constructors are too crude to support static, private properties or methods, which are essential in a toolkit. For example, I would like to add a createTime attribute to Article, but this attribute is not self-defined, but generated automatically when instantiated:

function Article(title, content) {
    this.title = title
   	this.content = content
    this.createTime = new Date()}const article = new Article("Constructor"."AAAA")
Copy the code

A screenshot of its data format is shown below:

Developers familiar with JavaScript know that, in general, objects can be changed at will, which means that the creatTime attribute is not secure. The specification proposed by the community looks like this: Private attributes are distinguished by a prefix underlined:

this._createTime = new Date(a)Copy the code

But this is just “cheating” because the specification is not a syntax and can still be modified at the code level through instantiated objects. So, is there a better way? Yes, but it’s a little more complicated and makes it harder to understand:

function Article(title, content) {
    // Private variables
    const createTime = new Date(a)this.title = title
   	this.content = content
    // Privilege function
    this.getCreateTime = function() {
        return createTime
    }
}
Copy the code

Declare the private property as a variable directly, and then create a privileged function inside the constructor to get the variable and return it. This property is partially hidden, but the problem is that the variable and privileged functions exist on every instance, increasing resource usage, which is obviously not a good solution.

Imperfection 2 – inheritance

Unlike other OOP languages, JavaScript inheritance makes subclasses closely related to their parent classes, and any changes to the parent class can affect the subclass. The reason for this is that JavaScript objects and constructors have the concept of “prototype”. The child class and its parent class inherit from each other through the prototype, and the prototype has the property of behavior delegation. Any changes to the parent class can affect the child class.

In plain English, behavior delegation means that when an object acquires a property or method that does not exist, it continues to look for that property or method on the prototype.

You can see this from archetypal inheritance:

function A() {}
A.prototype.print = function() {
    console.log(this.constructor.name)
}

function B() {}
B.prototype = new A

const b = new B
b.print()  // A
Copy the code

Link B. protoType to instantiated A, and the prototype chain of the final object B is shown in the figure:

Object graph LR A [b] - __proto__ - > b [A] after the instantiation - __proto__ - > C/Amy polumbo rototype - __proto__ [Object. The prototype] -- -- -- > D __proto__ --> F[null]

For b.string (), it follows the prototype chain until it is found at a.prototype, and then runs the method. Change the code to look like this:

A.prototype.printIn = function() {
    console.log(this.constructor.name)
}
Copy the code

Directly causing an instance of subclass B to run wrong. Archetypal inheritance is one of the simplest and best understood inheritance methods. You can click here to view the advantages and disadvantages of other inheritance methods summarized by predecessors. Although inheritance schemes are various, they are ultimately inseparable from the category of prototype chain. As long as the prototype chain is involved, there will be concerns about changing the parent class.

Imperfection 3 – polymorphism

Based on the previous stereotype inheritance fact, we know that when looking for a property that doesn’t exist on its own, the engine continues down the stereotype chain. If the search attribute exists both on itself and on the prototype chain, the result will be prototype masking:

function Person() {
    this.age = 20
}

function PersonA() {
    this.age = 21
}
PersonA.prototype = new Person

const persona = new PersonA
persona.age / / 21
Copy the code

The age attribute in the object Persona will mask all age attributes on the prototype chain, because the persona. Age will always select the age attribute at the bottom of the prototype chain, which is exactly one way to achieve polymorphism: override.

In addition to overwriting, there is another way to implement polymorphism that JavaScript does not currently support: overloading, where an object’s own method has the same name as an inherited method, and the number of input arguments can be used to tell the difference.

function B() {}
B.prototype = new A
B.prototype.print = function(param) {
    console.log(param)
}

const b = new B
b.print()  // A
b.print('b')  // b
Copy the code

The above code works when overloading techniques are available. But the same property or method name will mask the stereotype, so overloading can only be done by other means, such as calling different methods by judging the incoming arguments.

Latest technology Class

The arrival of ES6 has pumped new blood into JavaScript, with a large number of new features that have excited developers and increased understanding costs for many developers. Constructors, meanwhile, are enjoying a spring of their own, with a legitimate class keyword and related features.

More specific “classes”

class A{}

const a = new A
Copy the code

Compared to constructors, the new declarations are semantically straightforward, although they are essentially syntactic sugar for constructors. Note that classes declared by class cannot be called directly as functions, which is slightly different:

A()  // Uncaught TypeError: Class constructor A cannot be invoked without 'new'
Copy the code

The console displays an error telling us that we must instantiate the “class” using the new keyword. This is not available before, but can be simulated using the new-related features:

function A() {
	if (new.target ! == A) {throw new TypeError(`Class constructor A cannot be invoked without 'new'`)}}Copy the code

New.target points to the current constructor itself, which can be used to simulate the class behavior of not being able to call a function. By the way, strict mode is enabled by default when using the class keyword. Strict mode is not declared ahead of time.

More complete encapsulation

In addition to class declarations, class properties and method permissions have been updated, and the addition of static, private properties and methods makes it much easier to build a reasonable utility class.

class A {
  static value = 1
  #private_value = 2
}
Copy the code

Static identifiers start with the static keyword and private identifiers start with #, both of which can be used on properties and methods.

conclusion

ES6, while breathing new life into constructors, doesn’t change the fact that syntax is sugar. Encapsulation has improved to some extent, but there is still a long way to go to standard programming languages like Java; But if you think about it the other way around, why do we have to be benchmarked? JavaScript goes its own way to take advantage of dynamic languages.

Along with the written

Finally, I am holding out the second article. I feel wrong if I don’t write it. If I find myself writing it, WHY don’t I spend my time playing two games?

Again, if I could write, there would be a third!

Hello everyone!


  1. Package the data and the actions that manipulate the data in a single cell. ↩
  2. Cell A captures the data and behavior of cell B. ↩
  3. There are different results for the same behavior based on inheritance characteristics. ↩
  4. Capitalizing constructors is just a community specification, and since it is a specification and you can choose to follow it or not, the compiler does not adjust its compilation to such a specification. ↩