,大纲收到了!这标题够接地气,我直接开整。咱们不搞那些“一、二、三“的八股文,就当我在
写到这发现字数早就超了,但感觉还有好多没唠完。HTML5 出来都十年多了,现在聊它好像有点"考古"的意思,但说实话,我面试过的前端里,能把 Canvas 高清方案讲清楚的不到一半,能把 Web Workers 用在实际项目里的更是凤毛麟角。技术这东西,知道和会用是两码事,会用和用得漂亮又是两码事。希望这篇啰里吧嗦的文章,能让你少踩几个我当年踩过的坑。最后送句话:别被"新特性"三个字吓到,也别为了用

,大纲收到了!这标题够接地气,我直接开整。咱们不搞那些"一、二、三"的八股文,就当我在
- 前端萌新别慌:HTML5 新特性全解析(附实战避坑指南)
好的,大纲收到了!这标题够接地气,我直接开整。咱们不搞那些"一、二、三"的八股文,就当我在群里语音转文字,想到哪说到哪。
前端萌新别慌:HTML5 新特性全解析(附实战避坑指南)
刚入行就被老前端甩一脸"这都不用 HTML5?"
这事儿我太熟了。记得我第一份工作, mentor 看我写的代码,眉头皱得能夹死蚊子:“你这 div 套 div 的,俄罗斯套娃呢?HTML5 都出来十年了兄弟。”
我当时就懵了,HTML5 不就是加了个 video 标签吗?还能有啥?结果回家一查,好家伙,这哪是"加个标签"这么简单,这简直是给前端发了一套新装备,还是史诗级的那种。
今天咱就把这些装备一件件拆开看,顺便告诉你哪些是真香,哪些是鸡肋,以及——最重要的——实际项目里怎么踩坑怎么爬出来。
语义化标签:别再写 div 汤了,真的
先说最基础的。我知道你们刚学的时候,老师教的是"div 是块级元素,span 是行内元素,万事万物皆可 div"。这话没错,但那是 2008 年的玩法。现在都 2024 年了,你还在写 <div class="header"> 吗?
HTML5 给咱们塞了一堆语义化标签:header、nav、main、article、section、aside、footer、figure、figcaption、time、mark 等等。听着挺多,其实常用就那几个。
先来个反面教材,看看我刚入行时写的代码:
<!-- 别笑,我真的写过这种 -->
<div class="header">
<div class="logo">我的博客</div>
<div class="nav">
<div class="nav-item">首页</div>
<div class="nav-item">文章</div>
<div class="nav-item">关于</div>
</div>
</div>
<div class="main">
<div class="article">
<div class="title">HTML5 真香</div>
<div class="content">文章内容...</div>
</div>
</div>
<div class="footer">版权所有</div>
这啥啊?div 开会呢?屏幕阅读器读到这直接崩溃,SEO 爬虫来了也懵逼:这页面到底啥结构?
改成语义化版本:
<!-- 这才像人写的 -->
<header>
<h1>我的博客</h1>
<nav>
<ul>
<li><a href="/">首页</a></li>
<li><a href="/posts">文章</a></li>
<li><a href="/about">关于</a></li>
</ul>
</nav>
</header>
<main>
<article>
<header>
<h2>HTML5 真香</h2>
<time datetime="2024-01-15">2024年1月15日</time>
</header>
<section>
<p>文章内容...</p>
</section>
</article>
</main>
<footer>
<p>© 2024 我的博客</p>
</footer>
舒服了不?一眼看过去就知道哪是导航哪是正文。而且你猜怎么着,CSS 选择器都能省事儿了——直接 header { ... } 就行,不用写 .header { ... },类名都少想几个。
但这里有个大坑,我见过太多人为了语义化而语义化。比如:
<!-- 求你别这么干 -->
<section>
<article>
<section>
<article>
<div>套娃一时爽,维护火葬场</div>
</article>
</section>
</article>
</section>
语义化是让人看懂的,不是让你玩标签嵌套深度挑战的。原则很简单:能用合适的标签就别用 div,但别为了用而用。
SEO 到底管不管用? 这事儿我专门测过。Google 的爬虫确实会读这些标签,article 里的内容权重会比普通 div 高一点,但也就高一点。别指望换个标签就能从第十页蹦到第一页,内容才是王道。不过 accessibility(无障碍访问)这块是实打实的提升,屏幕阅读器用户会感谢你的。
表单验证:JS 写的校验逻辑可以删一半了
以前做表单校验,得写一堆 JS:获取 input、正则匹配、显示错误提示、阻止提交……烦都烦死了。HTML5 直接给 input 加了校验属性,原生就能干这事儿。
最基础的用法:
<!-- 邮箱校验,以前要写正则,现在一行搞定 -->
<input type="email" placeholder="请输入邮箱" required>
<!-- 手机号,配合 pattern 用 -->
<input type="tel" pattern="[0-9]{11}" placeholder="11位手机号" required>
<!-- 数字范围,做分页器的时候贼好用 -->
<input type="number" min="1" max="100" step="1" value="1">
<!-- URL 地址 -->
<input type="url" placeholder="https://example.com" required>
<!-- 日期选择,别再用日期插件了 -->
<input type="date" min="2024-01-01" max="2024-12-31">
<!-- 颜色选择器,做主题配置时直接白嫖 -->
<input type="color" value="#ff0000">
看到没?type="email" 会自动校验邮箱格式,required 就是必填,pattern 支持正则。用户输错了浏览器自己弹提示,都不用你写代码。
但原生的提示样式丑啊! 而且不同浏览器长得还不一样。Chrome 是黄色的,Firefox 是红色的,Safari 又是一种风格。做 toB 项目的时候,产品经理看了一眼直接摇头:“这提示框跟我们设计规范不符。”
解决方案:用 CSS 和 JS 接管
<form id="myForm">
<div class="form-item">
<input
type="email"
id="email"
placeholder="工作邮箱"
required
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
>
<span class="error-msg"></span>
</div>
<button type="submit">提交</button>
</form>
<style>
/* 验证失败的样式 */
input:invalid {
border-color: #ff4d4f;
}
input:invalid + .error-msg::after {
content: "邮箱格式不对啊兄弟";
color: #ff4d4f;
font-size: 12px;
}
/* 验证通过的样式 */
input:valid {
border-color: #52c41a;
}
/* 还没碰过的时候不显示错误(避免一进页面就红一片) */
input:placeholder-shown {
border-color: #d9d9d9;
}
input:placeholder-shown + .error-msg::after {
content: "";
}
</style>
<script>
const form = document.getElementById('myForm');
const emailInput = document.getElementById('email');
// 拦截提交,做自定义处理
form.addEventListener('submit', (e) => {
// 先让浏览器自己验一遍
if (!form.checkValidity()) {
e.preventDefault();
// 找到第一个错的 input,聚焦过去
const firstInvalid = form.querySelector(':invalid');
firstInvalid?.focus();
return;
}
// 验过了,走你的业务逻辑
e.preventDefault(); // 防止真提交,走 AJAX
console.log('邮箱合法,准备发送:', emailInput.value);
});
// 实时校验,输入时就去掉错误样式
emailInput.addEventListener('input', () => {
if (emailInput.validity.valid) {
// 可以在这里清掉错误提示
console.log('格式对了');
}
});
</script>
这里用了 CSS 的 :invalid 和 :valid 伪类,配合 :placeholder-shown 避免页面一加载就红彤彤一片。form.checkValidity() 是 HTML5 的 API,能触发所有 input 的校验并返回布尔值。
还有个坑: type="number" 在移动端会唤出数字键盘,这很好。但它有个诡异的行为——允许输入 e(科学计数法),而且 value 拿到的是空字符串。如果你做计算器或者金额输入,建议用 type="text" + inputmode="numeric" + pattern="[0-9]*",这样既能唤数字键盘,又不会搞出科学计数法。
多媒体支持:Flash 终于进棺材了,但视频播放还是一堆坑
HTML5 的 <video> 和 <audio> 标签,算是把 Flash 彻底送进了历史垃圾堆。现在你在网页里放视频,再也不用装插件了。
基础用法:
<!-- 最简单的视频播放 -->
<video src="movie.mp4" controls width="640" height="360"></video>
<!-- 实际项目里得考虑格式兼容 -->
<video controls width="640" height="360" poster="cover.jpg">
<source src="movie.mp4" type="video/mp4">
<source src="movie.webm" type="video/webm">
<source src="movie.ogv" type="video/ogg">
您的浏览器不支持视频播放,请升级浏览器或<a href="movie.mp4">下载视频</a>
</video>
poster 是封面图,controls 显示控制条。source 标签按顺序加载,浏览器支持哪个格式就播哪个。一般 mp4(H.264)兼容性最好,webm 是 Google 推的,体积小但 Safari 老版本不支持。
但这里坑多了去了。
坑一:自动播放 你想让视频自动播放?加个 autoplay 就行?太天真了。Chrome 从 2018 年开始就限制了自动播放——带声音的视频必须用户交互后才能播放。解决办法:
<!-- 静音自动播放,一般用于背景视频 -->
<video autoplay muted loop playsinline>
<source src="bg-video.mp4" type="video/mp4">
</video>
muted 静音,playsinline 防止 iOS 强制全屏。做官网背景视频时常用这个组合。
坑二:全屏播放 自定义播放器的时候,全屏 API 得这么写:
const video = document.querySelector('video');
const fullscreenBtn = document.querySelector('.fullscreen-btn');
fullscreenBtn.addEventListener('click', () => {
if (video.requestFullscreen) {
video.requestFullscreen();
} else if (video.webkitRequestFullscreen) {
// Safari 前缀
video.webkitRequestFullscreen();
} else if (video.msRequestFullscreen) {
// IE11,虽然没人用了但以防万一
video.msRequestFullscreen();
}
});
// 监听全屏变化,更新你的自定义按钮状态
document.addEventListener('fullscreenchange', () => {
if (document.fullscreenElement) {
fullscreenBtn.textContent = '退出全屏';
} else {
fullscreenBtn.textContent = '全屏';
}
});
坑三:视频预加载 preload 属性有三个值:none(不预加载)、metadata(只加载元数据)、auto(尽可能预加载)。但 auto 在移动端会被浏览器无视——省流量嘛。如果你做短视频列表,得自己用 Intersection Observer 做懒加载:
// 视频懒加载,进入视口才加载
const videoObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const video = entry.target;
const source = video.querySelector('source');
// 把 data-src 换成 src
if (source.dataset.src) {
source.src = source.dataset.src;
video.load(); // 手动触发加载
videoObserver.unobserve(video);
}
}
});
}, {
rootMargin: '100px' // 提前 100px 开始加载
});
document.querySelectorAll('video[data-lazy]').forEach(video => {
videoObserver.observe(video);
});
HTML 结构长这样:
<video controls data-lazy muted>
<source data-src="video1.mp4" type="video/mp4">
</video>
音频也是类似的套路,但音频有个坑:iOS Safari 里音频必须在用户交互后播放,而且页面不可见时会自动暂停。做背景音乐的功能时,别指望能一直播。
本地存储:cookie 那老古董该退休了
以前存点数据只能靠 cookie,大小限制 4KB,每次 HTTP 请求还自动带上,拖慢速度。HTML5 给了咱们 localStorage 和 sessionStorage,各 5MB 空间,存字符串妥妥的。
基础操作:
// localStorage - 关了浏览器还在
localStorage.setItem('username', '张三');
localStorage.getItem('username'); // "张三"
localStorage.removeItem('username');
localStorage.clear(); // 清空所有
// sessionStorage - 关了标签页就没了
sessionStorage.setItem('tempData', '一些临时数据');
实际项目里怎么用? 举个购物车的小例子:
class ShoppingCart {
constructor() {
this.key = 'cart_data';
this.cart = this.load();
}
// 从 localStorage 加载
load() {
try {
const data = localStorage.getItem(this.key);
return data ? JSON.parse(data) : [];
} catch (e) {
console.error('购物车数据坏了:', e);
return [];
}
}
// 保存到 localStorage
save() {
try {
localStorage.setItem(this.key, JSON.stringify(this.cart));
} catch (e) {
// 存满了或者用户禁用了
if (e.name === 'QuotaExceededError') {
alert('存储空间满了,请清理一下');
// 可以在这里降级到内存存储
}
}
}
addItem(item) {
const exist = this.cart.find(i => i.id === item.id);
if (exist) {
exist.quantity += 1;
} else {
this.cart.push({ ...item, quantity: 1 });
}
this.save();
}
removeItem(id) {
this.cart = this.cart.filter(item => item.id !== id);
this.save();
}
// 获取总数,用于显示小红点
getTotalCount() {
return this.cart.reduce((sum, item) => sum + item.quantity, 0);
}
// 清空(用户下单后)
clear() {
this.cart = [];
localStorage.removeItem(this.key);
}
}
// 使用
const cart = new ShoppingCart();
cart.addItem({ id: 1, name: '机械键盘', price: 299 });
console.log(cart.getTotalCount()); // 1
但坑来了:localStorage 被用户清了咋办? 这是常态,不是意外。Safari 的无痕模式、用户手动清缓存、iOS 空间不足时系统自动清理,都会导致数据丢失。所以永远不要只依赖 localStorage 做唯一数据源,它只是缓存,真正的数据得存后端。
还有个坑:存对象得序列化。 直接 localStorage.setItem('user', {name: '张三'}) 存进去的是 [object Object],必须 JSON.stringify。而且 JSON.parse 的时候可能报错,得包 try-catch。
存储事件: 同一个域名下的不同标签页,localStorage 是共享的。一个页面改了,其他页面能收到通知:
// A 页面
localStorage.setItem('message', '你好 from A 页面 ' + Date.now());
// B 页面(同域名)
window.addEventListener('storage', (e) => {
// e.key 是哪个 key 变了
// e.oldValue 旧值
// e.newValue 新值
console.log('收到消息:', e.newValue);
});
这个特性可以用来做跨标签页通信,比如一个页面登录了,其他页面自动刷新登录状态。
Canvas 和 SVG:让网页动起来,但 Retina 屏上糊成马赛克?
做图表、游戏、图片编辑的时候,Canvas 是神器。但第一次用的时候,我差点被分辨率搞疯。
基础用法:
<canvas id="myCanvas" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 画个矩形
ctx.fillStyle = '#1890ff';
ctx.fillRect(50, 50, 200, 100);
// 画个圆
ctx.beginPath();
ctx.arc(400, 300, 50, 0, Math.PI * 2);
ctx.fillStyle = '#ff4d4f';
ctx.fill();
ctx.strokeStyle = '#333';
ctx.lineWidth = 3;
ctx.stroke();
// 写字
ctx.font = 'bold 24px Arial';
ctx.fillStyle = '#333';
ctx.fillText('Hello Canvas', 50, 200);
</script>
看着没问题对吧?但在 MacBook Pro 或者 iPhone 这种 Retina 屏(设备像素比 DPR 是 2 或 3)上打开,你会发现线条发虚,文字有锯齿。
解决方案:根据 DPR 缩放 Canvas
function setupHiDPICanvas(canvas, width, height) {
// 获取设备像素比,没有就默认 1
const dpr = window.devicePixelRatio || 1;
// 设置 Canvas 的实际像素尺寸(物理像素)
canvas.width = width * dpr;
canvas.height = height * dpr;
// 设置 CSS 尺寸(逻辑像素,保持布局不变)
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
// 缩放上下文,让所有绘制操作按 DPR 来
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
return ctx;
}
// 使用
const canvas = document.getElementById('myCanvas');
const ctx = setupHiDPICanvas(canvas, 800, 600);
// 现在按 800x600 的逻辑坐标画就行,自动高清
ctx.fillRect(50, 50, 200, 100); // 实际画的是 100x200 物理像素(DPR=2 时)
画图片也要注意:
function drawImageHiDPI(ctx, img, x, y, width, height) {
const dpr = window.devicePixelRatio || 1;
// 如果原图是 @2x 或 @3x 的,按 DPR 截取对应区域
ctx.drawImage(
img,
0, 0, img.width, img.height, // 原图全部
x, y, width, height // 目标尺寸(逻辑像素)
);
}
SVG 是另一种方案,它是矢量图,天生不怕缩放。做图标、Logo、简单的数据可视化时,SVG 比 Canvas 更合适。
<!-- 内联 SVG -->
<svg width="200" height="200" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="#1890ff" stroke="#333" stroke-width="2"/>
<text x="50" y="55" text-anchor="middle" fill="white" font-size="14">SVG</text>
</svg>
viewBox 是关键,它定义了画布的坐标系,SVG 会自适应容器大小。不管放大多少倍,边缘都是锐利的。
Canvas vs SVG 怎么选?
- 像素操作、游戏、复杂动画、图片处理 → Canvas
- 图标、图表、需要交互的矢量图形、SEO 友好 → SVG
Web Workers:主线程终于不卡成 PPT 了
JS 是单线程的,这是它的原罪。你如果在主线程里做个复杂计算,页面直接卡死,用户点按钮没反应,滚动也卡住,体验极差。
Web Workers 让你能在后台线程跑 JS,主线程该干嘛干嘛。
基础用法:
// main.js(主线程)
const worker = new Worker('worker.js');
// 发消息给 Worker
worker.postMessage({
type: 'CALCULATE',
data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
});
// 接收 Worker 的消息
worker.onmessage = (e) => {
console.log('计算结果:', e.data.result);
};
// 错误处理
worker.onerror = (err) => {
console.error('Worker 报错:', err.message);
};
// 不用了记得关掉
// worker.terminate();
// worker.js(Worker 线程)
self.onmessage = (e) => {
const { type, data } = e.data;
if (type === 'CALCULATE') {
// 这里做耗时计算,不会卡主线程
const result = heavyCalculation(data);
self.postMessage({ result });
}
};
function heavyCalculation(arr) {
// 模拟复杂计算
let sum = 0;
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < 1000000; j++) {
sum += arr[i] * j;
}
}
return sum;
}
但 Worker 里不能用 DOM! 这是最大的限制。document、window 这些对象都没有,你只能做纯计算。如果需要操作 DOM,得算完把结果发回主线程,让主线程去更新。
实际场景:图片压缩。 用户上传了一张 10MB 的图,你要压缩成 1MB 再上传。如果在主线程做,用户界面会卡好几秒。放 Worker 里:
// image-worker.js
self.onmessage = async (e) => {
const { imageData, quality } = e.data;
// 用 OffscreenCanvas 处理图片(Worker 里不能用普通 Canvas)
const bitmap = await createImageBitmap(imageData);
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(bitmap, 0, 0);
// 压缩成 blob
const blob = await canvas.convertToBlob({
type: 'image/jpeg',
quality: quality || 0.8
});
self.postMessage({ blob, size: blob.size });
};
Inline Worker: 有时候你不想单独建个 worker.js 文件(比如用 Webpack 打包时),可以用 Blob URL:
const workerCode = `
self.onmessage = (e) => {
const result = e.data * 2;
self.postMessage({ result });
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
worker.postMessage(21);
worker.onmessage = (e) => {
console.log(e.data.result); // 42
};
坑: Worker 创建有开销,别为了算个 1+1 也开 Worker。而且 postMessage 传数据是拷贝(结构化克隆),大对象传输也有性能损耗。现代浏览器支持 Transferable Objects,可以零拷贝转移所有权:
// 主线程
const buffer = new ArrayBuffer(1024 * 1024 * 100); // 100MB
worker.postMessage({ buffer }, [buffer]);
// 转移后,主线程不能再访问 buffer,否则报错
那些隐藏彩蛋:Geolocation、拖拽、History API
HTML5 还塞了一堆小功能,平时不常用,但特定场景下能救命。
Geolocation 定位
// 获取当前位置
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude, accuracy } = position.coords;
console.log(`纬度: ${latitude}, 经度: ${longitude}, 精度: ${accuracy}米`);
// 可以配合地图 API 用
// 比如计算用户离门店有多远
},
(error) => {
switch(error.code) {
case error.PERMISSION_DENIED:
console.log('用户拒绝了定位请求');
break;
case error.POSITION_UNAVAILABLE:
console.log('位置信息不可用');
break;
case error.TIMEOUT:
console.log('获取位置超时');
break;
}
},
{
enableHighAccuracy: true, // 高精度,但费电
timeout: 5000, // 5秒超时
maximumAge: 0 // 不缓存,必须获取最新位置
}
);
// 持续追踪位置(比如做跑步 App)
const watchId = navigator.geolocation.watchPosition((pos) => {
console.log('新位置:', pos.coords);
});
// 停止追踪:navigator.geolocation.clearWatch(watchId);
坑: HTTPS 才能用(本地开发 localhost 除外)。而且用户可能拒绝授权,一定要做好降级方案,比如让用户手动输入地址。
拖拽 API
以前做拖拽要靠 mouse 事件自己算位置,HTML5 原生支持了。
<div class="drag-list">
<div class="item" draggable="true" data-id="1">任务 A</div>
<div class="item" draggable="true" data-id="2">任务 B</div>
<div class="item" draggable="true" data-id="3">任务 C</div>
</div>
<script>
let draggedItem = null;
document.querySelectorAll('.item').forEach(item => {
// 开始拖拽
item.addEventListener('dragstart', (e) => {
draggedItem = item;
e.dataTransfer.effectAllowed = 'move';
// 设置拖拽时的图像(可选)
e.dataTransfer.setData('text/plain', item.dataset.id);
item.style.opacity = '0.5';
});
// 拖拽结束
item.addEventListener('dragend', () => {
item.style.opacity = '1';
draggedItem = null;
});
// 拖拽经过(必须阻止默认行为才能放置)
item.addEventListener('dragover', (e) => {
e.preventDefault(); // 关键!
e.dataTransfer.dropEffect = 'move';
});
// 放置
item.addEventListener('drop', (e) => {
e.preventDefault();
if (draggedItem !== item) {
// 交换 DOM 位置
const parent = item.parentNode;
const siblings = [...parent.children];
const draggedIndex = siblings.indexOf(draggedItem);
const droppedIndex = siblings.indexOf(item);
if (draggedIndex < droppedIndex) {
parent.insertBefore(draggedItem, item.nextSibling);
} else {
parent.insertBefore(draggedItem, item);
}
// 这里可以发请求更新服务器顺序
console.log('新顺序:', [...parent.children].map(c => c.dataset.id));
}
});
});
</script>
坑: 移动端支持不好,iOS Safari 以前完全不支持,现在支持了但还有 bug。做移动端拖拽建议用 touch 事件或者直接用库(Sortable.js 之类的)。
History API:单页应用的路由基石
history.pushState 和 popstate 事件,是现代前端路由的基础。React Router、Vue Router 底层都靠这个。
// 不刷新页面,改变 URL
history.pushState(
{ page: 2, userId: 123 }, // state 对象,可以存数据
'页面标题', // 大部分浏览器无视这个参数
'/page/2' // 新 URL(必须同域)
);
// 用户点击后退按钮时触发
window.addEventListener('popstate', (e) => {
console.log('后退/前进了', e.state); // 拿到之前存的 state
// 根据 state 或当前 URL 渲染对应页面
renderPage(location.pathname);
});
// 替换当前历史记录(不能后退回来)
history.replaceState({ page: 1 }, '', '/page/1');
实际做个极简路由:
class SimpleRouter {
constructor() {
this.routes = new Map();
// 监听后退/前进
window.addEventListener('popstate', (e) => {
this.render(location.pathname, e.state);
});
// 拦截链接点击(不是外部链接的)
document.addEventListener('click', (e) => {
if (e.target.matches('a[href^="/"]')) {
e.preventDefault();
this.navigate(e.target.getAttribute('href'));
}
});
}
// 注册路由
on(path, handler) {
this.routes.set(path, handler);
}
// 导航
navigate(path, state = {}) {
history.pushState(state, '', path);
this.render(path, state);
}
// 渲染
render(path, state) {
const handler = this.routes.get(path) || this.routes.get('*');
if (handler) {
handler(state);
}
}
}
// 使用
const router = new SimpleRouter();
router.on('/', () => {
document.getElementById('app').innerHTML = '<h1>首页</h1>';
});
router.on('/about', () => {
document.getElementById('app').innerHTML = '<h1>关于我们</h1>';
});
router.on('*', () => {
document.getElementById('app').innerHTML = '<h1>404</h1>';
});
手把手教你把新特性塞进老项目还不翻车
老项目可能是 jQuery 时代的,甚至可能是前后端不分离的 JSP/PHP 模板。怎么渐进式升级?
策略一:局部试水,别一上来就重构全站
比如你想用 localStorage 存用户偏好设置,但老代码用 cookie 存登录态。别动登录态,只在新功能上用 localStorage:
// 封装一层,兼容老代码
const storage = {
get(key) {
// 优先读 localStorage,没有就读 cookie(兼容老数据)
return localStorage.getItem(key) || getCookie(key);
},
set(key, value) {
localStorage.setItem(key, value);
// 同时写 cookie,保证老代码能读到
setCookie(key, value, 7);
}
};
策略二:语义化标签渐进增强
老项目全是 div,你不可能一次性全改。新开发的模块用语义化标签,老的保持不动。CSS 选择器这样写,兼容新旧:
/* 既支持老的选择器,也支持新的标签 */
.header,
header {
background: #333;
color: white;
}
.article,
div.article {
margin-bottom: 20px;
}
策略三:Polyfill 填坑
IE11 这种老古董不支持某些 API,用 polyfill 垫一下。比如 localStorage:
// 简单的 localStorage polyfill(用 cookie 模拟)
if (!window.localStorage) {
window.localStorage = {
getItem(key) {
const match = document.cookie.match(new RegExp('(^| )' + key + '=([^;]+)'));
return match ? decodeURIComponent(match[2]) : null;
},
setItem(key, value) {
document.cookie = `${key}=${encodeURIComponent(value)};path=/`;
},
removeItem(key) {
document.cookie = `${key}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
}
};
}
策略四:用 Modernizr 做特性检测
别用 if (navigator.userAgent.indexOf('IE') > -1) 这种 UA 嗅探,不靠谱。用特性检测:
// 原生方式
if ('localStorage' in window) {
// 支持 localStorage
} else {
// 降级方案
}
// 或者用 Modernizr 库
if (Modernizr.canvas) {
// 支持 Canvas
} else {
// 用 Flash 或图片降级(虽然现在 Flash 也没了)
}
这些"新特性"其实早该扔进历史垃圾堆了
HTML5 也不是啥都是宝,有些东西设计得挺鸡肋,或者已经被更好的方案替代。
1. WebSQL 浏览器端的 SQL 数据库,Chrome、Safari 支持过,但标准被废弃了,别用。用 IndexedDB 或者直接用 localStorage 存简单数据。
2. Application Cache(AppCache) 离线应用缓存,manifest 文件那个。坑巨多,缓存更新了用户还是看到旧版,清都清不掉。已经被 Service Worker 取代。
3. Keygen 标签 生成密钥对的,从来就没被广泛支持过,HTML5.2 里删了。
4. 一些 input type 比如 type="datetime",已经被移除了,用 datetime-local 代替。type="month"、type="week" 这些移动端支持很差,不如用日期选择器组件。
团队协作时踩过的雷
最后唠点实际的。你学会了技术,但进了公司会发现,技术只占工作的 30%,剩下 70% 是沟通、撕逼和妥协。
雷一:设计师非要你用 figure 标签放 logo
设计师看了篇"HTML5 语义化指南",指着你的代码说:“logo 应该用 figure 标签包起来,再配个 figcaption。”
你一看,好家伙:
<!-- 设计师想要的 -->
<figure>
<img src="logo.png" alt="公司 Logo">
<figcaption>公司 Logo</figcaption>
</figure>
这啥啊?figure 是用在"自成一体的内容,可以独立存在"的场景,比如文章配图、代码块。logo 是页面装饰,用 h1 > a > img 或者直接用背景图更合适。
这时候你得委婉地解释:“figure 一般用于内容图片,logo 属于界面元素,用 header 里的 a 标签包裹对 SEO 更友好……” 同时把 Google 的 HTML 规范链接甩过去。
雷二:后端说"你们前端存本地不就行了"
做表单的时候,后端不想做自动保存草稿功能,让你用 localStorage 存。用户清缓存丢了数据,锅甩你头上。
这时候得坚持:“localStorage 不可靠,用户可能用无痕模式,或者手动清数据。关键数据还是得后端存,我可以做个防抖自动保存到服务器,localStorage 只作为离线时的降级方案。”
雷三:产品经理要"像 APP 一样的体验"
听到这句话就头大。他要离线可用、要推送通知、要后台定位、要流畅动画。你解释:“PWA 能做一部分,但 iOS 限制很多……” 他:“我不管,竞品能做我们为啥不能?”
这时候得拿出技术方案和时间评估,把限制条件列清楚,让他选:是做一套阉割版快速上线,还是投入两个月做原生 APP?
结语:HTML5 是起点,不是终点
写到这发现字数早就超了,但感觉还有好多没唠完。HTML5 出来都十年多了,现在聊它好像有点"考古"的意思,但说实话,我面试过的前端里,能把 Canvas 高清方案讲清楚的不到一半,能把 Web Workers 用在实际项目里的更是凤毛麟角。
技术这东西,知道和会用是两码事,会用和用得漂亮又是两码事。希望这篇啰里吧嗦的文章,能让你少踩几个我当年踩过的坑。
最后送句话:别被"新特性"三个字吓到,也别为了用而用。适合业务的才是最好的。有时候一个简简单单的 details 标签就能解决的手风琴效果,你非要上 React 组件,那才是真的 over engineering。
行了,就聊到这。代码写得开心,bug 少少。 🍻
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 |
|
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

更多推荐



所有评论(0)