Unlike class-based programming languages such as C + + and Java,javascript, inheritance is based on prototypes. And because JavaScript is a very flexible language, there are many ways to implement inheritance.
The primary basic concept is about constructors and prototype chains, where the constructor of the parent object is called, Parent
and the child object's constructor is called Child
, and the corresponding parent object and child object are respectively parent
child
.
Object has a hidden property [[prototype]]
(note not prototype
), is in Chrome __proto__
, and in some circumstances is inaccessible, it points to the object's prototype. When a property or method of any object is accessed, all properties of the object are searched first, and if not found, the [[prototype]]
properties on its prototype object are searched incrementally along the prototype chain until it is found, otherwise it is returned undefined
.
Prototype chain inheritance
The prototype chain is the default way to implement inheritance in JavaScript, and the simplest way to make child objects inherit from a parent object is to point the property of the child object constructor to prototype
an instance of the parent object:
function Parent() {}function Child() {}Child.prototype = new Parent()
This time, Child
the prototype
property is rewritten, pointing to a new object, but the property of the new object constructor
is not pointing correctly Child
, the JS engine does not automatically do the work for us, which requires us to manually go to Child
the prototype object's constructor
properties to point back to Child
:
Child.prototype.constructor = Child
The above is the default inheritance mechanism in JavaScript, migrating properties and methods that need to be reused into prototype objects, and setting the non-reusable part as the object's own property, but this inheritance requires a new instance as a prototype object, which is less efficient.
Prototype inheritance (non-prototype chain)
To avoid the problem of duplicating the creation of a prototype object instance on the previous method, you can direct the child object constructor to the prototype
parent object constructor, prototype
so that all Parent.prototype
properties and methods can be reused without having to recreate the prototype object instance:
Child.prototype = Parent.prototypeChild.prototype.constructor = Child
But we know that in JavaScript, the object is present as a reference type, and this method actually points to the Child.prototype
Parent.prototype
same object as the pointer being saved, so when we want to extend some properties in the child object prototype to continue inheriting later, The prototype of the parent object is also overwritten because there is always only one instance of the prototype object here, which is also a disadvantage of this inheritance method.
Temporary constructor inheritance
To solve the above problem, you can borrow a temporary constructor that acts as a middle tier, and all child object prototypes are done on an instance of the temporary constructor without affecting the parent object prototype:
var F = function() {}F.prototype = Parent.prototypeChild.prototype = new F()Child.prototype.constructor = Child
Also, in order to access the properties in the parent prototype in the child object, you can include a property on the child object constructor that points to the parent object's prototype, such that uber
you can child.constructor.uber
access the parent prototype object directly on the child object.
We can encapsulate the above work into a function that can be easily implemented by invoking this function later:
function extend(Child, Parent) { var F = function() {} F.prototype = Parent.prototype Child.prototype = new F() Child.prototype.constructor = Child Child.uber = Parent.prototype}
You can then invoke this:
extend(Dog, Animal)
Property Copy
This inheritance basically does not change the relationship of the prototype chain, but rather directly copies the properties of the parent prototype object into the child object prototype, of course, the copy here only applies to the basic data type, the object type only supports reference delivery.
function extend2(Child, Parent) { var p = Parent.prototype var c = Child.prototype for (var i in p) { c[i] = p[i] } c.uber = p}
In this way, some of the prototype properties are reconstructed and the object is less efficient to construct, but it can reduce the search for the prototype chain. But I personally think the advantages of this approach are not obvious.
Inter-Object inheritance
In addition to the constructor-based inheritance method, you can also leave the constructor to inherit directly between objects. This is a direct copy of the object's properties, including shallow and deep copies.
Shallow copy
Accept the object you want to inherit, create a new empty object, copy the properties of the inherited object to the new object, and return the new object:
function extendCopy(p) { var c = {} for (var i in p) { c[i] = p[i] } c.uber = p return c}
After the copy is complete, you can manually overwrite the properties that need to be overwritten in the new object.
Deep copy
A shallow copy of the problem is also obvious, it can not copy the properties of the object type and can only pass the reference, to solve the problem is to use a deep copy. The deep copy focuses on the recursive invocation of the copy, creating the corresponding object or array when the properties of the object type are detected, and copying the underlying type values in one.
function deepCopy(p, c) { c = c || {} for (var i in p) { if (p.hasOwnProperty(i)) { if (typeof p[i] === ‘object‘) { c[i] = Array.isArray(p[i]) ? [] : {} deepCopy(p[i], c[i]) } else { c[i] = p[i] } } } return c}
A ES5 Array.isArray()
method is used to determine if the parameter is an array, and an environment that does not implement this method requires you to manually encapsulate a shim.
Array.isArray = function(p) { return p instanceof Array}
However, it is instanceof
not possible to use operators to judge array variables from different frameworks, but this is less.
Prototype inheritance
With the parent object, a new object is created with the parent object as a prototype through the constructor:
function object(o) { var n function F() {} F.prototype = o n = new F() n.uber = o return n}
Here, the parent object is set directly to the prototype of the child object, and the method in ES5 Object.create()
is the way it is implemented.
Prototype inheritance and attribute copy blending
In the prototype inheritance method, the child object is built as a prototype of the passed-in parent, and you can also pass in additional objects that require copying properties beyond the properties provided by the parent object:
function ojbectPlus(o, stuff) { var n function F() {} F.prototype = o n = new F() n.uber = o for (var i in stuff) { n[i] = stuff[i] } return n}
Multiple inheritance
This approach does not involve the operation of the prototype chain, passing in multiple objects that require a copy of the property, followed by a full copy of the attribute:
function multi() { var n = {}, stuff, i = 0, len = arguments.length for (i = 0; i < len; i++) { stuff = arguments[i] for (var key in stuff) { n[i] = stuff[i] } } return n}
Copies are copied sequentially based on the order in which the objects are passed in, that is, if the subsequent incoming object contains the same properties as the previous object, the latter overrides the former.
Constructor borrowing
JavaScript call()
and methods are apply()
very useful, and the ability to change the execution context of a method can also work in an inherited implementation. The so-called constructor borrowing refers to the operation of borrowing the parent object's constructor in the child object builder this
:
function Parent() {}Parent.prototype.name = ‘parent‘function Child() { Parent.apply(this, arguments)}var child = new Child()console.log(child.name)
The great advantage of this approach is that in the constructor of a child object, it is a complete rebuild of its own property, and a variable of the reference type will generate a new value instead of a reference, so any manipulation of a child object will not affect the parent object.
The disadvantage of this approach is that the operator is not used during the construction of the child object, new
so the child object does not inherit any properties on the parent prototype object, and in the above code, child
the name
property will be undefined
.
To resolve this problem, you can manually set the child object constructor prototype to an instance of the parent object again:
new Parent()
However, this brings another problem, that is, the constructor of the parent object is called two times, one time in the parent object constructor borrowing, and the other during the inheritance prototype process.
To solve this problem, we need to remove the call of the parent object constructor, the constructor borrowing cannot be omitted, then the next call can only be removed, and another way to implement the inherited prototype is to iterate the replication:
extend2(Child, Parent)
Use the extend2()
method you implemented earlier.
Implementations that are inherited in JavaScript