在基于类的传统面向对象的编程语言中,对象由类实例化而来,实例化的过程中,类的属性和方法会拷贝到这个对象中;对象的继承实际上是类的继承,在定义子类继承于父类时,子类会将父类的属性和方法拷贝到自身当中。因此,这类语言中,对象创建和继承行为都是通过拷贝完成的。但在JavaScript中,对象的创建、对象的继承(更好的叫法是对象的代理,因为它并不是传统意义上的继承)
是不存在拷贝行为的。(ES6 中的class也只不过是语法糖,并非真正意义上的类)
一、 原型
在JavaScript中,函数可以有属性。 每个函数都有一个特殊的属性叫作原型prototype
,正如下面所展示的。原型是为了共享
多个对象之间的一些共有特性(属性或方法),这个功能也是任何一门面向对象的编程语言必须具备的。
1 | function doSomething(){} |
二、 原型链
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

JavaScript中的对象,都有一个内置属性[[Prototype]]
,指向这个对象的原型对象。当查找一个属性或方法时,如果在当前对象中找不到定义,会继续在当前对象的原型对象中查找;如果原型对象中依然没有找到,会继续在原型对象的原型中查找(原型也是对象,也有它自己的原型)
;如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined。可以看出,这个查找过程是一个链式的查找,每个对象都有一个到它自身原型对象的链接,这些链接组建的整个链条就是原型链。拥有相同原型的多个对象,他们的共同特征正是通过这种查找模式体现出来的。
在doSomething
的例子中,我们可以看到最顶层的原型对象,这个对象就是Object.prototype,这个对象中保存了最常用的方法,如toString、valueOf、hasOwnProperty等,因此我们才能在任何对象中使用这些方法。
三、 prototype
prototype 属性:继承成员被定义的地方
在 JavaScript 原型继承结构里面,规范中用 [[Prototype]]
表示对象隐式的原型,在 JavaScript 中用 __proto__
表示,在 Firefox 和 Chrome 浏览器中是可以访问得到这个属性的,在 IE 下不行。所有 JavaScript 对象都有 __proto__
属性,但只有 Object.prototype.__proto__
为 null
,前提是没有修改过这个属性。这个属性指向它的原型对象。 至于显示的原型,在 JavaScript 里用 prototype 属性表示。
四、 constructor
每个实例对象都从原型中继承了一个 constructor
属性,该属性指向了用于构造此实例对象的构造函数。
一个小技巧是,你可以在 constructor
属性的末尾添加一对圆括号(括号中包含所需的参数),从而用这个构造器创建另一个对象实例。毕竟构造器是一个函数,故可以通过圆括号调用;只需在前面添加 new
关键字,便能将此函数作为构造器使用。
1 | function deepClone(obj) { //递归拷贝 |
五、 注意事项
对象的原型
(可以通过Object.getPrototypeOf()或者已被弃用的__proto__属性获得)与构造函数的 prototype
属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。
1 | Object.getPrototypeOf(new String()) === String.prototype // true |
Q: 如何创造一个既没有属性方法也没有原型链的空对象?
1 | let nullObject = Object.create(null); |
参考文章: