引言
在 JavaScript 的世界里,原型(Prototype)是一个至关重要却又常让开发者困惑的概念。它是 JavaScript 实现面向对象编程的独特方式,与传统基于类的面向对象语言有所不同。本文将通过通俗易懂的例子,带你深入了解原型的方方面面,帮助你掌握这一核心知识。
一、从 “拿到小米 su7” 说起
想象我们要在代码中表示一辆小米 su7 汽车,这里可以借助 JavaScript 的类(通过构造函数模拟)和实例的概念。
- 类与构造函数:我们可以定义一个构造函数
Car,它就像一个汽车制造蓝图。例如:
javascript
function Car(color) {
// this 指向实例对象
// this.color = color;
// this.name = '小米su7';
// this.height = 1.4;
// this.weight = 1.5;
// this.long = 4800
}
- 创建实例:通过
new Car的方式,我们可以创建出具体的汽车实例,就像从蓝图制造出真实的汽车。例如:
javascript
const car1 = new Car('霞光紫');
const car2 = new Car('海湾蓝');
prototype对象:prototype对象扮演着关键角色。它上面设置的属性和方法,能让这个类的所有实例共享。比如我们为Car的prototype对象添加属性和方法:
javascript
Car.prototype = {
drive() {
console.log('drive,下赛道');
},
name: '小米su7',
height: 1.4,
weight: 1.5,
long: 4800,
};
这样,car1 和 car2 等所有 Car 类的实例都能访问到这些共享的属性和方法。
二、构造函数详解
-
constructor构造函数:当我们使用new关键字调用一个构造函数时,会发生一系列神奇的事情。构造函数内部的this会指向新创建的对象,这个过程就是构造新对象的过程。每个通过构造函数创建的实例都有自己独立的空间,比如我们可以在构造函数内部给实例添加独有的属性,像this.color = color;。但如果在构造函数中定义过多属性和方法,每个实例都会重复拥有这些内容,造成内存浪费。 -
prototype对象 - 原型- 共享属性和方法:
prototype对象的意义重大,它设置的属性和方法能被这个类的所有实例共享。这就好比孔子,虽然不是每个中国人都直接是孔子,但大家都能从文化传承(类似原型链)中受到孔子思想的影响。在 JavaScript 中,这是基于原型的面向对象编程方式。 - 实例与原型的连接:每个函数都有一个
prototype属性,它指向类的原型对象。这个对象的值是一个普通对象,里面的属性和方法会被所有实例共享。同时,每个实例都有一个__proto__属性,它指向类的原型对象。通过这种连接,实例可以通过原型链找到类的原型对象上的属性和方法。例如,car1.__proto__就指向Car.prototype,所以car1能访问到Car.prototype上定义的drive方法、name属性等。 - 与传统
class的关系:JavaScript 原型式的面向对象和传统基于类(如 Java 等语言)的面向对象不同,它不是基于严格的血缘关系(类继承),而是通过原型链来实现类似的效果。在 ES6 引入class语法糖之前,JavaScript 一直通过这种原型式的方式实现面向对象编程。虽然class语法看起来更像传统的类,但本质上还是基于原型的。
- 共享属性和方法:
三、代码实例分析
- 汽车构造函数示例
javascript
function Car(color) {
// this 指向实例对象
// this.color = color;
// this.name = '小米su7';
// this.height = 1.4;
// this.weight = 1.5;
// this.long = 4800
}
Car.prototype = {
drive() {
console.log('drive,下赛道');
},
name: '小米su7',
height: 1.4,
weight: 1.5,
long: 4800,
};
const car1 = new Car('霞光紫');
console.log(car1, car1.name, car1.weight);
car1.drive();
const car2 = new Car('海湾蓝');
console.log(car2, car2.name, car2.weight);
- 首先定义了
Car构造函数,虽然这里注释掉了在构造函数内部为实例添加属性的代码,但我们知道如果在这里添加属性,每个实例都会有自己独立的这些属性。 - 然后通过
Car.prototype重新定义了原型对象,添加了drive方法以及一些汽车的属性。 - 创建
car1和car2两个实例,通过console.log可以看到它们都能访问到prototype对象上的属性,并且能调用drive方法。这充分展示了实例通过原型链访问原型对象属性和方法的过程。
- 人物构造函数示例
javascript
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.speci = '人类';
const p1 = new Person('张三', 18);
console.log(p1.name, p1.speci);
const p2 = new Person('李四', 20);
console.log(p2.name, p2.speci);
console.log(p1.__proto__, '////');
- 定义
Person构造函数,在构造函数内部为实例添加了name和age属性。 - 通过
Person.prototype添加了speci属性,代表人物的类别。 - 创建
p1和p2实例,它们都能访问到prototype上的speci属性。最后打印p1.__proto__,可以看到它指向Person.prototype,进一步验证了实例与原型对象的连接关系。
总结
JavaScript 的原型机制是其面向对象编程的核心。理解构造函数、prototype 对象以及实例之间的关系,对于编写高效、可维护的 JavaScript 代码至关重要。通过共享属性和方法,原型机制不仅节省了内存,还提供了一种独特的代码组织方式。希望通过本文的介绍,你对原型有了更清晰的认识,能够在实际开发中灵活运用这一强大的特性。