要面试了整理了一份前端基础知识点速查手册,涵盖变量、数据类型、作用域、闭包、原型链、异步编程及 ES6 特性,直击要点,简单易懂。
内容经过ai二次编辑补全优化,看着没啥问题,有问题欢迎指出。
1. let、var 和 const 的区别
-
var:- 变量提升:声明被提升到作用域顶部,值为
undefined。 - 函数作用域:在函数内声明则为局部变量,否则为全局变量。
- 允许重复声明:可能导致变量覆盖。
- 变量提升:声明被提升到作用域顶部,值为
-
let:- 块级作用域:仅在
{}内有效(如if、for)。 - 暂时性死区(TDZ):声明前访问会抛出
ReferenceError。 - 禁止重复声明:同一作用域内不可重复定义。
- 块级作用域:仅在
-
const:- 块级作用域,同
let。 - 声明时必须初始化,且不可重新赋值。
- 引用类型(如对象、数组)内部属性可修改,但不可指向新对象。
- 块级作用域,同
2. 数据类型
-
基本类型(7种) :
String、Number、Boolean、Undefined、Null、BigInt(大整数)、Symbol(唯一值)。- 按值存储,直接比较值。
-
引用类型:
Object、Array、Function、Date、Set、Map等。- 按引用存储,比较时比较内存地址。
3. 堆和栈
-
栈内存:
- 存储基本类型值和引用类型的地址指针。
- 由系统自动分配和释放,效率高但空间有限。
-
堆内存:
- 存储引用类型的实际数据。
- 动态分配空间,需手动管理(JS通过垃圾回收自动处理)。
4. 深拷贝与浅拷贝
-
浅拷贝:
-
实现方式:
Object.assign({}, obj)- 展开运算符
{...obj}/[...arr] Array.prototype.slice()/concat()
-
注意:嵌套对象仍共享引用。
-
-
深拷贝:
-
JSON方法:
JSON.parse(JSON.stringify(obj))- 缺陷:丢失函数、
undefined、Symbol;无法处理循环引用;日期转为字符串。
-
递归实现:
- 需处理循环引用(使用
WeakMap缓存)、特殊类型(如Date、RegExp)。
- 需处理循环引用(使用
-
现代 API:
structuredClone()(浏览器环境,支持部分类型)。
-
库函数:Lodash
_.cloneDeep()。
-
5. this 的指向
this对象不同环境绑定的对象不同。- 有显示绑定,隐式绑定,默认绑定
- 可以通过
call、apply、bind修改
6. 普通函数 vs 箭头函数
-
普通函数:
this动态绑定- 默认绑定:非严格模式指向
window,严格模式为undefined。 - 隐式绑定:由调用对象决定(如
obj.fn(),this指向obj)。 - 显式绑定:
call、apply、bind指定this。
- 默认绑定:非严格模式指向
- 可通过
new Function()创建函数。 arguments
-
箭头函数:
- 无自身
this,继承外层作用域的this。 - 不可用作构造函数。
- 无
arguments对象。
- 无自身
7. call、apply、bind
-
共同点:修改函数
this指向。 -
区别:
call:立即执行,参数逐个传递(fn.call(obj, 1, 2))。apply:立即执行,参数以数组传递(fn.apply(obj, [1, 2]))。bind:返回新函数,可预设参数(const newFn = fn.bind(obj, 1))。
8. 数据类型判断
-
typeof:- 返回字符串,如
typeof 'str'→"string"。 - 局限性:
typeof null→"object",无法区分对象类型。
- 返回字符串,如
-
instanceof:- 检测构造函数的
prototype是否在对象原型链上。 - 示例:
arr instanceof Array→true。
- 检测构造函数的
-
Object.prototype.toString:- 最精确方式:
Object.prototype.toString.call(obj).slice(8, -1)。 - 结果如
"[object Array]"、"[object Date]"。
- 最精确方式:
9. instanceof 原理
-
递归检查对象的
__proto__是否等于构造函数的prototype。 -
手写实现:
javascript
复制
function myInstanceof(obj, Constructor) { let proto = Object.getPrototypeOf(obj); while (proto) { if (proto === Constructor.prototype) return true; proto = Object.getPrototypeOf(proto); } return false; }
10. 原型与原型链
-
构造函数:通过
new创建对象,其prototype属性为实例原型。 -
实例:通过
__proto__访问原型,形成链式结构。 -
原型链终点:
Object.prototype.__proto__→null。 -
示例:
javascript
复制
function Person() {} const p = new Person(); // p.__proto__ === Person.prototype // Person.prototype.__proto__ === Object.prototype
11. 继承
-
ES5 常用继承方式:
-
原型链继承:子类原型指向父类实例。
- 缺点:父类引用属性被所有子类实例共享。
-
构造函数继承:在子类构造函数中调用父类构造函数。
- 缺点:无法继承父类原型方法。
-
组合继承:结合原型链和构造函数。
- 缺点:父类构造函数被调用两次。
-
寄生组合继承:优化组合继承,通过
Object.create()避免重复调用父类构造函数。
-
-
ES6
class继承:- 使用
extends和super,本质是语法糖,底层基于原型链。
- 使用
12. new 操作符过程
- 创建空对象
obj,设置obj.__proto__ = Constructor.prototype。 - 执行构造函数,
Constructor.call(obj, ...args)。 - 若构造函数返回对象,则返回该对象;否则返回
obj。
13. ES6+ 核心特性
-
语法增强:
- 箭头函数、模板字符串、解构赋值。
- 默认参数、剩余参数
...、展开运算符。 class类与继承、super关键字。
-
数据结构:
Set(去重集合)、Map(键值对)、WeakMap(弱引用)。
-
异步编程:
Promise、async/await。
-
模块化:
import/export,支持静态分析和 Tree Shaking。
14. Promise
-
状态:
pending→fulfilled(resolve)或rejected(reject)。
-
链式调用:
javascript
复制
fetch(url)
.then(res => res.json())
.catch(err => console.log(err))
.finally(() => { /* 清理逻辑 */ });
-
静态方法:
Promise.all():全部成功则返回结果数组,任一失败则立即拒绝。Promise.race():首个完成的 Promise 决定结果。Promise.allSettled():等待所有 Promise 完成,返回状态描述数组。
15. 作用域与作用域链
-
共享属性的载体
构造函数的prototype对象用于存放所有实例共享的属性和方法。
实例对象的__proto__指向其构造函数的prototype。 -
原型链
当访问对象属性时,若该属性在对象本身不存在,会沿着__proto__一层层查找,直到找到为止。
最顶层为Object.prototype,其__proto__为null,查找到此处终止,这就构成了原型链。 -
优点
- 共享属性和方法,避免每个实例都持有一份,节省内存。
- 通过原型链实现继承,使得对象能访问到父级原型上定义的成员。
-
缺点
- 如果原型上存放了可变数据(例如数组、对象),一处的修改可能会影响所有实例,因为它们共享同一引用。
-
继承
原型机制是实现继承的重要手段,通过设置原型链,子对象可以继承父对象的属性和方法。
obj.__proto__.constructor.prototype.__proto__
16. 闭包
-
定义:函数和它当时能用到的变量“绑”在一起。即使这个函数被放到别的地方执行,它依然能记住并使用那些原本属于它的变量。表现就是函数访问外部的变量。
-
应用场景:
- 模块化(私有变量)。
- 防抖/节流、柯里化。
-
内存泄漏:
- 示例:未清理的 DOM 事件监听器引用闭包变量。
例子 1:计数器
// 在这个例子中,`createCounter` 函数返回一个内部函数,这个内部函数可以访问并修改外部函数中的变量 `count`。即使 `createCounter` 执行完毕后,返回的函数依然能记住 `count` 的值。
function createCounter() {
let count = 0; // 外部变量
// 内部函数,形成闭包,可以访问 count
return function () {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 输出: 1
counter(); // 输出: 2
例子 2:带参数的闭包
// 这个例子展示了如何通过闭包“记住”外部函数传入的参数,从而生成一个带有特定前缀的日志函数。
function createLogger(prefix) {
// 内部函数可以访问外部参数 prefix
return function(message) {
console.log(`${prefix}: ${message}`);
};
}
const infoLogger = createLogger("INFO");
const errorLogger = createLogger("ERROR");
infoLogger("服务器启动成功"); // 输出: INFO: 服务器启动成功
errorLogger("连接失败"); // 输出: ERROR: 连接失败
例子 3:延迟执行
// 这个例子展示了闭包在异步场景下的应用。内部函数 `delayedFunction` 延迟执行时依然能访问到外部变量 `msg`。
function delayedGreeting() {
const msg = "Hello, Closure!";
// 定时器内的回调形成闭包,能访问 msg
setTimeout(function delayedFunction() {
console.log(msg);
}, 1000);
}
delayedGreeting(); // 1秒后输出: Hello, Closure!
17. 防抖与节流
-
防抖(Debounce) :多次触发事件,只执行第一次或最后一次事件
function debounce(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } -
节流(Throttle) :多次触发事件,只执行固定间隔时间的事件
function throttle(fn, interval) { let lastTime = 0; return function(...args) { const now = Date.now(); if (now - lastTime >= interval) { fn.apply(this, args); lastTime = now; } }; }
18. 事件循环(Event Loop)
-
执行顺序:
- 同步代码(调用栈)。
- 所有微任务(
Promise.then、MutationObserver)。 - 一个宏任务(如
setTimeout)。 - 重复步骤 2-3。
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出顺序:1 → 4 → 3 → 2
19. 模块化
-
CommonJS:
- 同步加载,适用于 Node.js。
module.exports导出,require()导入。
-
ES Module:
- 静态分析,支持 Tree Shaking。
export/import语法,浏览器原生支持。
-
差异:
- CommonJS 输出值的拷贝,ES Module 输出值的引用。
- ES Module 顶层
this为undefined,CommonJS 为exports对象。
20. DOM 事件传播
-
事件委托:
- 利用冒泡,将事件监听器绑定到父元素,通过
event.target处理子元素事件。 - 优点:减少内存消耗,动态元素无需重新绑定。
- 利用冒泡,将事件监听器绑定到父元素,通过
-
阻止默认行为:
event.preventDefault()(如阻止表单提交)。
21. 垃圾回收
-
标记清除:从根对象(全局变量)出发,标记可达对象,清除未标记的。
-
引用计数:记录对象被引用次数,归零时回收。缺陷:循环引用无法回收。
-
内存泄漏场景:
- 意外全局变量。
- 未解绑的事件监听器、定时器。
- 闭包中未释放的变量。