Start with the problem and get an in-depth understanding of the prototype and prototype chain in JavaScript

Start with the problem and get an in-depth understanding of the prototype and prototype chain in JavaScript

Preface

Before opening, I want to ask three questions:

  1. Why can I call the toString method when I create a new object without adding any attributes?
  2. How to make different objects with the same constructor have the same behavior?
  3. What is the basis for the instanceof keyword to determine the object type?

If these three questions can be answered, then you don't need to read the following content. But if you still have doubts and puzzles about these issues, believe me, the following content will be exactly what you need.

text

Why can I call the toString method when I create a new object without adding any attributes?

I mentioned in the article on the in- depth understanding of the prototype-based inheritance mechanism in JavaScript, JavaScript uses the prototype-based inheritance mechanism , and its reference type and its corresponding value will have the __proto__ 1 attribute, which points to the inherited prototype. Object 2 . When the access to the object property fails, it will continue to search in its prototype object. If the query in the prototype object is still unsuccessful, then go to the prototype of its prototype object to search until the search succeeds or the prototype is null. 3 Will stop searching.

let obj = {
    
    
}
obj.toString();//"[object Object]"

This code is to find the toString method in the obj object, and the query fails, and then finds the toString method in its prototype 4 , which happens to contain the toString method in its prototype, so it can output "[object Object]" .

How to make different objects with the same constructor have the same behavior?

The following is a piece of code that implements the publish and subscribe mode :

let _indexOf = Array.prototype.indexOf;
let _push = Array.prototype.push;
let _slice = Array.prototype.slice;
let _concat = Array.prototype.concat;
let _forEach = Array.prototype.forEach;

function Publish(){
    
    
    this.subList;
    
    this.indexOf = function(sub){
    
    
        let index = -1;
        if(typeof this.subList === 'undefined' || this.subList === null){
    
    
            this.subList = [];
        }
        if(typeof sub !== 'undefined' && sub !== null){
    
    
            index = _indexOf.call(this.subList,sub);
        }
        return index;
    }

    this.addSub = function(sub){
    
    
        let index = this.indexOf(sub);
        index > -1 ?
            '' : 
            _push.call(this.subList,sub);
    };

    this.removeSub = function(sub){
    
    
        let index = this.indexOf(sub);
        index > -1 ?
            index === 0 ?  
                this.subList = _slice.call(this.subList,1) :
                this.subList = _concat.call(_slice.call(this.subList,0,index),_slice.call(this.subList,index + 1)) :  
            '';
    };

    this.notifySingle = function(sub,msg){
    
    
        let index = this.indexOf(sub);
        index > -1 ?
            (typeof sub.onReceive === 'function' ? 
                sub.onReceive(msg) : 
                '') : 
            '';
    };

    this.notifyAll = function(msg){
    
    
        if(typeof this.subList !== 'undefined' && this.subList !== null){
    
    
            _forEach.call(this.subList,(sub)=>{
    
    
                if(typeof sub !== 'undefined' && sub !== null){
    
    
                    typeof sub.onReceive === 'function' ? 
                        sub.onReceive(msg) : 
                        '';
                }
            })
        }
    };
}

function Subscription(name){
    
    
    this.name = name;
    this.onReceive = function(msg){
    
    
        console.log(this.name + ' 收到消息 : ' + msg);
    };
}

let pub = new Publish();
let sub1 = new Subscription('sub1');
let sub2 = new Subscription('sub2');
let sub3 = new Subscription('sub3');
let sub4 = new Subscription('sub4');

pub.addSub(sub1);
pub.addSub(sub1);
pub.addSub(sub2);
pub.addSub(sub3);
pub.addSub(sub4);

pub.notifyAll('这是一条全部推送的消息');
// sub1 收到消息 : 这是一条全部推送的消息
// sub2 收到消息 : 这是一条全部推送的消息
// sub3 收到消息 : 这是一条全部推送的消息
// sub4 收到消息 : 这是一条全部推送的消息

pub.notifySingle(sub2,"这是一条单独推送的消息");
// sub2 收到消息 : 这是一条单独推送的消息

pub.removeSub(sub3);

pub.notifyAll('这是一条全部推送的消息');
// sub1 收到消息 : 这是一条全部推送的消息
// sub2 收到消息 : 这是一条全部推送的消息
// sub4 收到消息 : 这是一条全部推送的消息

All objects in this code that have the same constructor have different methods.

sub1.onReceive === sub2.onReceive;//false
sub1.onReceive === sub3.onReceive;//false
sub1.onReceive === sub4.onReceive;//false
sub2.onReceive === sub3.onReceive;//false
sub2.onReceive === sub4.onReceive;//false
sub3.onReceive === sub4.onReceive;//false

This will lead to:
1. Waste of memory;
2. It is not easy to perform batch operations on methods.

Next is the improved version, using the prototype to achieve the effect of code reuse :

let _indexOf = Array.prototype.indexOf;
let _push = Array.prototype.push;
let _slice = Array.prototype.slice;
let _concat = Array.prototype.concat;
let _forEach = Array.prototype.forEach;

function Publish(){
    
    
    this.subList;
}

Publish.prototype.indexOf = function(sub){
    
    
    let index = -1;
    if(typeof this.subList === 'undefined' || this.subList === null){
    
    
        this.subList = [];
    }
    if(typeof sub !== 'undefined' && sub !== null){
    
    
        index = _indexOf.call(this.subList,sub);
    }
    return index;
}

Publish.prototype.addSub = function(sub){
    
    
    let index = this.indexOf(sub);
    index > -1 ?
        '' : 
        _push.call(this.subList,sub);
};

