JavaScript 的原型继承

一张经常被使用的关系图
JavaScript 中的每个对象都有一个 [[Prototype]]
的内部属性,该属性指向的对象就是当前对象的原型。每个原型对象也有自己的原型,依次向上遍历形成原型链。当我们访问一个对象的属性或者方法时,如果在当前对象中没有找到该属性或者方法,会通过原型链依次从原型对象中查找,从而实现对象继承的功能。我们可以通过 __proto__
属性或者 Object.getPrototypeOf()
/ Object.setPrototypeOf()
方法来访问对象的原型:
javascriptlet obj = {};
obj.__proto__ == Object.getPrototypeOf(obj) // true
虽然 __proto__
属性并不是 ECMAScript 标准的一部分,但是大部分 JavaScript 实现都提供了该方法。
JavaScript 通过 new Constructor()
语句创建对象,其中 Constructor
是对象的构造函数。JavaScript 标准内置对象,比如 Object
、Function
、Boolean
、Number
、String
、Array
等,都是函数类型:
javascripttypeof Object == 'function' //true
typeof Function == 'function' //true
typeof Boolean == 'function' //true
typeof Number == 'function' //true
typeof String == 'function' //true
typeof Array == 'function' //true
在 JavaScript 中,函数类型同时也是对象,因此也能拥有属性和方法。即便我们没有在代码中显式声明,每个函数也都会有 prototype
属性。如果该属性指向一个对象,那么 new
操作符会将其设置为新对象的原型:
javascriptfunction Constructor() {}
let obj = new Constructor()
obj.__proto__ == Constructor.prototype // true
函数默认的 prototype
是一个包含 constructor
属性的对象,而 constructor
指向这个函数自身:
javascriptfunction Constructor() {}
Constructor.prototype.constructor == Constructor // true
而创建的对象通过原型链继承,可以访问到原型的 constructor
函数:
javascriptfunction Constructor() {}
let obj = new Constructor()
obj.constructor == Constructor // true
在运行时,我们可以通过对象的 constructor
属性来 new
一个同类型的对象:
javascriptfunction Constructor() {}
let obj1 = new Constructor()
let obj2 = new obj1.constructor()
obj2 instanceof Constructor // true
但是 JavaScript 并不保证原型对象存在 constructor
属性。如果显式指定构造函数的 prototype
属性或者对象的 __proto__
属性,会覆盖原型默认的 constructor
属性,此时通过 obj.constructor
是无法获取到 obj
对象真正的构造函数的:
javascriptfunction Constructor() {}
let Prototype = {}
Constructor.prototype = Prototype
let obj = new Constructor()
(new obj.constructor) instanceof Constructor // false
obj.__proto__.__proto__.constructor == obj.constructor // true
此时的 obj.constructor
实际指向 Prototype.__proto__
对象的 constructor
。
对象的原型可以在运行时被修改或替换,从而改变对象的行为:
javascriptfunction Constructor() {}
Constructor.prototype.method = function () { console.log('original') }
let obj = new Constructor()
obj.method() // 控制台打印 original
// 修改原型
Constructor.prototype.method = function () { console.log('override') }
obj.method() // 控制台打印 override
// 替换原型
obj.__proto__ = {
method() { console.log('replace') }
}
obj.method() // 控制台打印 replace
不过运行时替换构造函数的 prototype
并不会影响到已经创建的对象行为:
javascriptfunction Constructor() {}
Constructor.prototype.method = function () { console.log('original') }
let obj = new Constructor()
obj.method() // 控制台打印 original
// 替换构造函数的 prototype
Constructor.prototype = {
method () { console.log('replace') }
}
obj.method() // 控制台打印 original
总结:
- 所有对象都具有隐含的
[[Prototype]]
属性,指向当前对象的原型对象,可以通过__proto__
属性访问; - 所有类型为
function
的对象默认具有prototype
属性,可以是null
或者指向一个原型对象; - 假设有构造函数
function Constructor() {}
,则Constructor.prototype
会被赋于(new Constructor).__proto__
; - 对象的
[[Prototype]]
属性以及构造函数的prototype
属性允许在运行时修改; - 替换构造函数的
prototype
属性不会对已经创建的对象产生影响。