理解JS原型和原型链
本文通过对《JavaScript高级程序设计》第六章的理解,加上自己的理解,重组了部分内容,形成下面的文字。
理解了原型这个概念,你的JS世界会清明很多。
为什么要为JS创造原型这个概念
在没有原型概念之前,我们可以通过创建各种形式的函数来模拟类,但总有这样那样的不足,比如下面的
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.sayName===person2.sayName; // true
其中的sayName()函数虽然可以被构造函数Person的多个实例共享,但无法被其他构造函数继承。
什么是原型
要想弄清楚原型链,需要先理解原型。
所谓原型,是其他OO语言中类和继承的JS自家解决方案。JS以引用类型数据特性为基础,通过为构造函数自动创建prototype对象属性,实现类和继承,这个自动创建的prototype就是原型,也叫原型对象。所以要想搞清楚原型这个概念,首先要掌握引用类型、类、继承这几个概念。JS不像其他编程语言,如Java,类、构造方法、方法等概念之间有明确的界限,它用于模仿类和继承的原型对象本质是引用类型数据(Object),同样的,构造方法和成员方法也是。原型是JS引入的一个概念,用于解决实现类和继承,接下来看看是原型这个概念如何引入的。
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype 属性所在函数的指针。就拿下面的例子来说,Person.prototype. constructor 指向Person。
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
如上代码涉及构造函数(Person)、原型对象(Person.prototype)和实例对象等三个概念。创建了自定义的构造函数之后,其原型对象默认只会取得constructor 属性;至于其他方法,则都是从Object 继承而来的。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262 第5 版中管这个指针叫[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari 和Chrome 在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的真正重要的一点就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。如下图:
什么是原型链
前面说,JS引入原型来更好的模仿类的功能,而原型链则用于实现继承。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //继承了SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
如上,SubType.prototype = new SuperType(),实现继承的本质是用被继承构造函数(类)的实例重写原型对象。所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。大家要记住,所有函数的默认原型都是Object 的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。下图为我们展示了该例子中完整的原型链:
---------------------------------------------------------------------------------------------------
最后再用简洁的方式表述一下:
class A extends B {
}
声明class A时,js引擎为A自动创建了原型对象,即A.prototype。extends B实际效果等同,A.prototype=new B()。