原型链与继承
JavaScript 的继承基于原型链,而非传统的类继承。ES6 的
class只是语法糖,底层仍是原型。
原型链结构
每个对象都有一个内部属性 [[Prototype]](可通过 __proto__ 或 Object.getPrototypeOf 访问),指向其原型对象。
js
const obj = { name: 'Alice' }
// 原型链:obj → Object.prototype → null
console.log(Object.getPrototypeOf(obj) === Object.prototype) // true
console.log(Object.getPrototypeOf(Object.prototype)) // null(链的终点)函数与 prototype
js
function Person(name) {
this.name = name
}
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}`
}
const alice = new Person('Alice')
const bob = new Person('Bob')
// alice 和 bob 共享同一个 greet 方法(节省内存)
console.log(alice.greet === bob.greet) // true
// 原型链:alice → Person.prototype → Object.prototype → null
console.log(Object.getPrototypeOf(alice) === Person.prototype) // truenew 操作符做了什么:
js
function myNew(Constructor, ...args) {
// 1. 创建空对象,原型指向 Constructor.prototype
const obj = Object.create(Constructor.prototype)
// 2. 执行构造函数,this 指向新对象
const result = Constructor.apply(obj, args)
// 3. 如果构造函数返回对象则用它,否则返回 obj
return result instanceof Object ? result : obj
}
const alice = myNew(Person, 'Alice')
console.log(alice.greet()) // Hi, I'm Alice属性查找机制
js
function Animal(name) {
this.name = name
}
Animal.prototype.type = 'animal'
Animal.prototype.speak = function() {
return `${this.name} makes a sound`
}
const dog = new Animal('Rex')
dog.breed = 'Labrador' // 实例自有属性
// 查找顺序:实例自有 → 原型 → 原型的原型 → ...
console.log(dog.breed) // 'Labrador'(自有属性)
console.log(dog.type) // 'animal'(原型属性)
console.log(dog.speak()) // 'Rex makes a sound'(原型方法)
console.log(dog.toString()) // '[object Object]'(Object.prototype 方法)
// hasOwnProperty 只检查自有属性
console.log(dog.hasOwnProperty('breed')) // true
console.log(dog.hasOwnProperty('type')) // falseES6 class 语法糖
js
class Animal {
#sound // 私有字段(ES2022)
constructor(name, sound) {
this.name = name
this.#sound = sound
}
speak() {
return `${this.name} says ${this.#sound}`
}
// 静态方法
static create(name, sound) {
return new Animal(name, sound)
}
// getter/setter
get info() {
return `[Animal: ${this.name}]`
}
}
class Dog extends Animal {
constructor(name) {
super(name, 'Woof') // 必须先调用 super
this.tricks = []
}
learn(trick) {
this.tricks.push(trick)
return this
}
// 重写父类方法
speak() {
return `${super.speak()} 🐕`
}
}
const rex = new Dog('Rex')
rex.learn('sit').learn('shake')
console.log(rex.speak()) // Rex says Woof 🐕
console.log(rex.info) // [Animal: Rex]
console.log(rex.tricks) // ['sit', 'shake']
// class 底层仍是原型
console.log(Object.getPrototypeOf(Dog) === Animal) // true(类继承)
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype) // true(实例继承)原型式继承模式对比
js
// 方式一:Object.create(最纯粹的原型继承)
const animalProto = {
speak() { return `${this.name} speaks` }
}
const cat = Object.create(animalProto)
cat.name = 'Whiskers'
console.log(cat.speak()) // Whiskers speaks
// 方式二:混入(Mixin)— 多继承的替代方案
const Serializable = {
serialize() { return JSON.stringify(this) },
deserialize(json) { return Object.assign(this, JSON.parse(json)) }
}
const Validatable = {
validate() { return Object.keys(this).every(k => this[k] !== null) }
}
class User {
constructor(name, email) {
this.name = name
this.email = email
}
}
// 混入多个能力
Object.assign(User.prototype, Serializable, Validatable)
const user = new User('Alice', 'alice@example.com')
console.log(user.serialize()) // {"name":"Alice","email":"alice@example.com"}
console.log(user.validate()) // trueinstanceof 原理
js
// instanceof 沿原型链查找
function myInstanceof(obj, Constructor) {
let proto = Object.getPrototypeOf(obj)
while (proto !== null) {
if (proto === Constructor.prototype) return true
proto = Object.getPrototypeOf(proto)
}
return false
}
const rex = new Dog('Rex')
console.log(myInstanceof(rex, Dog)) // true
console.log(myInstanceof(rex, Animal)) // true(原型链上有 Animal.prototype)
console.log(myInstanceof(rex, Array)) // false常见陷阱
js
// ❌ 直接修改 prototype 会影响所有实例
Array.prototype.sum = function() {
return this.reduce((a, b) => a + b, 0)
}
// 污染全局,与其他库冲突,for...in 会枚举到
// ✅ 用工具函数代替
function sum(arr) {
return arr.reduce((a, b) => a + b, 0)
}
// ❌ 原型属性共享引用类型
function Team() {}
Team.prototype.members = [] // 所有实例共享同一个数组!
const t1 = new Team()
const t2 = new Team()
t1.members.push('Alice')
console.log(t2.members) // ['Alice'] ← 被污染了
// ✅ 引用类型属性放在构造函数中
function Team() {
this.members = [] // 每个实例独立的数组
}总结
- 原型链是 JS 继承的底层机制,
class是语法糖 new创建对象时,实例的[[Prototype]]指向构造函数的prototype- 属性查找沿原型链向上,直到
null - 引用类型属性应定义在构造函数中,方法定义在
prototype上 - 优先使用
class语法,更清晰且支持私有字段