全面解析JavaScript中的原型
JavaScript 是一门面向对象的语言,但与其他面向对象语言 Java、python (基于类继承) 不同,它的继承方式是基于原型实现的。
原型
几乎每个JavaScript对象都有另一个与之关联的对象,这个对象被称为原型 ( prototype ),第一个对象从这个原型继承属性。
通过对象字面量创建的所有对象都有相同的原型对象,在JavaScript代码中可以通过 Object.prototype 引用这个原型对象。使用 new 关键字和构造函数调用创建的对象,使用构造函数 prototype 属性的值作为它们的原型。换句话说,使用 new Object () 创建的对象继承来自 Object.prototype 属性的值作为它们的原型。换句话说,使用 new Object () 创建的对象继承来自 Object.prototype ,与通过 {} 创建的对象一样。类似地,通过 new Array () 创建的对象以 Array.prototype 为原型,通过 new Date () 创建的对象以Date.prototype 为原型。
注意:几乎所有对象都有原型,但只有少数对象有 prototype 属性。正是这些有 prototype 属性的对象为所有其他对象定义了原型。
原型链
Object.prototype 是为数不多的没有原型的对象,因为它不继承任何属性。其他原型对象都是常规对象,都有自己的原型。多数内置构造函数 ( 和多数用户定义的构造函数 ) 的原型都继承自 Object.prototype 。例如,Date.prototype 从 Object.prototype 继承属性,因此通过 new Date () 创建的日期对象从 Date.prototype 和 Object.prototype 继承属性。这种原型对象链接起来的序列被称为原型链。
创建一个指定原型的对象
Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。
1 | const person = { |
Object.create() 的一个用途是防止对象被某个第三方库函数意外修改。这种情况下,不要直接把对象传给库函数,而要传入一个继承自它的对象。如果函数读取一个对象的属性,可以读到继承的值。而如果它设置这个对象的属性,则修改不会影响原始对象。
1 | let o = { x: "do not change this value" }; |
原型与继承
JavaScript 对象有一组 “自有属性” ,同时也从它们的原型对象继承一组属性。要理解这一点,必须更详细地分析属性存取。下面我将使用 Object.create() 函数以指定原型来创建对象。
假设要从对象 o 中查询属性 x 。如果 o 没有叫这个名字的自有属性,则会从 o 的原型对象查询属性 x 。如果原型对象也没有叫这个名字的自有属性,但它有自己的原型,则会继续查询这个原型的原型。这个过程一直持续,直至找到属性 x 或者查询到一个原型为 null 的对象。可见,对象通过其 prototype 属性创建了一个用于继承属性的链条或链表:
1 | let o = {} // o从Object.prototype继承对象方法 |
现在假设你为对象 o 的 x 属性赋值。 如果 o 有一个名为 x 的自有 (非继承) 属性,这次赋值就会修改已有 x 属性的值。否则,这次赋值会在对象 o 创建一个名为 x 的新属性。如果 o 之前继承了属性 x ,那么现在这个继承的属性会被新创建的同名属性隐藏。
属性赋值查询原型链只为确定是否允许赋值。如果 o 继承了一个名为 x 的只读属性,则不允许赋值。
如:
1 | const me = Object.create(person,{ |
不过,如果允许赋值,则只会在原始对象上创建或设置属性,而不会修改原型链中的对象。查询属性时会用到原型链,而设置属性时不影响原型链是一个重要的JavaScript特性,利用这一点,可以选择性地覆盖继承的属性:
1 | let unitcircle = { r: 1 }; //c继承自的对象 |
如果 o 继承了属性 x ,而该属性是通过一个设置方法定义的访问器属性 (get/set) ,那么就会调用该设置方法而不会在 o 上创建新属性 x 。要注意,此时会在对象 o 上而不是在定义该属性的原型对象上调用设置方法。因此如果这个设置方法定义了别的属性,那也会在 o 上定义同样的属性,但仍然不会修改原型链。
1 | const person = { |