在 JavaScript 中,没有传统意义上的“类”(class),取而代之的是基于原型(prototype)的面向对象模型。这种设计看似抽象,却蕴含着极高的灵活性与优雅性。要真正掌握 JS 的面向对象编程,必须深入理解构造函数、原型对象以及它们之间的关系。
构造函数:创建实例的起点
JavaScript 中的构造函数是一种特殊的函数,用于创建和初始化对象。其命名通常首字母大写,以示区别:
function Person(name, age) {
this.name = name;
this.age = age;
}
当使用 new 操作符调用时,JavaScript 会执行以下步骤:
- 创建一个空对象;
- 设置该对象的
__proto__指向构造函数的prototype; - 将
this绑定到新对象上; - 执行构造函数中的代码,为对象添加属性;
- 返回新对象。
const person1 = new Person('张三', 18);
此时,person1 是一个独立的实例,拥有自己的 name 和 age 属性。
prototype:共享方法的基石
每个函数都有一个 prototype 属性,它指向一个对象——这个对象被称为“原型对象”。我们可以在其中定义所有实例共有的方法和属性:
Person.prototype.species = '人类';
Person.prototype.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
现在,无论创建多少个 Person 实例,它们都能访问这些共享的方法:
person1.sayHi(); // 你好,我是张三
person2.sayHi(); // 你好,我是金总
更重要的是,这些方法只存储一份,极大节省了内存开销。
proto 与原型链:查找机制的核心
每个对象内部都隐藏着一个 __proto__ 属性(非标准但广泛支持),它指向该对象的原型。例如:
console.log(person1.__proto__ === Person.prototype); // true
这揭示了一个关键事实:实例通过 __proto__ 链接到构造函数的 prototype。
当访问一个属性或方法时,JavaScript 会按如下顺序查找:
- 先在实例自身查找;
- 若未找到,则沿着
__proto__向上查找; - 直至到达
Object.prototype; - 最终指向
null,停止查找。
var obj = {};
console.log(obj.toString()); // 能调用,因为继承自 Object.prototype
constructor:连接原型与构造函数的桥梁
prototype 对象中默认包含一个 constructor 属性,它指向其对应的构造函数:
console.log(Person.prototype.constructor === Person); // true
这个双向链接形成了一个完整的闭环:
Person.prototype.constructor === Personperson1.__proto__ === Person.prototype
正是这种结构,让 JavaScript 实现了“原型式”的继承机制。
原型链的完整图景
所有对象最终都会追溯到 Object.prototype,而 Object.prototype.__proto__ 为 null,标志着原型链的终点。
var obj = new Object();
console.log(obj.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
这意味着,任何对象都可以继承 Object.prototype 上的方法,如 toString()、hasOwnProperty() 等。
实例与原型的优先级
如果在实例上直接设置同名属性,会覆盖原型上的值:
su.species = 'LOL达人';
console.log(su.species); // LOL达人(实例属性优先)
这是因为查找机制遵循“就近原则”:只要在实例上找到了,就不会继续向上搜索。
多层原型链:构建复杂继承结构
我们可以将一个构造函数的 prototype 设置为另一个对象的实例,从而实现多层继承:
function Animal() {}
Animal.prototype.species = '动物';
function Person() {}
Person.prototype = new Animal();
var su = new Person();
console.log(su.species); // 动物
此时,Person 的实例不仅继承了 Animal 的属性,还通过 __proto__ 连接到了 Object.prototype,形成一条完整的原型链。
总结:原型机制的本质
JavaScript 的原型系统并非简单的“类复制”,而是一种动态、灵活的共享机制。它通过以下核心概念构建起面向对象的基础:
- 构造函数:定义实例的初始状态;
- prototype:存储共享的方法和属性;
- proto:实现对象间的链接;
- constructor:建立原型与构造函数的关联;
- 原型链:提供属性查找的路径。
这种“原型式”的设计,打破了传统面向对象中“血缘关系”的限制,赋予开发者更大的自由度。它不依赖于静态模板,而是通过运行时的动态链接,实现了高效、可扩展的对象行为共享。
理解原型机制,就是理解 JavaScript 的灵魂。它是语言最深邃的哲学之一——万物皆对象,一切皆可继承。