"JavaScript Advanced Programming" study notes | 8.3. Inheritance

"JavaScript Advanced Programming" study notes | 8.3. Inheritance

Pay attention to the front-end Xiaogu , read more original technical articles

inherit

  • Object-oriented language supports two inheritance methods: interface inheritance and implementation inheritance
  • JS functions have no signature (no need to declare variable types in advance), only support inheritance , relying on the prototype chain

Related code

Prototype chain

  • The prototype of the subtype constructor is rewritten as an instance of the supertype constructor
function SuperType () { this .property = true } SuperType.prototype.getSuperValue = function () { return this .property } function SubType () {} SubType.prototype = new SuperType() //SubType prototype = SuperType instance, SubType prototype is rewritten SubType inherits SuperType console .log(SubType.prototype.__proto__) //SuperType prototype, [[Prototype]] of SuperType instance points to SuperType prototype console .log(SubType.prototype.__proto__.constructor) //SuperType constructor, SuperType prototype constructor points to SuperType Constructor copy code
  • Supertype Examples of properties and methods , both present in the subtype prototype in
  • Instances of the subtype can access the methods on the supertype prototype, and the methods still exist in the supertype prototype
var instance = new SubType() console .log(instance.property) //true, SubType inherits the property property console .log(SubType.prototype.hasOwnProperty( 'property' )) //true, property is an instance property of SuperType, SubType prototype has been rewritten as examples SuperType Console .log (instance.getSuperValue ()) //to true, SubType inherited getSuperValue () method Console .log (SubType.prototype.hasOwnProperty ( 'getSuperValue' )) //to false , getSuperValue SuperType prototype method, the instance is not present in the SubType Console .log (SuperType.prototype.hasOwnProperty ( 'getSuperValue' )) //to true copy the code
  • After calling the subtype constructor to create an instance, the prototype of the subtype is rewritten :
    • Subclass instance
      [[Prototype]]
      Point to the superclass instance (originally point to the subclass prototype)
    • Subclass instance
      constructor
      Point to the constructor that rewrites the prototype of the subclass , that is, the superclass constructor (originally pointing to the subclass constructor)
  • Whoever (which constructor) rewrites the prototype, the constructor (of the instance and the prototype) points to whom
console .log(instance.__proto__) //SuperType instance, SubType prototype SubType.prototype has been overwritten by SuperType instance console .log(instance.constructor) //SuperType constructor, constructor points to the constructor that rewrites the prototype object, that is new SuperType () a constructor Console .log (instance.constructor === SubType.prototype.constructor) //to true, the constructor point Supertype duplicated code
  • After the prototype chain is implemented, the code reads the search process of object properties:

    • 1. Search for the object instance itself -> has attributes -> return attribute value -> end
    • 2. Object instance itself has no attributes -> search prototype object has attributes return attribute value -> end
    • 3. Prototype object has no attributes -> Search the prototype chain up, link by link with/without property return property value/undefined end

Default prototype

  • All reference types are inherited by default by
    Object
    , The default prototypes of all functions are Object instances
  • Inside the default prototype
    [[Prototype]]
    Pointer to
    Object
    The prototype is
    Object.prototype
  • Object.prototype
    Saved on
    constructor, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toString, valueOf, toLocaleString
    Such as the default methods, when these methods are called in the instance, the method on the Object prototype is actually called
Console .log (SuperType.prototype .__ proto__) //{}, the default prototype is SuperType Object instance, Object instances of [[the Prototype]] Object Prototype point Console .log (SuperType.prototype .__ proto__ === Object .prototype) //true, all point to the Object prototype console .log(SuperType.prototype.__proto__.constructor) //Object constructor console .log( Object .keys(SuperType.prototype.__proto__)) //[], Object prototype can be engraved For methods Console .log ( Object .getOwnPropertyNames (SuperType.prototype .__ proto__)) Example//['constructor','__defineGetter__','__defineSetter__','hasOwnProperty','__lookupGetter__','__lookupSetter__','isPrototypeOf','propertyIsEnumerable','toString','valueOf' ,'__proto__','toLocaleString '], all Object method on the prototype copy the code

Prototype and inheritance relationship

  • instanceof
    Operators, test cases and constructors that have appeared in the prototype chain
  • instanceof
    Specific meaning: to determine whether the object pointed to by the prototype property of a constructor exists in the prototype chain of the object (instance) to be detected
