谈谈JavaScript中的原型链?
问题解析
原型链是 JavaScript 实现继承的核心机制。理解原型链对于理解 JavaScript 的对象系统、继承机制以及属性查找过程至关重要。它是 JavaScript 区别于其他基于类(Class-based)语言的重要特性。
核心概念
什么是原型?
在 JavaScript 中,每个对象(除 null 外)都有一个内部属性 [[Prototype]](在浏览器中通过 __proto__ 访问),指向它的原型对象。原型对象也是一个对象,也有自己的原型,这样就形成了一条原型链。
构造函数、原型和实例的关系
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const person1 = new Person('Alice');
// 三者关系
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
console.log(Person.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
详细解答
1. 原型的基本概念
// 每个函数都有一个 prototype 属性
function Animal() {}
console.log(Animal.prototype);
// {
// constructor: ƒ Animal(),
// __proto__: Object.prototype
// }
// 通过构造函数创建实例
const cat = new Animal();
// 实例的 __proto__ 指向构造函数的 prototype
cat.__proto__ === Animal.prototype; // true
// 原型对象的 constructor 指向构造函数
Animal.prototype.constructor === Animal; // true
2. 属性查找机制
function Person(name) {
this.name = name; // 实例属性
}
Person.prototype.age = 25; // 原型属性
const person = new Person('Alice');
// 属性查找过程:
// 1. 先在实例自身查找
console.log(person.name); // "Alice"(实例属性)
// 2. 实例上没有,去原型上查找
console.log(person.age); // 25(原型属性)
// 3. 原型上没有,继续向上查找(原型链)
console.log(person.toString()); // "[object Object]"(Object.prototype 上的方法)
// 4. 原型链顶端是 null,找不到则返回 undefined
console.log(person.notExist); // undefined
3. 原型链的终点
// 原型链:instance -> Constructor.prototype -> Object.prototype -> null
function Dog() {}
const dog = new Dog();
console.log(dog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
// 完整的原型链
dog -> Dog.prototype -> Object.prototype -> null
4. 函数也是对象
// 函数是 Function 的实例
function fn() {}
console.log(fn.__proto__ === Function.prototype); // true
// Function 本身也是函数
console.log(Function.__proto__ === Function.prototype); // true
// Function.prototype 是特殊的函数对象
console.log(typeof Function.prototype); // "function"
console.log(Function.prototype.__proto__ === Object.prototype); // true
// 完整的函数原型链
fn -> Function.prototype -> Object.prototype -> null
深入理解
1. 原型链图示
┌─────────────────────────────────────────────────────────────────┐
│ 原型链结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ person │ │ Person.prototype│ │
│ │ {name:...} │────────→│ {constructor: │ │
│ │ __proto__ │ │ sayHello: fn} │ │
│ └─────────────┘ └─────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────┐ │
│ │ Object.prototype│ │
│ │ {toString: fn, │ │
│ │ hasOwnProperty:│ │
│ │ fn, ...} │ │
│ └─────────────────┘ │
│ │ │
│ ↓ │
│ null │
│ │
└─────────────────────────────────────────────────────────────────┘
2. instanceof 的原理
// instanceof 检查的是原型链上是否存在构造函数的 prototype
function Person() {}
const person = new Person();
console.log(person instanceof Person); // true
console.log(person instanceof Object); // true
console.log(person instanceof Function); // false
// instanceof 的实现原理
function myInstanceof(instance, Constructor) {
let proto = instance.__proto__;
const prototype = Constructor.prototype;
while (proto) {
if (proto === prototype) return true;
proto = proto.__proto__;
}
return false;
}
console.log(myInstanceof(person, Person)); // true
console.log(myInstanceof(person, Object)); // true
3. 原型链与继承
// 父类
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound`);
};
// 子类
function Dog(name, breed) {
Animal.call(this, name); // 借用构造函数继承实例属性
this.breed = breed;
}
// 建立原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修正 constructor
Dog.prototype.bark = function() {
console.log(`${this.name} barks`);
};
const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak(); // "Buddy makes a sound"(继承自 Animal)
dog.bark(); // "Buddy barks"(Dog 自己的方法)
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
4. ES6 Class 与原型链
// ES6 Class 是原型链的语法糖
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(`${this.name} barks`);
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
// 本质上仍然是原型链
dog.__proto__ === Dog.prototype; // true
Dog.prototype.__proto__ === Animal.prototype; // true
Animal.prototype.__proto__ === Object.prototype; // true
最佳实践
1. 创建对象的方式与原型链
// 方式1:对象字面量
const obj1 = {};
obj1.__proto__ === Object.prototype; // true
// 方式2:Object.create()
const obj2 = Object.create(null); // 没有原型
const obj3 = Object.create(Object.prototype); // 等同于 {}
const obj4 = Object.create({ foo: 'bar' }); // 指定原型
// 方式3:构造函数
function Person(name) {
this.name = name;
}
const person = new Person('Alice');
// 方式4:class(ES6)
class Animal {}
const animal = new Animal();
2. 检查属性的位置
function Person(name) {
this.name = name;
}
Person.prototype.age = 25;
const person = new Person('Alice');
// hasOwnProperty:检查是否是实例自身属性
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('age')); // false
// in 操作符:检查原型链上是否存在
console.log('name' in person); // true
console.log('age' in person); // true
console.log('toString' in person); // true
// Object.keys:只返回自身可枚举属性
console.log(Object.keys(person)); // ["name"]
// for...in:遍历自身和原型链上的可枚举属性
for (let key in person) {
console.log(key); // "name", "age"
}
// 使用 hasOwnProperty 过滤原型属性
for (let key in person) {
if (person.hasOwnProperty(key)) {
console.log(key); // "name"
}
}
3. 避免修改内置原型
// ❌ 不推荐:修改内置对象的原型
Array.prototype.myMethod = function() {
// ...
};
// ✅ 推荐:使用工具函数或扩展类
class MyArray extends Array {
myMethod() {
// ...
}
}
面试要点
-
什么是原型链?
- 每个对象都有一个
__proto__属性指向其原型 - 原型对象也有自己的原型,形成链式结构
- 原型链的终点是
Object.prototype.__proto__即 null
- 每个对象都有一个
-
__proto__和prototype的区别?__proto__:每个对象都有,指向该对象的原型prototype:只有函数有,是构造函数创建实例时使用的原型instance.__proto__ === Constructor.prototype
-
属性查找的过程?
- 先在对象自身查找
- 找不到则去原型上查找
- 沿着原型链一直向上查找
- 找到则返回,找不到返回 undefined
-
instanceof的原理?- 检查构造函数的 prototype 是否出现在实例的原型链上
- 顺着
__proto__一直向上查找
-
如何实现继承?
- 经典方式:
Sub.prototype = Object.create(Super.prototype) - ES6 Class:
class Sub extends Super - 都需要修正 constructor 指向
- 经典方式:
-
原型链的终点是什么?
Object.prototype.__proto__ === null- null 表示原型链的终点
-
为什么 Function.prototype 是函数?
- 这是 JavaScript 引擎的特殊处理
typeof Function.prototype === "function"- 但它的行为更像普通对象