Original link: blog.xqopen.com/about-oojs

preface

Object, one of JavaScript 7’s big data types, is a collection of unordered key-value pairs that we often use. It’s ubiquitous in our code, but when it comes to JavaScript’s object-oriented design, it’s often hard to see why. The definition of data types is probably only the tip of the iceberg when it comes to understanding objects.

1. Cut to the chase

1.1 What is object orientation

Object orientation is an approach to understanding and abstracting the real world: to simplify and abstract complex things into a model by taking advantage of some important properties of real things for programming purposes.

As shown in the figure below, the Person in reality is abstracted as a Person object with the status of name, age, gender and greeting behavior.

An object, on the other hand, is a collection of related data and methods that describe the state and behavior of the model we build. It usually consists of variables and functions, which are called member variables and member functions in C++ and properties and methods in Java.

In JavaScript, state and behavior are abstracted as properties of objects.

1.2 Object-oriented programming

The basic idea of Object-oriented programming is that we use objects to model real world things that we want to represent inside our programs, and/or provide a simple way to access functionality that would otherwise be hard or impossible to make use of.

Use objects to build models of the real world, making accessible simple descriptions of functionality that would otherwise be difficult or impossible to use.

The basic idea of OOP is to abstract things into objects, define their properties and methods, and encapsulate them into a template. Objects created from this template have corresponding states and behaviors. In the figure below, two objects are instantiated to represent a concrete Person through the abstraction described above.

In addition to encapsulating objects to instantiate concrete things, traditional OOP is characterized by inheritance and polymorphism.

In different programming languages, designers use different language features to describe objects abstractly, such as C++, the way Java language uses classes, and the way JavaScript uses prototypes to achieve object-oriented programming design.

Class-based OOP

2.1 “Traditional” classes

In class-based programming, new classes can be created based on other classes, and these new subclasses can inherit the data and functionality of their parent classes and define specific characteristics. The overwriting of a parent class in a subclass is polymorphic.

The Adult and Baby classes inherit from Person and add their own behavior walk and climb. And Baby rewrites sayHi’s behavior.

Class means copy. When a class is instantiated, its behavior is copied into the instance. When a class is inherited, its behavior is also copied into its subclasses.

JavaScript, on the other hand, has only objects; there are no “classes” that can be instantiated.

2.2 simulation class

To simulate the behavior of classes, JavaScript developers design mixin patterns. To copy the properties of one object into another, similar to calling the parent class call(this) in the constructor or method of a “subclass” :

var Something = {
  cool: function () {
    this.greeting = "Hello World";
    this.count = this.count ? this.count + 1 : 1; }};var Another = {
  cool: function() {
    // add Something to Another implicitly
    Something.cool.call( this); }};Copy the code

This often results in “ugly” and fragile syntax that does not fully mimic the copying behavior of the class. For example, when an object’s properties are also objects (functions are also objects), the reference to the object is copied instead of the object itself.

Emulating the behavior of classes makes JS code more difficult to understand and maintain.

3. Prototype based OOP

JavaScript implements object-oriented programming through prototype.

The object is created with a special **[[Prototype]]** built-in attribute (x.__proto__, __proto__ nonstandard) that points to its Prototype object (X.prototype). Objects “inherit” properties and methods from their prototypes as templates.

The [[Get]] operation is triggered when a reference to an object’s attribute. If the object itself does not have this attribute, the [[Prototype]] chain will continue to access the object’s [[Prototype]] chain until a matching attribute name is found or the entire Prototype chain is found:

// Define a constructor function
function Person(name) {
  this.name = name;
  this.sayHi = function() {
    console.log('Hi, I\'m ' + this.name); }}// Create an object instance
var person1 = new Person('Jack');
person1.sayHi()  // Hi, I'm Jack
Copy the code

The new object, the prototype object, and the constructor are not directly related as shown above. The **[[Prototype]]** object is actually a link between the object instance and its constructor.

3.1 prototype chain

The function object has a property called Prototype (only function objects have it) that points to another object. Call new Person (…). Create person1 and associate person1.__proto__ to the object pointed to by Person.prototype, which indirectly completes the association between the new object and the constructor.

Each object has a stereotype, and a stereotype object may have a stereotype from which it inherits methods and properties, layer by layer, and so on. This relationship is often referred to as the **[[Prototype]] chain **.

The prototype chain relationship for person1 and Person is as follows:

As shown, the constructor’s prototype object Person.prototype’s constructor property refers to the function associated with Person.prototype, namely Person.

person1.__proto__ === Person.prototype  // true
Person.prototype.constructor === Person; // true
person1.constructor === Person  // true
Copy the code

Here, the constructor property of the object person1 points to Person, but person1 does not have a constructor property. Instead, it traces the prototype chain back to the constructor of Person.prototype.

The problem with the example above is that when we call new Person(…) When person1 and person2 are created, 1 and 2 have their own name, but they also have their own sayHi function, although the function name and code are the same. In order to save memory, there is actually a common function:

