这三样是前端从「会写页面」到「能做项目」的必经之路:Ajax 负责跟服务器说话,Node.js 让 JS 能跑在服务端,webpack 把源码变成浏览器能高效加载的产物。一篇记完三块。
一、Ajax 篇
什么是 Ajax
Ajax(Asynchronous JavaScript and XML)= 在不刷新页面的情况下与服务器交换数据并更新页面的技术。
核心就是浏览器提供的 XMLHttpRequest 对象(现在也有 fetch)。名字里带 XML,但实际用得最多的是 JSON。
1 2 3 4 5 6 7 8 9 10
| const xhr = new XMLHttpRequest() xhr.open('GET', 'https://api.example.com/users') xhr.onload = () => { if (xhr.status === 200) { const data = JSON.parse(xhr.responseText) console.log(data) } } xhr.send()
|
axios(实际项目首选)
学习路径建议:先会用 axios,再回头看 XHR 理解底层原理。
引入
1
| <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13
| axios({ url: '目标资源地址', method: 'GET', params: { page: 1 } }).then(result => { console.log(result.data) })
axios.get('/api/users', { params: { page: 1 } }) axios.post('/api/users', { name: '张三' })
|
把数据渲染到页面:
1
| document.querySelector('.list').innerHTML = htmlString
|
async/await 写法(更推荐)
1 2 3 4 5 6 7 8
| async function getUsers() { try { const { data } = await axios.get('/api/users') return data } catch (err) { console.error('请求失败:', err.message) } }
|
常用配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const request = axios.create({ baseURL: 'https://api.example.com', timeout: 10000, headers: { 'Content-Type': 'application/json' } })
request.interceptors.request.use(config => { config.headers.Authorization = localStorage.getItem('token') return config }) request.interceptors.response.use( res => res.data, err => { if (err.response?.status === 401) location.href = '/login' return Promise.reject(err) } )
|
认识 URL
URL(Uniform Resource Locator)= 统一资源定位符,就是网址。
1 2 3
| https://api.example.com:443/users?id=1&page=2#top └─┬──┘ └──────┬──────┘└┬┘└───┬────┘└──┬──┘ 协议 域名 端口 资源路径 查询参数
|
- 协议:
http / https。HTTP 是超文本传输协议,规定浏览器与服务器之间传输数据的格式
- 域名:服务器的地址(会被 DNS 解析成 IP)
- 资源路径:服务器上资源的位置
- 查询参数:浏览器提供给服务器的额外信息,让服务器返回想要的数据
查询参数
写法是在 URL 后加 ?,多个参数用 & 连接:
用 axios 时不用手动拼字符串,写在 params 里即可:
1 2 3 4 5 6 7 8 9
| axios({ url: '/api/list', params: { 参数名: 值, page: 1, size: 20 } })
|
HTTP 状态码与请求方法
常用方法:
| 方法 |
用途 |
是否有请求体 |
| GET |
查询数据 |
否 |
| POST |
新增数据 |
是 |
| PUT |
整体更新 |
是 |
| PATCH |
局部更新 |
是 |
| DELETE |
删除 |
通常无 |
常见状态码:
| 码 |
含义 |
典型原因 |
| 200 |
成功 |
— |
| 201 |
创建成功 |
POST 成功的常见响应 |
| 301 / 302 |
重定向 |
资源已迁移 |
| 304 |
未修改,走缓存 |
命中协商缓存 |
| 400 |
请求参数错误 |
前端传参不对 |
| 401 |
未授权 |
token 缺失或过期 |
| 403 |
禁止访问 |
权限不足 |
| 404 |
资源不存在 |
URL 写错 |
| 500 |
服务器内部错误 |
后端 bug |
跨域(必踩的坑)
浏览器有同源策略:协议、域名、端口三者任一不同就是跨域,默认会被拦。
| 方案 |
做法 |
场景 |
| CORS(最常用) |
后端响应头加 Access-Control-Allow-Origin |
前后端分离项目 |
| 开发代理 |
Vite / webpack devServer 配置 proxy |
仅解决开发环境 |
| JSONP |
利用 <script> 不受同源限制 |
老方案,只支持 GET |
| Nginx 反向代理 |
运维层面转发 |
生产环境 |
Vite 代理配置示例:
1 2 3 4 5 6 7 8 9 10
| server: { proxy: { '/api': { target: 'https://api.example.com', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } }
|
二、Node.js 篇
它是什么
Node.js 让 JavaScript 跑在服务端(基于 Chrome V8 引擎)。前端学它主要有三个用途:
- 写接口 / 中间层(BFF)
- 跑构建工具(webpack、Vite 都依赖它)
- 写脚本处理文件、自动化
1 2 3
| node -v npm -v node app.js
|
模块化
| 规范 |
引入 |
导出 |
使用环境 |
| CommonJS(CJS) |
require() |
module.exports |
Node 传统方式 |
| ES Module(ESM) |
import |
export |
现代标准,浏览器原生支持 |
1 2 3 4 5 6 7
| const fs = require('fs') module.exports = { add }
import fs from 'fs' export function add(a, b) { return a + b }
|
常用内置模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const fs = require('fs') fs.readFileSync('a.txt', 'utf-8') fs.writeFileSync('b.txt', 'hello') fs.existsSync('a.txt')
const path = require('path') path.join(__dirname, 'public', 'index.html') path.resolve('src/index.js') path.extname('a.png')
const http = require('http') http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }) res.end('Hello Node.js') }).listen(3000)
|
npm 常用命令
1 2 3 4 5 6 7 8
| npm init -y npm install axios npm install webpack -D npm uninstall axios npm update npm run dev npx webpack npm config set registry https://registry.npmmirror.com
|
Express 起个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const express = require('express') const app = express()
app.use(express.json()) app.use(express.static('public'))
app.get('/api/users', (req, res) => { res.json({ code: 200, data: [{ id: 1, name: '张三' }] }) })
app.get('/api/users/:id', (req, res) => { res.json({ id: req.params.id }) })
app.post('/api/users', (req, res) => { res.status(201).json({ msg: '创建成功', data: req.body }) })
app.listen(3000, () => console.log('服务已启动:http://localhost:3000'))
|
中间件(middleware)是 Express 的核心概念:app.use(fn) 注册的函数会按顺序处理每个请求,可以做日志、鉴权、解析 body 等。调用 next() 交给下一个中间件,不调用则请求到此为止。
三、webpack 篇
为什么需要它
浏览器原生只认识 HTML/CSS/JS,而现代开发会用到 ES6+ 语法、TypeScript、Sass、Vue 单文件组件、模块化 import……webpack 的作用就是把这些源码转换、打包成浏览器可直接运行的静态资源,顺带做压缩、分割、加 hash 缓存等优化。
五个核心概念
| 概念 |
作用 |
| entry |
入口,从哪个文件开始分析依赖 |
| output |
输出,打包后放哪、叫什么 |
| loader |
转换器,让 webpack 能处理非 JS 文件(CSS、图片、TS…) |
| plugin |
插件,做 loader 做不了的事(生成 HTML、抽离 CSS、压缩…) |
| mode |
development / production,决定内置优化策略 |
最小可用配置
1
| npm install webpack webpack-cli -D
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = { mode: 'development', entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'js/[name].[contenthash:8].js', clean: true }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' }, { test: /\.(png|jpg|gif|svg)$/, type: 'asset', parser: { dataUrlCondition: { maxSize: 8 * 1024 } } } ] }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }) ], devServer: { port: 8080, open: true, hot: true } }
|
package.json 里加脚本:
1 2 3 4
| "scripts": { "dev": "webpack serve --mode development", "build": "webpack --mode production" }
|
常用 loader
| loader |
作用 |
babel-loader |
ES6+ / JSX 转 ES5(配合 @babel/preset-env) |
css-loader |
解析 CSS 中的 @import、url() |
style-loader |
把 CSS 注入到 <style> 标签 |
sass-loader |
编译 Sass/SCSS |
less-loader |
编译 Less |
postcss-loader |
自动加浏览器前缀、转换新语法 |
vue-loader |
处理 .vue 单文件组件 |
ts-loader / babel-loader |
处理 TypeScript |
use 数组里的 loader 执行顺序是从右到左(从后往前)。所以 ['style-loader', 'css-loader'] 是先跑 css-loader 再跑 style-loader,写反了会报错。
常用 plugin
| plugin |
作用 |
html-webpack-plugin |
自动生成 HTML 并注入打包后的 JS/CSS |
mini-css-extract-plugin |
把 CSS 抽离成独立文件(生产环境用,替代 style-loader) |
css-minimizer-webpack-plugin |
压缩 CSS |
terser-webpack-plugin |
压缩 JS(生产模式默认启用) |
clean-webpack-plugin |
清空输出目录(也可直接用 output.clean) |
copy-webpack-plugin |
拷贝无需处理的静态文件 |
webpack-bundle-analyzer |
可视化分析打包体积 |
生产环境优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| optimization: { usedExports: true, minimize: true, splitChunks: { chunks: 'all', cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', priority: 10 } } } }
|
几条实用建议:
- 文件名加
[contenthash] —— 内容不变则文件名不变,浏览器可长期缓存
- 路由懒加载 —— 用动态
import() 把不同页面拆成独立 chunk
- 按需引入 —— 组件库用
babel-plugin-import 之类只打包用到的部分
- 分析体积 —— 用
webpack-bundle-analyzer 找出超大依赖
环境变量区分
1
| npm install cross-env -D
|
1 2 3
| "scripts": { "build:prod": "cross-env NODE_ENV=production webpack" }
|
1 2 3
| new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) })
|
webpack vs Vite
| 对比 |
webpack |
Vite |
| 启动方式 |
先整体打包再启动 |
原生 ESM,按需编译 |
| 冷启动 |
项目大时较慢 |
极快 |
| 热更新 |
随项目增大变慢 |
几乎恒定快 |
| 生态成熟度 |
⭐ 极高,插件齐全 |
已很完善,仍在追赶 |
| 适用场景 |
大型存量项目、复杂定制 |
新项目、追求开发体验 |
不用纠结:新项目直接用 Vite,开发体验差距明显;公司老项目继续用 webpack,迁移成本往往不划算。
常见坑速查
| 现象 |
原因 |
解法 |
| 请求跨域被拦 |
浏览器同源策略 |
后端开 CORS 或配 devServer proxy |
result.data 是 undefined |
axios 把数据包在 data 里 |
用 res.data 或解构 const { data } = await ... |
| loader 顺序报错 |
use 数组写反了 |
记住从右往左执行 |
| 打包后图片 404 |
路径配置问题 |
设置 output.publicPath |
| 样式没生效 |
生产环境用了 style-loader |
换成 mini-css-extract-plugin |
| 打包很慢 |
没有排除 node_modules / 未开缓存 |
exclude: /node_modules/、开启 cache: { type: 'filesystem' } |
学习建议:先能用 axios 跑通一次接口 → 再用 Node 写个简单接口给自己调 → 最后用构建工具把项目跑起来。这条线走完,前后端是怎么配合的就彻底明白了。