深入理解 JavaScript 中的静态属性、原型属性与实例属性

用 "一家三口" 的家庭关系来类比,一眼就能懂:

  1. 静态属性:家里的 "户口本"
  • 归属:属于整个家(对应构造函数 / 类),不是某个人的。
  • 用法:只有提 "我们家" 时才用(比如 "我们家户口本地址是 XX"),你不能说 "这是我的户口本"。
  1. 原型属性:家里的 "公共物品"(比如冰箱、电视)
  • 归属:放在家里客厅,全家共用(对应原型对象)。
  • 用法:爸妈、你都能用来存东西 / 看电视(所有实例共享),但你不能把冰箱搬去自己房间说成 "我的"(实例不能独占)。
  1. 实例属性:你的 "私人用品"(比如你的手机、日记本)
  • 归属:只属于你个人(对应单个实例)。
  • 用法:只有你能改手机壁纸、写日记(实例独自控制),爸妈的手机(其他实例)和你没关系。

一、静态属性(Static Properties)

概念与本质

静态属性是直接挂载在函数本身的属性,与函数的实例无关,也不会出现在原型链中。从本质上讲,JavaScript 中函数是特殊的对象,静态属性就是这个 "函数对象" 自身的属性,类似于其他语言中 "类的静态成员"。

定义方式

通过 函数名.属性名 直接定义,无需依赖实例:

ini 复制代码
// 构造函数
function Tool() {}

// 定义静态属性(包括静态方法)
Tool.version = "2.1.0"; // 静态常量:版本号
Tool.count = 0; // 静态变量:工具调用次数
Tool.format = function(str) { // 静态方法:格式化字符串
  return str.toUpperCase();
};

访问规则

  1. 只能通过函数本身访问,无法通过实例访问;
  2. 不参与继承,子类无法继承父类的静态属性(除非手动复制);
  3. 所有访问共享同一属性,修改静态属性会影响所有通过函数访问的地方。

典型应用场景及详细举例

1. 工具类方法与常量(最常见场景)

当需要一组与实例无关的工具函数时,静态属性是最佳选择。例如 JavaScript 内置的 Math 对象:

javascript 复制代码
// 内置静态属性示例
console.log(Math.PI); // 3.141592653589793(静态常量)
console.log(Math.max(1, 3, 5)); // 5(静态方法)

// 自定义日期工具类
function DateUtils() {}
// 静态常量:常用日期格式
DateUtils.FORMATS = {
  DATE: "YYYY-MM-DD",
  DATETIME: "YYYY-MM-DD HH:mm:ss"
};
// 静态方法:格式化日期
DateUtils.format = function(date, format) {
  // 实现逻辑...
  return formattedStr;
};

// 使用:无需创建实例,直接调用
console.log(DateUtils.FORMATS.DATE); // "YYYY-MM-DD"
DateUtils.format(new Date(), DateUtils.FORMATS.DATETIME);

优势:工具方法无需依赖实例状态,直接通过类名调用,避免创建无意义的实例。

2. 实例计数器与全局状态

用于统计构造函数创建的实例数量,或维护全局唯一的状态:

ini 复制代码
function User(name) {
  this.name = name;
  User.totalCount++; // 每次创建实例时自增计数器
}
// 静态属性:统计用户总数
User.totalCount = 0;
// 静态属性:记录当前在线用户(全局状态)
User.onlineUsers = [];

// 静态方法:添加在线用户
User.addOnlineUser = function(user) {
  this.onlineUsers.push(user);
};

// 使用
const u1 = new User("张三");
const u2 = new User("李四");
console.log(User.totalCount); // 2(共创建2个实例)
User.addOnlineUser(u1);
console.log(User.onlineUsers.length); // 1

优势:全局状态由构造函数统一管理,避免散落在全局变量中,便于维护。

3. 命名空间与枚举值

通过静态属性创建命名空间,或定义枚举值(固定选项集合):

javascript 复制代码
// 订单状态枚举(静态属性集合)
function Order() {}
Order.STATUS = {
  PENDING: "pending", // 待支付
  PAID: "paid",       // 已支付
  SHIPPED: "shipped", // 已发货
  DELIVERED: "delivered" // 已送达
};

// 使用:通过类名访问枚举值,避免硬编码
const order = new Order();
if (order.status === Order.STATUS.PAID) {
  console.log("订单已支付");
}

优势:枚举值集中管理,修改时只需改一处,提高代码可维护性。

二、原型属性(Prototype Properties)

概念与本质

原型属性是挂载在函数的 prototype 对象 上的属性。JavaScript 中每个函数都有 prototype 属性(原型对象),通过该函数创建的所有实例会共享这个原型对象,因此原型属性是所有实例的 "公共资源"。

