浅入一下js中的原型

109 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

原型(一)

构造函数

了解原型之前我们需要明白另外一件事情,什么是构造函数。
function student(){} 这是一个函数,我们可以直接调用它student()因为它的函数体是空的,所以不会执行任何操作。
但我们还有另外一种办法调用它 那就是new let a = new student()这句代码意思是 创建一个 student 类型的对象 astudent()函数当构造函数使用。
其实所有的函数只要使用new来调用都是构造函数,反过来说构造函数和函数的区别就是是否用new来调用。

    function student() {}
    let a = new student();
    console.log(a); //student {}
    console.log(a instanceof student); //true

下面我们给出一个稍微标准点的构造函数。

    function student(name, age) {
      this.name = name;
      this.age = age;
      this.sayName = function () {
        console.log(this.name);
      };
    }

    const a = new student("小红", "18");
    const b = new student("小明", "20");

    console.log(a.name); // 小红
    a.sayName(); // 小红

    console.log(b.name); // 小明
    b.sayName(); // 小明

    console.log(a.sayName === b.sayName); //false

这里我们通过构造函数实例化了两个对象ab,但是请注意最后一行,a.sayNameb.sayName并不是同一个函数,这代表需要在内存中存放两个同样的sayName函数,如果我实例化一百个对象,那么就需要存放一百个同样的函数在内存中,这显然是不好的,后面我们会通过原型来优化这个“不好”的构造函数。

构造函数的原型

每个函数创建时js就会为这个函数创建一个prototype属性,这个属性是一个对象,也就是我们说的原型(原型对象)。
默认情况下原型对象会自动获得一个constructor属性,指回与之关联的构造函数。

    function student() {}
    console.log(student.prototype);// {constructor: ƒ student()}
    console.log(student.prototype.constructor === student); //true

现在我们知道了,每个函数都有一个原型(原型对象),其中默认包括一个constructor属性,而这个属性指向关联的构造函数,可以理解为 谁的 原型对象 里面的constructor属性 指向它自己,小红的原型对象里面的constructor指向小红自己,小明的则指向小明自己。

实例的原型

除了构造函数之外,被构造函数new出来的实例也是有原型对象的,在chrome浏览器中可以通过__proto__属性来访问。(注意proto前面和后面是两个下划线)

    function student() {}
    let a = new student();
    console.log(a.__proto__);// {constructor: ƒ student()}

这里我们发现实例的原型和构造函数的原型看起来一样,我们不妨测试一下。

    function student() {}
    let a = new student();
    console.log(a.__proto__ === student.prototype); // true

其实实例的原型就是从他的构造函数那里得来的,所以得出结论;
1.构造函数有一个原型。
2.原型中有一个constructor属性,指向构造函数本身。
3.实例的原型是从构造函数得到的,实例的原型===构造函数的原型。

原型的作用

我们已经知道原型是一个对象,有一个默认的constructor属性,那么如果给原型对象新增一些属性呢?

    function student() {}
    student.prototype.name = "小红";
    student.prototype.age = "18";
    student.prototype.sayName = function () {
      console.log(this.name);
    };

    console.log(student.prototype);// {name:'小红',age:'18',sayName: ƒ (), constructor: ƒ student()}
    student.prototype.sayName();// 小红

可以看到,成功添加并且可以使用,下面我们来创建一个实例。

    function student() {}
    student.prototype.name = "小红";
    student.prototype.age = "18";
    student.prototype.sayName = function () {
      console.log(this.name);
    };

    const a = new student()
    console.log(a.name); // 小红
    a.sayName();// 小红

这里发现一个问题,我们实例化一个对象a但是我们并没有给a赋任何属性,但是我们访问a.namea.sayName(),却能正常访问,输出的值刚好是原型中对应的值,why?
因为通过对象访问属性时,会首先搜索对象本身有没有这个属性,如果有则返回,如果没有则去搜索对象的原型有没有这个属性。
因此调用a.name会发生两步搜索。
第一步js引擎会问 对象aname这个属性吗? 答案是没有。
第二步js引擎会问 对象a的原型有name这个属性吗? 答案是有,返回原型的name属性。
调用a.sayName()时会发生同样的搜索。

还记得我们那个“不好”的构造函数吗?我们现在利用原型来改造一下。

    function student(name, age) {
      this.name = name;
      this.age = age;
    }
    student.prototype.sayName = function () {
      console.log(this.name);
    };

    const a = new student("小红", "18");
    const b = new student("小明", "20");

    a.sayName(); // 小红
    b.sayName(); // 小明

    console.log(a.sayName === b.sayName); //true

问题顺利解决,不管创建多个对象,sayName这个函数也只需要在内存中存储一次了。

菜鸟前端分享学习经验,希望对你有所帮助。