深入理解 JavaScript 中的 this 与数据存储的奥秘

深入理解 JavaScript 中的 this 与数据存储的奥秘

从浏览器缓存到函数调用,一次搞懂前端核心概念

作为一名前端开发者,我们每天都要面对两个绕不开的话题:数据存储this 指向。它们看似毫不相关,却在日常开发中频繁交织。今天,就让我们从存储技术切入,再到 JavaScript 中让人头疼的 this,一步步揭开它们的神秘面纱。

一、数据存储的多重宇宙

数据存储从来不是一个单一的概念,它就像一座分层的大厦,每一层都有自己的使命。

1.1 持久化存储:MySQL 与关系型数据库

当我们需要永久保存数据时,MySQL 这类关系型数据库是首选。它以表格形式组织数据,通过 SQL 语言进行增删改查,保证了数据的一致性持久性。用户注册信息、订单记录、文章内容......这些需要长期保存的数据,最终都会落入 MySQL 的"怀抱"。

sql 复制代码
-- 典型的 MySQL 查询
SELECT * FROM articles WHERE user_id = 123;

但 MySQL 也有它的"软肋"------每次查询都需要磁盘 I/O 和复杂的解析执行,当并发量上来后,性能瓶颈就会出现。

1.2 缓存层:Redis 的 KV 哲学

为了弥补 MySQL 的性能短板,Redis 应运而生。它基于内存运行,采用键值对(Key-Value)存储,读写速度比 MySQL 快数个数量级。

复制代码
第一次请求:MySQL 查询 → 结果存入 Redis
后续请求:直接走 Redis → 无需再查 MySQL

这种"先读缓存,缓存未命中再查数据库"的模式,是现代高并发系统的标配。Redis 就像 MySQL 的"加速器",让数据访问变得飞快。

1.3 浏览器缓存与本地存储

当数据来到前端,存储形态又发生了变化:

  • 浏览器缓存(HTTP Cache):让页面二次打开时"秒开",资源文件(CSS、JS、图片)被缓存下来,无需重新下载。
  • LocalStorage / SessionStorage:以键值对形式存储在浏览器中,容量约 5-10MB,适合存储用户偏好、主题设置等轻量数据。
  • Cookie:容量小(约 4KB),但会自动携带在请求头中,常用于身份认证。

1.4 前沿阵地:LLM 与 Embedding 存储

在 AI 时代,存储的边界被进一步拓展。大语言模型(LLM)使用 Embedding 向量来存储文本的语义信息,通过向量数据库(如 Pinecone、Milvus)进行相似度检索。这是一种全新的存储范式------数据智能

存储技术的发展,从 MySQL 到 Redis,从 LocalStorage 到向量数据库,本质是在速度、容量、持久性、智能性之间寻找平衡。


二、前端表单:从默认提交到 Ajax 进化

让我们回到前端开发的日常场景------表单。

index.html 中,我们有一个经典的 form 结构:

html 复制代码
<form class="add-items">
  <input type="text" name="item" placeholder="Add a new tapas">
  <input type="submit" value="+ Add Item">
</form>

当用户点击 type="submit" 的按钮时,表单会触发 submit 事件,浏览器会执行默认行为 :向 action 属性指定的地址发送请求,并刷新页面。

这种做法在传统 Web 应用中很常见,但在现代前端开发中,我们更倾向于用 JavaScript 接管提交逻辑,以提供更流畅的用户体验。

javascript 复制代码
const oForm = document.querySelector('.add-items');

function addItem(e) {
  console.log(e);
  console.log(this);
  // 阻止提交默认行为 ------ 不刷新页面
  e.preventDefault();
  // 这里可以写 fetch / ajax 逻辑
}

// 监听 submit 事件
oForm.addEventListener('submit', addItem);

通过 e.preventDefault() 阻止默认的页面刷新,然后用 fetchXMLHttpRequest 异步提交数据,这就是 Ajax 的核心思想。页面不再刷新,用户感知到的只有数据的局部更新,体验大大提升。


