前端八股之 storage & this:两个最容易卡住的点,一次讲透

面试八股文里,最容易卡住的有两块:

  • 「数据怎么存?」 ------ MySQL、Redis、localStorage、浏览器缓存,名字一大堆,到底啥关系?
  • 「this 到底指向谁?」 ------ 五种场景,再加 var 污染 window 的冷门坑,背了八遍还在忘。

这篇一次讲透。

写在前面:八股不是死记硬背

背八股的最高境界,是理解它要解决什么问题

存储是为了「下次还能找到 」,this 是为了「在运行时分清楚谁在干活」。

两个词的内核都不一样,但偏偏都跟前端工程的血泪史深度绑定。

五大存储方案:从 MySQL 到 localStorage

先一张总表镇场:

存储 位置 速度 容量 典型场景
MySQL 服务器 TB 级 主业务数据
Redis 内存 极快 GB 级 热点缓存(KV)
云盘 第三方服务 TB 级 文件备份、协作
浏览器缓存 浏览器 MB 级 静态资源、CDN 镜像
localStorage 浏览器 5MB 左右 前端 KV 持久化

典型链路

文章列表第一次走 MySQL,缓存到 Redis,下次读 Redis 不再查 MySQL------MySQL 不至于被打爆。

再细想一层:localStorage 和 sessionStorage 是浏览器给前端的「KV 数据库 」,有限制(5MB、仅字符串、同源策略),但就是胜在不用走后端

LLM 时代的 embedding 存储(向量数据库),本质还是「KV 缓存 + 语义索引」------速度最快,但容量有限、贵。

form 表单:别再用默认提交了

面试经典翻车现场:<form action="..."> 默认提交,会刷新整个页面

html

xml 复制代码
<!-- 这种坑,新手代码里常出现 -->
<form class="add-items" action="/add">
  <input type="text" name="item" placeholder="Add a new tapas" required>
  <input type="submit" value="+ Add Item">
</form>

为啥要避开?

维度 form 默认提交 fetch / ajax
体验 整页刷新,闪烁 局部更新,无缝
状态 丢失 保留
错误处理 跳错误页 弹 toast
现代框架支持 反人类 对接 React/Vue 一气呵成

正解

js

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

// bind 返回一个全新函数,不立即执行
const addItemBind = addItem.bind(obj2);
oForm.addEventListener('submit', addItemBind);

function addItem(e) {
  e.preventDefault();  // 阻止默认提交 → 阻止刷新页面
  // ...走 ajax / fetch
}

.preventDefault() 是关键一行------不写它,表单照样给你刷一次页面。

写完记得用 fetch('/api/add', { method: 'POST', body: ... }) 替换默认行为,整条链路才顺。

this:五种场景

重点来了。this 的指向,看你怎么「叫」这个函数,而不是在哪里「写」的。

调用方式 this 指向 例子
普通函数调用 全局 window(非严格模式)/ undefined(严格模式) fn()
对象方法调用 调用对象本身 obj.say()
构造函数调用 实例对象 new Person()
事件处理函数 事件触发元素 btn.onclick = function(){...}
手动指定 你指定谁就是谁 fn.call(obj)

为什么会有「引用式赋值」丢 this 的坑?

js

javascript 复制代码
const obj = { name: '羊羊', say() { console.log(this.name) } };
const fn = obj.say;    // 引用式赋值
fn();                  // 输出 undefined,this 是 window
obj.say();             // 输出「羊羊」,this 是 obj

一赋值,函数的「债主」就变了。obj.say() 时,调它的债主是 obj;fn() 时,没人认领,债主默认是 window。

call / apply / bind 三兄弟

如果丢失的 this 想找回来,手动指定

方法 参数形式 是否立即执行 返回值
call fn.call(thisArg, arg1, arg2, ...) 立即执行 函数返回值
apply fn.apply(thisArg, [arg1, arg2, ...]) 立即执行 函数返回值
bind fn.bind(thisArg, arg1, arg2, ...) 立即执行 一个新函数

看代码里这一段:

js

arduino 复制代码
obj.speak.call(obj2);                // 立即执行,this → obj2
obj.speak.apply(obj2, ['你好', '我是小羊']);  // 数组传参
const fn2 = obj.speak.bind(obj2);    // 返回新函数,不执行
fn2('你好', '我是小羊');               // 现在才执行

实战 tips

  • 已知参数 → call
  • 参数是数组 → apply
  • 想做事件回调 / 延时回调 → bind(用 addEventListener('submit', addItemBind) 这种最干净)

var 污染 window:一行代码引发的血案

js

javascript 复制代码
var name = '小小羊';   // 污染 window
let obj = {
  name: '两只羊',
  say() {
    console.log(this.name);   // 输出「两只羊」OK
    setTimeout(function() {
      console.log(this.name)  // 输出「小小羊」!糟了
    }, 1000);
  }
}
obj.say();

