[Front-end Interview] Prototype and Prototype Chain

1. Topic

  • How to Accurately Judge a Variable as an Array
  • Write an example of prototype chain inheritance
  • Other ways to inherit implementation
  • What is the underlying principle for es6 to implement inheritance
  • Describes the process of new an object
  • How to Use Prototype Chain in zepto and Other Source Codes

2. Knowledge points

2.1 constructor

Features: Start with capital letters

function Foo(name,age){
 //var obj = {}
 //this = {}
 this.name = name;
 this.age = age;
 this.class = 'class1'
 // return this
 }
 
 var f1 = new Foo('liming',19);

extend

Var o = {} is the syntax sugar of var o = new Object ()

Var a = [] is the syntax sugar of var a = new Array ()

Function Foo(){} is equivalent to var Foo = new Function(){}

2.2 Prototype Rules

Five rules:

1. All reference types (objects, arrays, functions) have object characteristics, that is, they can freely extend attributes

2. All reference types (objects, arrays, functions) have an __proto__ (implicit prototype) attribute and are ordinary objects

3. All functions have the prototype attribute and are also common objects.

4. All reference types (objects, arrays, functions) __proto__ values point to the prototype of its constructor

5. When trying to get the attribute of an object, if the variable itself does not have this attribute, it will go to its __proto__.

for (var key in object) {
//Attributes from prototypes have been blocked in advanced browsers
//It is suggested to add judgment to ensure the robustness of the program.
if (object.hasOwnProperty(key)) {
console.log(object[key]);
}
}

2.3 prototype chain

obj.__ proto.proto.proto __ …

Object.prototype === null

Instanceof is used to determine which constructor the reference type belongs to

obj instanceob Foo

Practical significance: judge if Foo.prototype is not on the prototype chain of obj

3. Answers to questions

3.1 How to Accurately Judge a Variable as an Array

arr instanceof Array

3.2 Write an Example of Prototype Chain Inheritance

Encapsulate dom query

function Elem(id){
 this.elem = document.getElementById(id);
 };
 
 Elem.prototype.html = function(val){
 var elem = this.elem;
 if (val) {
 elem.innerHTML = val;
 return this;
 }else{
 return elem.innerHTML;
 }
 }
 
 Elem.prototype.on = function(type,fun){
 var elem = this.elem;
 elem.addEventListener(type,fun);
 return this;
 }
 
 var div1 = new Elem('id1');
 div1.html("test").on('click',function(){
 Log ('click');
 })

3.3 Other Methods of Inheritance and Implementation

3.3.1 Prototype Inheritance

var obj = {
0:'a',
1:'b',
arr:[1]
}

function Foo(arr2){
this.arr2 = [1]
}

Foo.prototype = obj;

var foo1 = new Foo();
var foo2 = new Foo();

foo1.arr.push(2);
foo1.arr2.push(2);

console.log(foo2.arr);  //[1,2]
console.log(foo2.arr2);  //[1]

Advantages: simple implementation

Disadvantages:

1. Cannot pass parameters to parent class constructor

2. When two objects are new at the same time, when the attribute of the reference type in the prototype of one object is changed, the attribute of the other object will also be modified. Because the reference attribute from the prototype object is shared by all instances.

3.3.2 Structural Inheritance

function Super(b){
this.b = b;
this.fun = function(){}
}
function Foo(a,b){
this.a = a;
Super.call(this,b);
}

var foo1 = new Foo(1,2);
console.log(foo1.b);

Advantages: Parameters can be passed to the parent class; subclasses will not share the reference attributes of the parent class.

Disadvantages: Function reuse cannot be realized. Each subclass has a new fun. Too many FUN will affect performance and cannot inherit the prototype object of the parent class.

3.3.3 Combination Inheritance

function Super(){
 //Only the basic attribute and reference attribute are declared here
 this.val = 1;
 this.arr = [1];
 }
 //Declare function here
 Super.prototype.fun1 = function(){};
 Super.prototype.fun2 = function(){};
 //Super.prototype.fun3  ...
 function Sub(){
 Super.call(this);  //Core
 //   ...
 }
 Sub.prototype = new Super();

Advantages: There is no problem of sharing reference attributes, parameters can be transferred, and functions can be reused

Disadvantages: The attribute of the parent class will be instantiated twice, and the parent class of the real instance cannot be obtained (it is impossible to distinguish whether the instance is created by the parent class or by the parent class)

Optimization:

