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 Cat
are 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.name
color
cat1
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, cat1
and cat2
are two instances of the same constructor, both of which have meow
methods. Since meow
the method is generated on each instance object, two instances are generated twice. In other words, every time a new instance is created, a new meow
method will be created. This is both unnecessary and a waste of system resources, since all meow
methods 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 prototype
property that points to an object.
function f() {} typeof f.prototype // "object"
In the above code, the function f
has prototype
attributes 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'
Animal
In the above code, the properties of the constructor prototype
are the instance object cat1
and cat2
the prototype object. A color
property 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, color
the value of the property of the prototype object changes yellow
, and the properties of the two instance objects color
change immediately. This is because the instance object does not actually have color
attributes, and they all read color
the 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 cat1
of 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 .color
black
color
yellow
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.prototype
a method is defined on the object walk
, which can be Animal
called 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 Object
of the constructor prototype
. Object.prototype
That is, properties that all objects inherit . This is why all objects have valueOf
the and method, since this is inherited from .toString
Object.prototype
So, Object.prototype
does an object have its prototype? Object.prototype
The prototype for the answer is yes null
. null
Does 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.prototype
the prototype of the object is null
, since null
there are no properties, the prototype chain ends here. Object.getPrototypeOf
The 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 prototype
point 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, mine
it is the instance object of the constructor MyArray
. Since MyArray.prototype
it points to an array instance, mine
the array method can be called (these methods are defined on prototype
the object of the array instance). The last line instanceof
of expression is used to compare whether an object is an instance of a certain constructor, and the result is an instance that proves mine
to be Array
an instance. instanceof
For a detailed explanation of the operator, see below.
The above code also shows constructor
the properties of the prototype object. The meaning of this property will be explained in the next section.
constructor property
prototype
The object has a constructor
property, which by default points to prototype
the constructor where the object is located.
function P() {} P.prototype.constructor === P // true
Since constructor
the attribute is defined prototype
on 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, p
it is an instance object of the constructor P
, but p
it has no property itself constructor
. This property actually reads P.prototype.constructor
the properties on the prototype chain.
constructor
The 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, constructor
the attribute determines f
the constructor of the instance object is F
, not RegExp
.
On the other hand, with constructor
properties, 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, x
it is an instance of the constructor , and the constructor Constr
can be called indirectly. x.constructor
This makes it possible for an instance method to call its own constructor.
Constr.prototype.createCopy = function () { return new this.constructor(); };
In the above code, createCopy
the method calls the constructor to create another instance.
constructor
Attributes 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 constructor
to 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 Person
is changed, but constructor
the property is not modified, so the property no longer points to it Person
. Since Person
the new prototype of is an ordinary object, and constructor
the properties of the ordinary object point to Object
the constructor, it Person.prototype.constructor
becomes Object
.
Therefore, when modifying the prototype object, it is generally necessary to modify constructor
the 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 constructor
re-point the property to the original constructor, or only add the method on the prototype object, so as to ensure that instanceof
the operator will not be distorted.
If you can't determine constructor
what function the attribute is, there is another way: name
get the name of the constructor from the instance through the attribute.
function Foo() {} var f = new Foo(); f.constructor.name // "Foo"
instanceof operator
instanceof
The 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 v
is an instance of the constructor Vehicle
, so it is returned true
.
instanceof
The 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, Vehicle
it is the constructor of the object , v
and 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.prototype
isPrototypeOf()
Since instanceof
the 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, d
it is an instance of Date
and at the same time Object
, so both constructors are returned true
.
Since any object (except null
) is Object
an instance of , instanceof
the operator can determine whether a value is not null
an object.
var obj = { foo: 123 }; obj instanceof Object // true null instanceof Object // false
In the above code, except for the operation results null
of other objects .instanceOf Object
true
instanceof
The principle is to check whether the properties of the constructor on the right prototype
are 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, instanceof
the 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). Object
The properties of the constructor on the right prototype
are not on the prototype chain on the left, so it is not instanceof
considered an instance of it. This is the only case where operator evaluation will be distorted (an object's prototype is ).obj
Object
instanceof
null
instanceof
One 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, instanceof
the operator judges that the variable x
is an array and the variable y
is an object.
Note that instanceof
operators can only be used on objects, not primitive values.
var s = 'hello'; s instanceof String // false
In the above code, the string is not String
an instance of the object (because the string is not an object), so it is returned false
.
Also, for undefined
sums null
, instanceof
the operator always returns false
.
undefined instanceof Object // false null instanceof Object // false
Using instanceof
operators can also cleverly solve new
the 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 instanceof
operators to determine this
whether the keyword is an instance of the constructor inside the function body Fubar
. If not, it means that you forgot to add new
the 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, Sub
it is the constructor of the subclass and this
an instance of the subclass. Calling the constructor of the parent class on the instance Super
will 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.prototype
it 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.prototype
the prototype of the parent class together.Super.prototype
Another way of writing is Sub.prototype
equal 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 Shape
constructor.
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 Rectangle
the 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, instanceof
the 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 }
B
In the above code, the method of the subclass print
calls the method A
of the parent class first print
, and then deploys its own code. This is equivalent to inheriting the method A
of 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 S
inherits both the parent class M1
and 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 m1
and m2
, are encapsulated in module1
the 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, buffer
it 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 _count
variables.
console.info(module1._count); //undefined
The above module1
is 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 module1
adds a new method to the module m3()
and returns the new module1
module.
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 module1
module 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, finalCarousel
the object is output to the global, exposed to the outside world init
and destroy
the interface, and the internal methods go
, handleEvents
, initialize
, dieCarouselDie
cannot be called from the outside.
reference link
-
JavaScript Modules: A Beginner’s Guide, by Preethi Kasireddy