Skip to content

一、原型对象的定义

1. 两个关键概念

在 JavaScript 中,"原型"涉及两个不同但相关的概念:

概念含义访问方式
[[Prototype]]对象的内部链接,指向其原型对象Object.getPrototypeOf(obj)obj.__proto__
prototype函数对象的属性,指向实例的原型对象Constructor.prototype

关键区别

  • ** [[Prototype]] ** 存在于** 所有对象 **(除 null)上,是实例与原型之间的链接
  • ** prototype ** 只存在于** 函数对象 **上,用于构造函数创建实例时设置 [[Prototype]]

2. 原型对象的图形化结构

javascript
function Person(name) {
  this.name = name;
}

const person1 = new Person('Alice');

** 上述代码的内存结构 **:

person1 (实例对象)
  ├─ name: "Alice"          ← 自有属性
  └─ [[Prototype]] ─────────┐

Person.prototype (原型对象)   ← person1.__proto__ 或 Object.getPrototypeOf(person1)
  ├─ constructor: Person   ← 指向构造函数
  └─ [[Prototype]] ─────────┐

Object.prototype (顶层原型)
  ├─ toString(), valueOf() ← 所有对象共享的方法
  └─ [[Prototype]]: null    ← 原型链终点

二、** 原型链的工作原理 **

** 属性查找机制(原型链搜索) **

当访问 person1.sayHello() 时,JavaScript 引擎执行以下步骤:

  1. ** 检查自有属性 **:person1 自身是否有 sayHello?否。
  2. ** 查找原型 **:通过 person1.[[Prototype]] 找到 Person.prototype,是否有 sayHello?是 → 执行。
  3. ** 若未找到 **:继续查找 Person.prototype.[[Prototype]]Object.prototype
  4. ** 到达终点 **:若 Object.prototype 也未找到,返回 undefined 或抛出错误。

** 代码验证 **:

javascript
person1.__proto__ === Person.prototype;        // true
Person.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null;           // true ← 原型链终点

三、** 构造函数与原型对象的关系 **

** 1. 构造函数的原型属性 **

每个函数创建时,JS 引擎自动为其添加 prototype 属性:

