1. JavaScript基础核心知识点闯关练习
作为一名前端开发者,我深知JavaScript基础的重要性。很多同学在学习过程中容易陷入"一看就会,一写就废"的困境。为此,我设计了这个闯关练习,通过5大关卡、30+道精选题目,带你系统掌握JS核心知识点。
1.1 为什么需要这样的闯关练习?
在实际开发中,JavaScript的基础概念经常成为面试和工作的"拦路虎"。我见过太多同学因为对闭包、原型链等概念理解不深,导致代码出现各种难以排查的问题。这个练习的设计初衷就是帮助大家:
- 建立完整的知识体系,避免碎片化学习
- 通过实践加深理解,不只是停留在理论层面
- 发现自己的知识盲区,有针对性地提高
提示:建议先自己思考答案,再看解析。直接看答案的学习效果会大打折扣。
2. 第1关:数据类型与变量(基础入门)
2.1 JavaScript的数据类型系统
JavaScript的数据类型可以分为两大类:
-
基本数据类型(原始类型):
- Number
- String
- Boolean
- Null
- Undefined
- Symbol (ES6新增)
- BigInt (ES2020新增)
-
引用数据类型:
- Object
- Array
- Function
- Date
- RegExp等
存储方式差异:
- 基本类型:存储在栈内存中,直接访问值
- 引用类型:存储在堆内存中,栈中存储的是引用地址
2.2 类型检测与常见陷阱
javascript复制console.log(typeof null); // "object" (历史遗留问题)
console.log(typeof NaN); // "number" (NaN是特殊的数字值)
console.log(typeof []); // "object"
这里有几个关键点需要注意:
typeof null返回"object"是语言设计初期的错误,但为了兼容性一直保留- NaN虽然是"Not a Number",但它确实是Number类型
- 数组本质上是特殊的对象,所以typeof返回"object"
2.3 变量声明方式对比
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 是 | 否(TDZ) | 否(TDZ) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 初始值要求 | 不需要 | 不需要 | 必须初始化 |
| 可修改性 | 是 | 是 | 不可修改(基本类型) |
注意:const声明的对象属性可以修改,只是不能重新赋值整个对象
2.4 隐式类型转换详解
javascript复制console.log('5' + 3); // "53" (字符串拼接)
console.log('5' - 3); // 2 (数字运算)
console.log('5' * 3); // 15 (数字运算)
console.log([] + []); // "" (空字符串)
console.log([] + {}); // "[object Object]"
类型转换规则:
+操作符优先字符串拼接- 其他算术运算符会将操作数转为数字
- 对象转为原始值时,会调用valueOf()和toString()方法
3. 第2关:作用域与闭包(高频必考)
3.1 作用域链与执行上下文
JavaScript采用词法作用域(静态作用域),作用域链的构建是在函数定义时就确定的。执行上下文包含三个重要部分:
- 变量对象(VO)
- 作用域链
- this指向
作用域类型对比:
- 全局作用域:整个程序生命周期存在
- 函数作用域:函数调用时创建,执行完毕销毁
- 块级作用域:ES6引入,由let/const声明
3.2 经典闭包问题分析
javascript复制for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i); // 输出5个5
}, 0);
}
问题原因:
- var声明的i是函数作用域,整个循环共享同一个i
- setTimeout回调在循环结束后执行,此时i已经是5
解决方案:
- 使用let声明i(创建块级作用域):
javascript复制for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); // 0,1,2,3,4 }, 0); } - 使用IIFE创建闭包:
javascript复制for (var i = 0; i < 5; i++) { (function(j) { setTimeout(() => { console.log(j); // 0,1,2,3,4 }, 0); })(i); }
3.3 闭包的实际应用
闭包是指有权访问另一个函数作用域中变量的函数。实际应用场景包括:
- 模块模式(封装私有变量)
- 函数柯里化
- 记忆化(缓存计算结果)
- 事件处理回调
javascript复制// 计数器工厂
function createCounter() {
let count = 0;
return {
increment: () => ++count,
get: () => count,
reset: () => count = 0
};
}
const counter = createCounter();
counter.increment();
console.log(counter.get()); // 1
4. 第3关:this指向与原型/原型链(核心难点)
4.1 this绑定规则详解
this的指向由调用方式决定,有四种绑定规则:
-
默认绑定:非严格模式下指向window,严格模式为undefined
javascript复制function foo() { console.log(this); } foo(); // window/undefined -
隐式绑定:指向调用对象
javascript复制const obj = { foo: function() { console.log(this); } }; obj.foo(); // obj -
显式绑定:通过call/apply/bind指定
javascript复制function foo() { console.log(this); } foo.call({name: 'obj'}); // {name: 'obj'} -
new绑定:指向新创建的对象
javascript复制function Foo() { this.name = 'foo'; } const f = new Foo(); console.log(f.name); // 'foo'
4.2 原型与原型链机制
每个对象都有一个__proto__属性指向其原型对象,而函数有prototype属性。
javascript复制function Foo() {}
Foo.prototype.name = 'Foo';
const f1 = new Foo();
const f2 = new Foo();
f1.name = 'f1';
console.log(f1.name); // 'f1' (自身属性)
console.log(f2.name); // 'Foo' (原型属性)
console.log(Foo.prototype.name); // 'Foo'
原型链查找规则:
- 先在对象自身属性中查找
- 找不到则通过
__proto__向上查找 - 直到Object.prototype(原型链顶端)
5. 第4关:异步编程与Event Loop(现代JS必备)
5.1 JavaScript执行模型
虽然JS是单线程的,但通过Event Loop实现了非阻塞IO。执行顺序:
- 同步代码(调用栈)
- 微任务(Promise.then, process.nextTick)
- 宏任务(setTimeout, setInterval, I/O)
javascript复制console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出顺序:1, 4, 3, 2
5.2 async/await原理
async函数本质上是Generator函数的语法糖,返回一个Promise:
javascript复制async function asyncFunc() {
console.log('A');
await Promise.resolve();
console.log('B');
}
// 等价于:
function asyncFunc() {
return Promise.resolve().then(() => {
console.log('A');
return Promise.resolve();
}).then(() => {
console.log('B');
});
}
5.3 Promise实现核心
手写Promise.all要点:
- 返回一个新的Promise
- 维护一个结果数组和完成计数器
- 每个Promise resolve时将结果放入对应位置
- 全部完成后resolve结果数组
- 任何一个reject则立即reject
6. 第5关:数组、对象与ES6+特性(实战应用)
6.1 数组操作最佳实践
数组去重方法对比:
-
Set(ES6最简单方式):
javascript复制const unique = [...new Set(array)]; -
filter + indexOf:
javascript复制const unique = array.filter((item, index) => array.indexOf(item) === index); -
reduce:
javascript复制const unique = array.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], []);
6.2 深拷贝实现方案
浅拷贝只复制一层属性,深拷贝递归复制所有层级。实现深拷贝需要考虑:
- 循环引用
- 特殊对象(Date, RegExp等)
- 性能优化
javascript复制function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj);
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
6.3 ES6+核心特性
解构赋值:
javascript复制const { name, age } = person;
const [first, ...rest] = array;
扩展运算符:
javascript复制const newObj = { ...obj1, ...obj2 };
const newArr = [...arr1, ...arr2];
Map vs Object:
- Map的key可以是任意类型
- Map维护插入顺序
- Map有size属性
- Map在频繁增删键值对时性能更好
7. 学习建议与进阶路线
通过这5关的练习,你应该对JavaScript的核心概念有了更深入的理解。接下来可以:
- 深入理解V8引擎工作原理
- 学习设计模式在JS中的应用
- 掌握TypeScript类型系统
- 研究前端框架源码(如React、Vue)
- 参与开源项目实践
记住,编程能力的提升不在于看了多少教程,而在于写了多少代码。建议你把每个题目都实际运行一遍,尝试修改参数观察不同结果,这样才能真正掌握这些概念。