function Super(b){
 this.b = b;
 this.fun = function(){}
 }
 
 Super.prototype.c = function(){console.log(1111)}
 
 function Foo(a,b){
 this.a = a;
 Super.call(this,b);
 }
 
 
 Foo.prototype = Super.prototype;
 //Repair constructor:
 var foo1 = new Foo(1,2);

Disadvantage: It is impossible to distinguish whether an instance is created by a parent class or a subclass.

3.3.4 Inheritance of Parasitic Combinations

function Super(b){
 this.b = b;
 }
 
 Super.prototype.c = function(){console.log(1111)}
 
 function Foo(a,b){
 this.a = a;
 Super.call(this,b);
 }
 
 var f = new Function();
 f.prototype = Super.prototype;
 Foo.prototype = new f();
 //equivalent to foo.prototype = object.create (super.prototype);
 
 var foo1 = new Foo(1,2);

The prototype of the parent class is parasitized once, that is, it is packaged as a prototype of an empty object, and then the object is instantiated as peototype of the subclass.

Disadvantage: It is impossible to distinguish whether an instance is created by a parent class or a subclass.

You can add the following code:

Foo.prototype.constructor = Foo

This solution cannot be used for the above combination optimization method, because the parent class of the subclass refers to the same prototype object, and the modifications will be made at the same time.

Summary:

Inheritance is mainly to realize the reuse of subclasses to parent class methods and attributes.

Reference attributes from prototype objects are shared by all instances, so we should avoid inheriting attributes from prototypes.

In the constructor, the attributes and methods of the parent class constructor can be inherited through the call function, but the instances instantiated in this way will store the parent class method multiple times, affecting performance.

We can solve the above two problems by combining inheritance using call inheritance attributes and prototype inheritance method, but the object instantiated in this way will store the attributes in two parent class constructors.

Using the prototype of the parent class to construct a new object as the prototype of the subclass solves the problem of multiple storage, so the final parasitic combination inheritance is the best inheritance method, and its disadvantage is that it is more troublesome to write.

3.3.6 Implementation of Inheritance in Node Source Code

function inherits(ctor, superCtor) {
 ctor.super_ = superCtor;
 ctor.prototype = Object.create(superCtor.prototype, {
 constructor: {
 value: ctor,
 enumerable: false,
 writable: true,
 configurable: true
 }
 });
 };
 
 function Stream(){
 //  ...
 }
 
 function OutgoingMessage() {
 Stream.call(this);
 //  ...
 }
 
 inherits(OutgoingMessage, Stream);
 
 OutgoingMessage.prototype.setTimeout =   ...

The above is an example of inheritance of parasitic combinations.

1. Inherit the attributes in the Stream construction through call in the OutgoingMessage constructor.

2. Call the inherits method to inherit the attributes in the Stream prototype.

3. Function to extend OutgoingMessage’s own prototype.

The inherits method uses the Object.create method, which creates a new object by specifying prototype objects and attributes.

ctor.prototype=Object.create(superCtor.prototype,{.....});

This method actually does the work in the inheritance of parasitic combinations above us.

var f = new Function();
f.prototype =superCtor.prototype;
return new f();

The following parameter is to add an attribute to the prototype object, an optional attribute (not required), that is, to use itself as the constructor of the newly created object.

Value: Represents the property value of the constructor;
 Writable: Indicates whether the property value of the constructor is writable;  [Default: false]
 Enumerable: Indicates whether the property constructor can be enumerated;  [Default: false]
 Configurable: indicates whether the attribute constructor can be configured, for example, whether delete operation on obj.a is allowed;  [Default: false]

3.4 Implementation of ES6 Inheritance

Refer to my article:https://segmentfault.com/a/11 …

3.5 Describe the process of new an object

  1. Create an object
  2. {}. _ prototype _ = constructor. prototype
  3. This points to this object
  4. Execute code to assign value to this
  5. Return to this

3.6 How to Use Prototype Chain in Zepto and Other Source Codes

var Zepto = (function(){

var $,zepto = {}

//... omitting n lines of code  ...

$ = function(selector, context){
return zepto.init(selector, context)
}

zepto.init = function(selector, context) {
var dom

//For parameter cases, assign values to dom respectively

//the data returned by zepto.Z is finally called
return zepto.Z(dom, selector)
}

fnction Z(dom, selector) {
var i, len = dom ?  dom.length : 0
for (i = 0;   i < len;  i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}

zepto.Z = function(dom, selector) {
return new Z(dom, selector)
}

$.fn = {
//There are several tool functions in it
}


zepto.Z.prototype = Z.prototype = $.fn


//... omitting n lines of code  ...

return $
})()

window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)