默认只显示问题 —— 先自己在心里答一遍,再展开对答案。答不上来就标「不会」,下次抽认卡会先抽它。Only the question shows by default. Answer it in your head first, then open the answer. Mark the ones you miss; the flashcard round puts those first.
0 / 105道自评过self-assessed
0会Got it0模糊Shaky0不会No idea105还没做Not seen
正在读这台浏览器里的标记…Reading your marks from this browser…
标记不是打分,是给下一轮排队Marks are a queue, not a score
标「不会」的题会排到抽认卡最前面,标「会」的进低频池排最后。所以别客气 —— 觉得答得磕磕巴巴就标「模糊」。准备好了就去抽认卡。“No idea” jumps to the front of the next round; “got it” drops to the back. So be honest — if it came out shaky, mark it shaky. Then go run a flashcard round.
In one line: one codebase that lays out sensibly at any screen size — rather than building a separate mobile site.
Four techniques, most important first:
The viewport meta tag — the precondition; without it nothing else matters (see #381).
Flexible units — widths in % /fr / min() / clamp(), font sizes in rem; stop hard-coding px everywhere. clamp(16px, 4vw, 24px) gives you a fluid font size with hard limits in a single line.
Media queries — @media (max-width: 768px) to change layout.
Flexible layout — flex-wrap and grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) often adapt without a single media query.
Follow-up: “How do you choose breakpoints?” — the right answer is “from the content, not from device sizes”: drag the window narrower and add a breakpoint wherever it starts looking wrong. Chasing a list of iPhone dimensions is outdated, because device sizes change every year.
They will also ask about mobile-first: write the narrow layout as the default and add to it with min-width. Mobile then downloads the least CSS, and “adding” is easier to reason about than “undoing” — overriding downwards with max-width usually means repeatedly resetting properties.
In one line: the engine is the program that turns JS source into something the machine can run. Chrome and Node use V8, Firefox uses SpiderMonkey, Safari uses JavaScriptCore.
Roughly the pipeline: source → parse into an AST → emit bytecode → the interpreter starts running it while watching which parts run over and over (the hot paths), and hands those to the JIT compiler to be turned into machine code. Interpret first, compile later — that is what lets JS start instantly and still hit near-native speed where it counts.
Follow-up: “What is the difference between the engine and the runtime?” — this is the real question. The engine only executes the language itself. It knows nothing aboutsetTimeout, fetch, document or fs — those are APIs the host environment (browser or Node) hands you. So “is the event loop part of the engine?” — no, it belongs to the runtime (see #305). Getting this distinction right earns real credit.
Another follow-up, on memory: the engine manages two areas — the call stack (execution contexts, primitive values) and the heap (objects, arrays, functions). Which lines up exactly with the next question.
用来干什么:验证一小段语法、试一个 API 的返回值、 快速算个东西。不适合写多行逻辑—— 改一行要重敲一遍。
会追问:「在 REPL 里 let x = 1 会打印什么?」——undefined。 因为它打印的是表达式的值, 而变量声明语句的值就是 undefined。 这个小细节能看出你是不是真用过。
In one line: Read-Eval-Print-Loop — read a line, evaluate it, print the result, wait for the next one. Typing node in a terminal drops you into one, and the DevTools Console in a browser is one too.
What you use it for: checking a bit of syntax, seeing what an API returns, working out a quick number. Not for multi-line logic — changing one line means retyping the lot.
Follow-up: “What does let x = 1 print in a REPL?” — undefined. It prints the value of the expression, and the value of a variable declaration is undefined. A small detail, but it shows whether you have actually used one.
In one line: a primitive sits on the stack and holds the value itself; a reference value sits on the heap, and the stack holds only an address.
Seven primitive types (learn the list — they will make you count them): string, number, boolean, undefined, null, symbol, bigint. Everything else is a reference type: objects, arrays, functions, Date, Map, Set, regexes… (arrays and functions are objects underneath).
Three differences you can observe:
Immutability — a primitive value cannot be changed.s.toUpperCase()returns a new string; the original never moved.
Comparison — primitives compare by value, references compare by address. So {} === {} is false, and so is [1] === [1].
Assignment — a primitive gets copied; a reference copies only the address, so two variables point at the same object. That is #284.
Follow-up: “What is typeof null? ” — "object", a bug that has survived 30 years because fixing it would break too much. Test for null with x === null. “How do you reliably detect an array?” — Array.isArray(x). Never typeof, which just says "object".
Two rules cover almost all coercion — hold on to these and you can derive most of the trick questions:
+ becomes concatenation the moment one side is a string; every other arithmetic operator (-, *, /) converts to number. Hence 1 + "1" === "11" but "3" - 1 === 2.
An object in an operation goes through valueOf() first, then toString(). An array’s toString() joins its elements with commas, so [] + [] gives an empty string and [] + {} gives "[object Object]".
Memorise the six falsy values (everything else is truthy): false, 0, "", null, undefined, NaN. Watch out — [] and {} are both truthy, so check arr.length to tell whether an array is empty.
Follow-up: “What is the difference between parseInt and Number?” — parseInt("42px") gives 42 (it reads from the front until it cannot go on), while Number("42px") gives NaN (the whole string has to be valid). So validate user input with Number; parseInt waves dirty data straight through.
10[1,2]+[3]// "1,23" join with commas, then concatenate
11
12Number("42px")// NaN the whole string has to be valid
13parseInt("42px")// 42 reads until it cannot read further
14Number("")// 0 ← note: an empty string converts to 0
15Number(" ")// 0 ← whitespace is 0 too, so validate input carefully
面试不会让你背全表,但会给两三个式子让你推。掌握「+ 看字符串、其他看数字」和「六个假值」就够推。An interview will not ask you to recite the whole table, but it will give you two or three expressions to work out. Remember that + looks for a string while every other operator converts to number, plus the six falsy values, and that is enough.
In one line:===returns false straight away when the types differ; ==converts both sides to one type first, then compares.
The == conversion rules are convoluted, but four points cover practice:
null == undefined is true, and neither one is == to anything else (not 0, not "").
Number against string → the string becomes a number.
A boolean involved → the boolean becomes a number (true→1, false→0). That is why [] == false is true: []→""→0, and false→0.
Object against primitive → the object becomes a primitive.
The conclusion: use === everywhere. The one == that everybody accepts is x == null — it rules out null and undefined in a single check.
Follow-up: “NaN === NaN?” — false. NaNis not even equal to itself. Test it with Number.isNaN(x) — not the global isNaN, which coerces first, so isNaN("abc") is true. “Is there anything stricter?” — Object.is(x, y). It differs from === in exactly two places: Object.is(NaN, NaN) is true, and Object.is(0, -0) is false. This is what React uses to decide whether state changed.
JavaScript为什么别用 ==Why you should not use ==示意Illustrative
10=="0"// true 字符串转数字
20==""// true "" -> 0
30==false// true false -> 0
4null==undefined// true 特例
5null==0// false null 只和 undefined 相等
6[]==false// true [] -> "" -> 0
7NaN==NaN// false 和自己都不相等
8
90==="0"// false 类型不同,到此为止
10Object.is(NaN,NaN)// true ← React 用它判断 state 变没变
10=="0"// true the string converts to a number
20==""// true "" -> 0
30==false// true false -> 0
4null==undefined// true a special case
5null==0// false null only equals undefined
6[]==false// true [] -> "" -> 0
7NaN==NaN// false not even equal to itself
8
90==="0"// false different types, so it stops there
10Object.is(NaN,NaN)// true ← React uses this to decide whether state changed
In one line:&& and ||stop evaluating the moment the result is decided, and they hand back an operand, not a boolean.
a && b — returns a if a is falsy, otherwise b. “Both must be true”, so one falsy value ends the job.
a || b — returns a if a is truthy, otherwise b.
a ?? b (nullish coalescing) — returns b only when a is null or undefined.
The difference between || and ?? is a frequent follow-up and a real source of bugs: count || 10 gives you 10 when count is 0, because 0 is falsy. For default values, reach for ??.
The most common React use is conditional rendering: {loading && <Spinner />}. Here is the trap: if the left side is list.length and the list is empty, then 0 && … returns 0, and React renders that 0 — a stray “0” shows up on the page out of nowhere. Write list.length > 0 && … instead.
JSX短路的三个实际用法与两个坑Three real uses of short-circuiting, and two traps示意Illustrative
1// 短路返回的是操作数本身
2console.log(1&&2);// 2
3console.log(0&&2);// 0 ← 不是 false
4console.log(""||"默认");// "默认"
5
6// || 和 ?? 的区别
7constcount=0;
8count||10// 10 ✗ 0 被当成「没传」
9count??10// 0 ✓
10
11// React 条件渲染的经典坑
12{list.length&&<List/>}// ✗ 空列表时页面上多一个 0
13{list.length>0&&<List/>}// ✓
14{list.length?<List/>:null}// ✓ 也可以
1// Short-circuiting returns the operand itself
2console.log(1&&2);// 2
3console.log(0&&2);// 0 ← not false
4console.log(""||"default");// "default"
5
6// The difference between || and ??
7constcount=0;
8count||10// 10 ✗ 0 is treated as "nothing was passed"
9count??10// 0 ✓
10
11// The classic React conditional-rendering trap
12{list.length&&<List/>}// ✗ an empty list prints a stray 0 on the page
In one line:var is function-scoped, hoists to undefined and can be redeclared; let / const are block-scoped, have a TDZ and cannot be redeclared; const on top of that cannot be reassigned.
var
let
const
Scope
Function
Block {}
Block
Read before the declaration
undefined
throws ReferenceError
throws ReferenceError
Redeclare
Allowed
No
No
Reassign
Allowed
Allowed
No
Lands on window
Yes at top level
No
No
The TDZ (temporal dead zone) is the stretch between the start of the block and the let / const line itself. The variable really is hoisted, but flagged as “not usable yet”, so reading it throws instead of handing you undefined. That is deliberate — it makes mistakes surface early.
Follow-up: “Can you change the properties of a const object?” — yes. const locks the binding (the name cannot point at anything else), not the value. To freeze the contents, use Object.freeze().
Another follow-up — the classic loop question:for (var i…) with setTimeout prints three 3s; let prints 0 1 2. Because letcreates a fresh binding on every iteration, while var has one single i the whole way through. This gets asked together with closures (#298).
这题和 React 直接相关:React 判断 state 变没变是比引用, 所以「改了对象属性但界面不动」就是这个原理 —— 必须造新对象。
In one line — get this sentence exactly right: JavaScript is always pass by value. It is just that when the value happens to be an object, the “value” being passed is an address. The precise name for this is pass by sharing.
Why does the wording matter? Because it explains two things that look like a contradiction:
obj.n = 2 inside the function does show up outside — both variables point at the same object.
obj = { n: 2 } inside the function does not show up outside — all you did was point the parameter variable inside the function at a new object; the outer variable still points at the old one.
If this really were pass by reference, the second case would change the outside too. So it is pass by value.
Follow-up: “How do you avoid mutating the caller’s data?” — copy it first. { ...obj } and arr.slice() are shallow copies (one level only; nested objects are still shared). For a deep copy use structuredClone(obj) — native in modern browsers and Node 17+, and better than JSON.parse(JSON.stringify()), which drops undefined and functions, turns Date into a string, and cannot handle circular references at all.
This one ties straight into React: React compares references to decide whether state changed, which is exactly why “I changed a property and the UI did not move” happens — you have to build a new object.
JavaScript改属性 vs 换指向Changing a property vs pointing somewhere else示意Illustrative
In one line: a Setholds unique elements, checks membership in O(1), and has no indices; an array allows duplicates, has order and indices, and its includes is O(n).
Array
Set
Duplicates
Allowed
Dropped automatically
“Is it in there?”
includes O(n)
hasO(1)
Index access
arr[0]
None
Length
length
size
map / filter
Yes
No — convert to an array first
When to use a Set: deduping, and answering “have I seen this before?” inside a loop — the second is where the performance gap is biggest, because an array includes turns O(n) into O(n²).
Follow-up: “Can a Set dedupe identical objects?” — no. A Set uses SameValueZero (≈===), and two objects with the same contents are still two different references. To dedupe by content, key them yourself in a Map. “What about NaN?” — a Set stores NaN only once. That is the only difference between SameValueZero and ===.
JavaScriptSet 的两个真实用途Two real uses for Set示意Illustrative
In one line: a Maptakes keys of any type, guarantees insertion order, has size, and is iterable on its own; an object’s keys can only be strings or symbols, and it drags a prototype chain along with it.
Object
Map
Key types
String / symbol (numbers become strings)
Any value, objects and functions included
Order
Integer keys get sorted, the rest are insertion order
Strictly insertion order
Size
Object.keys(o).length
map.size
Iteration
Needs Object.entries first
Iterable itself — for…of just works
Prototype pollution
A risk — o["toString"] already has a value
None; a Map is clean
JSON serialisation
Works directly
No — convert to an array first
How to choose:
Use an Object for records with a fixed shape (one user, one config), for anything you serialise to JSON, and when the field names are written into the code.
Use a Map when the keys are dynamic, entries come and go often, there are a lot of them, or the keys are not strings.
Follow-up: “Why is an object a prototype pollution risk?” — because even an empty object “has” inherited keys like toString and constructor. Use an object as a dictionary and if (dict[key]) reports a hit when the user types "constructor". A Map does not have this problem; if you must use an object, build it with Object.create(null). “And WeakMap?” — its keys must be objects, and it does not hold off garbage collection. Good for hanging extra data on an object without leaking memory.
JavaScript两个真实会踩的差别Two differences you will actually hit示意Illustrative
In one line: five — function declaration, function expression, arrow function, the Function constructor, and method shorthand inside an object or class.
But what the interview actually wants is the differences, especially between the first three:
Hoisting
this
arguments
Can you new it
Function declaration
Hoisted whole, callable before its line
Decided at call time
Yes
Yes
Function expression
Only the variable name is hoisted
Decided at call time
Yes
Yes
Arrow function
Only the variable name is hoisted
The enclosing this where it was written
No
No
An arrow function is not “a shorter function” — it has no this of its own, no arguments, cannot be a constructor, and has no prototype. So if an object method needs this to be that object, it cannot be an arrow function.
Follow-up: “Why does nobody use the Function constructor?” — it takes the body as a string, which makes it eval in disguise: an injection risk, no access to the surrounding closure, and nothing the engine can optimise. Knowing it exists and saying it should not be used is the right answer.
JavaScript三种写法的实际差别How the three forms actually differ示意Illustrative
1sayHi();// ✓ 能跑 —— 函数声明整体提升
2functionsayHi(){console.log("hi");}
3
4sayHey();// ✗ TypeError: sayHey is not a function
99 道来自面试题库 #269–#387;TypeScript 深度那 6 道是 DrillLab 自出的(senior 补强,卡片上有标注)。答案都是 DrillLab 写的,所以讲解里的代码块一律标「示意」。每道题都能点回它出处的那一节课。99 questions come from the interview bank (#269–#387); the 6 TypeScript deep-dive ones are DrillLab-made (marked on the card). All answers are written by DrillLab, so every code block here is labelled “demo”. Each card links back to the lesson it came from.