Inheritance of objects in javascript object-oriented learning

object inheritance

An important aspect of object-oriented programming is object inheritance. By inheriting the B object, the A object can directly have all the properties and methods of the B object. This is very useful for code reuse.

Most object-oriented programming languages ​​implement object inheritance through "classes". Traditionally, the inheritance of JavaScript language is not implemented through class, but through "prototype object" (prototype). This chapter introduces JavaScript's prototype chain inheritance.

ES6 introduces the class syntax, class-based inheritance is not introduced in this tutorial, please refer to the relevant chapters in the book "Introduction to ES6 Standards".

Prototype Object Overview

Constructor Disadvantages

JavaScript generates new objects through constructors, so constructors can be thought of as templates for objects. The properties and methods of the instance object can be defined inside the constructor.

function Cat (name, color) {
  this.name = name;
  this.color = color;
}
var cat1 = new Cat('big hair', 'white');
cat1.name // 'big hair'
cat1.color // 'white'

In the above code, the function is a constructor, and attributes and attributes Catare defined inside the function . All instance objects (in the above example ) will generate these two attributes, that is, these two attributes will be defined on the instance object.namecolorcat1

Although it is convenient to define attributes for instance objects through constructors, it has a disadvantage. Attributes cannot be shared between multiple instances of the same constructor, resulting in a waste of system resources.

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function () {
    console.log('meow meow');
  };
}
var cat1 = new Cat('big hair', 'white');
var cat2 = new Cat('two hair', 'black');
cat1.meow === cat2.meow
// false

In the above code, cat1and cat2are two instances of the same constructor, both of which have meowmethods. Since meowthe method is generated on each instance object, two instances are generated twice. In other words, every time a new instance is created, a new meowmethod will be created. This is both unnecessary and a waste of system resources, since all meowmethods have the same behavior and should be shared at all.

The solution to this problem is JavaScript's prototype object (prototype).

The role of the prototype attribute

The design idea of ​​the JavaScript inheritance mechanism is that all properties and methods of the prototype object can be shared by the instance object. That is to say, if properties and methods are defined on the prototype, then all instance objects can be shared, which not only saves memory, but also reflects the connection between instance objects.

Next, let's first look at how to specify a prototype for an object. JavaScript stipulates that each function has a prototypeproperty that points to an object.

function f() {}
typeof f.prototype // "object"

In the above code, the function fhas prototypeattributes by default, pointing to an object.

For ordinary functions, this attribute is basically useless. However, for the constructor, when an instance is generated, this attribute will automatically become the prototype of the instance object.

function Animal(name) {
  this.name = name;
}
Animal.prototype.color = 'white';
var cat1 = new Animal('big hair');
var cat2 = new Animal('Two-haired');
cat1.color // 'white'
cat2.color // 'white'

AnimalIn the above code, the properties of the constructor prototypeare the instance object cat1and cat2the prototype object. A colorproperty is added to the prototype object, and as a result, the instance objects all share this property.

Properties of the prototype object are not properties of the instance object itself. As long as the prototype object is modified, the change will be reflected in all instance objects immediately.

Animal.prototype.color = 'yellow';
cat1.color // "yellow"
cat2.color // "yellow"

In the above code, colorthe value of the property of the prototype object changes yellow, and the properties of the two instance objects colorchange immediately. This is because the instance object does not actually have colorattributes, and they all read colorthe attributes of the prototype object. That is to say, when the instance object itself does not have a certain property or method, it will go to the prototype object to find the property or method. This is what makes prototype objects special.

If the instance object itself has a certain property or method, it will not go to the prototype object to find this property or method.

cat1.color = 'black';
cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow';

In the above code, the attribute cat1of the instance object is changed , so that it no longer reads the attribute of the prototype object, and the value of the latter is still .colorblackcoloryellow

To sum up, the role of the prototype object is to define the properties and methods shared by all instance objects. This is why it is called a prototype object, and an instance object can be regarded as a subobject derived from a prototype object.

Animal.prototype.walk = function () {
  console.log(this.name + ' is walking');
};

In the above code, Animal.prototypea method is defined on the object walk, which can be Animalcalled on all instance objects.

prototype chain

JavaScript stipulates that all objects have their own prototype object (prototype). On the one hand, any object can serve as the prototype of other objects; on the other hand, since the prototype object is also an object, it also has its own prototype. Therefore, a "prototype chain" (prototype chain) will be formed: object to prototype, and then to the prototype of the prototype...

If you trace up layer by layer, the prototype of all objects can eventually be traced back to Object.prototype, that is, the properties Objectof the constructor prototype. Object.prototypeThat is, properties that all objects inherit . This is why all objects have valueOfthe and method, since this is inherited from .toStringObject.prototype

