简单易懂的JS原型与原型链

599 阅读4分钟

前言

  理解 JS 中的原型和原型链可以帮助我们更加深入学习 JS。学习了一段时间的 JS,下面为大家阐述一下这段时间我对 JS 中的原型与原型链的理解。若有不对的地方,欢迎大家在评论区里指正。

构造函数

  在讲原型和原型链之前,我们先来介绍一下构造函数。构造函数最显著的特点是首字母大写,构造函数分为实例对象和静态成员。

实例成员

实例成员是构造函数内部通过 this 添加的成员,比如age sex等属性是实例成员,只能通过实例化对象访问。

静态成员

静态成员是在构造函数本身上添加的成员,比如Cat.name='汤姆',静态成员只能通过构造函数访问。

  构造函数创建一个对象如下所示(该过程也称为实例化) :

function Cat(age){
            this.age = age;
        }
        Cat.name = '汤姆';
        let cat = new Cat(1);
        console.log(cat);

image.png

为什么会有原型和原型链

  构造函数存在浪费内存的问题。当实例对象特别多的情况下,如果这些实例对象上有着一样的方法但是又要一个个去写一遍,这样添加起来不仅麻烦而且会浪费内存,这时候我们就可以通过在原型上添加它们共有的属性和方法,通过原型链来调用,这样不仅减少了代码量,并且还节省了资源。

原型和原型链的关系

  了解了构造函数以及为什么会有原型和原型链,现在我们来谈谈它们的内在联系。

prototype

什么是原型?简单点理解原型就是一个对象。原型指的就是 prototype,它是一个对象,我们在上面定义的属性和方法就是在原型上定义的

function Person(name,age){
            this.name=name;
            this.age=age;
        }
        let Jack = new Person('杰克',18);
        console.log(Jack);

image.png

  从上可以看出,函数对象有 prototype 属性,prototype 里面又有一个默认的 constructor 属性。constructor 主要用于记录对象引用哪个构造函数,它可以让原型对象重新指向原来的构造函数。

__ proto__

prototype 是函数才有的属性,而__proto__是每个对象都有的属性。我们可以把__proto__理解为构造器的原型,即__proto__ === constructor.prototype。

function Person(name,age){
            this.name=name;
            this.age=age;
        }
        let Jack = new Person('杰克',18);
        console.log(Jack);

image.png

  将 prototype 属性展开后即可看到__proto__。

两者之间的关系

  这里我们通过一段代码来展示两者之间的关系:

function Person(name,age){
            this.name=name;
            this.age=age;
        }
        let Jack = new Person('杰克',18);
        console.log(Jack.prototype);
        console.log(Jack.__proto__);
        console.log(Person.prototype);
        console.log(Jack.__proto__==Person.prototype);
        console.log(Person.prototype.__proto__);
        console.log(Object.prototype);
        console.log(Person.prototype.__proto__==Object.prototype);
        console.log(Jack.__proto__.__proto__==Object.prototype);
        console.log(Object.prototype.__proto__);

image.png

  以上就是它们之间的关系。

原型的作用

  前面说到当实例对象比较多的情况下,我们可以使用原型对象来存放实例对象中共有的属性和方法,这样可以大大的减少内存消耗。以下还是原来的代码,只是在原来的基础上增加了一个实例对象并通过原型对象添加了一个方法。

function Person(name,age){
            this.name=name;
            this.age=age;
        }
        Person.prototype.work=function(){
            console.log('我在工作');
        }
        let Tom = new Person('汤姆',19)
        let Jack = new Person('杰克',18);
        console.log(Jack);
        console.log(Tom);

image.png

  由此可见,通过原型添加方法不仅可以减少内存消耗,也可以减少相关代码量。

图解原型链

  原型与原型之间通过__proto__一层一层向上查找的过程称为原型链。任何一个实例对象都有一个__proto__ 指向构造函数的 prototype 对象。

  话不多说,直接上图:

image.png

  注意,通过一层层查找最终它会指向 null。另外,将此图与上面的代码结合一起看,会将更加清楚。

总结

构造函数里的实例成员只能通过实例化对象来访问,静态成员只能通过构造函数来访问。

构造函数有原型对象 prototype ,它创建的实例对象有__proto__原型。

原型 prototype 是一个对象,构造函数可以在原型上定义属性和方法。

prototype 是函数有的属性,而__proto__是每个对象都有的属性。

  综上就是我对原型和原型链的理解,若有改进或者不对的地方,欢迎大家在评论区里讨论。