javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS读写本地文件

JavaScript如何在前端代码中读、写本地文件

作者:dangfulin

在前端JavaScript中,由于安全考虑浏览器不允许直接操作文件系统,但浏览器提供了有限的文件操作能力,这篇文章主要介绍了JavaScript如何在前端代码中读、写本地文件的相关资料,需要的朋友可以参考下

一,在前端代码JavaScript中读写文件的限制与处理

在前端 JavaScript 中,浏览器环境没有直接提供操作文件系统的能力。也就是说,你不能像在 Node.js 环境中那样,使用 fs 模块来删除或创建文件。这是因为浏览器出于安全性的考虑,不允许网页随意访问用户的文件系统,以防止潜在的恶意行为。

然而,浏览器确实提供了一些有限的文件操作能力,主要是通过以下几种方式:

1,文件上传和下载

- 文件上传: 可以通过 <input type="file"> 元素让用户选择文件,然后通过 JavaScript 读取文件内容。
- 文件下载: 可以通过创建 Blob 对象和使用 a 标签的 download 属性来触发文件下载。

2,File API

2,File System Access API

File System Access API 是现代浏览器(主要是在 Chromium 内核的浏览器)引入的一种新 API,它允许网页直接与用户的文件系统交互,创建、读取、写入和删除文件。这是当前浏览器提供的最接近文件系统操作的能力。

二,读文件

(一)最简单方式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>读文件</title>
</head>
<body>
<input type="file" id="fileInput">
<button id="processButton">Process and Download</button>

<script>
    document.getElementById('processButton').addEventListener('click', function () {
        const fileInput = document.getElementById('fileInput');
        const file = fileInput.files[0];

        if (file) {
            const reader = new FileReader();

            reader.onload = function (e) {
                // 读取文件内容
                let content = e.target.result;
                console.log(content);
            };

            // 开始读取文件
            reader.readAsText(file);
        } else {
            alert('Please select a file first!');
        }
    });
</script>
</body>
</html>

HTML 部分:

JavaScript 部分:

(二)读取大文件

在上面的代码中,文件的读取是通过 FileReader 的 readAsText() 方法完成的。这个方法确实会一次性将整个文件内容加载到内存中。对于小型文件来说这没有问题,但如果文件非常大,可能会导致内存占用过高,影响性能,甚至导致页面崩溃。

1,分片读取

使用 FileReader 的 readAsArrayBuffer() 方法,然后使用 Blob 的 slice() 方法来分块读取文件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>读文件</title>
</head>
<body>
<input type="file" id="fileInput">
<button id="processButton">Process</button>

<script>
    document.getElementById('processButton').addEventListener('click', function () {
        const fileInput = document.getElementById('fileInput');
        const file = fileInput.files[0];

        if (file) {
            const CHUNK_SIZE = 1024 * 1024; // 1MB 分块大小
            let offset = 0;

            // 递归读取文件的函数
            function readNextChunk() {
                // 检查是否已经读取到文件末尾
                if (offset >= file.size) {
                    console.log("File processing complete.");
                    return;
                }

                // 读取当前块
                const chunk = file.slice(offset, offset + CHUNK_SIZE);
                const reader = new FileReader();

                reader.onload = function (e) {
                    // 处理当前块的数据
                    let content = e.target.result;
                    console.log(`Processing chunk from ${offset} to ${offset + CHUNK_SIZE}`);
                    console.log(content); // 此处可以进行更复杂的处理

                    // 更新偏移量,并读取下一块
                    offset += CHUNK_SIZE;
                    readNextChunk();
                };

                reader.onerror = function (e) {
                    console.error("Error reading file chunk:", e);
                };

                // 开始读取当前块
                reader.readAsText(chunk);
            }

            // 开始读取第一个块
            readNextChunk();
        } else {
            alert('Please select a file first!');
        }
    });
</script>
</body>
</html>

2,使用 stream

