用 "一家三口" 的家庭关系来类比,一眼就能懂:
- 静态属性:家里的 "户口本"
- 归属:属于整个家(对应构造函数 / 类),不是某个人的。
- 用法:只有提 "我们家" 时才用(比如 "我们家户口本地址是 XX"),你不能说 "这是我的户口本"。
- 原型属性:家里的 "公共物品"(比如冰箱、电视)
- 归属:放在家里客厅,全家共用(对应原型对象)。
- 用法:爸妈、你都能用来存东西 / 看电视(所有实例共享),但你不能把冰箱搬去自己房间说成 "我的"(实例不能独占)。
- 实例属性:你的 "私人用品"(比如你的手机、日记本)
- 归属:只属于你个人(对应单个实例)。
- 用法:只有你能改手机壁纸、写日记(实例独自控制),爸妈的手机(其他实例)和你没关系。
一、静态属性(Static Properties)
概念与本质
静态属性是直接挂载在函数本身的属性,与函数的实例无关,也不会出现在原型链中。从本质上讲,JavaScript 中函数是特殊的对象,静态属性就是这个 "函数对象" 自身的属性,类似于其他语言中 "类的静态成员"。
定义方式
通过 函数名.属性名 直接定义,无需依赖实例:
ini
// 构造函数
function Tool() {}
// 定义静态属性(包括静态方法)
Tool.version = "2.1.0"; // 静态常量:版本号
Tool.count = 0; // 静态变量:工具调用次数
Tool.format = function(str) { // 静态方法:格式化字符串
return str.toUpperCase();
};
访问规则
- 只能通过函数本身访问,无法通过实例访问;
- 不参与继承,子类无法继承父类的静态属性(除非手动复制);
- 所有访问共享同一属性,修改静态属性会影响所有通过函数访问的地方。
典型应用场景及详细举例
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}`;
};
访问规则
- 通过实例访问:实例会先查找自身属性,若不存在则沿原型链查找原型属性;
- 所有实例共享:修改原型属性会影响所有未被 "覆盖" 的实例;
- 可通过原型对象直接访问 :
函数名.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 关键字定义,或在实例创建后动态添加。每个实例的属性独立存储,互不干扰,是实例独有的状态或数据。
定义方式
-
构造函数内部通过
this.属性名初始化:inifunction Product(name, price) { this.name = name; // 实例属性:名称 this.price = price; // 实例属性:价格 } -
实例创建后动态添加:
iniconst product1 = new Product("手机", 5999); product1.stock = 100; // 动态添加实例属性:库存
访问规则
- 只能通过实例访问,无法通过函数或原型对象直接访问;
- 实例间相互独立:修改一个实例的属性不会影响其他实例;
- 优先级最高:若实例属性与原型属性同名,访问时优先使用实例属性。
典型应用场景及详细举例
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.color、Car.color、car2.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.level、User.level、user2.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);