Publish.prototype.removeSub = function(sub){
    
    
    let index = this.indexOf(sub);
    index > -1 ?
        index === 0 ?  
            this.subList = _slice.call(this.subList,1) :
            this.subList = _concat.call(_slice.call(this.subList,0,index),_slice.call(this.subList,index + 1)) :  
        '';
};

Publish.prototype.notifySingle = function(sub,msg){
    
    
    let index = this.indexOf(sub);
    index > -1 ?
        (typeof sub.onReceive === 'function' ? 
            sub.onReceive(msg) : 
            '') : 
        '';
};

Publish.prototype.notifyAll = function(msg){
    
    
    if(typeof this.subList !== 'undefined' && this.subList !== null){
    
    
        _forEach.call(this.subList,(sub)=>{
    
    
            if(typeof sub !== 'undefined' && sub !== null){
    
    
                typeof sub.onReceive === 'function' ? 
                    sub.onReceive(msg) : 
                    '';
            }
        })
    }
};

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

Subscription.prototype.onReceive = function(msg){
    
    
    console.log(this.name + ' 收到消息 : ' + msg);
};

let pub = new Publish();
let sub1 = new Subscription('sub1');
let sub2 = new Subscription('sub2');
let sub3 = new Subscription('sub3');
let sub4 = new Subscription('sub4');

pub.addSub(sub1);
pub.addSub(sub1);
pub.addSub(sub2);
pub.addSub(sub3);
pub.addSub(sub4);

pub.notifyAll('这是一条全部推送的消息');
// sub1 收到消息 : 这是一条全部推送的消息
// sub2 收到消息 : 这是一条全部推送的消息
// sub3 收到消息 : 这是一条全部推送的消息
// sub4 收到消息 : 这是一条全部推送的消息

pub.notifySingle(sub2,"这是一条单独推送的消息");
// sub2 收到消息 : 这是一条单独推送的消息

pub.removeSub(sub3);

pub.notifyAll('这是一条全部推送的消息');
// sub1 收到消息 : 这是一条全部推送的消息
// sub2 收到消息 : 这是一条全部推送的消息
// sub4 收到消息 : 这是一条全部推送的消息
sub1.onReceive === sub2.onReceive;//true
sub1.onReceive === sub3.onReceive;//true
sub1.onReceive === sub4.onReceive;//true
sub2.onReceive === sub3.onReceive;//true
sub2.onReceive === sub4.onReceive;//true
sub3.onReceive === sub4.onReceive;//true

Improved compared with the previous version has a feature: the object has the same constructor, property is unique, the behavior is the same 5 . All objects have properties independent of other objects, but they have the same behavior. This is precisely because in the improved version, the method exists on the prototype property value of the constructor, which will be inherited by the object it creates . It is precisely because of this that, although the onReceive method is not included in sub1, sub2, sub3, and sub4 at this time, the onReceive method can also be invoked through the inherited prototype object Subscription.prototype . And modifying the onReceive method on Subscription.prototype can immediately affect sub1, sub2, sub3, and sub4 . Defining the method to the prototype attribute value of the constructor can make different objects with the same constructor have the same behavior in order to achieve the purpose of code reuse .

What is the basis for the instanceof keyword to determine the object type?

In the in- depth understanding of the prototype-based inheritance mechanism in JavaScript, I declared the function Person and created the person object with it as the constructor .

function Person(){
    
    
	
}
let person = new Person();

The person object inherits the prototype attribute value of the Person function, and the prototype attribute value of the Person function inherits the prototype attribute value of the Object function. This layer-by-layer inheritance relationship constitutes the prototype chain.

The instanceof keyword judges the object type based on whether the prototype attribute value of the function exists in the prototype chain of the object.

Just as the prototype attribute value of the Person function and the prototype attribute value of the Object function exist on the prototype chain of the person object, use instanceof to determine that both are true.

person instanceof Person;//true
person instanceof Object;//true

The prototype attribute value of the Function function does not exist in the prototype chain of the person object, so instanceof is used to determine that the Function function is false.

person instanceof Function;//false

Finally, complete an instanceof.

/**
* obj 变量
* fn 构造函数
*/
function myInstanceof(obj,fn){
    
    
    let _prototype = Object.getPrototypeOf(obj);
    if(null === _prototype){
    
    
        return false;
    }
    let _constructor = _prototype.constructor;
    if(_constructor === fn){
    
    
        return true;
    }
    return myInstanceof(_prototype,fn);
}

//测试代码
myInstanceof({
    
    },Object);//true
myInstanceof([],Array);//true
myInstanceof(window,Window);//true
myInstanceof(new Map(),Map);//true
myInstanceof({
    
    },Array);//false
myInstanceof({
    
    },Function);//false

That's it.

end

The answers to these three questions explain the meaning of prototype and prototype chain and what role they play in JavaScript. However, due to the lack of knowledge of this talent, it is inevitable that I will encounter some errors in my personal understanding or expression. I hope that you will be able to point out when you encounter it.


  1. Although __proto__ has been deprecated, in order to be more intuitive, I will use the __proto__ property of the object to get the object prototype in this article. I hope to know. ↩︎

  2. The prototype inherited by Object.prototype points to null. ↩︎

  3. The prototype of Object.prototype is null, which is the apex of the prototype chain. When the prototype of Object.prototype is found, it will be reported that it cannot be found. ↩︎

  4. The prototype of the object obj is the prototype property of the constructor of obj, which is Object.prototype. ↩︎

  5. The attribute here means the attribute other than the method, and the behavior means the method. ↩︎

Guess you like

Origin blog.csdn.net/qq_35508835/article/details/113703655