1. 原型
每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上这个对象就是通过调用构造函数创建的对象的原型。这么讲太绕了。简而言之,就是构造函数的prototype是一个对象,其他函数的prototype是一个指向其对应的构造函数的prototype对象的指针。同时,所有prototype对象都有一个constructor属性指向与之相关的构造函数。
我的理解是这样:

在自定义构造函数时,原型对象默认只会获得constructor属性,其他属性都继承自Object。脚本中没有访问实力的prototype的标准方式,但是chrome、firefox、safari都会在每个对象上暴露_proto_属性,通过这个属性可以访问对象的原型。实际上实例与构造函数原型有直接的关系,但是实例和构造函数是没有直接的关系的。
用代码来理解:
1 | function Person(){} |
在通过对象访问属性时,会按照这个属性的名称开始搜索,搜索开始于对象实例本身。如果在这个实例上发现了给定的名称,则返回给定名称对应的值,如果没有找到这个属性,则会沿着指针进入原型对象,在原型对象中进行搜索。如果在原型对象中找到属性,则返回对应的值。这就是多个对象实例之间共享属性和方法的原理。
虽然可以通过实例读取原型对象上的值,但是不能通过实例修改原型对象上的值。如果在实例上添加了一个与原型对象中同名的属性,那就会在实例上创建这个属性。这个属性会遮住原型对象上的属性。同时会遮蔽(shadow)对原型对象上同名属性的访问。把原型对象上的这个属性设置为null并不能恢复其与原型的联系,只有使用delete
操作符才可以删除实例上的这个属性,从而让解析过程能够继续搜索原型对象。
代码示例如下:
1 | person1.name="Luna"; |
通过hasOwnProperty()
方法可以确定某个属性是在实例上还是在原型对象上。
1 | person1.name="Luna"; |
in
可以判断一个对象是否有这个属性,无论这个属性是在实例上还是在原型上。
1 | console.log("name" in person1)//true |
原型最主要的问题在于其共享特性,一般来说,不同的实例应该有属于自己的属性副本。
2. 原型链
原型链被定义为ECMAScript的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。