Storage 单例封装

用单例模式封装 localStorage 的 setItem 和 getItem 方法

问题

实现一个 Storage 类,要求:

  1. 该对象为单例,多次实例化返回同一个对象
  2. 封装 localStorage 的 setItem(key, value)getItem(key) 方法

解答

方式一:闭包实现

const Storage = (function() {
  let instance = null;
  
  function StorageClass() {
    // 防止通过 new StorageClass() 直接调用
    if (instance) {
      return instance;
    }
    instance = this;
  }
  
  StorageClass.prototype.setItem = function(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  };
  
  StorageClass.prototype.getItem = function(key) {
    const value = localStorage.getItem(key);
    return value ? JSON.parse(value) : null;
  };
  
  return StorageClass;
})();

// 测试
const s1 = new Storage();
const s2 = new Storage();

console.log(s1 === s2); // true

s1.setItem('name', 'Tom');
console.log(s2.getItem('name')); // 'Tom'

方式二:ES6 Class 静态方法

class Storage {
  static instance = null;
  
  constructor() {
    // 如果已存在实例,直接返回
    if (Storage.instance) {
      return Storage.instance;
    }
    Storage.instance = this;
  }
  
  setItem(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  }
  
  getItem(key) {
    const value = localStorage.getItem(key);
    return value ? JSON.parse(value) : null;
  }
  
  // 提供静态方法获取实例
  static getInstance() {
    if (!Storage.instance) {
      Storage.instance = new Storage();
    }
    return Storage.instance;
  }
}

// 测试
const s1 = Storage.getInstance();
const s2 = Storage.getInstance();
const s3 = new Storage();

console.log(s1 === s2); // true
console.log(s1 === s3); // true

方式三:Proxy 实现

class StorageBase {
  setItem(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  }
  
  getItem(key) {
    const value = localStorage.getItem(key);
    return value ? JSON.parse(value) : null;
  }
}

// 用 Proxy 拦截构造函数
const Storage = new Proxy(StorageBase, {
  instance: null,
  construct(target, args) {
    if (!this.instance) {
      this.instance = new target(...args);
    }
    return this.instance;
  }
});

// 测试
const s1 = new Storage();
const s2 = new Storage();

console.log(s1 === s2); // true

完整封装版本

class Storage {
  static instance = null;
  
  constructor() {
    if (Storage.instance) {
      return Storage.instance;
    }
    Storage.instance = this;
  }
  
  static getInstance() {
    if (!Storage.instance) {
      Storage.instance = new Storage();
    }
    return Storage.instance;
  }
  
  setItem(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  }
  
  getItem(key) {
    const value = localStorage.getItem(key);
    try {
      return JSON.parse(value);
    } catch {
      return value;
    }
  }
  
  removeItem(key) {
    localStorage.removeItem(key);
  }
  
  clear() {
    localStorage.clear();
  }
  
  // 获取所有键
  keys() {
    return Object.keys(localStorage);
  }
  
  // 获取存储大小
  size() {
    return localStorage.length;
  }
}

// 使用
const storage = Storage.getInstance();
storage.setItem('user', { name: 'Tom', age: 18 });
console.log(storage.getItem('user')); // { name: 'Tom', age: 18 }

关键点

  • 单例核心:用静态属性保存实例,构造时判断是否已存在
  • JSON 序列化:localStorage 只能存字符串,需要 JSON.stringify/parse 处理
  • 三种实现:闭包、Class 静态属性、Proxy,原理相同
  • getInstance vs new:两种方式都应返回同一实例
  • 错误处理getItem 时需要 try-catch 处理非 JSON 字符串