前端存储、表单异步提交与 this 指向完全指南

前端开发中的存储、表单与 this 指向深度解析

从 localStorage 到 redis,从 form 默认提交到 fetch/AJAX,从 this 的四种绑定到箭头函数的 Lexical Scope ------ 一篇打通前端核心知识体系


📌 前言

在日常前端开发中,我们经常面临几个经典问题:

  • 数据应该存到哪里?浏览器缓存、localStorage、还是云端?
  • 表单提交为什么页面会刷新?如何用 JS 优雅地发送请求?
  • this 到底指向谁?为什么箭头函数可以"继承"外层的 this?

这篇文章将围绕存储技术表单提交演进this 指向规则 三条主线展开,并结合一个完整的 LocalStorage Todo 示例,帮助你建立系统的认知框架。


一、存储的"金字塔":从前端到云端

现代应用的数据存储是一个分层架构,每一层都有其适用场景。

1.1 浏览器缓存(HTTP Cache)

打开之前访问过的页面时加载极快,这是因为浏览器利用了强缓存或协商缓存。

  • 强缓存:直接从磁盘/内存读取,不请求服务器。
  • 协商缓存:向服务器验证资源是否过期。

1.2 本地存储(LocalStorage / SessionStorage)

适合存储少量键值对数据(通常 5-10MB),数据持久化在浏览器中。

javascript 复制代码
// 存储
localStorage.setItem('username', '张三');

// 读取
const name = localStorage.getItem('username');

// 删除
localStorage.removeItem('username');

// 清空
localStorage.clear();

适用场景:用户偏好设置、草稿箱、购物车临时数据。


1.3 云端存储(Cloud Storage)

将文件(JSON、CSV、Excel、图片等)上传至 OSS(对象存储服务),适合需要跨设备、跨用户共享的数据。

优势:容量大、可扩展、支持 CDN 加速。


1.4 Redis:高性能 KV 缓存

Redis 是内存数据库,读写速度极快(微秒级)。常用于缓存数据库查询结果,减轻 MySQL 等持久化存储的压力。

javascript 复制代码
// 伪代码示例
// 第一次访问:从 MySQL 读取文章列表,存入 Redis
const articles = await mysql.query('SELECT * FROM articles');
await redis.set('articles:list', JSON.stringify(articles));

// 后续访问:直接从 Redis 读取
const cached = await redis.get('articles:list');

1.5 LLM 向量存储(Embedding)

在 AI 应用中,文本通过 embedding 模型转化为向量,存入向量数据库(如 Pinecone、Milvus),用于语义检索。

与 Redis 的区别:Redis 存精确键值,向量数据库存高维向量,支持相似度搜索。


二、表单提交的演进:从同步到异步

2.1 传统表单提交(刷新页面)

html 复制代码
<form action="/submit" method="POST">
  <input name="username" />
  <button type="submit">提交</button>
</form>

点击 submit 后,浏览器会向 action 地址发送请求,并刷新页面。用户体验较差,因为页面会闪烁,状态丢失。

2.2 AJAX / Fetch:无刷新提交

使用 JavaScript 拦截表单提交事件,通过 fetchXMLHttpRequest 异步发送数据。

javascript 复制代码
document.querySelector('form').addEventListener('submit', async (e) => {
  e.preventDefault(); // 阻止默认刷新行为
  
  const formData = new FormData(e.target);
  const response = await fetch('/api/submit', {
    method: 'POST',
    body: formData,
  });
  const result = await response.json();
  console.log('提交成功', result);
});

优势:页面无刷新,用户体验流畅,可局部更新 UI。


三、this 的完整指南(含 6 种场景)

this 是函数运行时动态绑定的执行上下文,与函数声明位置无关,只与调用方式有关

3.1 普通函数调用(非严格模式)

javascript 复制代码
function show() {
  console.log(this); // 非严格模式 → window,严格模式 → undefined
}
show();

启用严格模式避免污染全局

javascript 复制代码
'use strict';
function show() {
  console.log(this); // undefined
}
show();

3.2 作为对象的方法调用

javascript 复制代码
const obj = {
  name: '金小轩',
  say() {
    console.log(this.name); // '金小轩'
  }
};
obj.say();

3.3 引用式赋值丢失 this

javascript 复制代码
const obj = {
  name: '金小轩',
  say() {
    console.log(this.name);
  }
};

const fn = obj.say; // 引用式赋值
fn(); // undefined(非严格模式下 this 指向 window)

解决方案 :使用 bind 绑定 this。


3.4 作为构造函数调用

javascript 复制代码
function Person(name) {
  this.name = name;
}
const p = new Person('明明');
console.log(p.name); // '明明'

构造函数中的 this 指向新创建的实例对象,并且会自动返回该对象(除非显式返回其他对象)。


3.5 事件处理函数

javascript 复制代码
document.querySelector('.link').addEventListener('click', function(e) {
  console.log(this); // 指向 .link 元素
  e.preventDefault();
});

展示事件冒泡与捕获阶段,this 在事件处理中的指向


3.6 手动指定 this(call / apply / bind)

方法 执行时机 传参方式
call 立即执行 逐个传入
apply 立即执行 数组形式传入
bind 返回新函数,不立即执行 逐个传入(柯里化)
javascript 复制代码
const obj = { name: '明明' };

function speak(a, b) {
  console.log(this.name, a, b);
}

speak.call(obj, 'hello', 'world');  // 明明 hello world
speak.apply(obj, ['hello', 'world']); // 明明 hello world

const bound = speak.bind(obj, 'hello');
bound('world'); // 明明 hello world

3.7 箭头函数:没有自己的 this

箭头函数不绑定 this,它会继承外层作用域的 this(Lexical Scope)。

javascript 复制代码
const obj = {
  name: '姆巴佩',
  say() {
    console.log(this.name); // '姆巴佩'(this 指向 obj)
    setTimeout(() => {
      console.log(this.name); // '姆巴佩'(箭头函数继承外层 this)
    }, 1000);
  }
};
obj.say();

如果使用普通函数:

javascript 复制代码
setTimeout(function() {
  console.log(this.name); // undefined 或 window.name
}, 1000);

结论 :在需要保留外层 this 的场景(如定时器、事件回调)中,箭头函数是更优雅的选择。


四、综合实战:LocalStorage Todo 应用

下面是一个完整的"Tapas 清单"示例,结合了 LocalStorage 存储表单异步提交事件处理与 this 绑定

4.1 HTML 结构

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>LocalStorage Tapas</title>
  <link rel="stylesheet" href="./common.css">
</head>
<body>
  <div class="wrapper">
    <h2>🥘 LOCAL TAPAS</h2>
    <p>你的 Tapas 清单</p>
    <ul class="plates">
      <li>Loading Tapas...</li>
    </ul>
    <form class="add-items">
      <input type="text" name="item" placeholder="添加一道 Tapas" required>
      <input type="submit" value="添加">
    </form>
    <a href="https://www.baidu.com" class="link">去百度</a>
  </div>
  <script src="./common.js?v=4"></script>
</body>
</html>

4.2 JavaScript 逻辑(common.js)

javascript 复制代码
'use strict';

// 1. DOM 引用
const oForm = document.querySelector('.add-items');
const oList = document.querySelector('.plates');

// 2. 从 localStorage 读取数据
function loadItems() {
  const data = localStorage.getItem('tapas');
  return data ? JSON.parse(data) : [];
}

// 3. 渲染列表
function render(items) {
  oList.innerHTML = items.map((item, index) => `
    <li>
      <input type="checkbox" data-index="${index}" ${item.done ? 'checked' : ''}>
      <span>${item.text}</span>
    </li>
  `).join('');
}

// 4. 存储到 localStorage
function saveItems(items) {
  localStorage.setItem('tapas', JSON.stringify(items));
}

// 5. 添加新项(使用 bind 绑定 this)
function AddItem(e) {
  e.preventDefault(); // 阻止表单默认提交
  const input = this.querySelector('input[name="item"]');
  const text = input.value.trim();
  if (!text) return;

  const items = loadItems();
  items.push({ text, done: false });
  saveItems(items);
  render(items);
  input.value = ''; // 清空输入框
}

// 6. 绑定事件(使用 bind 手动指定 this 为表单元素)
oForm.addEventListener('submit', AddItem.bind(oForm));

// 7. 处理 checkbox 变更(事件委托)
oList.addEventListener('click', function(e) {
  const checkbox = e.target.closest('input[type="checkbox"]');
  if (!checkbox) return;
  
  const index = parseInt(checkbox.dataset.index);
  const items = loadItems();
  items[index].done = checkbox.checked;
  saveItems(items);
  render(items);
});

// 8. 初始化
render(loadItems());

// 9. 阻止百度链接跳转(演示 e.preventDefault)
document.querySelector('.link').addEventListener('click', function(e) {
  e.preventDefault();
  console.log('链接被拦截,不跳转');
});

五、关于版本号与缓存更新

在引入外部资源时,我们常使用 ?v=版本号 来强制浏览器加载最新文件,避免缓存旧版本。

html 复制代码
<script src="./common.js?v=4"></script>

每次修改 JS 后,递增版本号即可让用户获取最新代码,无需手动清除缓存。


六、总结

知识点 核心要点
存储体系 浏览器缓存(快)→ LocalStorage(持久)→ Redis(极速 KV)→ 向量数据库(AI)
表单提交 默认提交会刷新页面 → 使用 e.preventDefault() + fetch 实现无刷新
this 绑定 调用方式决定 this:方法调用 → 对象;事件 → 元素;构造函数 → 实例;call/apply/bind 手动指定
箭头函数 没有自己的 this,继承外层作用域,适合回调与定时器
LocalStorage 存储键值对,容量约 5-10MB,适合小型持久化数据

💡 思考题 :如果要在多个浏览器标签页之间同步数据,localStorage 能做到吗?如果不能,有什么替代方案?(提示:可以搜索 storage 事件或 BroadcastChannel


希望这篇文章能帮你建立起前端存储、表单交互与 this 指向的完整认知体系。如果觉得有用,欢迎点赞、收藏、转发!

相关推荐
cidy_981 小时前
Skills 技能库- JulyCode
前端
Kel1 小时前
管道、IPC、Socket:你以为的三种技术,其实是同一件事
javascript·设计模式·node.js
Csvn1 小时前
TypeScript 泛型约束的 3 层演进:从 `extends` 到条件类型到重载推导
前端
小四的小六1 小时前
AI产品翻车实录:模型总结幻觉的三大类型与检测方案
前端·openai·ai编程
心中有国也有家1 小时前
AtomGit Flutter 鸿蒙客户端: AnimatedScale 与 AnimatedContainer 联合实战
android·javascript·flutter·华为·harmonyos
狂师1 小时前
端开发必装 Skill 清单:6 款 AI 技能,让你的开发效率原地起飞
前端·程序员·开源
3630458411 小时前
KlineChartQuant Tooltip 高频交互 200FPS 性能优化完整复盘
前端·架构
嘉泰姆半导体官方1 小时前
CXAR41337C 双通道数字音频功放 | 2×20W | EQ+DRC | I²S | MCLK-Less - 嘉泰姆电子
前端·javascript·数据库
PedroQue991 小时前
uni-router v2.0.0:四大功能拆分为插件,按需加载
前端·uni-app