So, Object.prototypedoes an object have its prototype? Object.prototypeThe prototype for the answer is yes null. nullDoes not have any properties and methods, and does not have its own prototype. So the end of the prototype chain is null.

Object.getPrototypeOf(Object.prototype)
// null

The above code indicates that Object.prototypethe prototype of the object is null, since nullthere are no properties, the prototype chain ends here. Object.getPrototypeOfThe method returns the prototype of the parameter object. For details, please refer to the following text.

When reading a property of an object, the JavaScript engine first looks for the property of the object itself. If it cannot find it, it goes to its prototype. If it still cannot find it, it goes to the prototype of the prototype to find it. If none is found up to the top level Object.prototype, return undefined. If both the object itself and its prototype define a property with the same name, then the property of the object itself is read first, which is called "overriding".

Note that going up one level, looking for a certain property on the entire prototype chain has an impact on performance. The higher the prototype object whose property you are looking for has a greater impact on performance. If looking for a property that doesn't exist, the entire prototype chain will be traversed.

For example, if you let the properties of the constructor prototypepoint to an array, it means that the instance object can call the array method.

var MyArray = function () {};

MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;

var mine = new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true

In the above code, mineit is the instance object of the constructor MyArray. Since MyArray.prototypeit points to an array instance, minethe array method can be called (these methods are defined on prototypethe object of the array instance). The last line instanceofof expression is used to compare whether an object is an instance of a certain constructor, and the result is an instance that proves mineto be Arrayan instance. instanceofFor a detailed explanation of the operator, see below.

The above code also shows constructorthe properties of the prototype object. The meaning of this property will be explained in the next section.

constructor property

prototypeThe object has a constructorproperty, which by default points to prototypethe constructor where the object is located.

function P() {}
P.prototype.constructor === P // true

Since constructorthe attribute is defined prototypeon the object, it means that it can be inherited by all instance objects.

function P() {}
var p = new P();

p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false

In the above code, pit is an instance object of the constructor P, but pit has no property itself constructor. This property actually reads P.prototype.constructorthe properties on the prototype chain.

constructorThe function of the attribute is to know which constructor generated an instance object.

function F() {};
var f = new F();

f.constructor === F // true
f.constructor === RegExp // false

In the above code, constructorthe attribute determines fthe constructor of the instance object is F, not RegExp.

On the other hand, with constructorproperties, it is possible to create another instance from one instance object.

function Constr() {}
var x = new Constr();

var y = new x.constructor();
y instanceof Constr // true

In the above code, xit is an instance of the constructor , and the constructor Constrcan be called indirectly. x.constructorThis makes it possible for an instance method to call its own constructor.

Constr.prototype.createCopy = function () {
  return new this.constructor();
};

In the above code, createCopythe method calls the constructor to create another instance.

constructorAttributes represent the relationship between the prototype object and the constructor. If the prototype object is modified, the attributes will generally be modified at the same time constructorto prevent errors when referencing.

function Person(name) {
  this.name = name;
}

Person.prototype.constructor === Person // true

Person.prototype = {
  method: function () {}
};

Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true

In the above code, the prototype object of the constructor Personis changed, but constructorthe property is not modified, so the property no longer points to it Person. Since Personthe new prototype of is an ordinary object, and constructorthe properties of the ordinary object point to Objectthe constructor, it Person.prototype.constructorbecomes Object.

Therefore, when modifying the prototype object, it is generally necessary to modify constructorthe pointer of the attribute at the same time.

// bad spelling
C.prototype = {
  method1: function (...) { ... },
  // ...
};

// good writing
C.prototype = {
  constructor: C,
  method1: function (...) { ... },
  // ...
};

// better way to write
C.prototype.method1 = function (...) { ... };

In the above code, either constructorre-point the property to the original constructor, or only add the method on the prototype object, so as to ensure that instanceofthe operator will not be distorted.

If you can't determine constructorwhat function the attribute is, there is another way: nameget the name of the constructor from the instance through the attribute.

function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"

instanceof operator

instanceofThe operator returns a boolean indicating whether the object is an instance of a constructor.

var v = new Vehicle();
v instanceof Vehicle // true

In the above code, the object vis an instance of the constructor Vehicle, so it is returned true.

instanceofThe left side of the operator is the instance object, and the right side is the constructor. It checks whether the prototype object (prototype) of the constructor on the right is on the prototype chain of the object on the left. Therefore, the following two ways of writing are equivalent.

v instanceof Vehicle
// Equivalent to
Vehicle.prototype.isPrototypeOf(v)

In the above code, Vehicleit is the constructor of the object , vand its prototype object is the native method provided by JavaScript, which is used to check whether an object is the prototype of another object. See below for detailed explanation.Vehicle.prototypeisPrototypeOf()

