在理解每一个概念的时候,我习惯性的会从以下角度去思考:
同样在理解原型链的过程中,我也进行过相关思考,现对自己的理解做出总结。
理解原型链之前,最好先理解清楚以下两个概念:js中对象的创建方式和继承。
对象字面量
var stu = {
name:'andy',
gender:'female',
showInfo:function(){
console.log('common function');
}
};
使用字面量方式直接创建对象,虽然直观明了,但是当需要创建10个、100个、甚至1000个同一类型的对象的时候,这种方式的弊端就显而易见。我们不应为了实现需求而真的复制粘贴1000遍相同结构的代码,那么我们应该怎么做呢?这个时候,大家比较容易想到的就是函数,下面就介绍使用函数创建对象的方式–工厂方式。
工厂方式
function createStudent(name,gender){
return {
name:name,
gender:gender,
showInfo:function(){
console.log('common function');
}
}
}
var stu = createStudent('andy','female');
使用工厂函数这种方式,如果要创建10000个对象,只需要调用1000次函数即可。但是,每个对象都拥有公共方法showInfo(),这显然是不合理的。如何让每个对象都保有自己独特属性值的同时,共享同一个公共方法呢?
Object.create()方式
Object.create()以一个对象为原型,创建新对象的方式。
var A = {
name:'andy',
gender:'female',
showInfo:function(){
console.log('common function');
}
};
var B = Object.create(A);//B={}
var C = Object.create(A);//C={}
B和C都是以A对象为原型创建的新对象,并且刚创建出来都是空对象。这其中的原理又是什么呢?A可以看作是共享池,这个池子中放着很多共享的属性和方法,B和C通过某种“指针”__proto__指向A,这样B和C就“拥有”共享池中的方法和属性了。但是,这种方式也存在一定的弊端:
B.name = 'bob';
B.gender = 'male';
C.name = 'cindy';
C.gender = 'female';
使用Object.Create()方法创建1000个不同属性值的对象,虽然实现了方法的共享,但是也存在一些隐患。那么,有没有其他方式呢?
构造函数
function Student(name,gender){
this.name = name;
this.gender = gender;
}
Student.prototype.showInfo = function (){
console.log('common function');
}
var andy = new Student('andy','female');
andy.showInfo();//common function
var bob = new Student('bob','male');
bob.showInfo();//common function
使用构造函数的方式,如果要创建1000个对象,只需要 new 构造函数(参数) 1000次即可。它和工厂方式的区别是,同样创建1000个对象,使用构造函数方式时showInfo()在内存中占有的容量仅是工厂方式的1/1000。而这里new的本质是什么呢?这里简要总结一下:
1. 创建一个空对象{};//var obj = {};
2. 将这个空对象的原型,指向其构造函数的prototype对象;//obj.__proto__ = Object.create(构造函数.prototype)
3. 绑定this关键字到当前空对象;
4. 执行构造函数内部的代码,执行完毕返回对象实例。
但是有的构造函数和构造函数之间存在某种关系(比如学生包括小学生、中学生、大学生),而这个关系可以用“继承”来描述。
假如,我们需要创建这样一种对象:
function MidStudent(name,gender,school){
this.name = name;
this.gender = gender;
this.school = school;
}
MidStudent.prototype.showInfo = function (){
console.log('common function');
}
var cindy = new MidStudent('cindy','female','一中');
cindy.showInfo();//common function
var elle = new MidStudent('elle','female','十四中');
elle.showInfo();//common function
我们可以发现MidStudent和Student比起来只是多了一个属性school,如果重新定义MidStudent,实际上也是浪费空间的。那么有什么方式可以更节省空间呢?可以想到的方式是继承。那么,js又是如何实现继承的呢?
function MidStudent(name,gender,school){
Student.call(this,name,gender);//改变this指向
this.school = school;
}
MidStudent.prototype = Object.create(Student.prototype);//以Student.prototype对象为原型,创建MidStudent.prototype对象
MidStudent.prototype.constructor = MidStudent;//让MidStudent的constructor指向自身
var cindy = new MidStudent('cindy','female','一中');
cindy.showInfo();//common function
var elle = new MidStudent('elle','female','十四中');
elle.showInfo();//common function
关于使用call函数改变this指向,这里的原理不再展开介绍。
基于以上对对象的创建方式和继承的讨论,下面开始对原型链的探讨。在使用构造函数Student创建andy和bob对象的时候,这两个对象中并没有定义showInfo方法,那么它们是如何实现对showInfo方法的调用的呢?
在js中,所有对象都有__proto__属性,而函数除此之外还有prototype属性。回到刚刚的问题,实例对象andy和bob是如何实现对showInfo方法方法的调用的呢?
结合上图,andy.showInfo() 方法的调用过程如下:
总结下来,原型链描述的是实例对象和构造函数之间的一种关系,这种关系就是通过对象的__proto__属性和函数的prototype属性进行链接。
回到最开始讨论的问题,
这个概念为什么会被提出,它的提出背景是什么?
原型链主要就是为了在创建对象时更节省内存空间,对象可以通过链式查找的过程去共享属性和方法的定义。
这个概念是什么,它解决了什么问题?
原型链描述的是实例对象和构造函数之间的一种关系,可以解决对象间不用重复定义相同方法和属性的问题。
这个概念如何解决相关问题?
通过对象的__proto__属性和函数的prototype属性进行链接,不断向上查找的过程。