为啥 setTimeout 里 this.name 变成「小小羊」?

声明方式 是否挂载到 window 是否能通过 window.xxx 访问
var
let
const

定时器里 function() {} 是普通函数调用,this 默认指向 window,刚好在 window 上找到了 name

修正方案------箭头函数,没有自己的 this:

js

javascript 复制代码
setTimeout(() => {
  console.log(this.name);  // 输出「两只羊」✓
}, 1000);

箭头函数继承外层词法作用域的 this,只要外层是对象方法,里层就自动拿到对象。

完整代码 1:localStorage Todo 列表

完整能跑通的 demo(含新增/读取/删除):

js

javascript 复制代码
// 增:用 localStorage.setItem 存一条
function saveTodo(item) {
  const todos = JSON.parse(localStorage.getItem('todos') || '[]');
  todos.push({ id: Date.now(), text: item, done: false });
  localStorage.setItem('todos', JSON.stringify(todos));
}

// 查:渲染列表
function loadTodos() {
  return JSON.parse(localStorage.getItem('todos') || '[]');
}

// 删:通过 id 移除
function removeTodo(id) {
  const todos = loadTodos().filter(t => t.id !== id);
  localStorage.setItem('todos', JSON.stringify(todos));
}

// 改:勾选已完成
function toggleTodo(id) {
  const todos = loadTodos().map(t =>
    t.id === id ? { ...t, done: !t.done } : t
  );
  localStorage.setItem('todos', JSON.stringify(todos));
}

完整代码 2:this 五合一案例

js

javascript 复制代码
function normal() {
  console.log('普通调用 this →', this === window ? 'window' : this);
}
const obj2 = { name: '小羊' };

normal();                          // window
obj2.say = function () {
  console.log('对象方法 this →', this.name);
};
obj2.say();                        // 小羊

function Person(name) {
  this.name = name;
  console.log('构造函数 this →', this.name);
}
new Person('两只羊');              // 两只羊

const btn = document.querySelector('.lnk');
btn.addEventListener('click', function (e) {
  e.preventDefault();
  console.log('事件 this →', this === e.currentTarget ? '事件源' : this);
});

// call / apply / bind 三兄弟
obj2.speak = function (greet, intro) {
  console.log(`${greet}, ${intro}, 我是${this.name}`);
};
obj2.speak.call({ name: '强制A' }, '你好', '我是前端');
obj2.speak.apply({ name: '强制B' }, ['你好', '我是后端']);
const fnBind = obj2.speak.bind({ name: '强制C' });
fnBind('你好', '我是测试');
调用方式 this 指向 输出
normal() window window
obj2.say() obj2 小羊
new Person() 新实例 两只羊
btn.addEventListener(...) 事件源 事件源
.call({强制A}) {强制A} 你好,我是前端,我是强制A

踩坑提醒:这 5 个坑别再踩

  1. localStorage 只能存字符串 ------直接存对象会被调 toString(),得到 [object Object],记得 JSON.stringify + JSON.parse
  2. localStorage 容量 ≈ 5MB------存大 JSON 容易爆,敏感数据别存(明文、token),localStorage 能被同源脚本读到。
  3. form 默认提交会刷新页面 ------一定写 e.preventDefault(),或者直接用 <button type="button"> + JS 拦截。
  4. var 声明会污染 window ------能用 let/const 就别用 var,否则定时器 / 全局函数里 this 容易抽风。
  5. 箭头函数没有自己的 this ------别在箭头函数里再写 arguments 或想用 .call() 改 this,它就根本不吃这套。

结尾:八股的终点,是工程化

八股文的尽头,不是背得多熟,是遇到问题知道去哪查

下次被面试官问「localStorage 和 sessionStorage 区别」,或「箭头函数和普通函数 this 的差异」------别硬背,先想这句话:

存储是为了下次能找到,this 是为了运行时找对人。

相关推荐
用户921080262861 小时前
Bubble 打字机效果实践:从项目配置到源码实现
前端
weixin_461408582 小时前
npm npx yarn
开发语言·前端·javascript
四千岁2 小时前
RAG系统中的分块
前端·javascript·后端
START_GAME2 小时前
MSSQL$SQL2016
java·服务器·前端
Captaincc2 小时前
Show me your works & token -稀土掘金上线内测作品广场和用量统计
前端·掘金社区·vibecoding
进击的明明2 小时前
闭包:JavaScript里的“随身背包” 🎒
前端·javascript·面试
紫幽3 小时前
从 0 用 Vue 做屏并生成 LVGL 单片机代码(完整源码备忘)
前端·单片机
郭邯3 小时前
用 AI 辅助开发一个文件大小转换工具:从需求到上线的完整过程
前端