一、JavaScript 核心基础
1. 闭包 (Closure)
- 定义 :函数和其周围状态(词法环境)的引用捆绑在一起构成闭包。简单说,就是内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。
- 作用:数据私有化、延长变量生命周期、柯里化。
- 缺点:滥用会导致内存泄漏(变量无法被回收)。
2. 原型链 (Prototype Chain)
- 机制 :每个对象都有一个
__proto__指向它的原型对象 (prototype)。当访问一个属性时,如果自身没有,就会沿着__proto__向上查找,直到null。这条链条就是原型链。 - 终点 :
Object.prototype.__proto__ === null。
3. 数据类型
- 基本类型 (栈) :
String,Number,Boolean,Null,Undefined,Symbol,BigInt。按值访问,复制是复制值。 - 引用类型 (堆) :
Object,Array,Function,Date等。按引用访问,复制是复制地址(指针)。
4. 冒泡排序 (Bubble Sort)
- 原理:重复遍历数组,比较相邻元素,如果顺序错误就交换。每一轮将最大的元素"浮"到末尾。
- 复杂度:时间 O(n2)O(n2) ,空间 O(1)O(1) 。
- 代码简述:双重循环,外层控制轮数,内层控制比较交换。
5. 递归算法
- 定义:函数在内部调用自身。
- 关键 :必须有终止条件(基准情况),否则会栈溢出。
- 应用:阶乘、斐波那契数列、深拷贝、树形结构遍历。
6. 数组排序
- 原生方法 :
array.sort((a, b) => a - b)。注意默认是按字符串 Unicode 排序,数字需传比较函数。 - 常见算法:冒泡、选择、插入、快速排序 (Quick Sort, 最常用,平均 O(nlogn)O(nlogn) )、归并排序。
7. 浅拷贝 vs 深拷贝
- 浅拷贝 :只复制第一层属性。如果属性是引用类型,复制的是地址(改一处全变)。
- 方法:
Object.assign(), 展开运算符{...obj},Array.slice().
- 方法:
- 深拷贝 :递归复制所有层级,生成全新的对象,互不影响。
- 方法:
JSON.parse(JSON.stringify(obj))(有缺陷,不能处理函数/undefined/date),structuredClone()(新API), 手写递归,lodash_.cloneDeep。
- 方法:
8. this 的指向
- 默认绑定 :独立函数调用,指向全局 (
window/undefined)。 - 隐式绑定 :
obj.fn(),指向obj。 - 显式绑定 :
call,apply,bind,指向指定的对象。 - new 绑定:构造函数调用,指向新创建的实例。
- 箭头函数 :没有自己的 this ,继承自上一层作用域的
this。
9. 继承
- 原型链继承:子类原型 = 父类实例。缺点:引用属性共享。
- 构造函数继承 :
Parent.call(this)。缺点:方法无法复用。 - 组合继承:原型链 + 构造函数(最常用)。
- ES6 Class 继承 :
class Child extends Parent,底层仍是原型链,但语法更清晰。
10. 判断是不是原型的方法 / for in 遍历原型
-
判断原型 :
A.prototype.isPrototypeOf(B)或B instanceof A。 -
for in 遍历 :会 遍历对象自身可枚举属性 以及 原型链上的可枚举属性。
-
过滤原型 :通常配合
hasOwnProperty使用:javascriptjavascriptfor (let key in obj) { if (obj.hasOwnProperty(key)) { ... } // 只处理自身属性 }
11. 遍历数组/对象
- 数组 :
for,forEach,map(返回新数组),filter,reduce,for...of(推荐,可中断)。 - 对象 :
for...in(含原型),Object.keys(),Object.values(),Object.entries()。
12. 事件循环机制 (Event Loop)
- 流程 :
- 执行同步代码(调用栈)。
- 异步任务放入任务队列(宏任务:setTimeout, I/O; 微任务:Promise, nextTick)。
- 调用栈清空后,先执行完所有微任务。
- 再执行一个宏任务。
- 循环往复。
- 关键点:微任务优先级高于宏任务。
二、浏览器、网络与安全
13. 输入 URL 到页面展示的详细过程
- DNS 解析:域名 -> IP 地址。
- TCP 连接:三次握手。
- 发送 HTTP 请求。
- 服务器处理并返回响应。
- 浏览器渲染 :
- 构建 DOM 树。
- 构建 CSSOM 树。
- 生成渲染树 (Render Tree)。
- 布局 (Layout/Reflow)。
- 绘制 (Paint)。
- TCP 断开:四次挥手。
14. 常见的状态码
- 200:成功。
- 301/302:永久/临时重定向。
- 304:资源未修改(使用缓存)。
- 400:请求参数错误。
- 401:未授权。
- 403:禁止访问。
- 404:资源不存在。
- 500:服务器内部错误。
- 502/503/504:网关错误/服务不可用/超时。
15. 跨域解决
- 原因:浏览器的同源策略(协议、域名、端口任一不同即跨域)。
- 解决方案 :
- CORS (后端设置
Access-Control-Allow-Origin) - 最主流。 - Nginx 反向代理 - 生产环境常用。
- Webpack/Vite 开发代理 (
proxy)。 - postMessage (窗口间通信)。
- (旧方案:JSONP,仅支持 GET)。
- CORS (后端设置
16. HTTP 缓存
- 强缓存 :不发送请求,直接用本地。
- 头:
Cache-Control: max-age=3600,Expires。
- 头:
- 协商缓存 :发送请求,服务器判断是否过期(返回 304)。
- 头:
Last-Modified/If-Modified-Since(基于时间),Etag/If-None-Match(基于内容哈希,更精准)。
- 头:
17. XSS 攻击 vs CSRF 攻击
- XSS (跨站脚本攻击) :
- 原理:注入恶意脚本到网页,用户浏览时执行,窃取 Cookie 等。
- 防御:转义输入输出,CSP (内容安全策略),HttpOnly Cookie。
- CSRF (跨站请求伪造) :
- 原理:诱导用户在已登录状态下访问恶意网站,向后端发送请求(如转账)。
- 防御 :SameSite Cookie,验证 Referer/Origin,CSRF Token。
- 区别:XSS 是借用户的浏览器执行脚本;CSRF 是借用户的身份发送请求。
18. 怎么打断点
- Sources 面板:点击行号。
- 代码中 :
debugger;语句。 - DOM 断点:Elements 面板 -> 右键节点 -> Break on。
- XHR/Fetch 断点:Sources 面板右侧 XHR Breakpoints。
19. Git 冲突怎么解决
git pull或git merge时发现冲突。- 打开冲突文件,找到
<<<<<<<,=======,>>>>>>>标记。 - 手动保留需要的代码,删除标记符号。
git add <file>标记为已解决。git commit完成合并。
三、CSS、HTML5 与工程化
20. H5 (HTML5) & C3 (CSS3)
- H5 新特性 :语义化标签 (
header,footer),多媒体 (video,audio),存储 (localStorage,sessionStorage),绘图 (Canvas,SVG),地理定位,Web Workers。 - C3 新特性 :选择器增强,盒模型 (
box-sizing),Flex/Grid 布局,动画 (transition,animation,transform),媒体查询 (响应式),变量 (--color)。
21. Less/Sass 用法和编译
- 用法 :嵌套写法、变量 (
$color)、混合 (mixin)、函数、继承 (@extend)。 - 编译:浏览器不直接识别,需通过构建工具 (Webpack Loader, Vite Plugin) 或 CLI 编译成 CSS。
22. 兼容性解决
- CSS :加厂商前缀 (
-webkit-,-moz-),使用 Autoprefixer 插件自动添加;Polyfill。 - JS :Babel 转译 ES6+ 为 ES5;引入核心 Polyfill (
core-js)。 - 策略:渐进增强,优雅降级。
23. Rem 原理 & 移动端适配
- Rem :相对于根元素 (
html) 的font-size。 - 原理 :通过 JS 或 CSS 媒体查询,根据屏幕宽度动态设置
html的font-size。例如:屏幕宽 375px,设html font-size为 37.5px,则1rem = 37.5px。设计稿 100px -> 写2.66rem。 - 方案 :
flexible.js方案,或vw/vh单位(更现代,1vw = 1% 屏幕宽)。
24. 性能优化
- 网络:压缩资源 (Gzip/Brotli),图片优化 (WebP, 懒加载),CDN 加速,减少 HTTP 请求 (合并/雪碧图),HTTP/2。
- 渲染 :减少重排重绘,使用
will-change,虚拟列表 (长列表),防抖节流。 - 代码:路由懒加载,组件懒加载,Tree Shaking,移除无用代码。
- 缓存:合理利用强缓存和协商缓存。
四、Vue 框架专项
25. 生命周期 (Vue 2 vs Vue 3)
- Vue 2 :
beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed。 - Vue 3 (Composition API) :
setup()替代了beforeCreate和created。其他钩子前加on,如onMounted,onUpdated,onBeforeUnmount。 - 常用 :
created/mounted(发起请求),beforeUnmount(清除定时器/解绑事件)。
26. 传值的方式
- 父传子 :
props。 - 子传父 :
$emit。 - 兄弟/跨级 :EventBus (Vue2), Provide/Inject, Vuex/Pinia,
$attrs/$listeners。 - 透传 :
$attrs(Vue3 默认开启,Vue2 需配置inheritAttrs)。
27. 计算属性 (Computed) vs 监听属性 (Watch)
- Computed :
- 有缓存,依赖不变不重新计算。
- 必须return 一个值。
- 不支持异步。
- 适用:一个值受多个值影响(多对一)。
- Watch :
- 无缓存,数据变就执行。
- 支持异步操作。
- 可以监听对象深层变化 (
deep: true)。 - 适用:一个值变化影响多个操作(一对多),或需要执行副作用。
28. Vuex (状态管理)
- 核心概念 :
State:存放数据。Getters:数据的计算属性。Mutations:唯一修改 State 的地方(同步)。Actions:处理异步逻辑,提交 Mutations。Modules:模块化。
- Vue 3 趋势 :推荐使用 Pinia (更轻量,去掉了 Mutation,支持 TS 更好)。
29. 数据双向绑定原理
- Vue 2 :
Object.defineProperty()。- 缺点:无法监听对象属性的新增/删除,无法监听数组下标变化(需用
$set或替换方法)。
- 缺点:无法监听对象属性的新增/删除,无法监听数组下标变化(需用
- Vue 3 :
Proxy。- 优点:直接代理整个对象,完美监听增删改查,性能更好。
30. v-show vs v-if
- v-if :
- 真正销毁/创建 DOM 节点。
- 切换开销大。
- 支持
<template>分组。 - 适用:条件很少改变,或初始不显示。
- v-show :
- 始终渲染,通过 CSS
display: none控制显示。 - 切换开销小。
- 不支持
<template>。 - 适用:频繁切换显示状态。
- 始终渲染,通过 CSS
31. keep-alive
- 作用 :内置组件,用于缓存动态组件 或
router-view,保留组件状态,避免重复渲染。 - 生命周期 :被缓存的组件会多出
activated(激活) 和deactivated(停用) 钩子。 - 属性 :
include(缓存谁),exclude(不缓存谁),max(最大缓存数)。
32. nextTick
- 原理 :将回调函数延迟到下一次 DOM 更新循环结束之后执行。
- 场景 :当你修改了数据,想立即获取更新后的 DOM 元素(因为 DOM 更新是异步的),就需要用
this.$nextTick(() => { ... })或await nextTick()。
一、怎么实现 apply 函数?
Function.prototype.apply 的作用是改变函数运行时的 this 指向 ,并接收一个参数数组来调用函数。
核心思路
- 处理上下文 :如果传入的
thisArg是null或undefined,在非严格模式下指向全局对象(浏览器是window),严格模式下为undefined。 - 临时绑定 :将当前函数(调用
apply的函数)作为传入对象的一个临时属性。 - 执行函数 :通过对象调用该临时属性(此时
this指向该对象),并使用展开运算符...传递参数数组。 - 清理现场:删除临时属性,防止污染原对象。
- 返回结果:返回函数执行的结果。
代码实现
javascript
Function.prototype.myApply = function(context, argsArray) {
// 1. 处理 context:如果是 null/undefined,指向全局对象 (window)
// 注意:在严格模式下 this 可能是 undefined,这里做简单兼容处理
if (context === null || context === undefined) {
context = typeof window !== 'undefined' ? window : globalThis;
} else {
// 如果传入的是基本类型,需要转换为对象 (如 'str' -> new String('str'))
context = Object(context);
}
// 2. 获取调用 myApply 的函数本身
const fn = this;
// 3. 定义一个唯一的 Symbol 键,防止覆盖对象原有属性
const fnKey = Symbol('fn');
// 4. 将函数挂载到 context 上
context[fnKey] = fn;
// 5. 执行函数
let result;
if (!argsArray) {
// 如果没有传参数数组,直接调用
result = context[fnKey]();
} else {
// 如果有参数数组,使用展开运算符传递
// 需确保 argsArray 是类数组对象,否则报错
if (!Array.isArray(argsArray) && !(argsArray instanceof Object)) {
throw new TypeError('CreateListFromArrayLike called on non-object');
}
result = context[fnKey](...argsArray);
}
// 6. 删除临时属性
delete context[fnKey];
// 7. 返回结果
return result;
};
测试用例
javascript
function greet(greeting, punctuation) {
return greeting + ', ' + this.name + punctuation;
}
const person = { name: 'Alice' };
console.log(greet.myApply(person, ['Hello', '!']));
// 输出: "Hello, Alice!"
console.log(greet.myApply(null, ['Hi', '.']));
// 输出: "Hi, undefined." (非严格模式下指向 window,window.name 通常为空或特定值)
二、怎么优化 Webpack 打包?
Webpack 优化主要分为两个维度:构建速度优化 (开发体验)和 打包体积优化(用户体验)。结合 2026 年的技术背景,以下是核心策略:
1. 构建速度优化(让打包更快)
-
开启持久化缓存 (Webpack 5+ 必配) :
利用文件系统缓存,二次构建时复用未变动的模块,速度提升巨大。javascriptmodule.exports = { cache: { type: 'filesystem', // 使用文件系统缓存 buildDependencies: { config: [__filename] } // 配置变更时缓存失效 } }; -
缩小构建范围 :
include/exclude:在loader中明确指定只处理src目录,排除node_modules。resolve.modules:指定模块搜索路径,减少递归查找。resolve.extensions:减少后缀尝试次数,把常用的.js,.jsx放前面。
-
多线程并行处理 :
使用thread-loader将耗时的 loader(如babel-loader,ts-loader)放入 worker 池中并行运行。 -
减少插件使用 :
生产环境才需要的插件(如压缩、提取 CSS)不要放在开发环境配置中。
2. 打包体积优化(让文件更小)
-
Tree Shaking (摇树优化) :
- 确保代码使用 ES Module (
import/export) 语法。 - 在
package.json中设置"sideEffects": false或配置具体文件,标记无副作用模块。 - 生产模式默认开启,但需配合
usedExports: true。
- 确保代码使用 ES Module (
-
代码分割 (Code Splitting) :
- 多入口分离 :配置多个
entry。 - SplitChunksPlugin :提取公共代码(如
vendor包),避免重复打包。 - 动态导入 :使用
import()语法实现路由级或组件级的懒加载。 - optimization: { splitChunks: { chunks: 'all' } // 自动分割所有类型的 chunk }
- 多入口分离 :配置多个
-
压缩与优化 :
- JS/CSS 压缩 :Webpack 5 内置
TerserPlugin和CssMinimizerWebpackPlugin(需安装)。 - 图片压缩 :使用
image-minimizer-webpack-plugin或squash-images-webpack-plugin。 - Gzip/Brotli :使用
compression-webpack-plugin生成.gz或.br文件,由 Nginx 直接serve。
- JS/CSS 压缩 :Webpack 5 内置
-
外部化依赖 (Externals) :
将大型库(如 React, Vue, ECharts)通过 CDN 引入,不打包进 bundle.javascriptexternals: { react: 'React', 'react-dom': 'ReactDOM' } -
分析工具 :
使用webpack-bundle-analyzer可视化分析包体积,精准定位大文件。
三、讲解下 Webpack 机制
Webpack 是一个静态模块打包工具 。它的核心机制可以概括为:"一切皆模块",从入口出发,递归解析依赖,最终生成一个或多个 Bundle。
1. 核心工作流程 (四大步骤)
-
初始化 (Initialization):
- 读取配置文件 (
webpack.config.js) 和 Shell 命令参数。 - 实例化
Compiler对象(负责整个构建过程的核心对象)。 - 注册所有配置的插件 (
plugins),监听生命周期钩子。
- 读取配置文件 (
-
编译 (Compilation):
- 从
entry入口文件开始。 - 调用配置的 Loader 对模块进行翻译(如 TS -> JS, SCSS -> CSS)。
- 分析模块语法(AST),找出该模块依赖的其他模块(
import,require)。 - 递归地对每个依赖模块执行上述过程,直到所有依赖都被处理。
- 最终形成一张完整的 依赖图 (Dependency Graph)。
- 从
-
输出 (Emission):
- 根据依赖图和配置,将模块组合成一个或多个 Chunk。
- 将 Chunk 转换成最终的 Bundle 文件(包含运行时代码和模块代码)。
- 写入文件系统(磁盘)。
-
完成 (Done):
- 触发完成钩子,通知插件构建结束。
2. 核心概念解析
- Entry (入口):构建依赖图的起点。Webpack 从这里开始识别哪些模块应该被纳入。
- Output (输出) :定义 Bundle 文件的输出路径和文件名(通常配合
[contenthash]实现缓存优化)。 - Loader (加载器) :
- 作用:Webpack 原生只懂 JS 和 JSON。Loader 让它能处理其他类型的文件(CSS, Image, TS 等),将其转换为有效模块。
- 机制 :链式执行,从右向左(或从下到上)。例如
style-loader(css-loader('a.scss'))。
- Plugin (插件) :
- 作用:扩展 Webpack 的功能,执行更广泛的任务(如打包优化、资源管理、环境变量注入)。
- 机制 :基于 Tapable 库发布 - 订阅模式,在构建流程的特定生命周期钩子(Hook)中注入逻辑。
- Mode (模式) :
development:优化构建速度,启用 NamedModulesPlugin。production:优化输出体积,启用 Tree Shaking, Scope Hoisting, Uglify/Terser。
- Chunk & Bundle :
- Chunk:代码块,Webpack 内部编译过程中的中间产物。
- Bundle:最终输出的文件,由一个或多个 Chunk 组成。
3. 运行机制图解(简化版)
javascript
[Entry Files]
↓ (读取)
[Loader 转换] (TS->JS, SCSS->CSS)
↓ (AST 分析依赖)
[Dependency Graph] (依赖图)
↓ (合并)
[Chunks] (代码块)
↓ (Plugin 处理/压缩)
[Bundles] (最终文件 -> dist/)
4. 为什么 Webpack 慢?
因为它是全量编译。每次修改代码,它都需要重新走一遍"读取->解析->转换->合并"的流程(虽然有了缓存和 HMR 优化,但原理上仍比 Vite 的 ESM 按需编译重)。这也是为什么现在流行 Vite(开发环境用 ESM 原生能力,生产环境用 Rollup)的原因。