三、this:函数运行时的"灵魂"

如果说 JavaScript 中哪个概念最让人困惑,this 一定名列前茅。很多人把 this 理解为"函数声明时"确定的,这是错误的。正确的理解是:

this 在函数运行时确定,指向函数的调用者。

我们来看一段代码(来自 2.html):

javascript 复制代码
var name = "用户A";
let obj = {
  name: "用户B",
  say: function() {
    console.log(this.name);
  }
}
obj.say(); // 输出:用户B

这里 say 作为对象 obj 的方法被调用,this 指向 obj,所以输出 "用户B"

但如果把函数引用赋值给一个变量再调用呢?

javascript 复制代码
const fn = obj.say;
fn(); // 输出:用户A(在非严格模式下)

这时 fn 作为普通函数被调用,this 指向全局对象 window,所以输出 "用户A"。但如果在 common.js 中我们开启了严格模式:

javascript 复制代码
"use strict";
// 严格模式下,普通函数的 this 为 undefined

这就是 严格模式 的意义之一:减少隐式的全局污染。

3.1 this 的五种场景

场景 this 指向
普通函数调用(非严格模式) window / global
普通函数调用(严格模式) undefined
对象方法调用 调用该方法的对象
构造函数调用(new) 新创建的实例对象
事件处理函数 触发事件的 DOM 元素

3.2 事件处理函数中的 this

common.js 中,我们给链接绑定了点击事件:

javascript 复制代码
document.querySelector('.lnk')
  .addEventListener('click', goBaidu);

function goBaidu(e) {
  console.log(this); // 指向 <a class="lnk" href="..."> 元素
  e.preventDefault();
}

作为事件处理函数,this 默认指向触发事件的 DOM 元素,这与普通函数的调用方式不同。


四、手动指定 this:call / apply / bind

很多时候,我们并不想让 this 由调用方式决定,而是手动控制 。JavaScript 提供了三个方法:callapplybind

4.1 call:立即调用,逐个传参

javascript 复制代码
let obj = {
  name: "用户C",
  speak: function(a, b) {
    console.log(a, b);
    console.log(this.name);
  }
}

let obj2 = { name: "用户D" };

// 使用 call,将 this 指向 obj2
obj.speak.call(obj2, '你好', '我是用户D');
// 输出:你好 我是用户D  用户D

call 的第一个参数是想要绑定的 this 对象,后面的参数依次传递给函数。

4.2 apply:立即调用,数组传参

applycall 几乎相同,区别在于传递参数的方式:

javascript 复制代码
obj.speak.apply(obj2, ['你好', '我是用户D']);
// 效果与 call 相同,但参数以数组形式传递

当参数数量不确定或来自数组时,apply 会更加方便。

4.3 bind:延迟调用,返回新函数

bind 与前两者最大的区别是:它不立即执行函数,而是返回一个绑定好 this 的新函数

javascript 复制代码
const fn2 = obj.speak.bind(obj2);
console.log(fn2); // 返回一个新函数
fn2('你好', '我是用户D'); // 此时 this 已经绑定为 obj2

这在异步场景中非常有用,比如 setTimeout 或事件监听:

javascript 复制代码
// common.js 中的示例
const addItemBind = addItem.bind(obj);
oForm.addEventListener('submit', addItemBind);

这里用 bindaddItem 函数的 this 永久绑定为 obj,这样在事件触发时,this 就不会指向表单元素,而是指向我们指定的对象。

4.4 异步场景中的 this 陷阱

2.html 中,有一段被注释掉的代码:

javascript 复制代码
setTimeout((function() {
  console.log(this.name)
}).bind(this), 1000);

为什么要用 bind(this)?因为在 setTimeout 中,回调函数是作为普通函数被调用的,this 会指向 window(非严格模式)。为了让 this 保持在当前上下文中,我们需要手动绑定。

而箭头函数则天然解决了这个问题:

javascript 复制代码
setTimeout(() => {
  console.log(this.name)
}, 1000);

五、箭头函数:没有 this 的"另类"

