一张图 + 一段代码,彻底理清 JavaScript 原型链的所有困惑

178 阅读5分钟

“你是否也曾被这些问题困扰?

  • 为什么 f.__proto__ === F.prototype 成立,但 F.__proto__ 却指向 Function.prototype
  • Function.__proto__ === Function.prototype 是不是循环引用?
  • 箭头函数为什么不能当构造函数?

本文将用一张图 + 一段可运行代码,彻底打通你的原型链认知盲区。”

一图看懂实例、函数与原型之间的关联,带你从源头理解 JS 的“继承”本质。

image.png

image.png

基本概念

函数才有 prototype,对象才有 __proto__

虽然所有对象都有 __ proto__,但并非所有函数都有 prototype ——只有能用作构造函数的函数(如普通函数、class)才有 prototype;例如箭头函数是函数对象,却没有 prototype,也不能被new调用。

在 JavaScript 中,一切皆对象。每个对象内部都有一个隐式原型链接(即 [[prototype]]),可通过 __proto__或 Object.getPrototypeOf(obj) 访问。

只有函数对象才有 prototype 属性 准确地说:只有“可作为构造函数”的函数(即普通函数、类等)才自动拥有 prototype 属性。 这个 prototype 是一个对象,用于当该函数被用作构造函数(new Foo())时,作为新实例的原型。

// 普通对象
const obj = {};
console.log(obj.__proto__);        // Object.prototype
console.log(obj.prototype);        // undefined  没有 prototype

// 函数对象
function Foo() {}
console.log(Foo.__proto__);        // Function.prototype
console.log(Foo.prototype);        // { constructor: Foo }  有 prototype

// 箭头函数(特殊!)
const arrow = () => {};
console.log(arrow.prototype);      // undefined  箭头函数没有 prototype

💡 可以这样记忆:

prototype 是函数用来“赋予实例原型”的工具🛠️ __ proto__ 是所有对象用来“查找属性”的链条。

把 prototype 看作“模具”🔍 只有“能造东西的函数”才需要模具;而__ proto__ 是每个“造出来的东西”身上都有的“找模具的线索”。

所有构造函数都是 Function 的实例

Object、Function、Array、Date、Boolean、Number、String 等都是内置的构造函数(built-in constructor functions)。

根据概念一,像 ObjectArrayFunctionDate 等内置构造函数,本质上都是 “函数对象” ——它们既是函数,也是对象。

作为函数,它们具备以下特征:

  • 可以被直接调用:String("hello") 返回原始字符串;
  • 可以通过 new 创建实例:new Date() 生成一个日期对象;
  • 自动拥有 prototype 属性,用于定义所有实例共享的方法(如 Array.prototype.push);
  • 它们自身也是由 Function 构造出来的,因此其 __proto__ 指向 Function.prototype

这正是 JavaScript “一切皆对象”哲学的体现:函数是特殊的对象,而对象的行为由原型链驱动

console.log(typeof Array);        // "function"
console.log(Array instanceof Function); // true
console.log(Array.prototype);     // 包含 push, pop, map 等方法