使用 File API 的 stream() 方法(在较新的浏览器中支持),这允许你以流的方式读取文件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Stream Read File</title>
</head>
<body>
<input type="file" id="fileInput">
<button id="processButton">Process File Stream</button>

<script>
    document.getElementById('processButton').addEventListener('click', async function () {
        const fileInput = document.getElementById('fileInput');
        const file = fileInput.files[0];

        if (file) {
            const stream = file.stream();
            const reader = stream.getReader();

            // 读取流数据
            async function read() {
                let result;
                while (!(result = await reader.read()).done) {
                    const chunk = result.value; // Uint8Array
                    const textChunk = new TextDecoder().decode(chunk); // 转换为文本

                    console.log(textChunk); // 处理数据块
                }

                console.log("File processing complete.");
            }

            // 开始读取
            read().catch(error => console.error("Stream read error:", error));
        } else {
            alert('Please select a file first!');
        }
    });
</script>
</body>
</html>、

(三)前端代码读取本地文件需要注意的地方

1,安全性问题

问题: 浏览器出于安全考虑,限制了对用户文件系统的直接访问,以防止恶意脚本未经用户同意访问敏感文件或数据。前端代码只能通过用户明确选择的方式访问文件,比如通过 <input type="file"> 或 File System Access API

处理方法:

async function selectFile() {
  const [fileHandle] = await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  console.log(file.name);
}

2,隐私问题

问题: 用户文件可能包含敏感信息,如个人数据、财务信息等。前端读取文件时,必须确保用户的隐私不被泄露或滥用。

处理方法:

3,性能问题

题: 在前端处理大文件时,可能会导致浏览器内存占用过高或卡顿,影响用户体验。

处理方法:

4,兼容性问题

并非所有浏览器都支持最新的 File System Access API 或某些高级文件处理功能。需要确保代码在多个浏览器中都能正常工作,或者提供合理的回退机制。

问题: 并非所有浏览器都支持最新的 File System Access API 或某些高级文件处理功能。需要确保代码在多个浏览器中都能正常工作,或者提供合理的回退机制。

处理方法:

5,用户体验问题

问题: 前端文件操作通常涉及用户选择文件、上传文件、下载文件等操作,良好的用户体验可以提升用户的满意度。

处理方法:

6,权限管理问题

问题: 文件操作可能涉及权限问题,例如通过 File System Access API 访问文件系统时,权限可能会被撤销。

处理方法:

7,文件类型和内容验证

问题: 用户可能会选择错误类型的文件,或上传包含恶意内容的文件。

处理方法:

8,文件大小限制

问题: 处理非常大的文件可能会导致内存溢出或性能问题。

处理方法:

这里举个例子🌰:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件处理</title>
</head>
<body>
<input type="file" id="fileInput" accept=".pdf">
<button id="processButton">Process</button>
<progress id="progressBar" value="0" max="100" style="display: none;"></progress>
<p id="percentage">0%</p>
<p id="statusMessage"></p>

