凡是搞前端开发的或者玩 JavaScript 的同窗都知道,原型对象和原型链是 JavaScript 中最为重要的知识点之一,也是前端面试必问的题目,因此,掌握好原型和原型链势在必行。所以,我会用两篇文章(甚至更多)来分别讲解原型对象以及原型链。前端
在上一篇文章中,咱们详细介绍了构造函数的执行过程以及返回值,若是没有看的同窗,请点击连接 JS进阶(1): 人人都能懂的构造函数 阅读,由于这是本篇文章的基础知识。面试
废话很少说,进入正题。bash
经过上一篇文章的介绍,咱们知道:函数
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
。由于咱们在上一篇文章中就说过,每一次经过构造函数的形式来调用时,都会开辟一块新的内存空间,因此实例 p1
和 p2
所指向的内存地址是不一样的。但此时又会有一个尴尬的问题,p1
和 p2
调用的say
方法,功能倒是相同的,若是班里有 60 个学生,咱们须要调用 60 次相同方法,但却要开辟 60 块不一样的内存空间,这就会形成没必要要的浪费。此时,原型对象就能够帮助咱们解决这个问题。spa
当一个函数 (注意:不只仅只有构造函数) 建立好以后,都会有一个 prototype
属性,这个属性的值是一个对象,咱们把这个对象,称为原型对象。同时,只要在这个原型对象上添加属性和方法,这些属性和方法均可以被该函数的实例所访问。prototype
既然,函数的实例能够访问到原型对象上的属性和方法,那咱们不妨把上面的代码改造一下。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
和 实例 p2
的 say
指向同一块内存空间。这是什么缘由呢?咱们经过控制台的打印结果来看看。code
经过上面的截图咱们能够看到,Person.prototype
与 p1.__proto__
、p2.__proto__
彷佛是同样的。为了验证咱们的猜测,咱们试着在打印:
Person.prototype === p1.__proto__; // true
Person.prototype === p2.__proto__; // true
p1.__proto__ === p2.__proto___; // true
复制代码
咱们发现,全部的结果都为 true
。 而这正好解释了为何 p1.say === p2.say
为 true 。
如今你大概理解了原型对象,也知道了使用原型对象有什么好处。下面咱们经过绘制图形的方式再来深入地理解一下上面的过程。
咱们就如下面的代码为例:
function Person(name) {
this.name = name;
}
Person.prototype.say = function() {
console.log('I am saying');
}
var p1 = new Person('Tom');
复制代码
prototype
属性prototype
属性的值是一个对象,咱们称之为原型对象参照上面控制台的截图,咱们能够知道:
(1)原型对象上,有一个
constructor
属性指向 Person; (2)原型对象上,有一个say
方法,会开辟一块新的内存空间; (3)原型对象上,有一个__proto__
属性,这个咱们下篇文章再来解释。
根据上面咱们的分析,继续绘制:
当 p1
这个实例建立好以后,又会开辟一块新的内存空间。此时,依旧参照上面控制台的截图,咱们能够知道:
(1)
p1
实例中有一个name
属性; (2)p1
实例中有一个__proto__
属性,指向构造函数Person
的原型对象。
根据上面的分析,咱们继续绘制:
经过上面的解释,你们应该能够理解原型对象是什么以及为何要使用原型对象了。最后,咱们来总结一下本文的核心知识点。
一个函数建立好以后,就会有一个 prototype
属性,这个属性的值是一个对象,咱们把这个 prototype
属性所指向的内存空间称为这个函数的原型对象。
某个函数的原型对象会有一个 constructor
属性,这个属性指向该函数自己。
function Person() {
// ...
}
console.log(Person.prototype.constructor === Person); // true
复制代码
__proto__
属性,这个属性指向该实例的构造函数的原型对象(也能够称为该实例的原型对象)。function Person() {
// ...
}
var p1 = new Person();
console.log(p1.__proto__ === Person.prototype); // true
复制代码
最后,本文描述的仅仅是一个构造函数——原型对象——实例的关系图,并非完整的原型链。你们能够先理解这一部分,等到讲解原型链的时候,我会绘制一张完整的原型链图供你们理解。童鞋们能够先试着理解今天的文章,而且本身绘制一下构造函数——原型对象——实例的关系图,相信你的收获将会更大。
最后的最后,我所说的不必定都对,你必定要本身试试!
(本文完)