面试题
实现Storage,使得该对象为单例,基于localStorage进行封装。实现方法 setItem(key,value) 和 getItem(key)。
分析题目
- 实现
Storage:实现Storage类 - 使得该对象为单例:使用单例模式
- 基于
localStorage进行封装,实现方法setItem(key,value)和getItem(key):浏览器本地存储
所需技能点
JavaScript 中的类
什么是类?
- 类是一种模板(蓝图) ,用于创建对象。
- 它是 ES6 引入的语法糖,让面向对象编程更清晰。
为什么JS需要类?
在ES6前,JS并没有class关键字,所谓的“类”,其实是通过 构造函数 + prototype 原型 实现的,例如:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log("Hello, " + this.name);
};
const p = new Person("Tom");
p.sayHello(); // Hello, Tom
JavaScript是一门基于原型的语言,每个对象都有一个原型__proto__,它指向另一个对象,从而实现继承和共享方法。所以不需要像JAVA那样有复杂的类结构,直接操作对象就可以。
所以有人说:“在 JS 中,根本不需要学面向对象。”
那为什么要在JavaScript中设计类?
因为ES6后要推动JavaScript进入企业级、大型项目的开发领域,为了让更多的传统面向对象开发者更容易上手JavaScript,像 Java/C++ 程序员难以理解 JS 的原型机制:
- Java 和 C++ 是典型的基于类的语言。
- 它们的继承是通过类与类之间的关系定义的。
- 而 JS 的继承是通过对象之间的原型链实现的,这种思维方式完全不同。
因此很多从 Java/C++ 转过来的开发者会觉得 JS 的原型机制“奇怪”。
为了兼容性和可读性,ES6 引入了class
但是
class是语法糖,底层依旧是原型机制。
总结一句话:
JavaScript 的核心是原型式编程,class 只是语法糖,目的是让更多开发者容易接受这门语言,走向企业级开发。真正理解 JS,要回到原型的本质。
基本结构
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log('Hello, ' + this.name);
}
}
constructor:构造函数,初始化对象属性。- 方法写在类中,会被所有实例共享。
创建一个实例
const p1 = new Person('Tom');
pi.sayHello();// Hello,Tom
静态方法 static
static info() {
console.log('This is a Person class');
}
用 类名.方法名 调用,不通过实例调用。
继承
class Student extends Person {
constructor(name, grade) {
super(name);
this.grade = grade;
}
}
- 使用
extends实现继承。 super()调用父类构造函数。
私有字段
class Counter {
#count = 0;
increment() {
this.#count++;
}
}
使用 # 定义私有属性或方法,外部不能访问。
单例模式 Singleton Pattern
1. 定义:
- 单例模式是一种创建型设计模式。
- 它确保一个类在整个应用程序生命周期中只能创建一个实例,并提供一个全局访问点来访问这个实例。
2. 核心思想:
- 类内部私有化构造函数,防止外部直接实例化。
- 类内部维护一个静态私有实例。
- 提供一个静态方法(通常命名为
getInstance)用于获取该实例。
本地存储 localStorage
localStorage 是浏览器提供的一种本地存储数据的机制,用于在用户浏览器中持久化保存数据。
主要特点:
| 特点 | 说明 |
|---|---|
| 持久化存储 | 数据不会因为关闭页面或浏览器而消失,除非手动清除。 |
| 同源限制 | 只能访问和操作相同域名、端口、协议下存储的数据。 |
| 字符串存储 | 只能保存字符串,非字符串数据(如对象)需要先用JSON.stringify()转换。注意:取内容时要用 JSON.parse()转回JSON格式 |
| 容量较大 | 一般支持 5MB 左右的数据存储(比 cookie 大很多)。 |
| 同步操作 | 所有读写操作都是同步的(不涉及回调或 Promise)。 |
常用方法
// 存储数据
localStorage.setItem('key', 'value');
// 读取数据
const value = localStorage.getItem('key');
// 删除数据
localStorage.removeItem('key');
// 清空所有数据
localStorage.clear();
示例:
// 存储
localStorage.setItem('username', 'Tom');
localStorage.setItem('user', JSON.stringify({ name: 'Tom', age: 20 }));
// 获取
const username = localStorage.getItem('username'); // "Tom"
const user = JSON.parse(localStorage.getItem('user')); // { name: 'Tom', age: 20 }
// 删除
localStorage.removeItem('username');
注意事项:
- 不适合存储敏感信息(如密码),因为它是明文存储。
- 页面刷新后数据仍在。
- 多个标签页打开同一个网站时,数据共享。
实战解析
class Storage {
// 静态属性
static instance;
// static instance 未初始化为 null 是因为在 JavaScript 中,
// 类的静态属性若仅声明而不初始化,其默认值就是 undefined
// 对于单例模式而言, undefined 和 null 都能用于判断实例是否已创建。
constructor() {
console.log(this);
}
// 静态方法
static getInstance() {
// 静态属性 在类上直接访问的方法 不需要实例化
if(!Storage.instance) { // 单例模式核心
Storage.instance = new Storage(); // 只有第一次才new
}
return Storage.instance;
}
// 共享方法
getItem(key) {
return localStorage.getItem(key);
}
setItem(key, value) {
localStorage.setItem(key, value);
}
}
const storage1 = Storage.getInstance();
const storage2 = Storage.getInstance();
// 创建的就是同一个实例
console.log(storage1 === storage2); // false -> true
为什么用单例模式封装 localStorage?
1. ❗因为 localStorage 是浏览器全局对象:
- 所有对 localStorage 的操作都是全局生效的。
- 无论多少个 Storage 实例,底层操作的都是同一个 localStorage。
2. 使用单例可以:
- 避免重复创建实例,提高性能。
- 统一访问接口,便于维护。
- 确保行为一致性:所有访问都通过同一个对象进行,避免混乱。
结尾
本文介绍了单例模式的基本概念和实现方式,并结合 localStorage 封装了一个实用的 Storage 工具类。通过单例模式,我们实现了统一入口、节省资源、便于管理的本地存储方案。
JavaScript 虽然是基于原型的语言,但合理使用类和设计模式,可以提升代码结构的清晰度与可维护性。理解这些基础原理,是写出高质量前端代码的关键一步。