JavaScript原型链详解

148 阅读2分钟

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

原型链的详细解析

属性查找机制

当访问一个对象的属性时:

  1. 首先检查该对象本身是否含有此属性
  2. 如果没有,检查对象的 proto(即构造函数的 prototype)
  3. 继续沿着 proto 链向上查找
  4. 如果找到 null 仍未找到则返回 undefined

原型链的终点

所有原型链的终点都是Object.prototype, 而 Object.prototype.protonull

修改原型链

可以通过修改 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语法糖

ES6class 本质上是原型继承的语法糖:

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

原型链使用场景

  1. 实现继承 : 通过原型链可以实现类似传统面向对象语言的继承
  2. 共享方法: 所有实例共享原型上的方法,节省内存
  3. 扩展内置对象 : 可以扩展内置对象的原型(谨慎使用)
  4. 实现混入模式(Mixin)

注意事项

  1. 性能考虑: 过长的原型链会影响查找性能
  2. 扩展内置原型: 扩展 Object.prototype 等内置对象会影响所有的对象, 不推荐
  3. 枚举性: 原型上的属性会被 for...in枚举(可使用Object.defineProperty设置为不可枚举)
  4. 现代实践: ES6的class语法更清晰,推荐使用