“你是否也曾被这些问题困扰?
- 为什么
f.__proto__ === F.prototype成立,但F.__proto__却指向Function.prototype? Function.__proto__ === Function.prototype是不是循环引用?- 箭头函数为什么不能当构造函数?
本文将用一张图 + 一段可运行代码,彻底打通你的原型链认知盲区。”
一图看懂实例、函数与原型之间的关联,带你从源头理解 JS 的“继承”本质。
基本概念
函数才有 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)。
根据概念一,像 Object、Array、Function、Date 等内置构造函数,本质上都是 “函数对象” ——它们既是函数,也是对象。
作为函数,它们具备以下特征:
- 可以被直接调用:
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.isArray、Object.keys) - 有
__proto__属性,参与原型链
console.log(Array.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
Function.__proto__ === Function.prototype // true
Function.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // 原型链的终点
原型对象本身也有原型:原型链的递归结构
每个对象都有一个 __ 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 原型链
上图展示了 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 普通对象:
基本概念二:内置构造函数也是函数对象:
基本概念三:原型链的构建
特殊关系:Function 与 Object
这段代码完整还原了经典原型链图中的核心关系:
从普通对象到构造函数,从 prototype 到 __proto__,再到 Function 与 Object 的相互指向,每一条输出都对应图中的一条连线。
运行它,你看到的不只是 true 和 false,而是 JavaScript 对象系统底层的骨架。
小结
原型与原型链是 JavaScript 面向对象机制的基石。理解 __proto__ 与 prototype 的区别与联系,掌握函数对象、普通对象在原型链中的位置,不仅能帮你透彻理解 new、instanceof、继承等核心机制,还能在调试和设计代码时更加游刃有余。
记住三句话:
- 所有对象都有
__proto__,指向其原型; - 只有可构造函数才有
prototype,用于提供实例的共享原型; - 原型链最终止于
Object.prototype.__proto__ === null。
现在再回头看那张经典图,是不是每一根线都清晰如水? 吃透它,你就真正迈进了 JavaScript 的内核世界。