检测 localStorage 何时已满的唯一方法是在调用时使用 QuotaExceededError(localStorage.setItem), 最初的“MVP”想法是编写一个循环,逐字节增加 localStorage 大小直到它已满
例如:
try {
for (let i = 1; i < Infinity; i++) {
localStorage.setItem("__test__", "x".repeat(i));
}
} catch (err) {}
但是,循环写入并不是一个实际的选择。它的效率非常低,很容易使浏览器崩溃。
因此,受这个 StackOverflow 答案的启发,我选择先存储较大的字符串,然后存储较小的字符串,直到 localStorage 填满:
/*
将localStorage填满其最大容量。
首先,我们用100个字符的块填充localStorage,直到它
命中配额超出异常。
然后,我们再次使用1个字符的块。
最后,我们再一次逐个字符,以确保localStorage是完全充满。
要清除localStorage,你可以使用localStorage.clear()。不过,以防万一你想清理仅存储在这个函数的数据(可能是因为你想要在运行之前保存在localStorage中的东西),
我们返回一个方便的清理函数。
@return一个清理函数来删除我们存储的数据。
* /
function fillLocalStorage(): () => void {
function storeIncreasinglyBigItem(
key: string,
charactersIncrement: number
): void {
const MAX_ITERATIONS = 10_000; // Safeguard against OOM & crashes
for (let i = 1; i <= MAX_ITERATIONS; i++) {
localStorage.setItem(key, "x".repeat(i * charactersIncrement));
}
}
try {
storeIncreasinglyBigItem("__1", 100_000);
} catch (_err1) {
try {
storeIncreasinglyBigItem("__2", 1_000);
} catch (_err2) {
try {
storeIncreasinglyBigItem("__3", 1);
} catch (_err3) {
// 存储空间现在已经完全满了
}
}
}
return function cleanup() {
localStorage.removeItem("__1");
localStorage.removeItem("__2");
localStorage.removeItem("__3");
};
}
请注意,此解决方案有一个很小的边缘情况,fillLocalStorage
即没有 100% 填充 localStorage:由于浏览器确定 localStorage 大小的值和键,在极少数情况下,报告存储已满的时候可能还剩下大约 3 个字节的大小。
上面的代码片段肯定是可以优化的,有一个更优雅的方法:首先找到最高顺序的位,然后按递减顺序测试每个位,直到localStorage满了。
这种方法比我上面建议的方法性能更好,因为它涉及更少的分配和迭代,它使用单个localStorage项,还解决了我刚才提到的边缘情况。
function fillLocalStorage(): () => void {
const key = "__filling_localstorage__";
let max = 1; // This holds the highest order bit.
let data = "x"; // 我们要存储在localStorage中的字符串。
// Find the highest order bit.
try {
while (true) {
data = data + data;
localStorage.setItem(key, data);
max <= 1;
}
} catch {}
// 通过增加相反的字符串长度来填充剩余的空间
for (let bit = max > 1; bit > 0; bit >= 1) {
try {
localStorage.setItem(key, data.substring(0, max , bit));
max = bit;
} catch {
// 存储空间现在完全满了
}
}
// 清理
return function cleanup() {
localStorage.removeItem(key);
};
}