每天 10 道,每题带参考答案和代码。Day 1 专攻 JS 核心:类型、作用域、原型、异步、手写题。
第 1 题:JavaScript 有哪些数据类型?如何判断?
参考答案
原始类型(7 种): string、number、bigint、boolean、undefined、symbol、null
引用类型: object(含 Array、Function、Date、RegExp 等)
判断类型常用三种方式:
javascript
// 1. typeof ------ 适合原始类型 + function
typeof "hello"; // "string"
typeof 42; // "number"
typeof undefined; // "undefined"
typeof null; // "object" ← 历史 bug
typeof []; // "object"
typeof function(){}; // "function"
// 2. Array.isArray ------ 判断数组
Array.isArray([1, 2]); // true
// 3. Object.prototype.toString ------ 最精确
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(new Date()); // "[object Date]"
面试加分点: typeof null === 'object' 是遗留问题;跨 iframe 判断类型优先用 toString.call。
第 2 题:== 和 === 有什么区别?
参考答案
===:严格相等 ,类型不同直接false,不做类型转换==:宽松相等,类型不同时按规则强制转换后再比
javascript
0 == false; // true → false 转 0
"" == false; // true → 都转成 0
null == undefined; // true → 特殊规则
null === undefined; // false
// 对象比较比引用
{} === {}; // false
const a = {};
const b = a;
a === b; // true
工程建议: 团队规范一般禁用 ==,用 === + 显式转换。
javascript
// 显式转数字
Number(input) === 0;
// 显式转字符串
String(id) === "123";
第 3 题:什么是闭包?有哪些应用场景?
参考答案
闭包 = 函数 + 其词法作用域中的自由变量。
内层函数引用了外层函数的变量,且外层已执行完毕,变量仍被保留。
javascript
function createCounter() {
let count = 0;
return {
inc() { return ++count; },
get() { return count; },
};
}
const counter = createCounter();
counter.inc(); // 1
counter.inc(); // 2
counter.get(); // 2
// count 无法从外部直接访问,但被闭包持有
常见场景:
| 场景 | 示例 |
|---|---|
| 数据私有化 | 模块模式、计数器 |
| 柯里化 | 参数分步传入 |
| 防抖/节流 | 保存 timer 引用 |
| 循环 + 异步 | 用 IIFE 或 let 固定 index |
javascript
// 经典坑:var + 循环
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 3 3 3
}
// 闭包修复
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 0); // 0 1 2
})(i);
}
// 更简洁:let 块级作用域
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 0 1 2
}
第 4 题:解释 JavaScript 的原型链
参考答案
每个对象有内部属性 [[Prototype]](可通过 __proto__ 或 Object.getPrototypeOf 访问)。
访问属性时:自身没有 → 沿原型链向上找 → 直到 null。
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function () {
console.log(`Hi, ${this.name}`);
};
const p = new Person("Alice");
p.sayHi(); // "Hi, Alice"
// 原型链关系
p.__proto__ === Person.prototype; // true
Person.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
手写 new 四步:
javascript
function myNew(Ctor, ...args) {
const obj = Object.create(Ctor.prototype);
const result = Ctor.apply(obj, args);
return result instanceof Object ? result : obj;
}
ES6 class 是语法糖,本质仍是原型:
javascript
class Animal {
speak() { return "..."; }
}
typeof Animal; // "function"
Animal.prototype.speak; // 仍在 prototype 上
第 5 题:说说 this 的指向规则
参考答案
| 调用方式 | this 指向 |
|---|---|
| 普通函数直接调用 | 非严格模式 window,严格模式 undefined |
| 对象方法调用 | 调用对象 |
new |
新实例 |
call/apply/bind |
指定对象 |
| 箭头函数 | 定义时的外层 this(词法绑定) |
javascript
const obj = {
name: "obj",
fn() { return this.name; },
arrow: () => this?.name,
};
obj.fn(); // "obj"
const f = obj.fn;
f(); // undefined(严格模式)或 window
obj.arrow(); // 外层 this,不是 obj
// 丢失 this 的修复
const bound = obj.fn.bind(obj);
bound(); // "obj"
DOM 事件中的经典坑:
javascript
button.addEventListener("click", function () {
console.log(this); // button 元素
});
button.addEventListener("click", () => {
console.log(this); // 外层 this,不是 button
});
第 6 题:解释事件循环(Event Loop)
参考答案
JS 是单线程。异步靠 任务队列 调度:
- 执行同步代码(调用栈)
- 微任务全部清空(
Promise.then、MutationObserver、queueMicrotask) - 取一个宏任务(
setTimeout、setInterval、I/O) - 重复 2~3
javascript
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// 输出顺序:1 → 4 → 3 → 2
进阶题:
javascript
console.log("start");
setTimeout(() => {
console.log("timeout");
Promise.resolve().then(() => console.log("promise in timeout"));
}, 0);
Promise.resolve()
.then(() => {
console.log("promise1");
return Promise.resolve();
})
.then(() => console.log("promise2"));
console.log("end");
// start → end → promise1 → promise2 → timeout → promise in timeout
记忆口诀: 同步 → 微任务清完 → 一个宏任务 → 再清微任务。
第 7 题:Promise 常用 API 及手写思路
参考答案
Promise 三种状态:pending → fulfilled / rejected,不可逆。
javascript
// 基础用法
fetchData()
.then((data) => process(data))
.catch((err) => console.error(err))
.finally(() => hideLoading());
// Promise.all ------ 全部成功才成功,一个失败即失败
Promise.all([p1, p2, p3]).then(([a, b, c]) => {});
// Promise.race ------ 谁先 settle 用谁
Promise.race([timeout(3000), fetch("/api")]);
// Promise.allSettled ------ 等全部结束,不因单个失败中断
Promise.allSettled([p1, p2]).then((results) => {
results.forEach((r) => {
if (r.status === "fulfilled") console.log(r.value);
else console.log(r.reason);
});
});
极简手写 Promise.then 核心:
javascript
class MyPromise {
constructor(executor) {
this.state = "pending";
this.value = undefined;
this.callbacks = [];
const resolve = (val) => {
if (this.state !== "pending") return;
this.state = "fulfilled";
this.value = val;
this.callbacks.forEach((cb) => cb.onFulfilled(val));
};
executor(resolve);
}
then(onFulfilled) {
return new MyPromise((resolve) => {
if (this.state === "fulfilled") {
resolve(onFulfilled(this.value));
} else {
this.callbacks.push({
onFulfilled: (v) => resolve(onFulfilled(v)),
});
}
});
}
}
第 8 题:浅拷贝和深拷贝的区别?如何实现?
参考答案
| 浅拷贝 | 深拷贝 | |
|---|---|---|
| 一层对象 | 新对象 | 新对象 |
| 嵌套引用 | 共享 | 递归复制 |
javascript
const original = { a: 1, nested: { b: 2 } };
// 浅拷贝
const shallow1 = { ...original };
const shallow2 = Object.assign({}, original);
shallow1.nested.b = 99;
original.nested.b; // 99 ← 嵌套仍共享
// 深拷贝 ------ 简单场景
const deep1 = structuredClone(original); // 现代浏览器 / Node 17+
// 深拷贝 ------ 手写(处理循环引用)
function deepClone(value, cache = new WeakMap()) {
if (value === null || typeof value !== "object") return value;
if (cache.has(value)) return cache.get(value);
if (Array.isArray(value)) {
const arr = [];
cache.set(value, arr);
value.forEach((item, i) => {
arr[i] = deepClone(item, cache);
});
return arr;
}
const obj = {};
cache.set(value, obj);
for (const key of Object.keys(value)) {
obj[key] = deepClone(value[key], cache);
}
return obj;
}
const deep2 = deepClone(original);
deep2.nested.b = 0;
original.nested.b; // 99 ← 不受影响
注意: JSON.parse(JSON.stringify(obj)) 无法拷贝 undefined、函数、Symbol、循环引用。
第 9 题:手写防抖(debounce)和节流(throttle)
参考答案
- 防抖: 连续触发时,只执行最后一次(搜索框输入)
- 节流: 固定时间窗口内最多执行一次(滚动、resize)
javascript
// 防抖
function debounce(fn, delay = 300) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流(时间戳版)
function throttle(fn, interval = 300) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn.apply(this, args);
}
};
}
// 使用示例
const onSearch = debounce((keyword) => {
console.log("搜索:", keyword);
}, 500);
const onScroll = throttle(() => {
console.log("scroll", window.scrollY);
}, 200);
input.addEventListener("input", (e) => onSearch(e.target.value));
window.addEventListener("scroll", onScroll);
防抖「立即执行版」------ 第一次立刻触发,之后等待静默期:
javascript
function debounceLeading(fn, delay) {
let timer = null;
return function (...args) {
if (!timer) fn.apply(this, args);
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
}, delay);
};
}
第 10 题:数组去重有哪些方法?
参考答案
javascript
const arr = [1, 2, 2, 3, "3", {}, {}];
// 1. Set(最常用)
const unique1 = [...new Set(arr)];
// [1, 2, 3, "3", {}, {}] ← 对象引用不同,不会去重
// 2. filter + indexOf(仅 primitive)
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);
// 3. reduce
const unique3 = arr.reduce((acc, cur) => {
return acc.includes(cur) ? acc : [...acc, cur];
}, []);
// 4. 对象数组按 id 去重
const users = [
{ id: 1, name: "A" },
{ id: 2, name: "B" },
{ id: 1, name: "A2" },
];
const map = new Map();
users.forEach((u) => map.set(u.id, u));
const uniqueUsers = [...map.values()];
// [{ id:1, name:"A2" }, { id:2, name:"B" }]
// 5. 按 JSON 字符串去重(简单对象可用,有局限)
function uniqueByJson(arr) {
const seen = new Set();
return arr.filter((item) => {
const key = JSON.stringify(item);
if (seen.has(key)) return false;
seen.add(key);
return true;
});
}
面试总结: 原始值用 Set;对象数组用 Map 按 key 去重;要知道 Set 对 {} 是按引用判等。
Day 1 小结
| 题号 | 考点 | 必会程度 |
|---|---|---|
| 1 | 类型判断 | ⭐⭐⭐ |
| 2 | == vs === | ⭐⭐⭐ |
| 3 | 闭包 | ⭐⭐⭐ |
| 4 | 原型链 | ⭐⭐⭐ |
| 5 | this | ⭐⭐⭐ |
| 6 | 事件循环 | ⭐⭐⭐ |
| 7 | Promise | ⭐⭐⭐ |
| 8 | 深拷贝 | ⭐⭐⭐ |
| 9 | 防抖节流 | ⭐⭐⭐ |
| 10 | 数组去重 | ⭐⭐ |
练习方式: 每题先遮住答案,出声答 1 分钟,再对照代码跑一遍。
系列预告 & 引流(文末)
📅 系列更新计划
- Day 2:CSS 布局 10 题
- Day 3:Vue 3 十题
- Day 4:React 十题
🔗 面试帮站内还有更多 JS 专题题(含完整长答案):
https://mianshibang.cn/questions/frontend-javascript
刷完通用题,还可以用「简历 + JD」做 AI 押题:
https://mianshibang.cn/interview/start
新用户注册送 1 次免费 AI 简历押题 体验。
收藏本文,明天更 Day 2 👋
#JavaScript #前端面试 #每日十题 #闭包 #事件循环 #春招 #八股文 #面试帮
复制说明
- 打开本文件,从 「正文(以下整段复制到编辑器)」 下方
---之后复制到文末标签 - 粘贴到 CSDN Markdown 编辑器(代码块会保留格式)
- 若用富文本编辑器,建议切换为 Markdown 模式再粘贴
系列文件规划
| 文件 | 状态 |
|---|---|
引流-前端每日10题-Day1-JavaScript.md |
✅ 本篇 |
引流-前端每日10题-Day2-CSS.md |
待写 |
引流-前端每日10题-Day3-Vue3.md |
待写 |
引流-前端每日10题-Day4-React.md |
待写 |