定义方式

通过 函数名.prototype.属性名 定义:

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

// 定义原型属性(包括原型方法)
Person.prototype.species = "人类"; // 原型常量:所有实例共享的物种
Person.prototype.greet = function() { // 原型方法:公共行为
  return `你好,我是${this.name}`;
};

访问规则

  1. 通过实例访问:实例会先查找自身属性,若不存在则沿原型链查找原型属性;
  2. 所有实例共享:修改原型属性会影响所有未被 "覆盖" 的实例;
  3. 可通过原型对象直接访问函数名.prototype.属性名 可直接操作原型属性。

典型应用场景及详细举例

1. 实例公共方法(节省内存的核心场景)

当多个实例需要共享同一方法时,将方法定义在原型上可避免重复创建(每个实例无需单独存储方法):

javascript 复制代码
// 错误示例:每个实例都创建独立的方法(浪费内存)
function Student(name) {
  this.name = name;
  this.study = function() { // 每个实例都会复制这个函数
    return `${this.name}在学习`;
  };
}

// 正确示例:原型方法(所有实例共享)
function Student(name) {
  this.name = name;
}
Student.prototype.study = function() { // 仅在原型上定义一次
  return `${this.name}在学习`;
};

// 使用
const s1 = new Student("小明");
const s2 = new Student("小红");
console.log(s1.study === s2.study); // true(共享同一方法)

优势:对于创建 1000 个实例的场景,原型方法仅占用 1 份内存,而实例方法会占用 1000 份内存。

2. 默认属性与基础配置

为所有实例提供默认属性值,实例可根据需要覆盖:

javascript 复制代码
function Button(text) {
  this.text = text; // 实例独有的文本
}
// 原型属性:所有按钮的默认样式
Button.prototype.style = {
  width: "100px",
  height: "40px",
  color: "black"
};
// 原型方法:渲染按钮
Button.prototype.render = function() {
  return `<button style="width:${this.style.width};height:${this.style.height}">${this.text}</button>`;
};

// 使用
const btn1 = new Button("确定");
const btn2 = new Button("取消");

//  btn1 覆盖默认样式(不影响其他实例)
btn1.style.width = "150px";

console.log(btn1.render()); // 宽度150px的按钮
console.log(btn2.render()); // 宽度100px的默认按钮

优势:默认配置集中管理,实例可灵活定制,兼顾复用与个性化。

3. 原型链继承与方法扩展

通过修改原型对象实现继承,或为内置对象扩展方法:

javascript 复制代码
// 为数组扩展原型方法(谨慎使用,避免污染内置对象)
Array.prototype.sum = function() {
  return this.reduce((total, item) => total + item, 0);
};

// 使用
const arr = [1, 2, 3];
console.log(arr.sum()); // 6(所有数组实例均可调用)

注意:扩展内置对象原型需谨慎,可能与未来的 JavaScript 标准方法冲突。

三、实例属性(Instance Properties)

概念与本质

实例属性是绑定到具体实例 的属性,通过构造函数内部的 this 关键字定义,或在实例创建后动态添加。每个实例的属性独立存储,互不干扰,是实例独有的状态或数据。

定义方式

  1. 构造函数内部通过 this.属性名 初始化:

    ini 复制代码
    function Product(name, price) {
      this.name = name; // 实例属性:名称
      this.price = price; // 实例属性:价格
    }
  2. 实例创建后动态添加:

    ini 复制代码
    const product1 = new Product("手机", 5999);
    product1.stock = 100; // 动态添加实例属性:库存

访问规则

  1. 只能通过实例访问,无法通过函数或原型对象直接访问;
  2. 实例间相互独立:修改一个实例的属性不会影响其他实例;
  3. 优先级最高:若实例属性与原型属性同名,访问时优先使用实例属性。

典型应用场景及详细举例

1. 实例独有数据(核心场景)

存储每个实例独有的信息,如用户的个人信息、商品的具体参数:

javascript

运行

ini 复制代码
function User(id, name, email) {
  this.id = id;       // 实例独有的ID
  this.name = name;   // 实例独有的姓名
  this.email = email; // 实例独有的邮箱
}

// 使用
const user1 = new User(1, "张三", "zhangsan@example.com");
const user2 = new User(2, "李四", "lisi@example.com");

console.log(user1.name); // "张三"
console.log(user2.email); // "lisi@example.com"
user1.email = "new@example.com"; // 修改user1的邮箱,不影响user2

核心价值:每个实例的个性化数据必须通过实例属性存储,确保数据隔离。

