JavaScript 原型详解:从概念到实践

JavaScript 原型详解:从概念到实践

JavaScript 的面向对象编程与传统的类式面向对象不同,它基于原型(prototype) 实现。理解原型是掌握 JS 面向对象的核心,本文结合实例代码,详细解析原型的概念、工作原理及实践应用。

一、什么是原型?

在 JavaScript 中,每个函数都有一个特殊的属性prototype,它的值是一个对象(称为 "原型对象")。当通过new关键字调用函数(作为构造函数)创建实例时,实例会自动关联到构造函数的prototype对象,从而共享原型对象上的属性和方法。

用一句话概括:原型是构造函数的 "共享仓库",存储所有实例需要共享的属性和方法

二、构造函数与原型的关系

构造函数是用于创建实例的 "模板",而原型对象则是构造函数为实例准备的 "共享资源"。两者的关系可以通过代码直观体现:

示例 1:基础构造函数与原型

javascript 复制代码
// 定义构造函数(首字母大写,约定俗成)
function Person(name, age) {
  // 实例自身的属性(每个实例独立拥有)
  this.name = name;
  this.age = age;
}

// 原型对象:所有实例共享的属性
Person.prototype.species = '人类';

// 创建实例
const persona1 = new Person('张三', 18);
const persona2 = new Person('金总', 19);

// 实例可访问自身属性和原型属性
console.log(persona1.name); // 张三(自身属性)
console.log(persona1.species); // 人类(原型属性)
console.log(persona2.species); // 人类(原型属性)

代码解释

  • Person是构造函数,通过this为每个实例定义独立属性(nameage)。
  • Person.prototype是原型对象,定义了共享属性species,所有Person实例都能访问。
  • 即使创建多个实例,species属性只在原型中存储一份,实现了属性共享,节省内存。

三、实例与原型的关联:__proto__

每个实例对象都有一个私有属性__proto__(ES5 中可通过Object.getPrototypeOf()访问),它指向创建该实例的构造函数的prototype对象。这是实例能访问原型属性的核心原因。

示例 2:__proto__prototype的关系

javascript 复制代码
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 重写原型对象(注意:用对象字面量重写时需手动指定constructor)
Person.prototype = {
  species: '人类',
  sayHi: function() {
    console.log(`你好,我是${this.name}`);
  }
};

const su = new Person('舒老板', 19);

// 实例的__proto__指向构造函数的prototype
console.log(su.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(su) === Person.prototype); // true(更推荐的写法)

代码解释

  • 实例su__proto__属性直接指向Person.prototype,因此能访问speciessayHi
  • Object.getPrototypeOf()是 ES5 规范中获取原型的方法,比__proto__更推荐使用。

图解

四、原型链:属性查找的规则

当访问实例的某个属性时,JS 会先在实例自身查找;如果找不到,会沿着__proto__指向的原型对象查找;如果原型对象中也没有,会继续查找原型对象的原型(即prototype.__proto__),直到找到null(原型链的终点)。这就是原型链

示例 3:原型链的查找机制

javascript 复制代码
function Person(name) {
  this.name = name;
}

// 原型对象1:Person.prototype
Person.prototype.species = '人类';

const su = new Person('舒老板');

// 1. 实例自身有name属性,直接返回
console.log(su.name); // 舒老板

// 2. 实例自身没有species,查找原型对象
console.log(su.species); // 人类

// 3. 实例和Person.prototype都没有toString,查找原型的原型(Object.prototype)
console.log(su.toString()); // [object Object](来自Object.prototype)

// 4. Object.prototype的原型是null,停止查找
console.log(Object.prototype.__proto__); // null

代码解释

  • su.toString()的查找路径:su自身 → Person.prototypeObject.prototype(找到toString方法)。
  • 所有对象的原型链最终都会指向Object.prototype,而Object.prototype.__proto__null,标志着原型链的结束。

图解

示例 4:实例属性覆盖原型属性

ini 复制代码
function Person(name) {
  this.name = name;
}

Person.prototype.species = '人类';

const su = new Person('舒老板');
su.species = 'LOL达人'; // 给实例添加同名属性

console.log(su.species); // LOL达人(优先访问实例自身属性)
console.log(su.__proto__.species); // 人类(原型属性未被修改)

代码解释

  • 当实例有与原型同名的属性时,会优先访问实例属性(不会修改原型属性)。
  • 若要修改原型属性,需直接操作构造函数.prototype.属性

五、原型继承

通过修改构造函数的prototype指向另一个构造函数的实例,可以实现原型继承,让子类实例共享父类原型的属性和方法。

示例 5:原型继承实现

javascript 复制代码
// 父构造函数
function Animal() {}
Animal.prototype.species = '动物';

// 子构造函数
function Person() {}

// 关键:让Person的原型指向Animal的实例,实现继承
Person.prototype = new Animal();

// 创建子类实例
const su = new Person();

console.log(su.species); // 动物(继承自Animal的原型)
console.log(su.__proto__); // Animal实例(Person.prototype)
console.log(su.__proto__.__proto__); // Animal.prototype(原型链的上一级)

代码解释

  • Person.prototype = new Animal()Person的原型成为Animal的实例,因此Person的实例su能通过原型链访问Animal.prototype上的species
  • 原型继承是 JS 实现继承的基础,本质是通过原型链共享父类资源。

六、总结

  1. 原型核心概念

    • 构造函数的prototype属性是原型对象,存储所有实例的共享属性 / 方法。
    • 实例的__proto__属性指向构造函数的prototype,是实例与原型的连接纽带。
  2. 原型链规则

    • 属性查找沿__proto__链条向上,直至null
    • 实例属性优先于原型属性。
  3. 实践价值

    • 通过原型实现属性 / 方法共享,减少内存消耗。
    • 通过原型链实现继承,构建复杂的对象关系。
相关推荐
晴栀ay32 分钟前
JS中原型式面向对象的精髓
前端·javascript
Amy_yang1 小时前
js 封装时间格式化,将单位有秒(s)的数据转换为'00:00:00'格式
javascript
interception1 小时前
爬虫js逆向,jsdom补环境,抖音,a_bogus
javascript·爬虫·python
一树论1 小时前
浏览器插件开发经验分享二:如何处理日期控件
前端·javascript
Yanni4Night1 小时前
LogTape:零依赖的现代JavaScript日志解决方案
前端·javascript
重铸码农荣光1 小时前
一文吃透 ES6 Symbol:JavaScript 里的「独一无二」标识符
前端·javascript
2503_928411561 小时前
11.25 Vue内置组件
前端·javascript·vue.js
q***49861 小时前
MySQL数据的增删改查(一)
android·javascript·mysql
我有一个object1 小时前
uniapp上传文件报错:targetSdkVersion设置>=29后在Android10+系统设备不支持当前路径。请更改为应用运行路径!
前端·javascript·vue.js·uniapp