Storage and `this` in JavaScript: The Two Interview Topics That Trip Up Every Front-End Candidate
Front-End Interview Classics: Storage & this — The Two Trickiest Topics, Explained Once and for All
In interview question banks, two areas are the easiest to get stuck on:
- 「How to store data?」 — MySQL, Redis, localStorage, browser cache — so many names, what's the actual relationship?
- 「What does this point to?」 — Five scenarios, plus the obscure pitfall of var polluting window. You've memorized it eight times and still forget.
This article explains them thoroughly, once and for all.
Preface: Interview Classics Aren't About Rote Memorization
The highest level of memorizing interview classics is understanding what problem they solve.
Storage is about 「being able to find it next time」, and this is about 「figuring out who is doing the work at runtime」.
The core concepts of the two words are different, yet both are deeply tied to the blood-and-tears history of front-end engineering.
Five Storage Solutions: From MySQL to localStorage
First, a summary table to set the stage:
| Storage | Location | Speed | Capacity | Typical Scenario |
|---|---|---|---|---|
| MySQL | Server | Slow | TB-level | Main business data |
| Redis | Memory | Extremely Fast | GB-level | Hot cache (KV) |
| Cloud Drive | Third-party Service | Medium | TB-level | File backup, collaboration |
| Browser Cache | Browser | Fast | MB-level | Static assets, CDN mirror |
| localStorage | Browser | Fast | ~5MB | Front-end KV persistence |
Typical Chain:
An article list first hits MySQL, gets cached in Redis, and next time reads from Redis without querying MySQL again — preventing MySQL from being overwhelmed.
Think one layer deeper: localStorage and sessionStorage are the browser's 「KV database」 for the front end, with limitations (5MB, strings only, same-origin policy), but the advantage is no need to hit the backend.
In the LLM era, embedding storage (vector databases) is essentially still 「KV cache + semantic indexing」 — fastest speed, but limited capacity and expensive.
form Forms: Stop Using Default Submission
A classic interview stumble: <form action="..."> default submission refreshes the entire page.
html
<!-- This kind of pitfall often appears in beginner code -->
<form class="add-items" action="/add">
<input type="text" name="item" placeholder="Add a new tapas" required>
<input type="submit" value="+ Add Item">
</form>
Why avoid it?
| Dimension | form default submit | fetch / ajax |
|---|---|---|
| Experience | Full page refresh, flicker | Partial update, seamless |
| State | Lost | Preserved |
| Error Handling | Redirect to error page | Show toast |
| Modern Framework Support | Anti-human | Integrates smoothly with React/Vue |
The Correct Way:
js
const oForm = document.querySelector('.add-items');
// bind returns a brand new function, does not execute immediately
const addItemBind = addItem.bind(obj2);
oForm.addEventListener('submit', addItemBind);
function addItem(e) {
e.preventDefault(); // Prevent default submission → prevent page refresh
// ...proceed with ajax / fetch
}
.preventDefault() is the critical line — without it, the form will still refresh the page.
After writing it, remember to replace the default behavior with
fetch('/api/add', { method: 'POST', body: ... })to make the whole chain smooth.
this: Five Scenarios
Here comes the key point. The target of this depends on how you 「call」 the function, not where it was 「written」.
| Invocation Method | this Target | Example |
|---|---|---|
| Regular function call | Global window (non-strict) / undefined (strict) | fn() |
| Object method call | The calling object itself | obj.say() |
| Constructor call | The instance object | new Person() |
| Event handler | The element that triggered the event | btn.onclick = function(){...} |
| Manual binding | Whatever you specify | fn.call(obj) |
Why does 「reference assignment」 lose this?
js
const obj = { name: '羊羊', say() { console.log(this.name) } };
const fn = obj.say; // Reference assignment
fn(); // Outputs undefined, this is window
obj.say(); // Outputs 「羊羊」, this is obj
Once assigned, the function's 「creditor」 changes. When calling obj.say(), the creditor calling it is obj; when calling fn(), no one claims it, and the creditor defaults to window.
The Three Brothers: call / apply / bind
If you want to reclaim a lost this, specify it manually:
| Method | Parameter Form | Executes Immediately | Return Value |
|---|---|---|---|
| call | fn.call(thisArg, arg1, arg2, ...) |
Immediately | Function return value |
| apply | fn.apply(thisArg, [arg1, arg2, ...]) |
Immediately | Function return value |
| bind | fn.bind(thisArg, arg1, arg2, ...) |
Does NOT execute immediately | A new function |
Look at this code snippet:
js
obj.speak.call(obj2); // Executes immediately, this → obj2
obj.speak.apply(obj2, ['你好', '我是小羊']); // Array arguments
const fn2 = obj.speak.bind(obj2); // Returns new function, does not execute
fn2('你好', '我是小羊'); // Now it executes
Practical tips:
- Known arguments →
call - Arguments are an array →
apply - Need an event callback / delayed callback →
bind(usingaddEventListener('submit', addItemBind)is the cleanest)
var Pollutes window: A Bloodbath Caused by One Line of Code
js
var name = '小小羊'; // Pollutes window
let obj = {
name: '两只羊',
say() {
console.log(this.name); // Outputs 「两只羊」 OK
setTimeout(function() {
console.log(this.name) // Outputs 「小小羊」! Disaster
}, 1000);
}
}
obj.say();
Why does this.name inside setTimeout become 「小小羊」?
| Declaration Method | Mounted on window? | Accessible via window.xxx? |
|---|---|---|
| var | Yes | Yes |
| let | No | No |
| const | No | No |
Inside the timer, function() {} is a regular function call, so this defaults to window, and name happens to be found on window.
The Fix — Arrow functions, which have no own this:
js
setTimeout(() => {
console.log(this.name); // Outputs 「两只羊」 ✓
}, 1000);
Arrow functions inherit this from the outer lexical scope. As long as the outer scope is an object method, the inner scope automatically gets the object.
Complete Code 1: localStorage Todo List
A fully runnable demo (including add/read/delete):
js
// Add: use localStorage.setItem to store an item
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));
}
// Read: render list
function loadTodos() {
return JSON.parse(localStorage.getItem('todos') || '[]');
}
// Delete: remove by id
function removeTodo(id) {
const todos = loadTodos().filter(t => t.id !== id);
localStorage.setItem('todos', JSON.stringify(todos));
}
// Update: toggle completed
function toggleTodo(id) {
const todos = loadTodos().map(t =>
t.id === id ? { ...t, done: !t.done } : t
);
localStorage.setItem('todos', JSON.stringify(todos));
}
Complete Code 2: Five-in-One this Case
js
function normal() {
console.log('Regular call this →', this === window ? 'window' : this);
}
const obj2 = { name: '小羊' };
normal(); // window
obj2.say = function () {
console.log('Object method this →', this.name);
};
obj2.say(); // 小羊
function Person(name) {
this.name = name;
console.log('Constructor this →', this.name);
}
new Person('两只羊'); // 两只羊
const btn = document.querySelector('.lnk');
btn.addEventListener('click', function (e) {
e.preventDefault();
console.log('Event this →', this === e.currentTarget ? 'Event source' : this);
});
// call / apply / bind three brothers
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('你好', '我是测试');
| Invocation Method | this Target | Output |
|---|---|---|
normal() |
window | window |
obj2.say() |
obj2 | 小羊 |
new Person() |
New instance | 两只羊 |
btn.addEventListener(...) |
Event source | Event source |
.call({强制A}) |
{强制A} | 你好,我是前端,我是强制A |
Pitfall Reminders: Don't Step on These 5 Again
- localStorage can only store strings — Storing an object directly calls
toString(), yielding[object Object]. RememberJSON.stringify+JSON.parse. - localStorage capacity ≈ 5MB — Storing large JSON can easily blow up. Don't store sensitive data (plain text, tokens); localStorage is readable by same-origin scripts.
- Default form submission refreshes the page — Always write
e.preventDefault(), or directly use<button type="button">+ JS interception. - var declarations pollute window — Use
let/constinstead ofvarwhenever possible, otherwisethisin timers / global functions can easily go haywire. - Arrow functions have no own this — Don't write
argumentsinside an arrow function or try to change itsthiswith.call(); it simply doesn't accept that.
Conclusion: The Endgame of Interview Classics is Engineering
The end of the road for interview classics isn't how well you've memorized them, but knowing where to look when you encounter a problem.
Next time an interviewer asks you 「the difference between localStorage and sessionStorage」, or 「the difference in this between arrow functions and regular functions」 — don't force memorization. First, think of this sentence:
Storage is for finding it next time, and
thisis for finding the right person at runtime.