3. 是否了解 typeof?
✅ 3.1 基础数据类型 & 引用数据类型(ES6 前):
- 基本数据类型:
string,number,boolean,undefined,null,symbol,bigint - 引用数据类型:
object,array,function,date,regexp,自定义对象等
✅ 3.2 为什么 typeof null === 'object'?
-
历史遗留 Bug,JS 最初的实现是用 低位 type tag 来标识数据类型:
- object 的 type tag 是
0 null被表示为指针地址0x00,因此其 tag 也被识别为object
- object 的 type tag 是
-
无法修复,兼容性问题太大,已被写入规范
✅ 3.3 除了 typeof,还有其它获取具体类型的方法吗?
- 是的,可以用:
Object.prototype.toString.call(value);
示例:
Object.prototype.toString.call([]); // '[object Array]'
Object.prototype.toString.call(null); // '[object Null]'
Object.prototype.toString.call(() => {}); // '[object Function]'
✅ 3.4 Object.prototype.toString.apply() 可以吗?
- 可以,和
call等效,只是参数形式不同:
Object.prototype.toString.call(value); // call(thisArg, arg1, ...)
Object.prototype.toString.apply(value); // apply(thisArg, [args])
对 toString 来说没有参数,因此 call(value) 和 apply(value) 等价。
✅ 3.5 call / apply / bind 区别?
| 方法 | 含义 | 参数形式 | 是否立即执行 |
|---|---|---|---|
| call | 调用函数,指定 this | 逗号分隔参数列表 | ✅ |
| apply | 调用函数,指定 this | 参数为数组 | ✅ |
| bind | 返回一个 this 被绑定的新函数 | 参数逗号分隔 | ❌(延迟执行) |
示例:
fn.call(thisArg, a, b);
fn.apply(thisArg, [a, b]);
const newFn = fn.bind(thisArg, a); // newFn() 才执行
4. 了解事件委托机制吗?
事件委托:通过把事件监听器绑定在父元素上,利用事件冒泡机制,监听子元素的行为。
document.getElementById('parent').addEventListener('click', (e) => {
if (e.target.matches('.child')) {
// 只处理子元素点击
}
});
✅ 4.1 好处:
- 减少事件绑定次数,节省内存(如列表、表格中绑定大量子元素)
- 方便处理动态插入的子元素
- 提升性能、降低耦合
✅ 4.2 target 与 currentTarget 区别:
| 属性 | 含义 | 举例 |
|---|---|---|
event.target | 实际触发事件的元素 | 点击了某个 <li> |
event.currentTarget | 当前监听事件的元素(绑定 listener 的那个) | 父元素 <ul> |
5. 了解进程和线程吗?
- 进程(Process) :程序的独立运行单位,有独立内存空间。
- 线程(Thread) :进程中的最小执行单位,同一进程内线程共享内存空间。
✅ 5.1 什么是多线程?
- 同一进程中并发运行多个线程。
- 比如:浏览器中渲染线程、JS 引擎线程、网络线程并发运行。
✅ 5.2 多线程会有哪些问题?怎么解决?
常见问题:
- 竞争条件(两个线程同时写数据)
- 死锁(两个线程相互等待)
- 资源同步(线程之间顺序不一致)
解决方案:
- 加锁机制(mutex)
- PV 信号量机制:P(wait)和 V(signal)操作,控制临界区
- 原子操作:如 CAS(Compare and Swap)
✅ 5.3 为什么 JavaScript 是单线程的?
- JS 是单线程的,源于浏览器设计初衷:为了避免 DOM 操作混乱。
- 同一时间只能有一个执行上下文,避免状态混乱。
但浏览器环境通过 事件循环机制 + Web APIs 实现了伪多线程(宏任务、微任务、异步事件、Web Worker 等)来模拟并发。
✅ 1. CSS 动画中 transform 的优势
- 不会触发布局(reflow)或重绘(repaint)
transform改变的是合成层(compositing) ,由 GPU 加速,性能远高于top/left - 触发硬件加速(GPU)
CSS3 中transform和opacity被称为 GPU 友好属性 - 与
transition、animation联动良好
面试表达句式:
使用
transform动画相比top/left不会引起布局重排,能走合成线程,提升性能。
✅ 2. 浏览器进程与线程 & Web Worker 是否能加速渲染?
-
浏览器多进程架构:每个标签页/插件/渲染进程独立
-
渲染线程负责:HTML 解析 → DOM 构建 → 样式计算 → 布局 → 绘制
-
Web Worker:
- 是 JS 的多线程能力
- 用于耗时计算、IO,不会阻塞主线程
- 但 不能操作 DOM,无法加速渲染
✅ 3. HTTP 缓存核心字段
强缓存(不发请求):
Cache-Control: max-age=3600Expires: GMT时间
协商缓存(需要服务端确认):
ETag/If-None-MatchLast-Modified/If-Modified-Since
缓存流程可画一张图或举例说明
✅ 4. 闭包 VS 类 的区别
-
闭包:函数携带其定义时的词法作用域,保存局部变量
function makeCounter() { let count = 0; return function () { return ++count; } } -
类:通过
this保存状态,用new实例化class Counter { constructor() { this.count = 0; } next() { return ++this.count; } }
✅ 关键区别:
- 闭包基于作用域链保存状态;类基于实例化对象(this)
- 闭包更轻量但可能造成内存泄漏;类更易于扩展和继承
✅ 5. Promise / await 原理(封装递归)
await是Promise.then的语法糖,本质是将异步代码串行化async函数返回一个 Promise- 实现
await行为底层可通过递归处理 promise 队列
可手写一个:
function run(generatorFn) {
const gen = generatorFn();
function step(val) {
const result = gen.next(val);
if (result.done) return;
Promise.resolve(result.value).then(step);
}
step();
}
✅ 6. Vue 对 DOM 的理解
- Vue2 使用虚拟 DOM,通过 diff 算法最小化真实 DOM 操作
- Vue3 使用 Proxy 实现响应式系统(替代 Vue2 的 defineProperty)
- DOM 更新是异步的,封装在
nextTick中
✅ 7. 垃圾回收 & WeakMap
-
JS 使用 标记清除 和 引用计数
-
弱引用(WeakMap)不会阻止垃圾回收
-
使用场景:
- 缓存(不影响被缓存对象生命周期)
- 私有属性封装
✅ 8. AOP 和 IoC
-
AOP(面向切面编程) :关注横切逻辑(如日志、权限),可通过高阶函数封装:
const withLog = (fn) => (...args) => { console.log('before'); const res = fn(...args); console.log('after'); return res; } -
IoC(控制反转) :框架控制对象的创建和注入,开发者只关注逻辑
常见于依赖注入容器、Spring/Angular 等框架中
✅ 9. Token 和 JWT
- Token:用于标识用户登录状态,通常由服务器生成
- JWT(JSON Web Token):结构为
Header.Payload.Signature,是 Token 的一种实现形式
优点:
- 无需服务器存储状态
- 前端可解析 payload 中的信息(非敏感)
✅ 10. Webpack Loader
常见 loader:
babel-loader:转译 ES6+css-loader:解析 CSS 文件style-loader:将样式插入 DOMfile-loader/url-loader:处理图片、字体等资源vue-loader/ts-loader:框架专属
✅ 11. 手写链表(含 add、print、reverse)
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
class LinkedList {
constructor() {
this.head = null;
}
add(val) {
const node = new Node(val);
if (!this.head) {
this.head = node;
} else {
let p = this.head;
while (p.next) p = p.next;
p.next = node;
}
}
print() {
let p = this.head;
const res = [];
while (p) {
res.push(p.val);
p = p.next;
}
console.log(res.join(' -> '));
}
reverse() {
let prev = null, curr = this.head;
while (curr) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
this.head = prev;
}
}