백엔드 개발에서 스토리지는 작업의 일반적인 부분입니다. 애플리케이션 데이터는 데이터베이스에 저장되고, 파일은 객체 스토리지에 저장되고, 임시 데이터는 캐시에 저장됩니다. 모든 유형의 데이터를 저장할 수 있는 가능성은 무한해 보입니다. 하지만 데이터 저장은 백엔드에만 국한되지 않고, 프런트엔드(브라우저)에도 데이터 저장을 위한 다양한 옵션이 있습니다. 이 저장 방법을 활용하고, 사용자 기본 설정을 저장하고, 여러 세션과 심지어 다른 컴퓨터에서도 애플리케이션 상태를 유지함으로써 애플리케이션의 성능을 향상시킬 수 있습니다.
이 기사에서는 브라우저에 데이터를 저장하는 다양한 가능성을 살펴보겠습니다. 각 접근 방식의 장단점을 파악하기 위해 세 가지 사용 사례를 다룰 것입니다. 결국에는 사용 사례에 가장 적합한 스토리지를 결정할 수 있습니다.
시작해 보세요!
localStorage
는 브라우저에서 가장 인기 있는 저장소 옵션 중 하나이며 많은 개발자가 가장 먼저 선택합니다. 데이터는 세션 전반에 걸쳐 저장되며 서버와 공유되지 않으며 동일한 프로토콜 및 도메인 아래의 모든 페이지에서 사용할 수 있습니다. 저장용량 한도는 ~5MB입니다. localStorage
是浏览器中最受欢迎的存储选项之一,也是许多开发人员的首选。数据跨会话存储,从不与服务器共享,并且可用于同一协议和域下的所有页面。存储空间限制为〜5MB。
令人惊讶的是,谷歌Chrome团队并不建议使用这个选项,因为它屏蔽了主线程,而且web workers和service workers无法访问。他们推出了一个实验:KV Storage,作为一个更好的版本,但这只是一个试验,似乎还没有任何进展。
localStorage API
可作为 window.localStorage
使用,并且只能保存UTF-16字符串。在将数据保存到 localStorage
之前,我们必须确保将其转换为字符串。主要的三个功能是:
setItem('key',
'value')
getItem('key')
removeItem('key')
它们都是同步的,因此使用起来很简单,但是它们会阻塞主线程。
值得一提的是,localStorage
有一个称为 sessionStorage
的双胞胎。唯一的区别是,存储在 sessionStorage
中的数据将仅持续当前会话,但API相同。
这个太简单了,相信大家都用过。
IndexedDB是浏览器中的现代存储解决方案。它可以存储大量的结构化数据,甚至文件和Blob。和每一个数据库一样,IndexedDB对数据进行索引,以便高效地运行查询。使用IndexedDB比较复杂,我们必须创建一个数据库,表,并使用事务。
与 localStorage
相比,IndexedDB需要更多代码。在例子中,我使用了原生API与Promise包装器,但我强烈建议使用第三方库来帮助你。我推荐的是localForage,因为它使用了同样的 localStorage
API,但实现方式是逐步增强的,也就是说,如果你的浏览器支持IndexedDB,就会使用它;如果不支持,就会退回到 localStorage
。
让我们来编写代码,前往我们的用户偏好示例吧!
<input> <label>Dark theme</label><br>
let db; function toggle(on) { if (on) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } async function save(on) { const tx = db.transaction('preferences', 'readwrite'); const store = tx.objectStore('preferences'); store.put({key: 'darkTheme', value: on}); return tx.complete; } async function load() { const tx = db.transaction('preferences', 'readonly'); const store = tx.objectStore('preferences'); const data = await store.get('darkTheme'); return data && data.value; } async function onChange(checkbox) { const value = checkbox.checked; toggle(value); await save(value); } function openDatabase() { return idb.openDB('my-db', 1, { upgrade(db) { db.createObjectStore('preferences', {keyPath: 'key'}); }, }); } openDatabase() .then((_db) => { db = _db; return load(); }) .then((initialValue) => { toggle(initialValue); document.querySelector('#darkTheme').checked = initialValue; });
效果
idb
是我们使用的Promise包装器,而不是使用基于事件的低级API。首先要注意的是,对数据库的每次访问都是异步的,这意味着我们不会阻塞主线程,与 localStorage
相比,这是一个主要优势。
我们需要打开与数据库的连接,以便在整个应用程序中都可以使用它进行读写。我们给数据库起一个名字 my-db
,一个模式版本 1
,以及一个更新函数,以在版本之间应用更改,这与数据库迁移非常相似。我们的数据库架构很简单:只有一个object store preferences
。object store 等效于SQL表,要写入或读取数据库,必须使用事务,这是使用IndexedDB的乏味部分。看一下演示中新的 save
和 load
功能。
毫无疑问,IndexedDB具有更多的开销,并且与 localStorage
相比,学习曲线更陡峭。对于键值的情况,使用 localStorage
或第三方库可能更有意义,它们将帮助我们提高效率。
<p>loading...</p>
let db; async function loadPokemons() { const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=10'); const data = await res.json(); return data.results; } function removeLoading() { const elem = document.querySelector('#loading'); if (elem) { elem.parentNode.removeChild(elem); } } function appendPokemon(pokemon) { const node = document.createElement('li'); const textnode = document.createTextNode(pokemon.name); node.appendChild(textnode); document.querySelector('#list').appendChild(node); } function clearList() { const list = document.querySelector('#list'); while (list.firstChild) { list.removeChild(list.lastChild); } } function saveToCache(pokemons) { const tx = db.transaction('pokemons', 'readwrite'); const store = tx.objectStore('pokemons'); pokemons.forEach(pokemon => store.put(pokemon)); return tx.complete; } function loadFromCache() { const tx = db.transaction('pokemons', 'readonly'); const store = tx.objectStore('pokemons'); return store.getAll(); } function openDatabase() { return idb.openDB('my-db2', 1, { upgrade(db) { db.createObjectStore('pokemons', {keyPath: 'name'}); }, }); } openDatabase() .then((_db) => { db = _db; return loadFromCache(); }) .then((cachedPokemons) => { if (cachedPokemons) { removeLoading(); cachedPokemons.forEach(appendPokemon); console.log('loaded from cache!'); } return loadPokemons(); }) .then((pokemons) => { removeLoading(); saveToCache(pokemons); clearList(); pokemons.forEach(appendPokemon); console.log('loaded from network!'); });
效果
你可以在此数据库中存储数百兆甚至更多。您可以将所有Pokémon存储在IndexedDB中,并使其脱机甚至建立索引!这绝对是用于存储应用程序数据的一种选择。
我跳过了第三个示例的实现,因为与 localStorage
localStorage API
는 window.localStorage
로 사용할 수 있으며 UTF-16 문자열만 저장할 수 있습니다. 데이터를 localStorage
에 저장하기 전에 문자열로 변환되었는지 확인해야 합니다. 주요 세 가지 함수는 다음과 같습니다: 🎜setItem('key',
'value')
getItem( ' key')
removeItem('key')
localStorage
에는 sessionStorage
라는 쌍이 있다는 점을 언급할 가치가 있습니다. 유일한 차이점은 sessionStorage
에 저장된 데이터가 현재 세션 동안만 유지되지만 API는 동일하다는 것입니다. 🎜🎜정말 간단해서 다들 사용해 보셨을 거라 믿습니다. 🎜🎜IndexedDB API🎜🎜IndexedDB는 브라우저의 최신 저장소 솔루션입니다. 파일과 Blob까지 포함하여 대량의 구조화된 데이터를 저장할 수 있습니다. 모든 데이터베이스와 마찬가지로 IndexedDB는 쿼리를 효율적으로 실행하기 위해 데이터를 인덱싱합니다. IndexedDB를 사용하는 것은 더 복잡합니다. 데이터베이스, 테이블을 만들고 트랜잭션을 사용해야 합니다. 🎜🎜localStorage
에 비해 IndexedDB에는 더 많은 코드가 필요합니다. 예제에서는 Promise 래퍼와 함께 기본 API를 사용했지만 도움이 되도록 타사 라이브러리를 사용하는 것이 좋습니다. 제가 추천하는 것은 localForage입니다. 동일한 localStorage
API를 사용하지만 구현이 점차 향상되기 때문입니다. 즉, 브라우저가 IndexedDB를 지원하면 지원하지 않으면 사용됩니다. 사용되면 localStorage
로 대체됩니다. 🎜🎜코드를 작성하고 사용자 기본 설정 예시를 살펴보겠습니다! 🎜<input> <label>Dark theme</label>rrree🎜Effect🎜🎜🎜🎜
idb
는 하위 수준 이벤트 기반 API를 사용하는 대신 사용하는 Promise 래퍼입니다. 가장 먼저 주목해야 할 점은 데이터베이스에 대한 모든 액세스가 비동기식이라는 점입니다. 즉, 메인 스레드를 차단하지 않는다는 의미이며 이는 localStorage
에 비해 큰 이점입니다. 🎜🎜애플리케이션 전체에서 읽기 및 쓰기에 사용할 수 있도록 데이터베이스에 대한 연결을 열어야 합니다. 데이터베이스에 이름 my-db
, 스키마 버전 1
, 데이터베이스 마이그레이션과 마찬가지로 버전 간 변경 사항을 적용하는 업데이트 기능을 제공합니다. 우리의 데이터베이스 아키텍처는 매우 간단합니다. 개체 저장소 기본 설정
이 하나만 있습니다. 객체 저장소는 SQL 테이블과 동일하며, 데이터베이스에 쓰거나 데이터베이스에서 읽으려면 트랜잭션을 사용해야 합니다. 이는 IndexedDB 사용의 지루한 부분입니다. 데모에서 새로운 저장
및 로드
기능을 살펴보세요. 🎜🎜IndexedDB가 localStorage
에 비해 오버헤드가 더 많고 학습 곡선이 더 가파르다는 것은 의심의 여지가 없습니다. 키-값의 경우 localStorage
또는 타사 라이브러리를 사용하는 것이 더 합리적일 수 있으며 이는 우리의 효율성을 높이는 데 도움이 됩니다. 🎜function getCookie(cname) { const name = cname + '='; const decoded = decodeURIComponent(document.cookie); const split = decoded.split(';'); const relevantCookie = split.find((cookie) => cookie.indexOf(`${cname}=`) === 0); if (relevantCookie) { return relevantCookie.split('=')[1]; } return null; } function toggle(on) { if (on) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } function save(on) { document.cookie = `dark_theme=${on.toString()}; max-age=31536000; SameSite=None; Secure`; } function load() { return getCookie('dark_theme') === 'true'; } function onChange(checkbox) { const value = checkbox.checked; toggle(value); save(value); } const initialValue = load(); toggle(initialValue); document.querySelector('#darkTheme').checked = initialValue;
key1=value1;key2=value2;key3=value3🎜Effect🎜🎜🎜🎜이 데이터베이스에는 수백 메가바이트 이상을 저장할 수 있습니다. 모든 포켓몬을 IndexedDB에 저장하고, 오프라인으로 전환하고, 심지어 색인을 생성할 수도 있습니다! 이는 확실히 애플리케이션 데이터를 저장하기 위한 옵션입니다. 🎜🎜이 경우 IndexedDB는
localStorage
와 비교하여 아무런 차이가 없기 때문에 세 번째 예의 구현을 건너뛰었습니다. IndexedDB를 사용하더라도 사용자는 선택한 페이지를 다른 사람과 공유하거나 나중에 사용하기 위해 북마크에 추가하지 않습니다. 그 중 어느 것도 이 사용 사례에 적합하지 않습니다. 🎜使用cookies是一种独特的存储方式,这是唯一的与服务器共享的存储方式。Cookies作为每次请求的一部分被发送。它可以是当用户浏览我们的应用程序中的页面或当用户发送Ajax请求时。这样我们就可以在客户端和服务器之间建立一个共享状态,也可以在不同子域的多个应用程序之间共享状态。本文中介绍的其他存储选项无法实现。需要注意的是:每个请求都会发送 cookie
,这意味着我们必须保持 cookie 较小,以保持适当的请求大小。
Cookies的最常见用途是身份验证,这不在本文的讨论范围之内。就像 localStorage
一样,cookie
只能存储字符串。这些cookie被连接成一个以分号分隔的字符串,并在请求的cookie头中发送。你可以为每个cookie设置很多属性,比如过期、允许的域名、允许的页面等等。
在例子中,我展示了如何通过客户端来操作cookie,但也可以在你的服务器端应用程序中改变它们。
<input> <label>Dark theme</label>
function getCookie(cname) { const name = cname + '='; const decoded = decodeURIComponent(document.cookie); const split = decoded.split(';'); const relevantCookie = split.find((cookie) => cookie.indexOf(`${cname}=`) === 0); if (relevantCookie) { return relevantCookie.split('=')[1]; } return null; } function toggle(on) { if (on) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } function save(on) { document.cookie = `dark_theme=${on.toString()}; max-age=31536000; SameSite=None; Secure`; } function load() { return getCookie('dark_theme') === 'true'; } function onChange(checkbox) { const value = checkbox.checked; toggle(value); save(value); } const initialValue = load(); toggle(initialValue); document.querySelector('#darkTheme').checked = initialValue;
效果还是跟前面一样
将用户的喜好保存在cookie中,如果服务器能够以某种方式利用它,就可以很好地满足用户的需求。例如,在主题用例中,服务器可以交付相关的CSS文件,并减少潜在的捆绑大小(在我们进行服务器端渲染的情况下)。另一个用例可能是在没有数据库的情况下,在多个子域应用之间共享这些偏好。
用JavaScript读写cookie并不像您想象的那么简单。要保存新的cookie,您需要设置 document.cookie
——在上面的示例中查看 save
函数。我设置了 dark_theme
cookie,并给它添加了一个 max-age
属性,以确保它在关闭标签时不会过期。另外,我添加 SameSite
和 Secure
属性。这些都是必要的,因为CodePen使用iframe来运行这些例子,但在大多数情况下你并不需要它们。读取一个cookie需要解析cookie字符串。
Cookie字符串如下所示:
key1=value1;key2=value2;key3=value3
因此,首先,我们必须用分号分隔字符串。现在,我们有一个形式为 key1=value1
的Cookie数组,所以我们需要在数组中找到正确的元素。最后,我们将等号分开并获得新数组中的最后一个元素。有点繁琐,但一旦你实现了 getCookie
函数(或从我的例子中复制它:P),你就可以忘记它。
将应用程序数据保存在cookie中可能是个坏主意!它将大大增加请求的大小,并降低应用程序性能。此外,服务器无法从这些信息中获益,因为它是数据库中已有信息的陈旧版本。如果你使用cookies,请确保它们很小。
分页示例也不适合cookie,就像 localStorage
和 IndexedDB
一样。当前页面是我们想要与他人共享的临时状态,这些方法都无法实现它。
URL本身并不是存储设备,但它是创建可共享状态的好方法。实际上,这意味着将查询参数添加到当前URL中,这些参数可用于重新创建当前状态。最好的例子是搜索查询和过滤器。如果我们在CSS-Tricks上搜索术语flexbox,则URL将更新为https://css-tricks.com/?s=fle...。看看我们使用URL后,分享搜索查询有多简单?另一个好处是,你只需点击刷新按钮,就可以获得更新的查询结果,甚至可以将其收藏。
我们只能在URL中保存字符串,它的最大长度是有限的,所以我们没有那么多的空间。我们将不得不保持我们的状态小,没有人喜欢又长又吓人的网址。
同样,CodePen使用iframe运行示例,因此您看不到URL实际更改。不用担心,因为所有的碎片都在那里,所以你可以在任何你想要的地方使用它。
<input> <label>Dark theme</label>
function toggle(on) { if (on) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } } function save(on) { const params = new URLSearchParams(window.location.search); params.set('dark_theme', on.toString()); history.pushState(null, null, `?${params.toString()}`); } function load() { const params = new URLSearchParams(window.location.search); return params.get('dark_theme') === 'true'; } function onChange(checkbox) { const value = checkbox.checked; toggle(value); save(value); } const initialValue = load(); toggle(initialValue); document.querySelector('#darkTheme').checked = initialValue;
效果还是一样
我们可以通过 window.location.search
访问查询字符串,幸运的是,可以使用 URLSearchParams
类对其进行解析,无需再应用任何复杂的字符串解析。当我们想读取当前值时,可以使用 get
函数,当我们想写时,可以使用 set
。仅设置值是不够的,我们还需要更新URL。这可以使用 history.pushState
或 history.replaceState
来完成,取决于我们想要完成的行为。
我不建议将用户的偏好保存在URL中,因为我们必须将这个状态添加到用户访问的每一个URL中,而且我们无法保证;例如,如果用户点击了谷歌搜索的链接。
就像Cookie一样,由于空间太小,我们无法在URL中保存应用程序数据。而且即使我们真的设法存储它,网址也会很长,而且不吸引人点击。可能看起来像是钓鱼攻击的一种。
<p>Select page:</p> <p> <button>0</button> <button>1</button> <button>3</button> <button>4</button> <button>5</button> </p>
async function loadPokemons(page) { const res = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=10&offset=${page * 10}`); const data = await res.json(); return data.results; } function appendPokemon(pokemon) { const node = document.createElement('li'); const textnode = document.createTextNode(pokemon.name); node.appendChild(textnode); document.querySelector('#list').appendChild(node); } function clearList() { const list = document.querySelector('#list'); while (list.firstChild) { list.removeChild(list.lastChild); } } function savePage(page) { const params = new URLSearchParams(window.location.search); params.set('page', page.toString()); history.pushState(null, null, `?${params.toString()}`); } function loadPage() { const params = new URLSearchParams(window.location.search); if (params.has('page')) { return parseInt(params.get('page')); } return 0; } async function updatePage(page) { clearList(); savePage(page); const pokemons = await loadPokemons(page); pokemons.forEach(appendPokemon); } const page = loadPage(); updatePage(page);
效果
就像我们的分页例子一样,临时应用状态是最适合URL查询字符串的。同样,你无法看到URL的变化,但每次点击一个页面时,URL都会以 ?page=x
查询参数更新。当网页加载时,它会查找这个查询参数,并相应地获取正确的页面。现在,我们可以把这个网址分享给我们的朋友,让他们可以享受我们最喜欢的神奇宝贝。
Cache API是网络级的存储,它用于缓存网络请求及其响应。Cache API非常适合service worker,service worker可以拦截每一个网络请求,使用 Cache API 它可以轻松地缓存这两个请求。service worker也可以将现有的缓存项作为网络响应返回,而不是从服务器上获取。这样,您可以减少网络负载时间,并使你的应用程序即使处于脱机状态也能正常工作。最初,它是为service worker创建的,但在现代浏览器中,Cache API也可以在窗口、iframe和worker上下文中使用。这是一个非常强大的API,可以极大地改善应用的用户体验。
就像IndexedDB一样,Cache API的存储不受限制,您可以存储数百兆字节,如果需要甚至可以存储更多。API是异步的,所以它不会阻塞你的主线程,而且它可以通过全局属性 caches
来访问。
如果你建立一个浏览器扩展,你有另一个选择来存储你的数据,我在进行扩展程序daily.dev时发现了它。如果你使用Mozilla的polyfill,它可以通过 chrome.storage
或 browser.storage
获得。确保在你的清单中申请一个存储权限以获得访问权。
有两种类型的存储选项:local和sync。local存储是不言而喻的,它的意思是不共享,保存在本地。sync存储是作为谷歌账户的一部分同步的,你在任何地方用同一个账户安装扩展,这个存储都会被同步。两者都有相同的API,所以如果需要的话,来回切换超级容易。它是异步存储,因此不会像 localStorage
这样阻塞主线程。不幸的是,我不能为这个存储选项创建一个演示,因为它需要一个浏览器扩展,但它的使用非常简单,几乎和 localStorage
一样。有关确切实现的更多信息,请参阅Chrome文档。
浏览器有许多选项可用于存储数据。根据Chrome团队的建议,我们的首选存储应该是IndexedDB,它是异步存储,有足够的空间来存储我们想要的任何东西。不鼓励使用 localStorage
,但它比 IndexedDB
更易于使用。Cookies是与服务器共享客户端状态的一种好方法,但通常用于身份验证。
如果你想创建具有可共享状态的页面,如搜索页面,请使用URL的查询字符串来存储这些信息。最后,如果你建立一个扩展,一定要阅读关于 chrome.storage
。
更多编程相关知识,请访问:编程视频!!
위 내용은 브라우저의 다양한 저장 유형에 대해 알아보세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!