1.基本概念
原型对象(Prototype)
每个JavaScript对象(除 null 外)都有一个关联的原型对象( proto 属性指向的对象),从中可以继承属性和方法。
构造函数(Constructor)
用来创建对象的函数,通过 new 关键词调用。 每个构造函数的 prototype 属性指向其原型对象。
function Person(name) {
this.name = name
}
const x = new Person('someone')
console.log(x.__proto__ === Person.prototype) // true
原型链
当访问一个对象属性时,如果该对象自身没有这个属性, JavaScript 会沿着原型链向上查找,直到找到该属性或达到原型链末端(即为 null)
原型链的构成
构造函数,实例及原型对象的关系
function Person(name) {
this.name = name
}
Person.prototype.sayName = function () {
console.log(`Hi everyone! My name is ${this.name}`)
}
const p = new Person('sheep')
console.log(p.__proto__ === Person.prototype) // true
console.log(Person.prototype.constructor === Person) // true
console.log(Object.getPrototypeOf(p) === Person.prototype) // true
完整的原型链
一个典型的原型链如下:
实例对象 -> 构造函数.prototype -> Object.prototype -> null
例如
// p -> Person.prototype -> Object.prototype -> null
原型链的详细解析
属性查找机制
当访问一个对象的属性时:
- 首先检查该对象本身是否含有此属性
- 如果没有,检查对象的 proto(即构造函数的 prototype)
- 继续沿着 proto 链向上查找
- 如果找到 null 仍未找到则返回 undefined
原型链的终点
所有原型链的终点都是Object.prototype, 而 Object.prototype.proto 是 null
修改原型链
可以通过修改 proto( 不推荐 ) 或使用 Object.setPrototypeOf() 来修改原型链:
const parent = { familyName: 'Xie' }
const child = {
personalName: 'xiaoxiao',
sayName: function() {
return this.familyName + ' ' + this.personalName
}
}
Object.setPrototypeOf(child, parent)
console.log(child.sayName()) // Xie xiaoxiao
构造函数与继承
构造函数继承
function Parent(name) {
this.name = name
}
Parent.prototype.sayName = function() {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name)
this.age = age
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.sayAge = function () {
console.log(this.age)
}
const guy = new Child('lili', 12)
guy.sayName() // lili
guy.sayAge() // 12
ES6的class语法糖
ES6 的 class 本质上是原型继承的语法糖:
class Parent{
constructor(name) {
this.name = name
}
sayName() {
console.log(this.name)
}
}
class Child extends Parent{
constructor(name, age) {
super(name)
this.age = age
}
sayAge() {
console.log(this.age)
}
}
const child = new Child('DT', 1)
child.sayName() // DT
child.sayAge() // 1
原型链相关属性及方法
常用方法
Object.create(): 创建一个新对象,使用现有对象作为新对象的原型Object.getPrototypeOf(): 获取对象的原型Object.setPrototypeOf(): 设置对象的原型instanceof: 检查构造函数的 prototype 是否出现在对象的原型链上isPrototypeOf(): 检查一个对象是否出现在另一个对象的原型链上
实例
const protoObj = { value: 1 }
const obj = Object.create(protoObj)
obj.age = 5
console.log(Object.getPrototypeOf(obj) === protoObj) // true
console.log(protoObj.isPrototypeOf(obj)) // true
console.log(obj instanceof protoObj.__proto__.constructor) // true
原型链使用场景
- 实现继承 : 通过原型链可以实现类似传统面向对象语言的继承
- 共享方法: 所有实例共享原型上的方法,节省内存
- 扩展内置对象 : 可以扩展内置对象的原型(谨慎使用)
- 实现混入模式(Mixin)
注意事项
- 性能考虑: 过长的原型链会影响查找性能
- 扩展内置原型: 扩展
Object.prototype等内置对象会影响所有的对象, 不推荐 - 枚举性: 原型上的属性会被
for...in枚举(可使用Object.defineProperty设置为不可枚举) - 现代实践: ES6的class语法更清晰,推荐使用