JS进阶(2):人人都能懂的原型对象

封面.jpeg

凡是搞前端开发的或者玩 JavaScript 的同窗都知道,原型对象和原型链是 JavaScript 中最为重要的知识点之一,也是前端面试必问的题目,因此,掌握好原型和原型链势在必行。所以,我会用两篇文章(甚至更多)来分别讲解原型对象以及原型链。前端

在上一篇文章中,咱们详细介绍了构造函数的执行过程以及返回值,若是没有看的同窗,请点击连接 JS进阶(1): 人人都能懂的构造函数 阅读,由于这是本篇文章的基础知识。面试

废话很少说,进入正题。bash

1、为何要使用原型对象

经过上一篇文章的介绍,咱们知道:函数

function Person(name, age) {
    this.name = name;
    this.age = age;
}

var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 34);
console.log(p1.name, p1.age);   // 'Tom', 18
console.log(p2.name, p2.age);   // 'Jack', 34
复制代码

可是,在一个对象中可能不只仅存在属性,还存在方法:post

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.say = function() {
        console.log('Hello');
    };
}

var p1 = new Person('Tom', 18);
p1.say();  // 'Hello'
var p2 = new Person('Jack', 34);
p2.say();  // 'Hello'
复制代码

咱们发现,实例 p1 和 实例 p2 调用了相同的方法,都打印出 Hello 的结果。可是,它们的内存地址是同样的么?咱们打印看看:this

console.log(p1.say == p2.say); // false
复制代码

结果固然为 false 。由于咱们在上一篇文章中就说过,每一次经过构造函数的形式来调用时,都会开辟一块新的内存空间,因此实例 p1p2 所指向的内存地址是不一样的。但此时又会有一个尴尬的问题,p1p2 调用的say 方法,功能倒是相同的,若是班里有 60 个学生,咱们须要调用 60 次相同方法,但却要开辟 60 块不一样的内存空间,这就会形成没必要要的浪费。此时,原型对象就能够帮助咱们解决这个问题。spa

2、如何使用原型对象

当一个函数 (注意:不只仅只有构造函数) 建立好以后,都会有一个 prototype 属性,这个属性的值是一个对象,咱们把这个对象,称为原型对象。同时,只要在这个原型对象上添加属性和方法,这些属性和方法均可以被该函数的实例所访问。prototype

原型对象1.png

既然,函数的实例能够访问到原型对象上的属性和方法,那咱们不妨把上面的代码改造一下。3d

function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.say = function() {
    console.log('Hello');
};

var p1 = new Person('Tom', 18);
var p2 = new Person('Jack', 34);

console.log(p1.say === p2.say); // true
复制代码

此时,咱们看到实例 p1 和 实例 p2say 指向同一块内存空间。这是什么缘由呢?咱们经过控制台的打印结果来看看。code

原型对象2.png

经过上面的截图咱们能够看到,Person.prototypep1.__proto__p2.__proto__ 彷佛是同样的。为了验证咱们的猜测,咱们试着在打印:

Person.prototype === p1.__proto__;   // true
Person.prototype === p2.__proto__;   // true
p1.__proto__ === p2.__proto___;      // true
复制代码

咱们发现,全部的结果都为 true 。 而这正好解释了为何 p1.say === p2.say 为 true 。

3、绘制 构造函数——原型对象——实例 关系图

如今你大概理解了原型对象,也知道了使用原型对象有什么好处。下面咱们经过绘制图形的方式再来深入地理解一下上面的过程。

咱们就如下面的代码为例:

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

Person.prototype.say = function() {
    console.log('I am saying');
}

var p1 = new Person('Tom');
复制代码

1. Person 函数建立以后,会产生一块内存空间,而且有一个 prototype 属性

原型对象3.png

2. prototype 属性的值是一个对象,咱们称之为原型对象

原型对象4.png

3. 原型对象中的属性和方法

参照上面控制台的截图,咱们能够知道:

(1)原型对象上,有一个 constructor 属性指向 Person; (2)原型对象上,有一个 say 方法,会开辟一块新的内存空间; (3)原型对象上,有一个 __proto__ 属性,这个咱们下篇文章再来解释。

根据上面咱们的分析,继续绘制:

原型对象5.png

4. 实例中的属性和方法

p1 这个实例建立好以后,又会开辟一块新的内存空间。此时,依旧参照上面控制台的截图,咱们能够知道:

(1)p1 实例中有一个 name 属性; (2)p1 实例中有一个 __proto__ 属性,指向构造函数 Person 的原型对象。

根据上面的分析,咱们继续绘制:

原型对象6.png

4、总结

经过上面的解释,你们应该能够理解原型对象是什么以及为何要使用原型对象了。最后,咱们来总结一下本文的核心知识点。

  1. 一个函数建立好以后,就会有一个 prototype 属性,这个属性的值是一个对象,咱们把这个 prototype 属性所指向的内存空间称为这个函数的原型对象。

  2. 某个函数的原型对象会有一个 constructor 属性,这个属性指向该函数自己。

function Person() {
    // ...
}
console.log(Person.prototype.constructor === Person); // true
复制代码
  1. 当某个函数当成构造函数来调用时,就会产生一个构造函数的实例。这个实例上会拥有一个 __proto__ 属性,这个属性指向该实例的构造函数的原型对象(也能够称为该实例的原型对象)。
function Person() {
    // ...
}
var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); // true
复制代码

最后,本文描述的仅仅是一个构造函数——原型对象——实例的关系图,并非完整的原型链。你们能够先理解这一部分,等到讲解原型链的时候,我会绘制一张完整的原型链图供你们理解。童鞋们能够先试着理解今天的文章,而且本身绘制一下构造函数——原型对象——实例的关系图,相信你的收获将会更大。

最后的最后,我所说的不必定都对,你必定要本身试试!

(本文完)

相关文章
相关标签/搜索