箭头函数是 ES6 引入的语法,它没有自己的 this ,而是继承外层作用域的 this

javascript 复制代码
var name = "用户A";
let obj = {
  name: "用户B",
  say: function() {
    setTimeout(() => {
      console.log(this.name); // 继承 say 函数的 this → obj
    }, 1000);
  }
}
obj.say(); // 1秒后输出:用户B

say 方法中,箭头函数的 this 沿用了外层 say 函数的 this(即 obj),所以输出 "用户B"

如果用普通函数:

javascript 复制代码
setTimeout(function() {
  console.log(this.name); // 普通函数,this 指向 window
}, 1000);
// 输出:用户A(非严格模式)

这就是箭头函数在异步回调中广受欢迎的原因------它帮我们绕过了 this 的动态绑定问题

但要注意,箭头函数不能作为构造函数(不能使用 new),也不能使用 callapplybind 改变其 this(因为根本没有自己的 this)。


六、综合思考:从表单提交到 this 绑定

让我们把这些知识串联起来,看一个完整的场景:

common.js 中,我们使用 bindaddItemthis 绑定为 obj

javascript 复制代码
const oForm = document.querySelector('.add-items');
let obj = {
  name: "用户C",
  // ...
}

const addItemBind = addItem.bind(obj);
oForm.addEventListener('submit', addItemBind);

为什么要这样做?因为事件监听函数默认的 this 指向 DOM 元素(oForm),但我们希望在 addItem 函数中能访问到 obj 的数据或方法。通过 bind,我们既保留了事件对象 e,又让 this 指向了我们想要的对象。

如果使用 callapply,它们会立即执行函数,而 addEventListener 需要的是一个函数引用(在事件触发时被调用),所以 bind 是最合适的选择。


七、总结

概念 核心要点
数据存储 MySQL(持久化)→ Redis(缓存加速)→ LocalStorage(浏览器存储)→ Embedding(AI 存储),各有适用场景
表单提交 默认提交会刷新页面,用 e.preventDefault() + Ajax 实现无刷新提交
this 指向 函数运行时确定,取决于调用方式(普通调用、方法调用、构造函数、事件处理)
call / apply 立即执行,手动指定 this,区别在于传参方式(逐个 vs 数组)
bind 返回新函数,延迟执行,适合事件监听和异步回调
箭头函数 没有自己的 this,继承外层作用域,简化代码但使用场景受限

最后思考 :理解 this 的关键,不是背诵规则,而是理解"函数被调用时,谁在调用它"。而理解存储的关键,是明白"数据在何时、以何种方式被访问"。这两者看似不同,但都指向同一个核心------对上下文(Context)的深刻理解

希望这篇文章能帮你理清这些概念,在开发中更加得心应手。如果有什么疑问,欢迎在评论区交流讨论!


本文所有示例代码均可在附带的 HTML 文件中运行验证。

相关推荐
Tian_Hang1 小时前
eclipse ditto 学习笔记
运维·服务器·开发语言·javascript·3d
JNX_SEMI2 小时前
AT2659 L1频段多模卫星导航低噪声放大器技术解析
前端·单片机·嵌入式硬件·物联网·硬件工程
Profile排查笔记4 小时前
指纹浏览器环境异常排查:Fingerprint、Profile、Proxy、Session 和 Task Log 怎么看
前端·人工智能·后端·自动化
京韵养生记4 小时前
【无标题】
java·服务器·前端
大气的小蜜蜂4 小时前
领域层的服务
java·前端·数据库
星栈4 小时前
LiveView 的 LiveComponent:比 React 组件更轻,但我一开始真的把它用错了
前端·前端框架·elixir
竹林8184 小时前
用 Pinata + IPFS 存 NFT 元数据踩了三天坑,我总结了这份完整的前端实现方案
javascript
林希_Rachel_傻希希5 小时前
web性能优化之延迟加载图片和<inframe>
前端·javascript·面试
maxmaxma5 小时前
Konva 从入门到实践 - day1
前端