console .log(instance instanceof Object ) //true, instance is an instance of Object console .log(instance instanceof SuperType) //true, instance is an instance of SuperType console .log(instance instanceof SubType) //true, instance is of SubType Example copy code
  • isPrototypeOf()
    Methods, test cases and prototypes on the prototype chain
  • isPrototypeOf()
    Specific meaning: determine whether an object (prototype object) exists in the prototype chain of the object (instance) to be detected
console .log( Object .prototype.isPrototypeOf(instance)) //true, Object.prototype is the prototype on the instance prototype chain console .log(SuperType.prototype.isPrototypeOf(instance)) //true, SuperType.prototype is the instance prototype the prototype chain Console .log (SubType.prototype.isPrototypeOf (instance)) //to true, the instance SubType.prototype prototype prototype chain duplicated code

About the method

  • In the subtype prototype to add or rewrite the code of the supertype method , it must be placed after the replacement prototype statement
SubType.prototype.getSubValue = function () { //Add a new method to the subclass prototype return false } SubType.prototype.getSuperValue = function () { //The method of rewriting the superclass prototype in the subclass prototype return false } var instance2 = new SubType() console .log(instance2.getSubValue()) //false console .log(instance2.getSuperValue()) //false, the method is overridden var instance3 = new SuperType() console .log(instance3 .getSuperValue ()) method//true, does not affect the supertype prototype copy the code
  • When inheriting through the prototype chain, cannot use object literals to create prototype methods , because this will rewrite the prototype chain and cause the inheritance relationship to become invalid
function SubType2 () {} SubType2.prototype = new SuperType() //inherit SubType2.prototype = { //Object literal rewrite prototype, the inheritance relationship is invalid (subclass prototype is rewritten as Object instance) someFunction : function () { return false }, } var instance4 = new new SubType2 () Console .log (instance4.getSuperValue ()) //error, rewrite the object literal prototype inheritance has expired copy the code

The problem with the prototype chain

  • For instance of a subclass of reference types when the property to be modified (non redefined), will superclass instance of a reference type attribute impact
function SuperTypePro ( name ) { this .nums = [ 1 , 2 , 3 ]// Supertype attribute, reference type this .name = name// Supertype attribute, primitive type } SuperTypePro.prototype.getSuperNums = function () { return this .nums } function SubTypePro () {} SubTypePro.prototype = new SuperTypePro() //inherit var instance5 = new SubTypePro() instance5.nums.push( 4 ) //In the subclass instance, modify (not redefine) inherited reference type attributes console .log(instance5.nums) //[1,2,3,4] var instance6 = new SubTypePro() console .log(instance6.nums) //[1,2,3,4], the superclass instance is affected var instance7 = new SubTypePro() instance7.nums = [] //In the subclass instance, redefine (overwrite) the inherited reference type attributes console .log(instance7.nums) //[] console .log(instance6.nums) //[1,2 , 3,4] superclass instance unaffected duplicated code
  • (Without affecting all object instances) When creating a subtype instance, you cannot pass parameters to the supertype constructor
var person = new SuperTypePro( 'Simon' ) //Create supertype instance console .log(person.name) //'Simon' var person2 = new SubTypePro( 'Simon' ) //Create subtype instance, parameter passing is meaningless Console .log (person2.name) //undefined copy the code

Misappropriating the constructor

  • Inside the subclass constructor, pass
    apply()
    or
    call()
    The superclass constructor 's scope to bind to the instance of this subclass and then call the superclass constructor
function SuperTypeBorrow () { this .nums = [ 1 , 2 , 3 ] } function SubTypeBorrow () { console .log( this ) //this inside the SubTypeBorrow constructor, points to an instance of SubTypeBorrow SuperTypeBorrow.call( this ) //binds the scope of the superclass to this, which is an instance of the subclass } var instance8 = new SubTypeBorrow() console .log(instance8.nums) //[1, 2, 3] instance8.nums.push( 4 ) console .log(instance8.nums) //[1, 2, 3, 4] var instance9 = new SubTypeBorrow() console .log(instance9.nums) //[1, 2, 3 ], the superclass is not affected copy the code

Pass parameters

  • You can pass parameters to the superclass constructor in the subclass constructor
  • To ensure that the superclass constructor does not override the properties of the subclass, call the superclass constructor first, and then add the properties defined in the subclass
function SuperTypeParam ( name ) { this .name = name } function SubTypeParam () { SuperTypeParam.call ( the this , 'Nicholas' ) //inherited first call type constructor ultra the this .age = 29 //add subtype attribute defined } var instance10 = new new SubTypeParam () Console .log (instance10.name, instance10.age) //29 Nicholas duplicated code

The problem of embezzling the constructor

  • The problem with the constructor pattern -methods are defined in the superclass constructor, each method will be created on the instance again, the function is not reused , and the methods defined in the superclass prototype are not visible in the subclass .

Combinatorial inheritance

  • Also known as pseudo-classical inheritance , the prototype chain is used to inherit the properties and methods on the prototype, and the instance properties are inherited by embezzling the constructor
  • Not only realizes function reuse through the methods defined on the superclass prototype , but also ensures that each instance has its own properties
/* super type constructor*/ function SuperTypeMix ( name ) { this .name = name this .nums = [ 1 , 2 , 3 ] } SuperTypeMix.prototype.sayName = function () { console .log( this .name) } /* Subtype constructor*/function SubTypeMix ( name, age ) { SuperTypeMix.call ( the this , name) //constructor theft inheritance, property instance the this .age = Age } SubTypeMix.prototype = new SuperTypeMix() //Prototype chain inheritance, inherit the prototype method SubTypeMix.prototype.sayAge = function () { console .log( this .age) // Subtype prototype adding method (must be after replacing prototype statement) } var instance11 = new SubTypeMix( 'Nicholas' , 29 ) instance11.nums.push( 4 ) console .log(instance11.nums) //[1, 2, 3, 4 ], inherited from the embezzled constructor, the properties are stored in the supertype instance and subtype prototype instance11.sayName() //'Nicholas', inherited from the prototype chain, the method is stored in the supertype prototype instance11.sayAge() //29, non-inheritance, the method is stored in the subtype prototype var instance12 = new new SubTypeMix ( 'Greg' , 27 ) Console .log (instance12.nums) //[. 1, 2,. 3] instance12.sayName () //'Greg' instance12.sayAge () //27 duplicated code
  • Composition inheritance also has its own shortcomings, it will call the super class constructor twice
    • The first time, when rewriting the prototype of the subclass, the properties of the superclass instance are assigned to the prototype of the subclass
    • The second time, when the subclass constructor is called to create a subclass instance , the superclass instance attributes are assigned to the subclass instance
/* Supertype constructor*/function SuperTypeMix ( name ) { this .name = name this .nums = [ 1 , 2 , 3 ] } /* Subtype constructor*/function SubTypeMix ( name ) { SuperTypeMix.call( this , name) //Embezzle constructor inheritance and inherit properties (when creating a subclass instance, the superclass constructor is called for the second time, and the subclass instance inherits the properties of the superclass instance) } = SubTypeMix.prototype new new SuperTypeMix () //chain prototype of an inherited methods (first call super class constructor, subclasses have inherited superclass prototype and prototype example methods and properties) copy the code
  • Calling the supertype constructor twice affects efficiency , and:
    • Subclass prototype and subclass instance , the inherited and includes superclass instance properties
    • Sub-class prototype in the super class instance attributes are sub-class instance attributes of the coverage of the same name, thus a sub-class prototype is not necessary to
    • From the subclass instance remove the inherited property from the superclass instance , property still exists in the sub-class prototype , can still be accessed
var instance11 = new SubTypeMix( 'Nicholas' ) //Create a subclass instance instance11.nums.push( 4 ) console .log(SubTypeMix.prototype) //SuperTypeMix {name: undefined, nums: [1, 2, 3 ], sayAge: [Function] }, subclass prototype (rewritten as superclass instance) console .log(instance11 ) //SuperTypeMix {name:'Nicholas', nums: [1, 2, 3, 4 ], age: 29 }, subclass instance delete instance11.nums //Delete from the subclass instance (inherited from the superclass instance ) Attribute console .log(instance11) //SuperTypeMix {name:'Nicholas', age: 29 }, subclass instance console .log(instance11.nums) //[1, 2, 3], still possible (from subclass Prototype) access to the property copy code

Prototype inheritance

  • Create a function, receive a parameter object (required)
    • Create a temporary constructor inside the function
    • Use the passed object as the prototype of this constructor
    • Return a new instance of this constructor
  • Essentially, the function performs a shallow copy of the object passed in
function object ( o ) { function F () {} //Create a temporary constructor inside the function F.prototype = o //Use the passed object as the prototype of this constructor return new F() //Return a new instance of this constructor } Copy code
  • As the basis of another object, the passed-in object is the prototype of the new object returned by the function , and its property values (basic type value & reference type value) are taken by the new object shared
  • The new object returned is equivalent to the copy created by the passed object
var person = { name : 'Nicholas' , } var anotherPerson = object(person) console .log(anotherPerson.name) //'Nicholas', from person console .log(anotherPerson.hasOwnProperty( 'name' )) //false anotherPerson.name = 'Greg' //Overwrite the same name Property console .log(anotherPerson.hasOwnProperty( 'name' )) //true console .log(anotherPerson.name) //'Greg', from the copy console .log(person.name) //'Nicholas', from the person copy Code
  • ES5
    Object.create()
    Method standardization prototypal inheritance , receiving 2 parameters
    • Parameter 1: The object used as the prototype of the new object, must be passed
    • Parameter 2: Object that defines additional attributes for the new object, not mandatory
  • When the second parameter is not passed
    Object.create()
    Method and the aforementioned
    object()
    Functions behave the same
var anotherPerson2 = Object .create (person) Console .log (anotherPerson2.name) //'Nicholas', from person to copy the code
  • The second parameter is in the same format as the second parameter of Object.defineProperties()-the method of defining object properties. The properties of the object to be returned are defined through the descriptor
var anotherPerson3 = Object .create(person, { name : { value : 'Greg' }, //The descriptor defines the properties of the object, if there is a property with the same name, it will be overwritten }) Console .log (anotherPerson3.name) //'Greg', from copy to copy the code
  • You can use prototypal inheritance when you don t need to create a constructor and just want to keep one object similar to another.
  • Create objects in the same prototype mode . The reference type attributes of the prototype object are always shared by the prototype object and the copy . Modifying (non-redefined) the value of the reference type in the copy will affect the reference type attribute of the prototype object
var person2 = { nums : [ 1 , 2 , 3 ], } var anotherPerson4 = Object .create(person2) anotherPerson4.nums.push( 4 ) //The attribute of the reference type is modified, not redefining the console .log(anotherPerson4.nums)//[1, 2, 3, 4], from person console .log(person2.nums) //[1, 2, 3, 4], as a prototype reference type attribute affected duplicated code

Parasitic inheritance

  • versusClosely related prototype inheritance , the idea is similar to parasitic constructor and factory pattern :
    • Create a function that is only used to encapsulate the inheritance process , receiving a parameter, the parameter is the object as the prototype
    • Inside the function, call the function encapsulated by prototype inheritance , return an instance object, and then enhance the instance object in some way
    • Finally return this instance object
function createAnother ( original ) { var clone = Object .create(original)//Prototype inheritance and return an empty instance console .log(clone) //{}, empty instance, its prototype is the orginal object clone.sayHi = function () { console .log( 'Hi' ) //Add a method to the returned instance object (recreate the method for each instance) } return clone } var person3 = { name : 'Nicholas' , } var anotherPerson5 = createAnother(person3) console .log(anotherPerson5.name) //'Nicholas' console .log(anotherPerson5.hasOwnProperty( 'name' )) //false, the name property is stored on the object person3 as the prototype anotherPerson5.sayHi () //'Hi' Console .log (anotherPerson5.hasOwnProperty ( 'the sayHi' )) //to true, the sayHi method stored on the returned object instance Console .log (anotherPerson5) //{the sayHi: [Function]} copy the code
  • Prototype inheritance can be used when objects are mainly considered instead of custom types and constructors
  • The same problem as the constructor mode -the methods are defined in the parasitic inherited encapsulation function, and the method cannot be reused and the efficiency is reduced

Parasitic combined inheritance

  • Hybrid form of the prototype chain:
    • Do not assign (overwrite) the prototype of the subclass by calling the constructor of the superclass, only a copy of the prototype of the superclass
    • Use parasitic inheritance to inherit the prototype of the superclass, and then assign the result to the prototype of the subclass
    • Core: The prototype of the subclass inherits the prototype of the superclass
//Encapsulation: the mixed form of the prototype chain function inherit ( subType, superType ) { //1. Create an object and inherit the prototype of the superclass var superPrototype = Object .create(superType.prototype) //The prototype of superPrototype is the superclass prototype console .log(superPrototype.__proto__) //Point to the superType.prototype superclass prototype console .log(superPrototype.__proto__ === superType.prototype) //true console .log(superPrototype.constructor) //At this point, the constructor points to the supertype construction Function //2. Let the constructor point to the subclass constructor superPrototype.constructor = subType //3. Assign the created object to the prototype of the subclass subType.prototype = superPrototype console .log(subType.prototype.__proto__ === superType.prototype) //true, subclass prototype inherits superclass prototype } Copy code
  • Use the embezzled constructor to inherit instance attributes , and inherit the prototype method through the mixed form of the prototype chain
/* SuperType */function SuperTypeMixParasitic ( name ) { this .name = name this .nums = [ 1 , 2 , 3 ] } SuperTypeMixParasitic.prototype.sayName = function () { console .log( this .name) } /* Subclass*/ function SubTypeMixParasitic ( name, age ) { SuperTypeMixParasitic.call( this , name) //Embezzle the constructor and inherit the properties (only call the super class constructor once) this .age = age } inherit(SubTypeMixParasitic, SuperTypeMixParasitic) // Hybrid form of prototype chain, inherit method SubTypeMixParasitic.sayAge = function () { console .log( this .age) } Copy code
  • Parasitic combined inheritance is the most ideal inheritance paradigm for reference types

    • Only call the super class constructor once, and no extra properties will be created on the subclass prototype
    var instance13 = new SubTypeMixParasitic( 'Nicholas' , 29 ) instance13.nums.push( 4 ) console .log(instance13.nums) //[1, 2, 3, 4 ], inherited from the embezzled constructor, the properties are stored in the subclass instance ([ 1, 2, 3, 4 ]) and the superclass instance ([ 1, 2, 3 ]) in console .log(SubTypeMixParasitic.prototype) //SubTypeMixParasitic {constructor: {[Function: SubTypeMixParasitic] sayAge: [Function]} }, the subclass prototype contains no extra properties, superclass inherit only a prototype, and to sub-class constructor constructor duplicated code
    • The prototype chain remains unchanged
    console .log(SubTypeMixParasitic.prototype.constructor) //SubTypeMixParasitic constructor console .log(instance13.__proto__ === SubTypeMixParasitic.prototype) //true console .log(SubTypeMixParasitic.prototype.__proto__) //SuperTypeMixParasitic prototype console .log( SubTypeMixParasitic.prototype.__proto__ === SuperTypeMixParasitic.prototype ) //true console .log(SubTypeMixParasitic.prototype.__proto__.constructor) //SuperTypeMixParasitic constructor console .log(SubTypeMixParasitic.prototype.__proto__.__proto__) //Object prototype console .log( SubTypeMixParasitic.prototype.__proto__.__proto__ === Object .prototype ) //true console .log(SubTypeMixParasitic.prototype.__proto__.__proto__.constructor)//constructor Object duplicated code
    • Can be used normally
      instanceof
      with
      isPrototypeOf()
      --because
      constructor
      Still points to the subtype constructor
    Console .log (instance13 the instanceof SubTypeMixParasitic) //instance13 examples are SubTypeMixParasitic Console .log (instance13 the instanceof SuperTypeMixParasitic) //instance13 examples are SuperTypeMixParasitic Console .log (SubTypeMixParasitic.prototype.isPrototypeOf (instance13)) //to true, SubTypeMixParasitic.prototype is the prototype of the prototype chain instance13 Console .log (SuperTypeMixParasitic.prototype.isPrototypeOf (instance13)) //to true, the instance SuperTypeMixParasitic.prototype13 prototype prototype chain duplicated code

Summary & Questions

  • What is a function signature? Why is the JS function not signed? What kind of inheritance does JS support? What is its dependence?
  • What is the principle of prototype chain inheritance? Where are the properties and methods on the supertype instance stored? What about methods on supertype prototypes?
  • When implementing inheritance through the prototype chain, after calling the subtype constructor to create an instance, since the prototype of the subtype is rewritten, what happens to the [[Prototype]] and constructor pointers of the instance? why?
  • After inheriting through the prototype chain, what is the search process for the code to read the object properties?
  • What do all reference types inherit from by default? What are the default prototypes of all functions? Where does the [[Prototype]] inside the default prototype point?
  • When calling common methods such as toString() and valueOf() in the instance, what method is actually called?
  • What are the methods to determine the relationship between prototype and instance? What are their respective meanings and usages?
  • When implementing inheritance through the prototype chain, why adding or overriding the superclass method to the subclass prototype must be after replacing the prototype statement? Why can't I use object literals to create subclass prototype methods?
  • What are the limitations of using the prototype chain alone to implement inheritance?
  • What is the principle of stolen constructor inheritance? What are the advantages over prototype chain inheritance? What are its disadvantages?
  • What is the principle of combinatorial inheritance? As the most commonly used inheritance model, what are its advantages and disadvantages?
  • What is the principle of prototypal inheritance? Under what circumstances can this inheritance method be used? What are the disadvantages?
  • What is the principle of parasitic inheritance? Under what circumstances can this inheritance method be used? What are the disadvantages?
  • Please use the code to fully demonstrate the process of parasitic combined inheritance, and explain why it is "the most ideal inheritance paradigm for reference types"?