它们也是对象(因为函数是对象的子集),所以:

  • 可以拥有属性和方法(如 Array.isArrayObject.keys
  • 有 __proto__ 属性,参与原型链
console.log(Array.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true

image.png

Function.__proto__ === Function.prototype   // true
Function.prototype.__proto__ === Object.prototype // true

image.png

Object.prototype.__proto__ === null  // 原型链的终点

image.png

原型对象本身也有原型:原型链的递归结构

每个对象都有一个 __ proto__ 属性,它指向其原型对象;而这个原型对象(如 Foo.prototype)通常包含一个 constructor 属性(指回构造函数),并且自身也有__proto__,从而形成原型链。

let arr = [];
console.log(arr.__proto__); // 指向 Array.prototype

// 而 Array.prototype 是一个普通对象,它确实有:
console.log(Array.prototype.constructor === Array); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true

所以: arr. __ proto__ 指向 Array.prototype —— 这是所有数组实例共享的原型对象。

  • Array.prototype 本身也是一个普通对象,因此它同样遵循原型链规则:
  • 它拥有 constructor 属性,指向其构造函数 Array,用于标识“我是谁创建的”;
  • 它的 __ proto__ 指向 Object.prototype,因为 Array.prototype 本质上是一个由 Object 构造出来的对象;
  • 最终,Object.prototype 的 proto 为 null,标志着整个原型链的终点。

这意味着,当你调用 arr.toString() 或 arr.hasOwnProperty() 时,JavaScript 引擎会沿着这条链向上查找:

arr → Array.prototype → Object.prototype → 找到方法或返回 undefined。

这种层层委托的设计,正是 JavaScript 实现“继承”和“方法复用”的核心机制。 总结一句话:

__proto__ 是对象用来链接到其原型的属性,而那个被链接到的原型对象(如 XXX.prototype)才真正拥有 constructor 和自己的 __proto__

用一段代码验证完整的 JavaScript 原型链

image.png

image.png

上图展示了 JavaScript 原型链的核心关系,与下方代码一一对应。 对照上图的原型链结构,下面这段代码的逻辑将一目了然。

// ==============================
// 基本概念一:函数对象 vs 普通对象
// ==============================

// 1. 普通对象:只有 __proto__,没有 prototype
const obj = {};
console.log('【普通对象】obj.__proto__ === Object.prototype:', obj.__proto__ === Object.prototype);
console.log('【普通对象】obj.prototype:', obj.prototype);

// 2. 可构造函数:有 prototype(用于实例共享),也有 __proto__(自身是 Function 的实例)
function Foo() {}
console.log('【构造函数】Foo.prototype:', Foo.prototype);
console.log('【构造函数】Foo.__proto__ === Function.prototype:', Foo.__proto__ === Function.prototype);

// 3. 箭头函数:是函数对象,但不能作为构造函数,因此没有 prototype
const arrow = () => {};
console.log('【箭头函数】arrow.prototype:', arrow.prototype);


// ==============================
// 基本概念二:内置构造函数也是函数对象
// ==============================

// 所有内置构造函数(如 Object、Array、Date 等)都是 Function 的实例
console.log('\n【内置构造函数】验证:');
console.log('Array instanceof Function:', Array instanceof Function);
console.log('Date.__proto__ === Function.prototype:', Date.__proto__ === Function.prototype);
console.log('String.prototype.constructor === String:', String.prototype.constructor === String);


// ==============================
// 基本概念三:原型链的构建
// ==============================

// 创建实例
const f1 = new Foo();
const arr = [1, 2, 3]; // 等价于 new Array(1, 2, 3)

// 实例的 __proto__ 指向其构造函数的 prototype
console.log('\n【原型链起点】:');
console.log('f1.__proto__ === Foo.prototype:', f1.__proto__ === Foo.prototype);
console.log('arr.__proto__ === Array.prototype:', arr.__proto__ === Array.prototype);

// 原型对象本身也是普通对象,其 __proto__ 指向 Object.prototype
console.log('\n【原型对象的内部结构】:');
console.log('Foo.prototype.constructor === Foo:', Foo.prototype.constructor === Foo);
console.log('Array.prototype.__proto__ === Object.prototype:', Array.prototype.__proto__ === Object.prototype);

// 原型链终点
console.log('\n【原型链终点】:');
console.log('Object.prototype.__proto__ === null:', Object.prototype.__proto__ === null);


// ==============================
// 特殊关系:Function 与 Object
// ==============================

console.log('\n【特殊关系】:');
console.log('Function.__proto__ === Function.prototype:', Function.__proto__ === Function.prototype);
console.log('Function.prototype.__proto__ === Object.prototype:', Function.prototype.__proto__ === Object.prototype);
console.log('Object.__proto__ === Function.prototype:', Object.__proto__ === Function.prototype);

// 使用示例
printPrototypeChain(f1, 'f1 (new Foo())');
printPrototypeChain(arr, 'arr ([1,2,3])');
printPrototypeChain(Foo, 'Foo (function)');

基本概念一:函数对象 vs 普通对象: image.png

基本概念二:内置构造函数也是函数对象: image.png 基本概念三:原型链的构建

image.png

image.png

特殊关系:Function 与 Object image.png 这段代码完整还原了经典原型链图中的核心关系:
从普通对象到构造函数,从 prototype__proto__,再到 FunctionObject 的相互指向,每一条输出都对应图中的一条连线。
运行它,你看到的不只是 truefalse,而是 JavaScript 对象系统底层的骨架。

小结

原型与原型链是 JavaScript 面向对象机制的基石。理解 __proto__prototype 的区别与联系,掌握函数对象、普通对象在原型链中的位置,不仅能帮你透彻理解 newinstanceof、继承等核心机制,还能在调试和设计代码时更加游刃有余。

记住三句话:

  • 所有对象都有 __proto__,指向其原型;
  • 只有可构造函数才有 prototype,用于提供实例的共享原型;
  • 原型链最终止于 Object.prototype.__proto__ === null

现在再回头看那张经典图,是不是每一根线都清晰如水? 吃透它,你就真正迈进了 JavaScript 的内核世界。