function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function () {
  console.log('Hi, I\'m ' + this.name);
}
Copy the code

The sayHi function is defined on the object’s common Prototype, and the instance object no longer creates the corresponding property of the function, but accesses it through the prototype chain.

3.2 Prototype Inheritance

The mechanism by which objects are linked through stereotype chains is often referred to as stereotype inheritance.

It is important to note that this does not create a full copy like class inheritance. For object properties, JavaScript creates an association between two objects so that one object can delegate to the other object’s properties and functions. This mechanism is also referred to in documentation as delegation.

It can be seen that if you want to inherit Person to define a more specific Person Adult, you need to associate Adult with Person. When an object constructed by Adult accesses attributes, it follows the chain of archetypes by accessing Adult’s archetypes and then Person’s archetypes, thus inheriting the state and behavior of Person.

So how do you make the archetype of Adult relate to the archetype of Person? Here are two forms of association, although there are some problems:

// Adult.prototype directly references the Person.prototype object
// Assign to adult. prototype and modify person. prototype itself directly
Adult.prototype = Person.prototype;

// Create a new object associated with Person.prototype. Adult. Prototype shares the attributes of Person
Adult.prototype = new Person();
Copy the code

If you want to define your behavior for Adult, you can’t point adult. prototype directly to Person.prototype. If you use Person to construct a new object, Adult.prototype will add the data attributes of Person, which is not necessary and may affect Adult “descendants”.

We need to create an appropriate association object that is a prototype of Adult. Prototype and points to Person.prototype without generating any extra information:

Another way to create objects object.create (..) , creates a new object and associates the object’s [[Prototype]] with the specified object, with no constructor side effects.

Here is the inheritance of the “archetypal style” :

function Person(name) {
  this.name = name;
}
Person.prototype.sayHi = function () {
  console.log('Hi, I\'m ' + this.name);
}
function Adult(name) {
  Person.call(this, name);
}
// Creates a new bar.prototype object and associates it with Person.prototype
Adult.prototype = Object.create(Person.prototype);
// Manually repair constructor
Adult.prototype.constructor = Adult;
Adult.prototype.walk = function () {
  console.log('I can walk well');
};

var adult1 = new Adult("Bob");
adult1.sayHi();  // Hi, I'm Bob
adult1.walk();  // I can walk well
Copy the code

The downside of this approach is that you need to create a new object and then discard the old one, resulting in a slight performance penalty. Another Adult. The prototype to a new object, will lose the constructor, if this attribute is needed to manually fix, or you will find to the Person. The prototype. The constructor, namely the Person.

If you don’t want to create a new object, you can modify the Adult. Prototype prototype directly:

// ES6 added helper functions
Object.setPrototypeOf( Adult.prototype, Person.prototype );
Copy the code

ES6 previously could only do this by setting the __proto__ attribute, but this method was not standard and was not compatible with all browsers.

3.3 class grammar sugar

The inheritance of the prototype style obviously makes code harder to read and harder to maintain.

ES6 introduces the class keyword to simplify coding and avoid code errors. The above example can be modified as follows:

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log('Hi, I\'m ' + this.name); }}class Adult extends Person {
  constructor(name) {
    super(name)
  }
  walk() {
    console.log('I can walk well'); }}Copy the code

The code style is much more beautiful, but also solved many problems and shortcomings in the prototype style code, such as no longer reference clutter. Prototype, no need to manually set the prototype association, etc.. But this does not mean that JavaScript has introduced a class mechanism.

Whereas traditional class-oriented languages statically copy all behavior at declaration time, classes in JS actually use real-time delegation based on prototypes. If you change or replace a method in the parent class, the child class and all instances are affected. So class is basically just a syntactic sugar on top of the existing prototype delegation mechanism.

4. Summary

We also use inheritance and stereotype chains all the time in everyday coding, such as calling the methods and properties of built-in objects like strings and arrays.

arr ----> Array.prototype ----> Object.prototype ----> null
Copy the code

While the mechanics of prototyping and inheritance are complex, this is where JavaScript is powerful and flexible, and understanding how it works helps us understand JavaScript from point to surface.

In contrast to class-based OOP, which recommends that parent classes and subclasses use the same method name to represent a particular behavior, prototype-based OOP tries to avoid using the same name at different levels of the prototype chain, which can reduce code readability and robustness.

Inheritance of a class is organized vertically like a superclass to subclass relationship, whereas inheritance of archetypes is organized side-by-side through delegation associations in any direction, more like fraternal relationships.

Because there is no direct support for polymorphism, the way inheritance is implemented is quite different and does not conform to object-oriented characteristics. Many people support the statement that JavaScript is not object-oriented but an object-based language.

While OOJS is designed to follow the basic idea of OOP, and JavaScript can create objects directly without using classes, it seems JavaScript is the language that really deserves to be called “object-oriented”?