<script>
    document.getElementById('processButton').addEventListener('click', function () {
        const fileInput = document.getElementById('fileInput');
        const file = fileInput.files[0];
        const MAX_SIZE = 300 * 1024 * 1024; // 最大300MB
        const progressBar = document.getElementById('progressBar');
        const percentageDisplay = document.getElementById('percentage');
        const statusMessage = document.getElementById('statusMessage');

        // 重置状态
        progressBar.style.display = 'none';
        progressBar.value = 0;
        percentageDisplay.textContent = '0%';
        statusMessage.textContent = '';

        // 检查是否选择了文件
        if (!file) {
            alert('Please select a file first!');
            return;
        }

        // 检查文件大小
        if (file.size > MAX_SIZE) {
            alert('File is too large! Please select a file under 300MB.');
            return;
        }

        console.log(`Selected file: ${file.name}, ${file.size} bytes, ${file.type}`);
        // // 检查文件类型(假设只接受文本文件)
        // if (file.type !== "text/plain") {
        //     alert('Invalid file type! Please select a .pdf file.');
        //     return;
        // }

        const CHUNK_SIZE = 1024 * 1024; // 1MB 分块大小
        let offset = 0;

        // 显示进度条
        progressBar.style.display = 'block';

        // 递归读取文件的函数
        function readNextChunk() {
            // 检查是否已经读取到文件末尾
            if (offset >= file.size) {
                statusMessage.textContent = "File processing complete.";
                progressBar.style.display = 'none';
                return;
            }

            // 读取当前块
            const chunk = file.slice(offset, offset + CHUNK_SIZE);
            const reader = new FileReader();

            reader.onload = function (e) {
                // 处理当前块的数据
                let content = e.target.result;
                console.log(`Processing chunk from ${offset} to ${offset + CHUNK_SIZE}`);
                console.log(content); // 此处可以进行更复杂的处理

                // 更新偏移量,并读取下一块
                offset += CHUNK_SIZE;

                // 计算百分比并更新显示
                const percentage = Math.min((offset / file.size) * 100, 100).toFixed(2);
                progressBar.value = percentage;
                percentageDisplay.textContent = `${percentage}%`;

                readNextChunk();
            };

            reader.onerror = function (e) {
                console.error("Error reading file chunk:", e);
                statusMessage.textContent = "Error reading file!";
                progressBar.style.display = 'none';
            };

            // 开始读取当前块
            reader.readAsText(chunk);
        }

        // 开始读取第一个块
        readNextChunk();
    });
</script>
</body>
</html>

三,写文件

在前端代码中将信息写入本地文件是一个常见的需求,但由于浏览器的安全限制,这个过程并不像在后端那样直接。我们有几种方法可以实现这个功能,每种方法都有其优缺点。

(一)最常用的方法

最常用的方法时使用 Blob 和 URL.createObjectURL()

