You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将包含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字符串),同时保留原对象的键名来维持顺序。

具体实现步骤

  1. 存储阶段:转换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);
                }
            });
        }
    });
    
  2. 读取阶段:重构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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 18:01:24