JavaScript原型链没那么难:一文彻底搞懂

1. 原型(Prototype)

什么是原型?

每个 JavaScript 对象都有一个隐藏的 [[Prototype]] 属性,它指向另一个对象,这个被指向的对象就是它的"原型"。

如何访问原型?

javascript 复制代码
// 通过 __proto__ 属性(非标准,但浏览器都支持)
const obj = {};
console.log(obj.__proto__); // 指向 Object.prototype

// 通过 Object.getPrototypeOf()(推荐的标准方式)
console.log(Object.getPrototypeOf(obj)); // 同样指向 Object.prototype

示例:对象的原型

javascript 复制代码
const person = {
    name: 'Alice',
    age: 25
};

console.log(person.__proto__ === Object.prototype); // true

2. 构造函数与原型

构造函数创建对象

javascript 复制代码
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 在原型上添加方法
Person.prototype.sayHello = function() {
    console.log(`Hello, I'm ${this.name}`);
};

const alice = new Person('Alice', 25);
const bob = new Person('Bob', 30);

alice.sayHello(); // "Hello, I'm Alice"
bob.sayHello();   // "Hello, I'm Bob"

为什么使用原型?

javascript 复制代码
// 如果不使用原型,每个实例都会有独立的方法副本
function BadPerson(name) {
    this.name = name;
    this.sayHello = function() { // ❌ 每个实例都有独立的函数
        console.log(`Hello, I'm ${this.name}`);
    };
}

// 使用原型,所有实例共享同一个方法
function GoodPerson(name) {
    this.name = name;
}
GoodPerson.prototype.sayHello = function() { // ✅ 所有实例共享
    console.log(`Hello, I'm ${this.name}`);
};

3. 原型链(Prototype Chain)

什么是原型链?

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

  1. 先在对象自身查找
  2. 如果找不到,就去它的原型上找
  3. 如果还找不到,就去原型的原型上找
  4. 直到找到 null 为止

这种一层层的查找关系就形成了"原型链"。

原型链示例

javascript 复制代码
const grandparent = { grandpa: '老爷爷' };
const parent = { papa: '爸爸' };
const child = { child: '孩子' };

// 设置原型链:child -> parent -> grandparent
Object.setPrototypeOf(parent, grandparent);
Object.setPrototypeOf(child, parent);

console.log(child.child);    // "孩子" - 自身属性
console.log(child.papa);     // "爸爸" - 父级原型属性
console.log(child.grandpa);  // "老爷爷" - 祖父级原型属性
console.log(child.xxx);      // undefined - 找不到

4. 完整的原型链图示

javascript 复制代码
[你的对象] → Object.prototype → null
    ↑
[数组对象] → Array.prototype → Object.prototype → null
    ↑  
[函数对象] → Function.prototype → Object.prototype → null

实际代码验证

javascript 复制代码
const arr = [1, 2, 3];

// 数组的原型链
console.log(arr.__proto__ === Array.prototype);        // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__);               // null

// 函数的原型链  
function test() {}
console.log(test.__proto__ === Function.prototype);    // true
console.log(Function.prototype.__proto__ === Object.prototype); // true

5. 重要的内置原型

Object.prototype

javascript 复制代码
// 所有对象的最终原型
console.log(Object.prototype.toString()); // "[object Object]"
console.log(Object.prototype.hasOwnProperty); // 检查自身属性的方法

Array.prototype

javascript 复制代码
const arr = [1, 2, 3];
console.log(Array.prototype.push === arr.push); // true
console.log(Array.prototype.map === arr.map);   // true

Function.prototype

javascript 复制代码
function test() {}
console.log(Function.prototype.call === test.call);     // true
console.log(Function.prototype.bind === test.bind);     // true

6. 实际应用场景

1. 继承的实现

javascript 复制代码
function Animal(name) {
    this.name = name;
}
Animal.prototype.eat = function() {
    console.log(`${this.name} is eating`);
};

function Dog(name, breed) {
    Animal.call(this, name); // 调用父类构造函数
    this.breed = breed;
}

// 设置原型链继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
    console.log('Woof!');
};

const myDog = new Dog('Buddy', 'Golden');
myDog.eat();  // "Buddy is eating" (继承的方法)
myDog.bark(); // "Woof!" (自身的方法)

2. 方法扩展(猴子补丁)

javascript 复制代码
// 为数组添加自定义方法
Array.prototype.last = function() {
    return this[this.length - 1];
};

const arr = [1, 2, 3, 4];
console.log(arr.last()); // 4

3. 属性检查

javascript 复制代码
const obj = { a: 1 };

console.log(obj.hasOwnProperty('a')); // true - 自身属性
console.log(obj.hasOwnProperty('toString')); // false - 继承属性
console.log('toString' in obj); // true - 包括继承属性

7. 注意事项

1. 性能考虑

javascript 复制代码
// 原型链太长会影响查找性能
for (let key in obj) {
    if (obj.hasOwnProperty(key)) { // 只遍历自身属性
        console.log(key);
    }
}

2. 现代替代方案

javascript 复制代码
// ES6 class 语法(本质还是基于原型)
class Person {
    constructor(name) {
        this.name = name;
    }
    
    sayHello() { // 这个方法会在 Person.prototype 上
        console.log(`Hello, ${this.name}`);
    }
}

// 等同于
function Person(name) {
    this.name = name;
}
Person.prototype.sayHello = function() {
    console.log(`Hello, ${this.name}`);
};

总结

概念 说明 示例
原型 每个对象都有一个指向原型的链接 obj.__proto__
原型链 通过原型链接形成的查找链条 obj → proto1 → proto2 → null
作用 实现继承和方法共享 所有数组共享 Array.prototype 的方法
重点 属性查找会沿着原型链向上 obj.toString() 找到 Object.prototype.toString

原型机制是 JavaScript 实现面向对象编程的基础,理解它对于掌握 JavaScript 至关重要!

相关推荐
GISer_Jinger几秒前
Trae Solo模式生成一个旅行足迹App
前端·javascript
zhangbao90s1 分钟前
Intl API:浏览器原生国际化API入门指南
前端·javascript·html
努力的小郑10 分钟前
别再说你会 new Object() 了!JVM 类加载的真相,绝对和你想的不一样
java·jvm·面试
s3xysteak14 分钟前
我要成为vue高手02:数据传递
前端·javascript·vue.js
Aphasia31125 分钟前
react常用hook
前端·react.js·面试
文艺理科生1 小时前
Nuxt 状态管理权威指南:从 useState 到 Pinia
前端·javascript·vue.js
汪子熙1 小时前
解决 Node.js 无法获取本地颁发者证书问题的详细分析与代码示例
javascript·后端
AAA修煤气灶刘哥1 小时前
被参数校验 / 日志逼疯?AOP:1 个切入点,所有方法自动加 buff
java·后端·面试
似水流年流不尽思念1 小时前
mysql日志文件有哪些,分别介绍下作用 ?
后端·mysql·面试
AAA修煤气灶刘哥1 小时前
ThreadLocal:后端仔的「线程私藏小仓库」,再也不用到处传 userId 啦!
java·后端·面试