JavaScript的原型链是为了实现继承的。面向对象的语言都会有继承这个概念,实现继承的主要目的是为了代码复用。将一些共有的特性和方法共享出来就可以实现代码复用和节省内存。
根据ECMAScript中的描述,原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法(JavaScript中的数据类型分为引用类型和基本类型,引用类型具有属性和方法,基本类型只能表示字面值)。下面通过一个例子理解这个过程:
function Person(){
this.name = "张三"
}
Person.prototype.getName = function(){
return this.name;
}
function Male(){
this.gender = "男"
}
Male.prototype = new Person(); // Male继承Person
Male.prototype.getGender = function(){
return this.gender;
}
var male = new Male();
console.log(male.getName()) // 张三
上面的代码定义了两个类型Person和Male,它们分别都有一个属性和方法。主要的区别在于Male的原型对象是Person的一个实例,male实例能够通过getName()方法返回“张三”,原因就是Male从Person中继承了getName()方法。这种方式是通过手动改写原型对象的指向实现的。其实我们在JavaScript中使用function声明一个方法的时候,JavaScript当前的实现(例如浏览器环境或者node.js环境)都会默认为这个方法创建一个原型对象,这个对象中默认只有一个constructor属性,并且constructor指向的是当前声明的function(比如上面代码中的Person)。声明的function通过prototype属性可以找到这个原型对象。用代码验证的话,可以这样做:
Person.prototype.constructor == Person // true
上面的原型链其实还少了一环。JavaScript中的所有引用类型都继承了Object,而这个继承也是通过原型链实现的。所有函数的默认原型对象都是Object的实例,因此默认原型对象都会包含一个内部指针,指向Object.prototype。我们知道,每个自定义的类型都会有toString()、valueOf()等默认方法,这是因为JavaScript的实现中给Object的原型对象内置了这些方法。所以,JavaScript中所有的引用类型都会继承这些方法。下图就是例子中完整的原型链
Male继承了Person,而Person继承了Object。当实例male调用toString方法时,实际上调用的是Object.prototype中的toString()方法。