文中引用摘要出自《JavaScript高级高级程序设计》第三版 (总结归纳内容)
原型对象
1.了解原型模式
创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以 由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,prototype就是通过调用构造函数儿创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法
function Person () {}
Person.prototype.name = "简单举个例子"
Person.prototype.sayEasy = function () {
console.log(this.name)
}
let p1 = new Person()
p1.sayEasy() // 简单举个例子
let p2 = new Person()
p2.sayEasy() // 简单举个例子
2.原型对象、构造函数与实例之间
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。默认情况下,所有的原型对象都会自动获得一个constructor(构造函数)属性,并指向prototype属性所在函数的指针,前面的例子中, Person.prototype.constructor指向Person,通过构造函数可以继续为原型对象添加其他属性和方法。
console.log(Person.prototype.constructor === Person) // true
创建自定义构造函数之后,其原型对象默认只会取的constructor属性,其他方法属性都是从Object继承而来的。当调用构造函数创建一个新实例后,该实例会默认获得一个属性指针,指向构造函数的原型对象。ECMA-262第五版中管这个指针叫[[prototype]]。 虽然在javascript脚本中没有标准的方式去访问[[prototype]],但是在大部分主流浏览器中,会给每一个对象设置支持访问的属性_proto_,然而[[prototype]]这个属性对javascript脚本是完全不可见的。要明确真正重要的一点是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间
虽然不能在所有实现中访问到[[prototype]],但是可以通过ECMA提供的一些方法来确定对象之间是否存在这种关系。例如:
// 用原型对象的isPrototypeOf()方法测试p1、p2,因为他们内部都有一个指向Person.prototype的指针,因此返回true
console.log(Person.prototype.isPrototypeOf(p1)); // true
console.log(Person.prototype.isPrototypeOf(p2)); // true
// ECMA5新增的方法Object.getPrototypeOf()返回对象的原型
console.log(Object.getPrototypeOf() === Person.prototype) //true
上图:
如上图所示:
Person.prototype指向原型对象,而Person.prototype.constructor又指回了Person。Person的每个实例,如person都包含一个内部属性,该属性仅仅指向Person.prototype,换句话说它们与构造函数没有直接关系。上面的例子中,p1与p2两个实例都不包含属性和方法,但是我们可以调用p1.sayEasy(),这是通过查找对象属性的过程实现的。(下文中的原型链)
p1.sayEasy()的执行过程:
在读取一个对象属性时,都会执行一次搜索,目标是具有给定名称的属性。搜索首先在对象本身开始,如果本身存在则直接返回该属性值并且停止搜索;否则继续搜索对象的原型对象,如果原型对象上存在则返回该属性值。搜索到最后都没有该属性则返回undefined
虽然可以通过对象实例访问原型中的属性,但是不能通过对象实例重写原型中的值。如下:
// 改变p1的name属性
p1.name = '托儿所'
console.log(p1.sayEasy()) // '托儿所' --- > 来自实例
console.log(p2.sayEasy()) // '简单举个例子' --- > 来自原型
console.log(p1.name, p2.name) // '托儿所', '简单举个例子'
在访问p1的name属性时,在实例本身存在则直接返回属性值并停止搜索原型中的name属性(屏蔽了原型中的name)同时访问p2的name属性时,依旧返回原型中的属性值。换句话说,实例p1添加name属性只会阻止我们访问原型中的name属性但是不会修改原型中的name属性
如果p1在添加了name属性后,需要重新使他访问原型中的name属性则可以通过delete操作符删除实例上的name属性
p1.name = '托儿所'
console.log(p1.name) // '托儿所'
delete p1.name
console.log(p1.name) // '简单举个例子'
通过继承自Object的方法hasOwnProperty,可以检测一个属性是否是对象实例的属性
p1.name = '托儿所'
console.log(p1.hasOwnProperty(name)) // true
delete p1.name
console.log(p1.hasOwnProperty(name)) // false
上图:
3.原型链
回顾构造函数、实例与原型的关系:每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。假如让原型对象等于另一个类型的实例,结果会怎样呢?显然此时的原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进就构成了实例与原型的链条,这就是原型链的基本概念。原型链最强大的功能就是用来实现继承功能。
上图: