JavaScript原型与继承指南

11 阅读4分钟

JavaScript 显示原型与隐式原型完全指南

前言:从构造函数说起

让我们先看一段揭示原型本质的代码:

function Person(name) {
  this.name = name
}

Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`)
}

const john = new Person('John')
john.sayHello() // 输出:Hello, I'm John

这段代码展示了 JavaScript 中一个核心机制:原型继承。理解显示原型(prototype)和隐式原型(__proto__)是掌握 JavaScript 面向对象编程的关键。

正文

1. 显示原型(prototype)详解

1.1 基本定义
  • 显示原型函数对象特有的属性(prototype
  • 它是一个对象,包含应该被继承的属性和方法
  • 只有函数才有 prototype 属性(箭头函数除外)
function Foo() {}
console.log(Foo.prototype) // 输出:{ constructor: Foo }

const arrowFn = () => {}
console.log(arrowFn.prototype) // 输出:undefined
1.2 核心作用
  1. 作为新实例对象的原型模板
  2. 实现属性和方法的共享
  3. 构成原型链的基础
1.3 原型操作(增删改查)
function Car(brand) {
  this.brand = brand
}

// 增加原型属性
Car.prototype.color = 'red'

// 修改原型属性
Car.prototype.color = 'blue'

// 查询原型属性
console.log(Car.prototype.color) // 'blue'

// 删除原型属性
delete Car.prototype.color

重要特性

  • 实例对象不能直接修改或删除原型上的属性
  • 实例对象的属性访问会触发原型链查找
const myCar = new Car('BMW')
myCar.color = 'black' // 这是在实例上添加属性,不是修改原型
console.log(myCar.color) // 'black'
console.log(Car.prototype.color) // undefined(之前已删除)

2. 隐式原型(__proto__)详解

2.1 基本定义
  • 每个对象都有 __proto__ 属性(现推荐使用 Object.getPrototypeOf()
  • 指向创建该对象的构造函数的 prototype
  • 是 JavaScript 内部原型链查找的实际通道
const arr = []
console.log(arr.__proto__ === Array.prototype) // true
console.log(Object.getPrototypeOf(arr) === Array.prototype) // true
2.2 原型链形成原理
function Animal() {}
Animal.prototype.eat = function() { console.log('Eating') }

function Dog() {}
Dog.prototype = new Animal() // 原型继承

const myDog = new Dog()
myDog.eat() // 查找路径:myDog → Dog.prototype → Animal.prototype
2.3 原型链图示
myDog → Dog.prototypeAnimal.prototypeObject.prototypenull

3. 显示原型 vs 隐式原型

特性显示原型 (prototype)隐式原型 (__proto__)
所有者函数对象所有对象
访问方式Func.prototypeobj.__proto__Object.getPrototypeOf(obj)
作用作为构造函数创建实例时的原型用于原型链查找
关系instance.__proto__ === Constructor.prototype指向创建该对象的构造函数的 prototype

4. 特殊原型情况分析

4.1 Object.prototype 的原型
console.log(Object.prototype.__proto__) // null
4.2 函数对象的双重原型
function Foo() {}

// 函数作为对象时的原型
console.log(Foo.__proto__ === Function.prototype) // true

// 函数作为构造函数时的原型
console.log(Foo.prototype.__proto__ === Object.prototype) // true
4.3 基本类型的包装对象
const str = 'hello'
console.log(str.__proto__ === String.prototype) // true
console.log(String.prototype.__proto__ === Object.prototype) // true

5. 面试常见问题与回答技巧

问题1:什么是原型链?

回答要点

  1. 解释原型链是 JavaScript 实现继承的机制
  2. 描述对象属性查找的向上追溯过程
  3. 举例说明原型链的实际应用

示例回答: "原型链是 JavaScript 中对象之间通过 __proto__ 连接形成的链式结构。当访问对象属性时,如果对象本身没有该属性,就会沿着原型链向上查找,直到找到属性或到达 null。比如数组实例可以调用 Array.prototype 上的方法,就是因为原型链的存在。"

问题2:new 操作符做了什么?

回答要点

  1. 创建新对象
  2. 设置原型链接
  3. 绑定 this 并执行构造函数
  4. 返回对象

标准答案

function myNew(Constructor, ...args) {
  // 1. 创建新对象,并链接到构造函数的原型
  const obj = Object.create(Constructor.prototype)
  
  // 2. 执行构造函数,绑定this
  const result = Constructor.apply(obj, args)
  
  // 3. 如果构造函数返回对象则使用该对象,否则返回新对象
  return result instanceof Object ? result : obj
}
问题3:如何实现继承?

回答要点

  1. 原型链继承的优缺点
  2. 组合继承的实现
  3. ES6 class 语法糖

示例代码

// 组合继承
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.constructor = Child
问题4:Object.create(null){} 的区别?

回答要点

  1. 前者创建没有原型的纯净对象
  2. 后者继承自 Object.prototype
  3. 使用场景分析

6. 高级原型应用

6.1 原型污染与防御
// 不安全的扩展
Object.prototype.includes = function() { /* 恶意代码 */ }

// 防御方案
const safeObj = Object.create(null) // 无原型的对象
6.2 原型方法借用
const arrayLike = { 0: 'a', 1: 'b', length: 2 }
Array.prototype.forEach.call(arrayLike, item => {
  console.log(item)
})
6.3 性能优化技巧
// 将常用方法放在原型上
function HeavyObject(data) {
  this.data = data
}
HeavyObject.prototype.process = function() {
  // 处理逻辑
}

// 避免在构造函数中定义方法(每个实例都会创建新函数)

结语

理解 JavaScript 的原型系统需要把握几个关键点:

  1. 函数才有 prototype:普通对象只有 __proto__
  2. 原型链终点是 nullObject.prototype.__proto__ === null
  3. 实例与构造函数的关系obj.__proto__ === Constructor.prototype
  4. 属性查找机制:先自身后原型链

在实际开发中:

  • 使用 Object.getPrototypeOf() 替代 __proto__
  • 谨慎扩展原生原型(可能引发冲突)
  • 理解 ES6 class 只是原型继承的语法糖
  • 合理使用原型实现方法共享

掌握这些知识后,你将能够:

  • 深入理解 JavaScript 的继承机制
  • 在面试中从容应对原型相关问题
  • 编写更高效的面向对象代码
  • 诊断原型链相关的 bug

记住这个简单的原型关系图:

实例对象 --__proto__--> 构造函数.prototype --__proto__--> Object.prototype --__proto__--> null

祝你在 JavaScript 的原型世界中探索愉快!🚀