2. 实例状态管理

记录实例的动态状态(如是否激活、当前进度等):

ini 复制代码
function Task(title) {
  this.title = title;
  this.status = "todo"; // 实例状态:待办(初始值)
  this.progress = 0;    // 实例状态:进度(0-100)
}

// 实例方法:更新进度(依赖实例状态)
Task.prototype.updateProgress = function(percent) {
  this.progress = percent;
  if (percent === 100) {
    this.status = "done"; // 更新状态为"已完成"
  }
};

// 使用
const task1 = new Task("完成报告");
task1.updateProgress(50);
console.log(task1.progress); // 50(task1的进度)
console.log(task1.status); // "todo"(仍未完成)

const task2 = new Task("整理文件");
task2.updateProgress(100);
console.log(task2.status); // "done"(task2的状态独立)

优势:状态与实例绑定,多个实例的状态变化互不干扰,逻辑清晰。

3. 动态临时属性

为特定实例添加临时数据(如缓存、临时标记):

ini 复制代码
function Article(id, content) {
  this.id = id;
  this.content = content;
}

// 使用
const article = new Article(1, "这是一篇长文...");

// 动态添加临时缓存属性(仅当前实例有效)
article.tempCache = {
  summary: "文章摘要(临时计算结果)",
  keywords: ["前端", "JavaScript"]
};

// 处理完成后清除临时属性
delete article.tempCache;

优势:临时数据无需定义在构造函数中,避免污染其他实例,灵活应对临时需求。

四、三类属性的核心区别对比

属性类型 定义位置 访问方式 共享性 内存占用
静态属性 函数本身(函数名.xxx 仅函数可访问 函数级共享 全局唯一,占用一份内存
原型属性 函数的 prototype 实例访问(原型链查找) 所有实例共享 原型对象中存储,一份内存
实例属性 构造函数内 this 仅实例可访问 实例独立不共享 每个实例单独存储

简单测试掌握与否(有兴趣的可以瞅一眼)

1

以下代码执行后,控制台输出的三个结果分别是什么?

ini 复制代码
function Player(name) {
  this.name = name; // 实例属性
}

// 静态属性
Player.game = "足球";
// 原型属性
Player.prototype.position = "前锋";

const player1 = new Player("梅西");
const player2 = new Player("C罗");

player1.position = "中场";
Player.game = "篮球";

console.log(player1.name);
console.log(player2.position);
console.log(Player.game);

2

以下代码中,car1.colorCar.colorcar2.getColor() 的输出结果分别是什么?

ini 复制代码
function Car(brand) {
  this.brand = brand;
  this.color = "白色"; // 实例属性
}

// 静态属性
Car.color = "黑色";
// 原型方法
Car.prototype.getColor = function() {
  return `颜色:${this.color}`;
};

const car1 = new Car("特斯拉");
const car2 = new Car("比亚迪");

car1.color = "红色";
Car.prototype.color = "蓝色";

console.log(car1.color);
console.log(Car.color);
console.log(car2.getColor());

3

执行以下代码后,user1.levelUser.leveluser2.getLevel()User.prototype.level 分别输出什么?

ini 复制代码
function User() {
  this.level = 1; // 实例属性
}

// 静态属性
User.level = 10;
// 原型属性
User.prototype.level = 5;

// 原型方法
User.prototype.getLevel = function() {
  return this.level + User.level;
};

const user1 = new User();
const user2 = new User();

user1.level = 3;
User.prototype.level = 7;
User.level = 20;

console.log(user1.level);
console.log(User.level);
console.log(user2.getLevel());
console.log(User.prototype.level);
相关推荐
x***13392 小时前
【MyBatisPlus】MyBatisPlus介绍与使用
android·前端·后端
z***75154 小时前
【Springboot3+vue3】从零到一搭建Springboot3+vue3前后端分离项目之后端环境搭建
android·前端·后端
fruge5 小时前
仿写优秀组件:还原 Element Plus 的 Dialog 弹窗核心逻辑
前端
an86950015 小时前
vue新建项目
前端·javascript·vue.js
w***95496 小时前
SQL美化器:sql-beautify安装与配置完全指南
android·前端·后端
therese_100866 小时前
面试试试试试试题-答
面试
顾安r7 小时前
11.22 脚本打包APP 排错指南
linux·服务器·开发语言·前端·flask
万邦科技Lafite7 小时前
1688图片搜索商品API接口(item_search_img)使用指南
java·前端·数据库·开放api·电商开放平台
czhc11400756637 小时前
c# 1121 构造方法
java·javascript·c#
yinuo8 小时前
网页也懂黑夜与白天:系统主题自动切换
前端