原型链:JavaScript 世界的家族族谱

从一段代码说起

javascript 复制代码
const arr = [1, 2, 3];
arr.push(4);        // push 方法从哪来?
console.log(arr);   // [1, 2, 3, 4]

你有没有想过,一个普通的数组 arr,明明我们只给了它三个数字,它为什么会自带 push、map、filter 这些方法?这些方法藏在哪里?答案就藏在 JavaScript 的原型链机制里。

原型是什么

简单来说,原型就是一个普通的对象,它是其他对象的"样板间"或"公共仓库"。

当我们创建一个数组时,JavaScript 会悄悄做一件事:给这个数组对象添加一个内部引用(在浏览器中叫 __proto__),指向 Array.prototype 这个原型对象。而 push、pop 这些方法,就定义在这个原型对象上。

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

所以当你调用 arr.push(4) 时,实际发生的事情是:先在 arr 自身找 push 方法 → 没找到 → 顺着 __proto__ 去 Array.prototype 里找 → 找到了 → 调用。

原型链:层层向上的查找链路

原型对象本身也是对象,它也有自己的原型。这样就形成了一条链,直到某个原型的原型是 null 为止。

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

// 原型链:arr → Array.prototype → Object.prototype → null
console.log(arr.__proto__);              // Array.prototype
console.log(arr.__proto__.__proto__);    // Object.prototype
console.log(arr.__proto__.__proto__.__proto__);  // null

这就是原型链 的本质:一个对象通过 __proto__ 指针连接起来的链条,用于实现属性和方法的继承与共享。

一张图胜过千言万语

复制代码
┌─────────────┐      ┌─────────────────┐      ┌─────────────────┐      ┌──────┐
│    arr      │      │  Array.prototype │      │ Object.prototype │      │ null │
│             │      │                 │      │                 │      │      │
│ 0: 1        │      │ push            │      │ toString        │      │      │
│ 1: 2        │      │ pop             │      │ hasOwnProperty  │      │      │
│ 2: 3        │      │ map             │      │ valueOf         │      │      │
│ length: 3   │      │ ...             │      │ ...             │      │      │
│             │      │                 │      │                 │      │      │
│ __proto__ ──┼────→ │ __proto__ ──────┼────→ │ __proto__ ──────┼────→ │      │
└─────────────┘      └─────────────────┘      └─────────────────┘      └──────┘

原型链的查找规则

当访问一个对象的属性时,JavaScript 引擎会按以下顺序查找:

  1. 先查自己:对象自身有没有这个属性?
  2. 再查原型 :没有就去 __proto__ 指向的原型对象里找
  3. 层层往上:还没有就继续往原型的原型找
  4. 最终返回 :找到就返回,找到 null 还没找到就返回 undefined
javascript 复制代码
const obj = { a: 1 };
obj.__proto__ = { b: 2 };
obj.__proto__.__proto__ = { c: 3 };

console.log(obj.a);  // 1(自身属性)
console.log(obj.b);  // 2(原型上的属性)
console.log(obj.c);  // 3(原型的原型上的属性)
console.log(obj.d);  // undefined(整条链都没有)

函数、构造函数和 prototype 属性

上面我们一直在用 __proto__,但实际开发中更常用的是函数的 prototype 属性。这两个概念容易混淆:

  • __proto__:每个对象都有的内部指针,指向它的原型
  • prototype :只有函数才有的属性,当这个函数作为构造函数(使用 new)时,prototype 会被赋值给实例对象的 __proto__
javascript 复制代码
function Person(name) {
  this.name = name;
}

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

const alice = new Person('Alice');

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

实际应用:方法共享与性能优化

原型链最大的价值在于共享 。想象一下,如果每个数组实例都拷贝一份 push 方法,内存消耗会非常恐怖。通过原型链,所有数组共享同一个 push 方法,既节省内存,又能统一更新。

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

const arr1 = [1, 2, 3];
const arr2 = ['a', 'b', 'c'];

console.log(arr1.last());  // 3
console.log(arr2.last());  // 'c'
// 只需定义一次,所有数组实例都能用

原型链继承

基于原型链,JavaScript 实现了自己的继承模式:

javascript 复制代码
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;

// 添加子类自己的方法
Dog.prototype.bark = function() {
  console.log('Woof!');
};

const buddy = new Dog('Buddy', 'Golden Retriever');
buddy.speak();  // Buddy makes a sound(来自 Animal)
buddy.bark();   // Woof!(来自 Dog)

几个需要知道的关键点

1. 原型链的终点是 null

Object.prototype.__proto__ === null,链条到此为止。

2. 属性屏蔽

如果自身和原型链上都有同名属性,自身属性会"屏蔽"原型链上的:

javascript 复制代码
const obj = { toString: 'hello' };
console.log(obj.toString);  // 'hello',不会去找 Object.prototype.toString

3. hasOwnProperty 的重要性

这个方法可以判断属性是对象自身的,还是从原型链上继承的:

javascript 复制代码
const arr = [1, 2, 3];
console.log(arr.hasOwnProperty('push'));   // false(push 来自原型)
console.log(arr.hasOwnProperty('length')); // true(length 是自身的)

4. 性能注意事项

属性查找沿着原型链越深,性能损耗越大。频繁跨多层原型链访问属性时,可以考虑缓存到局部变量。

现代 JavaScript 与原型链

ES6 的 class 语法本质上是原型链的语法糖:

javascript 复制代码
// 现代写法
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a sound`);
  }
}

// 编译后还是原型链那套
// Animal.prototype.speak = function() { ... }

总结

原型链是 JavaScript 最核心的特性之一,它回答了"对象的属性和方法到底从哪来"这个基本问题。理解原型链,你就能理解:

  • 为什么所有对象都能用 toString()?
  • 为什么数组有一堆好用的方法?
  • JavaScript 的继承到底是怎么工作的?
  • 如何给内置类型添加自定义方法?

记住这个最简单的模型:每个对象都有个隐藏的 __proto__ 指针,指向它的原型对象,而原型对象也有自己的 __proto__,就这样连成一条链,直到 null。属性查找,就沿着这条链一路往上找。

这就是原型链,JavaScript 世界的家族族谱。


立即进入

相关推荐
阿狗童鞋6 分钟前
Python爬虫进阶实战指南
开发语言·爬虫·python
下页、再停留12 分钟前
【C#桌面客户端系列学习-7】数据查询展示
开发语言·c#·visual studio
皓月盈江22 分钟前
Go读写TXT文件
开发语言·后端·golang·go·读写txt文件·bufio·go读写txt
qq_4017004127 分钟前
Qt Signal Mapper:一堆按钮,怎么共用一个槽函数
开发语言·qt
OpsEye31 分钟前
大模型会话里的手机号、身份证,怎么自动脱敏?
javascript·ai编程
蜗牛互联网40 分钟前
Java HttpClient 调用 Gemini 图像理解与金额字段校验
java·开发语言·人工智能·后端·python
蜗牛互联网41 分钟前
Python Responses API视觉输入与本地金额校验最小实现
java·开发语言·人工智能·后端·python
爱喝水的小周43 分钟前
实验十 Vue‑Router 王者荣耀战绩查询 APP 开发实践
前端·javascript·vue.js
yexianglunbai43 分钟前
Vue 3 全面详解:从核心特性到工程化实践
前端·javascript·vue.js
czq_26867194871 小时前
Python打卡第34天
开发语言·python·机器学习