Since instanceofthe entire prototype chain is checked, the same instance object may be returned by multiple constructors true.

var d = new Date();
d instanceof Date // true
d instanceof Object // true

In the above code, dit is an instance of Dateand at the same time Object, so both constructors are returned true.

Since any object (except null) is Objectan instance of , instanceofthe operator can determine whether a value is not nullan object.

var obj = { foo: 123 };
obj instanceof Object // true

null instanceof Object // false

In the above code, except for the operation results nullof other objects .instanceOf Objecttrue

instanceofThe principle is to check whether the properties of the constructor on the right prototypeare on the prototype chain of the object on the left. There is a special case where there are only objects on the prototype chain of the left object null. At this time, instanceofthe judgment will be distorted.

var obj = Object.create(null);
typeof obj // "object"
obj instanceof Object // false

In the above code, Object.create(null)a new object is returned obj, and its prototype is null( Object.create()see later for details). ObjectThe properties of the constructor on the right prototypeare not on the prototype chain on the left, so it is not instanceofconsidered an instance of it. This is the only case where operator evaluation will be distorted (an object's prototype is ).objObjectinstanceofnull

instanceofOne use of operators is to determine the type of a value.

var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true

In the above code, instanceofthe operator judges that the variable xis an array and the variable yis an object.

Note that instanceofoperators can only be used on objects, not primitive values.

var s = 'hello';
s instanceof String // false

In the above code, the string is not Stringan instance of the object (because the string is not an object), so it is returned false.

Also, for undefinedsums null, instanceofthe operator always returns false.

undefined instanceof Object // false
null instanceof Object // false

Using instanceofoperators can also cleverly solve newthe problem of forgetting to add commands when calling the constructor.

function Fubar (foo, bar) {
  if (this instanceof Fubar) {
    this._foo = foo;
    this._bar = bar;
  } else {
    return new Fubar(foo, bar);
  }
}

The above code uses instanceofoperators to determine thiswhether the keyword is an instance of the constructor inside the function body Fubar. If not, it means that you forgot to add newthe command.

constructor inheritance

It is a very common requirement for a constructor to inherit another constructor. This can be achieved in two steps. The first step is to call the constructor of the parent class in the constructor of the subclass.

function Sub(value) {
  Super.call(this);
  this.prop = value;
}

In the above code, Subit is the constructor of the subclass and thisan instance of the subclass. Calling the constructor of the parent class on the instance Superwill make the subclass instance have the attributes of the parent class instance.

The second step is to make the prototype of the subclass point to the prototype of the parent class, so that the subclass can inherit the prototype of the parent class.

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';

In the above code, Sub.prototypeit is the prototype of the subclass, and it should be assigned a value Object.create(Super.prototype)instead of being directly equal Super.prototype. Otherwise, the operations in the next two lines will modify Sub.prototypethe prototype of the parent class together.Super.prototype

Another way of writing is Sub.prototypeequal to a parent class instance.

Sub.prototype = new Super();

The above writing method also has the effect of inheritance, but the subclass will have the method of the parent class instance. Sometimes, this may not be what we need, so this way of writing is not recommended.

For example, the following is a Shapeconstructor.

function Shape() {
  this.x = 0;
  this.y = 0;
}

Shape.prototype.move = function (x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

We need to make Rectanglethe constructor inherit Shape.

// In the first step, the subclass inherits the instance of the parent class
function Rectangle() {
  Shape.call(this); // call parent class constructor
}
// another way of writing
function Rectangle() {
  this.base = Shape;
  this.base();
}

// In the second step, the subclass inherits the prototype of the parent class
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

After using this way of writing, instanceofthe operator will return both the constructor of the subclass and the parent class true.

var rect = new Rectangle();

rect instanceof Rectangle  // true
rect instanceof Shape  // true

In the above code, the subclass inherits the parent class as a whole. Sometimes only the inheritance of a single method is needed, then the following writing method can be used.

ClassB.prototype.print = function() {
  ClassA.prototype.print.call(this);
  // some code
}

BIn the above code, the method of the subclass printcalls the method Aof the parent class first print, and then deploys its own code. This is equivalent to inheriting the method Aof the parent class print.

multiple inheritance

JavaScript does not provide multiple inheritance, that is, it does not allow an object to inherit multiple objects at the same time. However, this functionality can be achieved through a workaround.

function M1() {
  this.hello = 'hello';
}

function M2() {
  this.world = 'world';
}

function S() {
  M1.call(this);
  M2.call(this);
}

// Inherit M1
S.prototype = Object.create(M1.prototype);
// Add M2 to the inheritance chain
Object.assign(S.prototype, M2.prototype);

// specify the constructor
S.prototype.constructor = S;

var s = new S();
s.hello // 'hello'
s.world // 'world'

In the above code, the subclass Sinherits both the parent class M1and the M2. This mode is also called Mixin (mixed in).

module

As websites have become "Internet applications," the JavaScript code embedded in web pages has grown larger and more complex. Web pages are becoming more and more like desktop programs, requiring a team to work together, progress management, unit testing, etc...Developers must use software engineering methods to manage the business logic of web pages.

JavaScript modular programming has become an urgent need. Ideally, developers only need to implement the core business logic, and other modules can be loaded by others.

However, JavaScript is not a modular programming language, and ES6 only started to support "classes" and "modules". The following introduces the traditional approach, how to use the object to achieve the effect of the module.

Basic implementation method

A module is an encapsulation of a set of properties and methods that implement a specific functionality.

The simple way is to write the module as an object, and all the module members are placed in this object.

var module1 = new Object({
 _count : 0,
 m1 : function (){
  //...
 },
 m2 : function (){
   //...
 }
});

The above functions m1and m2, are encapsulated in module1the object. When used, it is to call the properties of this object.

module1.m1();

However, this way of writing will expose all module members, and the internal state can be rewritten externally. For example, external code can directly change the value of an internal counter.

module1._count = 5;

Encapsulating private variables: how to write constructors

We can use constructors to encapsulate private variables.

function StringBuilder() {
  var buffer = [];

  this.add = function (str) {
     buffer.push(str);
  };

  this.toString = function () {
    return buffer.join('');
  };

}

In the above code, bufferit is a private variable of the module. Once an instance object is generated, it cannot be accessed directly from the outside buffer. However, this method encapsulates private variables in the constructor, resulting in the constructor being integrated with the instance object, which always exists in memory and cannot be cleared after use. This means that the constructor has a dual role, not only used to shape the instance object, but also used to save the data of the instance object, which violates the principle of separating the data of the constructor and the instance object (that is, the data of the instance object should not be saved outside the instance object). At the same time, it is very memory intensive.

function StringBuilder() {
  this._buffer = [];
}

StringBuilder.prototype = {
  constructor: StringBuilder,
  add: function (str) {
    this._buffer.push(str);
  },
  toString: function () {
    return this._buffer.join('');
  }
};

This method puts private variables into the instance object. The advantage is that it looks more natural, but its private variables can be read and written from the outside, which is not very safe.

Encapsulating private variables: writing the function immediately

Another approach is to use Immediately-Invoked Function Expression (IIFE) to encapsulate related properties and methods in a function scope, so as not to expose private members.

var module1 = (function () {
 var _count = 0;
 var m1 = function () {
   //...
 };
 var m2 = function () {
  //...
 };
 return {
  m1 : m1,
  m2: m2
 };
})();

Using the above writing method, external code cannot read internal _countvariables.

console.info(module1._count); //undefined

The above module1is the basic way of writing JavaScript modules. Next, we will process this writing method.

Amplification mode of the module

Augmentation is necessary if a module is large and must be divided into several parts, or if a module needs to inherit from another module.

var module1 = (function (mod){
 mod.m3 = function () {
  //...
 };
 return mod;
})(module1);

The above code module1adds a new method to the module m3()and returns the new module1module.

In a browser environment, parts of a module are often fetched from the web, and it is sometimes impossible to know which part will load first. If the above writing method is used, the first executed part may load an empty object that does not exist, and then "Loose augmentation" (Loose augmentation) must be used.

var module1 = (function (mod) {
 //...
 return mod;
})(window.module1 || {});

Compared with "enlargement mode", "wide enlargement mode" means that the parameter of "immediate execution function" can be an empty object.

import global variables

Independence is an important feature of a module, and it is best not to interact directly with other parts of the program inside the module.

In order to call a global variable inside a module, other variables must be explicitly entered into the module.

var module1 = (function ($, YAHOO) {
 //...
})(jQuery, YAHOO);

The above module1module needs to use the jQuery library and the YUI library, so these two libraries (actually two modules) are input as parameters module1. In addition to ensuring the independence of the modules, this also makes the dependencies between the modules obvious.

Immediate functions can also act as namespaces.

(function($, window, document) {

  function go(num) {
  }

  function handleEvents() {
  }

  function initialize() {
  }

  function dieCarouselDie() {
  }

  //attach to the global scope
  window.finalCarousel = {
    init : initialize,
    destroy : dieCarouselDie
  }

})( jQuery, window, document );

In the above code, finalCarouselthe object is output to the global, exposed to the outside world initand destroythe interface, and the internal methods go, handleEvents, initialize, dieCarouselDiecannot be called from the outside.

reference link

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132135158