1. JavaScript 的原型基础
JavaScript 是一门**基于原型(Prototype)的面向对象语言,不同于 Java、C++ 等基于“类”的语言。在 JavaScript 中,对象之间的继承关系通过原型链(Prototype Chain)**来实现。
1.1 prototype 的本质
prototype是函数对象的一个属性;- 它指向一个对象,这个对象就是“原型对象”;
- 所有通过该函数创建的实例,都会共享这个原型对象上的属性和方法;
- 当我们说 “prototype” 时,通常指的是它所指向的那个对象,而非属性本身——这也是“原型对象”名称的由来。
function Person(name, age) {
this.name = name;
this.age = age;
}
每个构造函数都自带一个 prototype 属性:
Person.prototype.species = '人类';
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
为什么要把公共方法定义在 prototype 上?
对比下面两种写法:
✅ 推荐方式(定义在 prototype 上):
Person.prototype.sayHi = function() { ... };
❌ 不推荐方式(定义在构造函数内部):
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHi = function() { ... }; // 每个实例都会创建一份副本
}
优点:将公共方法定义在
prototype上,可以让所有实例共享同一个方法引用,避免重复创建,大幅节省内存。
1.2 实例的 __proto__ 属性
使用 new 调用构造函数创建实例时,实例会自动拥有一个内部属性 __proto__(规范中称为 [[Prototype]]),它指向其构造函数的 prototype 对象。
const person1 = new Person('张三', 18);
console.log(person1.__proto__ === Person.prototype); // true
注意:
__proto__并非仅限于new创建的对象——所有 JavaScript 对象都有__proto__,它是实现原型链和属性查找机制的核心。
2. 原型链:继承与查找机制
2.1 原型链的结构
每个原型对象本身也是一个普通对象,因此它也有自己的 __proto__,从而形成一条链式结构,即原型链。
例如:
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
这意味着:
Person.prototype继承自Object.prototype;Object.prototype是整个原型链的终点,它的__proto__为null。
2.2 原型对象的 constructor 属性
原型对象上有一个特殊的属性 constructor,默认指回其关联的构造函数:
function Person() {}
console.log(Person.prototype.constructor === Person); // true
const p = new Person();
console.log(p.constructor === Person); // true(通过原型链继承)
⚠️ 注意:如果你重写了整个 prototype 对象,会丢失默认的 constructor:
function Person() {}
Person.prototype = {
sayHi() { console.log('Hi'); }
};
// 此时 constructor 指向 Object!
console.log(new Person().constructor === Object); // true ❌
// 需要手动修复:
Person.prototype.constructor = Person;
console.log(new Person().constructor === Person); // true ✅
总结:
constructor用于从实例反查其构造函数,但在重写prototype后需手动维护。
2.3 Object.prototype 的特殊地位
Object.prototype 是 JavaScript 原型体系的“根节点”,具有以下特性:
- 所有对象(包括函数、数组、字面量等)最终都继承自它;
- 它提供了通用方法,如
toString()、valueOf()、hasOwnProperty()等; - 其
__proto__为null,标志着原型链的终点。
// 1. 字面量对象
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
console.log(obj.toString()); // "[object Object]"
// 2. 构造函数实例
function Person() {}
const p = new Person();
console.log(p.__proto__.__proto__ === Object.prototype); // true
console.log(p.hasOwnProperty); // function
// 3. 终点验证
console.log(Object.prototype.__proto__ === null); // true
因此,
Object.prototype是整个 JavaScript 对象世界的“根原型”。
2.4 原型链查找机制详解
来看一个典型例子:
var obj = new Object(); // {}
obj.species = '动物';
function Animal() {}
Animal.prototype = obj;
function Person() {}
Person.prototype = new Animal();
var su = new Person();
console.log(su.species, su.__proto__, su.toString());
原型链结构图解:
su (实例)
│
└── __proto__ → Person.prototype (即 new Animal())
│
└── __proto__ → Animal.prototype (即 obj)
│
├── species: '动物'
│
└── __proto__ → Object.prototype
│
├── toString: function
│
└── __proto__ → null
查找过程:
-
访问
su.species:su自身无species;- 查
su.__proto__(Person.prototype)→ 无; - 查
Person.prototype.__proto__(即obj)→ 找到'动物'。
-
调用
su.toString():su无toString;Person.prototype无;obj无;Object.prototype有 → 执行"[object Object]"。
关键规则:
- 沿
__proto__链向上查找;- 找到即停;
- 到
null仍未找到,则返回undefined。
补充说明:
__proto__是非标准属性(已废弃),推荐使用Object.getPrototypeOf(obj);- 若实例自身定义了与原型同名的属性,会**遮蔽(shadow)**原型上的属性。
3. 实践示例:创建小米 SU7 车型
3.1 定义构造函数
function Car(color) {
this.color = color;
}
3.2 在 prototype 上定义共享属性和方法
Car.prototype = {
drive() {
console.log('drive, 下赛道');
},
name: 'su7',
height: 1.4,
weight: 1.5,
long: 4800,
};
// 修复 constructor
Car.prototype.constructor = Car;
3.3 创建实例
const car1 = new Car('霞光紫');
console.log(car1.name, car1.weight); // su7, 1.5
car1.drive(); // "drive, 下赛道"
const car2 = new Car('海湾蓝');
console.log(car2.weight); // 1.5
所有
Car实例共享同一套方法和默认属性,高效且节省内存。
4. 原型链的实际应用
4.1 实现继承
function Animal() {}
Animal.prototype.species = '动物';
function Person() {}
Person.prototype = new Animal(); // 继承 Animal
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
const su = new Person();
su.name = 'shu老板';
console.log(su.species); // "动物"(继承自 Animal.prototype)
su.sayHi(); // "你好,我是shu老板"
此时 su 的原型链为:
su → Person.prototype → Animal.prototype → Object.prototype → null
附手绘图一张(字丑):
4.2 动态修改原型的影响
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.species = '人类';
const p1 = new Person('张三', 18);
console.log(p1.species); // "人类"
// 修改原型
Person.prototype.species = '高等灵长类';
console.log(p1.species); // "高等灵长类" —— 实例自动“感知”变化
注意:修改原型会影响所有已有实例;但在实例上赋值不会影响原型。
5. 总结
| 概念 | 说明 |
|---|---|
| 构造函数 | 用于初始化新对象,首字母通常大写 |
prototype | 函数的属性,指向原型对象,供实例共享 |
__proto__ | 对象的属性,指向其原型,构成原型链 |
| 原型链 | 通过 __proto__ 连接的链式结构,用于属性查找和继承 |
Object.prototype | 原型链的终点,提供基础方法 |
constructor | 原型对象上的属性,指回构造函数,重写 prototype 时需手动修复 |
理解 JavaScript 的原型与原型链,是掌握其面向对象编程的关键。它不仅解释了“继承如何工作”,也揭示了“为何所有对象都能调用 toString()”等底层机制。
掌握这些知识,你就能写出更高效、更符合 JavaScript 本质的代码。
💡 提示:现代开发中虽多用
class语法,但其底层仍是基于原型链实现的。理解原型,才能真正驾驭 JavaScript。