JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链,它解释了为何一个对象会拥有定义在其他对象中的属性和方法。
准确地说,这些属性和方法定义在Object的构造器函数之上的 prototype 属性上,而非对象实例本身。
在Java中需要构造器才可以实例化对象,构造器被定义在类代码块中。
在JavaScript中构造器无需被定义在特定代码块中,一个函数便可以实例化对象。函数的名要遵循驼峰命名法,标识该函数为对象的构造器。
function Range() {} function Range(x, y) { this.x = x this.y = y } let a = new Range() // 实例化对象,使用无参构造 let b = new Range(1, 10) // 实例化对象,使用有参构造
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
let a = new Array()
如图所示,Array 的原型对象指向的是 Object :
Date、Math、Boolean 等 JavaScript 对象的原型指向的最终都是 Object。
JavaScript 对象都会从原型对象中继承属性和方法。
Object.prototype.print = function _print() { console.log('hello world!') } let o = new Range(1, 10) // 实例化Range
对象 o 的原型对象是 Object,并且它会继承来自原型对象的属性或方法(函数):
当我们执行 o.print()
时,若它自身没有此函数,则从原型对象中寻找。该过程是持续的,直到匹配到对应的函数或原型对象为 null 才停止。形成一种链式关系,也叫做原型链。
当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
从 ES6 开始,可以通过 Object.getPrototypeOf() 和 Object.setPrototypeOf() 访问器来访问原型对象。它等同于非标准但许多浏览器实现的属性 __proto__
。
function MaskFactory() {} MaskFactory.prototype.product = '口罩' function Market() {} Market.prototype = new MaskFactory() let market = new Market() // 实例化Market对象 console.dir(Object.getPrototypeOf(market).product) // 口罩 // 等同于 console.dir(Market.prototype.product)
对象的原型与构造函数的 prototype 属性之间的区别是很重要的。前者是每个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(market).product 和 Market.prototype.product 指向着同一个对象的属性。