function saveToFile(content, filename) {
    const blob = new Blob([content], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    
    const link = document.createElement('a');
    link.href = url;
    link.download = filename;
    
    // 这行是必要的,用于在浏览器中触发下载
    document.body.appendChild(link);
    
    link.click();
    
    // 清理并移除链接
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
}

// 使用示例
saveToFile('Hello, World!', 'example.txt');

优点:

缺点:

(二)使用 File System Access API

这是一个较新的API,提供了更强大的文件操作能力,但目前只有部分现代浏览器支持。

async function writeToFile(content) {
    if ('showSaveFilePicker' in window) {
        try {
            const handle = await window.showSaveFilePicker({
                types: [{
                    description: 'Text file',
                    accept: { 'text/plain': ['.txt'] },
                }],
            });
            const writable = await handle.createWritable();
            await writable.write(content);
            await writable.close();
            console.log('File saved successfully');
        } catch (err) {
            console.error('Error saving file:', err);
        }
    } else {
        console.error('File System Access API not supported');
    }
}

// 使用示例
writeToFile('Hello, World!');

优点:

缺点:

(三)使用 LocalStorage 或 IndexedDB

这些方法不是直接将数据保存为文件,而是将数据存储在浏览器的本地存储中。

对于 LocalStorage:

function saveToLocalStorage(key, value) {
    localStorage.setItem(key, value);
}

// 使用示例
saveToLocalStorage('myData', 'Hello, World!');

对于 IndexedDB,代码会相对复杂一些,这里是一个简化的例子:

let db;
const dbName = "MyDatabase";

const request = indexedDB.open(dbName, 1);

request.onerror = function(event) {
    console.error("Database error: " + event.target.error);
};

request.onsuccess = function(event) {
    db = event.target.result;
    console.log("Database opened successfully");
};

request.onupgradeneeded = function(event) {
    db = event.target.result;
    const objectStore = db.createObjectStore("files", { keyPath: "id" });
};

function saveToIndexedDB(id, content) {
    const transaction = db.transaction(["files"], "readwrite");
    const objectStore = transaction.objectStore("files");
    const request = objectStore.put({ id: id, content: content });
    
    request.onerror = function(event) {
        console.error("Error saving data: " + event.target.error);
    };
    
    request.onsuccess = function(event) {
        console.log("Data saved successfully");
    };
}

// 使用示例(需要在数据库打开后调用)
saveToIndexedDB('file1', 'Hello, World!');

优点:

缺点:

一个完整的示例🌰:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件保存示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }

        textarea {
            width: 100%;
            height: 100px;
            margin-bottom: 10px;
        }

        button {
            margin-right: 10px;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
<h1>文件保存示例</h1>
<textarea id="content" placeholder="在此输入要保存的内容"></textarea>
<div>
    <button onclick="saveUsingBlob()">使用Blob下载</button>
    <button onclick="saveUsingFileSystem()">使用File System API保存</button>
    <button onclick="saveToLocalStorage()">保存到LocalStorage</button>
    <button onclick="saveToIndexedDB()">保存到IndexedDB</button>
</div>
<div id="status"></div>

<script>
    // 使用Blob和URL.createObjectURL()方法
    function saveUsingBlob() {
        const content = document.getElementById('content').value;
        const blob = new Blob([content], {type: 'text/plain'});
        const url = URL.createObjectURL(blob);

        const link = document.createElement('a');
        link.href = url;
        link.download = 'example.txt';

        document.body.appendChild(link);
        link.click();

        document.body.removeChild(link);
        URL.revokeObjectURL(url);

        updateStatus('文件已准备下载');
    }

    // 使用File System Access API
    async function saveUsingFileSystem() {
        const content = document.getElementById('content').value;
        if ('showSaveFilePicker' in window) {
            try {
                const handle = await window.showSaveFilePicker({
                    types: [{
                        description: 'Text file',
                        accept: {'text/plain': ['.txt']},
                    }],
                });
                const writable = await handle.createWritable();
                await writable.write(content);
                await writable.close();
                updateStatus('文件保存成功');
            } catch (err) {
                updateStatus('保存文件时出错: ' + err);
            }
        } else {
            updateStatus('此浏览器不支持File System Access API');
        }
    }

    // 使用LocalStorage
    function saveToLocalStorage() {
        const content = document.getElementById('content').value;
        try {
            localStorage.setItem('savedContent', content);
            updateStatus('内容已保存到LocalStorage');
        } catch (err) {
            updateStatus('保存到LocalStorage时出错: ' + err);
        }
    }

    // 使用IndexedDB
    let db;
    const dbName = "MyDatabase";
    const dbVersion = 1;

    const request = indexedDB.open(dbName, dbVersion);

    request.onerror = function (event) {
        updateStatus("打开数据库时出错: " + event.target.error);
    };

    request.onsuccess = function (event) {
        db = event.target.result;
        updateStatus("数据库已成功打开");
    };

    request.onupgradeneeded = function (event) {
        db = event.target.result;
        const objectStore = db.createObjectStore("files", {keyPath: "id"});
        updateStatus("数据库已创建");
    };

    function saveToIndexedDB() {
        const content = document.getElementById('content').value;
        if (!db) {
            updateStatus("数据库未准备好");
            return;
        }
        const transaction = db.transaction(["files"], "readwrite");
        const objectStore = transaction.objectStore("files");
        const request = objectStore.put({id: "file1", content: content});

        request.onerror = function (event) {
            updateStatus("保存到IndexedDB时出错: " + event.target.error);
        };

        request.onsuccess = function (event) {
            updateStatus("内容已成功保存到IndexedDB");
        };
    }

    function updateStatus(message) {
        document.getElementById('status').textContent = message;
    }
</script>
</body>
</html>

总结 

到此这篇关于JavaScript如何在前端代码中读、写本地文件的文章就介绍到这了,更多相关JS读写本地文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文