如何将包含File对象的表单数据存储至localStorage
问题描述
我想把表单数据存储到localStorage,让用户刷新页面后还能看到数据。普通表单字段的存储没问题,但表单里包含带图片File对象的对象,找不到合适的存储方式。
我的数据对象示例:
{file_9wyh8: File, file_cwjg0: File}
从localStorage取出后文件内容丢失,变成:
{file_9wyh8: {…}, file_cwjg0: {…}}
我需要完整存储data对象,因为文件顺序和用户在页面看到的一致,所以没法直接用FileReader。
我的代码如下:
// 当用户刷新页面等离开页面时触发unload事件 $(window).on('unload', function() { // 把表单字段值保存到localStorage $(':file, :checkbox, select, textarea, input').each(function() { // 因为是JS添加的input而非select,只获取input值并存储选中项 if ( !$(this).hasClass('add-item__select') && !$(this).is(':checkbox') && !$(this).is(':file') ) { // 保存字段值到localStorage localStorage.setItem($(this).attr('name'), $(this).val()); } else if ( $(this).is(':checked') && !$(this).is(':file') ) { // 只保存选中的checkbox名称 localStorage.setItem($(this).attr('name'), $(this).val()); } else if ( $(this).is(':file') ) { dataToString = JSON.stringify(data); localStorage.setItem('file', dataToString); } }) }); // 页面刷新后从localStorage获取值 $(window).on('load', function() { // 从localStorage获取表单字段值 $(':file, :checkbox, select, textarea, input').each(function() { // 如果input有这个类则不保存值 if ( !$(this).hasClass('add-item__select') && ( !$(this).is(':checkbox') && !$(this).is(':file') ) ) { // 获取字段值 fieldValue = localStorage.getItem($(this).attr('name')); // 如果fieldValue不为空则显示字段值 if (fieldValue !== 'null') $(this).val(fieldValue); $(this).addClass('black-text'); // 仅处理checkbox } else if ( $(this).is(':checkbox') && !$(this).is(':file') ) { // 获取字段值 fieldValue = localStorage.getItem($(this).attr('name')); // 如果checkbox名称和localStorage中保存的一致则设为选中 if ( fieldValue === $(this).val() ) $(this).prop('checked', true); // 每次移除localStorage中的checkbox值 - 因为要处理选中状态变化 localStorage.removeItem(fieldValue); } else if ( $(this).is(':file') ) { // 从localStorage获取整个data对象 var dataStringFromLocalStorage = localStorage.getItem('file'); var dataFromLocalStorage = JSON.parse(dataStringFromLocalStorage); console.log(dataFromLocalStorage); $.each(dataFromLocalStorage, function(index, value) { /* // 创建图片临时URL生成缩略图,将带临时src的img添加到thumbnail__wraper div $('.thumbnail__wrapper').append('<div class="thumbnail" id="file_'+ mark +'"><img class="imageThumb" src="' + url + '" title="' + upload.name + '"/><br/><a class="remove">Zmazať</a></div>'); $('.add-item__add-photo-btn-area').find('p').hide(); // 设置第一张图片为主图 - 用标签标记 var firstImg = $('.thumbnail__wrapper').children().first(); $('<p class="main-img">Hlavná fotka</p>').appendTo(firstImg); // 修改照片数量状态 $('#photo-num').text('Fotky ' + count + '/50'); */ }) } })
我尝试过「如何将输入的File对象序列化为JSON」的解决方案,但没效果。
解决方案
核心原理
File对象无法直接被JSON.stringify序列化,因为它包含二进制数据和浏览器内部引用。要保留文件顺序和完整数据,需要把每个File对象转换为可序列化的格式(比如Base64字符串),同时保留原对象的键名来维持顺序。
具体实现步骤
存储阶段:转换File对象并保存
遍历data对象,把每个File对象用FileReader转换为Base64,同时记录文件的元信息(名称、类型、大小),最后将包含这些信息的对象序列化为JSON存入localStorage:$(window).on('unload', function() { // ... 其他字段存储逻辑保持不变 ... else if ($(this).is(':file')) { const serializedFiles = {}; const fileKeys = Object.keys(data); let completedCount = 0; // 遍历data中的每个File对象 fileKeys.forEach(key => { const file = data[key]; if (file instanceof File) { const reader = new FileReader(); reader.onload = function(e) { serializedFiles[key] = { name: file.name, type: file.type, size: file.size, data: e.target.result, // Base64字符串 lastModified: file.lastModified // 记录原始修改时间 }; completedCount++; // 所有文件转换完成后存入localStorage if (completedCount === fileKeys.length) { localStorage.setItem('file', JSON.stringify(serializedFiles)); } }; reader.readAsDataURL(file); } }); } });读取阶段:重构File对象并恢复顺序
从localStorage取出序列化后的对象,遍历每个键值对,用Base64数据和元信息重构File对象,同时保持原有的键名顺序:$(window).on('load', function() { // ... 其他字段读取逻辑保持不变 ... else if ($(this).is(':file')) { const dataStringFromLocalStorage = localStorage.getItem('file'); if (!dataStringFromLocalStorage) return; const serializedFiles = JSON.parse(dataStringFromLocalStorage); const restoredData = {}; // 按原键名顺序重构File对象 Object.keys(serializedFiles).forEach(key => { const fileData = serializedFiles[key]; // 将Base64转换为Blob,再转为File对象 const byteString = atob(fileData.data.split(',')[1]); const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } const blob = new Blob([ab], { type: fileData.type }); restoredData[key] = new File([blob], fileData.name, { type: fileData.type, lastModified: fileData.lastModified // 还原原始修改时间 }); }); // 现在restoredData就是和原data结构、顺序一致的对象 console.log(restoredData); // 恢复缩略图逻辑 $.each(restoredData, function(index, file) { const url = URL.createObjectURL(file); $('.thumbnail__wrapper').append(`<div class="thumbnail" id="file_${index}"><img class="imageThumb" src="${url}" title="${file.name}"/><br/><a class="remove">Zmazať</a></div>`); $('.add-item__add-photo-btn-area').find('p').hide(); // 设置第一张图为主图 const firstImg = $('.thumbnail__wrapper').children().first(); if (!firstImg.find('.main-img').length) { $('<p class="main-img">Hlavná fotka</p>').appendTo(firstImg); } // 更新照片数量 const count = Object.keys(restoredData).length; $('#photo-num').text(`Fotky ${count}/50`); }); } });
注意事项
- Base64格式会让文件体积增加约30%,如果存储大量图片可能触发
localStorage的5MB容量限制,此时建议改用IndexedDB,它支持更大存储容量且可直接存储Blob/File对象。 - 若不需要完整的
File对象,仅需展示缩略图,可直接存储Base64字符串,读取时直接用于图片src,无需重构File对象,能简化逻辑。
内容的提问来源于stack exchange,提问作者Dump
相关产品推荐
相关产品推荐