javascript
function Foo() {}
Foo.prototype; // { constructor: Foo, [[Prototype]]: Object.prototype }
  • ** constructor **:指向函数本身(Foo.prototype.constructor === Foo
  • ** [[Prototype]] **:默认指向 Object.prototype

** 2. new 运算符的底层操作 **

const f = new Foo() 内部执行了:

javascript
// 伪代码
const f = {};                           // 1. 创建空对象
f.[[Prototype]] = Foo.prototype;        // 2. 链接原型
Foo.call(f);                            // 3. 执行构造函数(this 指向新对象)
return f;                               // 4. 返回新对象

四、** 原型对象的三大作用 **

** 1. 实现方法共享 **

** 错误方式 **(每个实例都复制一份方法):

javascript
function Person(name) {
  this.name = name;
  this.say = function() { console.log('Hi'); }; // ❌ 每个实例独立函数
}

** 正确方式 **(通过原型共享):

javascript
function Person(name) {
  this.name = name;
}
Person.prototype.say = function() { console.log('Hi'); }; // ✅ 所有实例共享

const p1 = new Person('A');
const p2 = new Person('B');
p1.say === p2.say; // true ← 同一个函数

** 2. 实现继承 **

javascript
// 父类
function Animal(type) {
  this.type = type;
}
Animal.prototype.eat = function() { console.log('eating'); };

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

// 设置原型链:Dog.prototype ← Animal.prototype ← Object.prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;  // 修复 constructor 指向

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

const myDog = new Dog('旺财');
myDog.eat();   // 继承自 Animal.prototype
myDog.bark();  // 自有方法

** 3. 动态扩展所有实例 **

javascript
function Person() {}
const p1 = new Person();
const p2 = new Person();

// 动态添加原型方法
Person.prototype.walk = function() { console.log('walking'); };

p1.walk(); // ✅ 可用
p2.walk(); // ✅ 可用 ← 即使实例已经创建

五、** 现代 ES6+ 的类语法 **

class 是原型继承的** 语法糖 **,底层仍然是原型机制:

javascript
class Person {
  constructor(name) { this.name = name; }
  say() { console.log('Hi'); }
}

// 等价于 ES5
function Person(name) { this.name = name; }
Person.prototype.say = function() { console.log('Hi'); };

// 验证
typeof Person;              // 'function' ← 仍是函数
Person === Person.prototype.constructor; // true

** 类继承的底层 **:

javascript
class Dog extends Animal {
  bark() { console.log('woof'); }
}

// 等价于
function Dog() { Animal.call(this); }
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;


六、** 核心要点总结 **

  1. ** 原型是对象**:每个对象(除 null)都有 [[Prototype]] 内部链接
  2. 函数有 prototype:构造函数的 prototype 属性是其创建实例的原型
  3. 原型链是链接obj → 原型 → 原型的原型 → ... → Object.prototype → null
  4. 属性查找是委托:找不到属性时沿原型链向上搜索,而非复制
  5. 共享机制:原型上的方法被所有实例共享,节省内存
  6. 动态性:修改原型会影响所有已创建和未来的实例

一句话概括:JavaScript 的原型对象是对象之间共享属性和方法的桥梁,通过 [[Prototype]] 链接形成原型链,实现了"无类"的继承机制。

一、历史渊源:向Self语言致敬

"原型"一词源自**原型继承(Prototype-based Inheritance)**这一编程范式,其命名有深厚的历史和设计哲学背景:


JavaScript的原型机制直接借鉴了Self语言(一种基于原型的面向对象语言)。1995年Brendan Eich在10天内创造JavaScript时,刻意避开了传统的"类继承"模型,选择了更灵活的原型委托机制。"Prototype"在英文中意为:

  • 原始模型:第一个用于测试和复制的模板
  • 蓝本:后续对象以此为基础创建

在JavaScript中,prototype对象正是扮演了 "对象蓝本" 的角色。


二、"原型"的工程学内涵

1. 字面意义:对象的"原始模板"

就像制造业中用**原型(Prototype)**来批量生产产品一样:

javascript
// Person.prototype 是"原型对象"——所有Person实例的原始模板
function Person(name) {
  this.name = name;
}

Person.prototype.say = function() {
  console.log(`我是${this.name}`);
};

// 所有实例都从这个"模板"复制链接行为
const p1 = new Person('Alice');  // p1 以 Person.prototype 为原型
const p2 = new Person('Bob');    // p2 也以 Person.prototype 为原型

在这个模型中:

  • Person.prototype原型对象(模板)
  • p1p2实例对象(从原型复制链接的产品)
  • 实例不复制方法,而是委托给原型查找

2. 与传统"类"的命名对比

范式术语含义实例创建方式
基于类Class抽象蓝图,定义结构但不直接可用new Class() → 实例
基于原型Prototype可使用的对象,既是模板也是对象Object.create(proto) → 新对象

关键区别

  • 类是抽象概念:你无法直接调用类上的方法
  • 原型是真实对象:你可以直接调用Person.prototype.say(),它本身就是对象

三、为什么不用"Class"这个名字?

1. JavaScript诞生时的妥协

1995年JavaScript初创时,Java正流行。Netscape要求"看起来像Java",但Brendan Eich坚持函数式+原型的设计:

"我被告知'必须看起来像Java',但我偷偷塞进了函数式特性和原型继承。" —— Brendan Eich

如果叫"Class",会误导开发者认为这是Java那种静态类继承,而Prototype更能体现其动态、可变的对象委托本质。

2. 动态原型 vs 静态类

javascript
// 类的静态性(Java/C++):定义后结构固定
class Person {
  void say() { /* 无法运行时修改 */ }
}

// 原型的动态性(JavaScript):可随时修改
function Person() {}
Person.prototype.say = function() { console.log('Hi'); };

// 运行后还能给所有实例增加新方法
Person.prototype.walk = function() { console.log('Walking'); };

// 即使p1已经创建,也能用新方法
const p1 = new Person();
p1.walk(); // ✅ 可用!

这种 "模板即对象,对象可修改" 的特性,只有"原型"这个词能准确表达。


四、"原型"在中文语境的契合

翻译成"原型"而非"样本"或"模板",是因为:

  1. 原型可实例化:汽车原型本身能驾驶,Person.prototype本身也是对象
  2. 原型可演进:产品原型会迭代,prototype可在运行时修改
  3. 实例与原型联动:批量产品随原型升级而升级,实例自动获得原型的新方法

这与面向对象中的"类"(静态蓝图)形成鲜明对比。


五、ES6 Class语法的命名妥协

2015年引入的class关键字只是语法糖,底层仍是原型:

javascript
class Person {
  constructor(name) { this.name = name; }
  say() { console.log(this.name); }
}

// 编译后仍是原型链
console.log(typeof Person); // 'function'
console.log(Person.prototype.say); // [Function: say]

JavaScript 保留 class 名称是为了降低学习成本,但文档仍强调"基于原型的继承",因为这才是本质。


核心总结

概念为什么叫"原型"体现
词源源自Self语言,向原型继承范式致敬Prototype-based Programming
工程学它是可复用的"原始模型"Person.prototype 是实例的蓝本
动态性它是可修改的真实对象运行时增减方法影响所有实例
区别于类避免与静态类继承混淆体现JavaScript"无类"的设计哲学

一句话:因为它既是对象的模板,又是可使用的对象本身,这种"模板即实例"的动态特性,只有"原型"一词能精准表达。



为什么 JavaScript 的函数也是对象?

在 JavaScript 中,函数(function)被设计成**"可调用的对象"(callable objects)**。这一设计并非偶然,而是根植于语言的历史目标、技术实现和哲学理念。下面从 6 个维度彻底解释"为什么"。


一、历史原因:10 天内的"偷懒"设计

1995 年,Brendan Eich 被要求"让网页动起来",并被告知**"必须看起来像 Java"**。

需求决策
要有类、但来不及实现完整类体系把函数当构造函数用(new Foo()
要有方法、但不想新增语法允许函数作为属性值(obj.method()
要有回调、但不想单独设计类型让函数可以像变量一样传递

于是,函数被直接做成了一种特殊对象,一次性解决三大需求。

"我必须在 10 天内让 JS 跑起来,把函数做成对象是最快路径。" —— Brendan Eich


二、技术实现:引擎内部如何"既是函数又是对象"

1. 对象模型的底层结构

V8(Chrome 引擎)内部伪代码:

cpp
// 普通对象
JSObject {
  Map* shape;        // 隐藏类,记录属性布局
  Object* properties; // 属性存储
}

// 函数对象 = JSObject + 可调用标记
JSFunction : JSObject {
  Code* code;        // 机器码入口地址
  SharedFunctionInfo* info; // 源码、参数长度等
  bool is_callable;  // 特殊标记,设为 true
}

关键:

  • 内存布局上,函数对象就是 JSObject 的扩展
  • 多了一个 [[Call]] 内部方法,让引擎知道"此对象可以被执行"

2. 规范视角:ECMAScript 内部插槽

插槽普通对象函数对象
[[Prototype]]✅ 指向原型✅ 指向 Function.prototype
[[Call]]❌ 不存在✅ 存在,指向调用逻辑
[[Construct]]❌ 不存在✅ 存在(若函数可做构造函数)

结论:函数对象比普通对象多两个内部插槽,其余完全一样。


三、语言表现:代码层面的"对象证据"

1. 可以添加属性(像对象一样)

javascript
function greet(name) {
  console.log('Hello ' + name);
}

// 函数作为对象,随意增删属性
greet.counter = 0;
greet.inc = function () { this.counter++; };

greet.inc();
console.log(greet.counter); // 1

2. 可以赋值、传参、返回(像变量一样)

javascript
// 高阶函数:函数作参数,也作返回值
function createGreeter(greeting) {
  return function (name) {
    console.log(greeting + ', ' + name);
  };
}

const sayHi = createGreeter('Hi');
sayHi('Alice'); // Hi, Alice

3. 可以原型继承(像对象一样)

javascript
function Foo() {}
Foo.prototype.speak = () => console.log('Speaking');

// 函数本身也继承自 Function.prototype
console.log(Foo.__proto__ === Function.prototype); // true
console.log(Foo.toString()); // 继承自 Function.prototype.toString

四、哲学理念:统一的最小化设计

JavaScript 采用**"一切皆对象"**的极端统一哲学:

类型在 JS 中的对待
函数可调用的对象
数组特殊的对象(索引是键)
原始值临时包装为对象(如 new Number(1)

好处

  1. 语法简洁:无需区分"函数指针""方法""委托"
  2. 元编程能力强:可在运行时给函数加属性、方法、元数据
  3. 易于实现模式:装饰器、依赖注入、插件系统都依赖"函数即对象"

五、对比其他语言:JS 的独特之处

语言函数类型能否给函数加属性能否运行时修改
Java单独的 Function 接口❌ 不行❌ 不行
C/C++函数指针❌ 不行❌ 不行
Python第一类对象(可调用)✅ 可以✅ 可以
JavaScript可调用的对象✅ 可以✅ 可以

JS 把函数提升到**"一等公民"**地位,甚至超越 Python:函数既是对象,又能被 new 实例化。


六、函数作对象的副作用与注意点

1. 内存占用略大

函数对象比普通对象多持有字节码/机器码指针,大量闭包可能更耗内存。

2. 命名函数 vs 匿名函数

javascript
// 命名函数:函数对象有 name 属性
function foo() {}
console.log(foo.name); // "foo"

// 匿名函数:name 为空或推断
const bar = function () {};
console.log(bar.name); // "bar"(引擎推断)

3. 可构造性判断

javascript
function Foo() {}
console.log(typeof Foo);              // "function"
console.log(Foo instanceof Object);   // true
console.log(Foo instanceof Function); // true
console.log(Foo.prototype);           // 存在 → 可构造

核心总结

维度回答
历史10 天速成需求,函数兼作构造器、方法、回调最省事
技术引擎内部=普通对象+[[Call]]/[[Construct]]插槽
语言可以增删属性、继承原型、当变量传递,完全对象化
哲学"最小化、统一化"——用一套模型解决多种场景
结果函数是**"可调用对象"**,JavaScript 最具特色的设计之一

一句话:因为 Brendan Eich 需要一种结构同时胜任函数、方法、构造器、模块四种角色,而"对象"是唯一能在 10 天内满足所有需求的超集类型。