🌟WebP 是什么?
WebP 是 Google 推出的一种现代图片格式,支持有损/无损压缩、透明通道(alpha)、动画,目标是"比 JPG 小 30%,比 PNG 小 80%"。
✅ 优势:
- 体积小 → 加载快 → LCP 提升
- 支持透明(PNG 级)
- 支持动图(GIF 替代)
- 渐进式加载
❌ 缺点:
- iOS Safari < 14 不支持(2020年前)
- 解码耗 CPU(低端机卡顿)
- 编码慢(构建时压力大)
🧩 如何判断是否支持 WebP?
js
function checkWebP() {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(true); // 能加载成功
img.onerror = () => resolve(false); // 失败则不支持
img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA+0mEqqqqmaAA=';
});
}
👉这行 base64 是一个 1x1 的 WebP 小图
「Q1: 为什么用 base64 而不是真实 URL?」
A1: 避免网络请求干扰,纯客户端检测 ❓
「Q2: 这个检测会影响首屏性能吗?」
A2: 会!建议缓存到 localStorage 💡
「Q3: 服务端怎么配合做降级?」
A3: 通过 Accept: image/webp
请求头判断!服务端返回对应格式 🌐
🛠️ 真实业务落地示例:电商详情页
双格式生成
WebP + JPG"]:::process end %% ========== 3. CDN 分发 ========== subgraph CDN 智能分发 direction LR B["
CDN 边缘节点"]:::process B --> C["
检测请求头
Accept: image/webp"] C --> D{是否支持 WebP?} end %% ========== 4. 回退机制 ========== subgraph 兼容性处理 direction TB D -->|是| E["
返回 WebP"]:::result D -->|否| F["
返回 JPG
自动回退"]:::result end %% ========== 5. 性能优化 ========== subgraph 前端优化 G["
图片懒加载"]:::process H["
WebP 检测缓存"]:::process G --> H end %% ========== 6. 结果展示 ========== I["
首屏提速 35%
性能指标"]:::emphasis %% ========== 连接关系 ========== 构建阶段 --> CDN智能分发 CDN智能分发 --> 兼容性处理 兼容性处理 --> 前端优化 前端优化 --> I %% ========== 样式增强 ========== style A stroke-width: 3px,stroke-dasharray: 5 5 style B stroke-width: 3px,stroke-dasharray: 5 5 style G stroke-width: 3px,stroke-dasharray: 5 5 linkStyle 0 stroke:#0ea5e9,stroke-width:2px linkStyle 1 stroke:#0ea5e9,stroke-width:2px linkStyle 2 stroke:#0ea5e9,stroke-width:2px linkStyle 3 stroke:#0ea5e9,stroke-width:2px linkStyle 4 stroke:#10b981,stroke-width:3px %% ========== 动效提示 ========== %% 部署时可通过 CSS 实现: %% .node:hover { transform: scale(1.03); transition: 0.2s; } %% .edgePath:hover { stroke-width: 4px; }
🔄 连环追问链(准备接招💥):
「Q4: WebP 和 AVIF 比怎么样?🤯」
A4: AVIF 更小(50%+),但兼容性更差(Safari 16+),目前建议 WebP 为主,AVIF 试验性用
「Q5: 如何让低版本浏览器也用 WebP?👉继续看?」
A5: 不行!但可用 <picture>
标签优雅降级:
html
<picture>
<source srcset="img.webp" type="image/webp">
<img src="img.jpg" alt="fallback">
</picture>
「Q6: WebP 动图能替代 GIF 吗?」
A6: 能!体积小 90%,但注意:GIF 有广泛工具链,WebP 编辑工具少 ⚠️
「Q7: 如何批量转换图片为 WebP?」
A7: Webpack 用 webp-loader
,Vite 用 vite-plugin-image-presets
,Node.js 用 sharp
库
「Q8: WebP 解码卡顿怎么破?」
A8: 控制尺寸!大图分块加载,或用 Canvas 分帧解码防主线程阻塞 💣
🌟Koa2 是什么?(顺手答了)
Koa2 是 Node.js 的轻量级 Web 框架,由 Express 原班人马打造,核心是 中间件 + async/await。
js
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('A');
await next(); // 等待后续中间件
console.log('D');
});
app.use(async (ctx) => {
console.log('B');
ctx.body = 'Hello';
console.log('C');
});
// 输出:A → B → C → D
⚠️洋葱模型,你真的懂执行顺序吗?
「Q9: Koa 中间件为什么用 async 函数?」
A9: 让异步流程可 await,避免回调地狱,错误能被 try/catch 捕获 ❗
「Q10: 和 Express 中间件区别在哪?」
A10: Express 是函数调用 next()
,Koa 是 await next()
,控制更精细 🎯
🚀Promise 如何实现?(手撕 ≤20 行)
js
function MyPromise(executor) {
this.state = 'pending';
this.value = undefined;
this.callbacks = []; // 存储 then 回调
const resolve = (value) => {
if (this.state !== 'pending') return;
this.state = 'fulfilled';
this.value = value;
this.callbacks.forEach(cb => cb.onResolved(value));
};
const reject = (reason) => {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.value = reason;
this.callbacks.forEach(cb => cb.onRejected(reason));
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
MyPromise.prototype.then = function(onResolved, onRejected) {
return new MyPromise((resolve, reject) => {
// 包装回调,支持链式调用
this.callbacks.push({
onResolved: () => {
const res = onResolved(this.value);
resolve(res); // 简化版,未处理返回 Promise
},
onRejected: () => {
const res = onRejected(this.value);
reject(res);
}
});
});
};
⚠️这版没处理 then 返回 Promise 的情况,面试官偷笑😏
「Q11: 如何实现 Promise.then 的链式调用?」
A11: 每次 then 返回新 Promise,并解析回调返回值(需判断是否为 Promise)🔗
「Q12: Promise.all 怎么实现?」
A12: 监控所有 Promise 状态,全 fulfilled 才 resolve,任一 reject 就 reject 🧱
「Q13: 为什么 Promise 构造函数要 try/catch?」
A13: 执行器可能抛错,需转为 reject,符合 Promise A+ 规范 📜
🌐 异步请求:低版本 fetch 如何适配?
fetch
不支持 IE,可用:
- polyfill :
whatwg-fetch
库 - 降级到 XMLHttpRequest
- 构建时转换(Babel + polyfill)
js
// 手动封装兼容层
function myFetch(url, options) {
if (window.fetch) {
return fetch(url, options);
} else {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => resolve({ text: () => xhr.responseText });
xhr.onerror = reject;
xhr.send();
});
}
}
⚠️注意:fetch 默认不带 cookie,需加 credentials: 'include'
「Q14: fetch 和 ajax 区别?」
A14: fetch 是标准 API,返回 Promise;ajax 是 XHR 封装,更底层 🔄
「Q15: fetch 如何中断请求?」
A15: 用 AbortController + signal 传入 options 💥
🚫Ajax 如何处理跨域?
同源策略(Same-Origin Policy):协议 + 域名 + 端口 必须完全相同,否则禁止读取响应。
跨域解决方案:
方法 | 原理 | 限制 |
---|---|---|
CORS | 服务端加 header | 需服务端配合 |
JSONP | script 标签绕过 | 只能 GET |
代理 | 本地启服务转发请求 | 构建时/开发用 |
postMessage | iframe 通信 | 复杂,需双方配合 |
🔐CORS 如何设置?
服务端设置 header:
http
Access-Control-Allow-Origin: https://a.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
前端发请求:
js
fetch('http://b.com/api', {
credentials: 'include' // 带 cookie
})
⚠️注意:*
和 credentials: true
不能共存!
「Q16: 预检请求(preflight)什么时候触发?」
A16: 当请求是"非简单请求"时(如 PUT、带自定义 header)先发 OPTIONS 💣
❓JSONP 为什么不支持 POST?
因为 JSONP 是靠 <script src="...">
实现的,而 script 标签只支持 GET 请求。
html
<script src="http://b.com/api?callback=fn"></script>
服务端返回:fn({"data": 1})
→ 执行全局函数
「Q17: JSONP 有安全风险吗?」
A17: 有!XSS 攻击,只能用于可信接口,且需校验 referer 🔐
🔗原型链是什么?
JavaScript 通过 __proto__
和 prototype
实现继承。
js
function Person(name) {
this.name = name;
}
Person.prototype.say = function() { console.log(this.name); };
const p = new Person('Tom');
p.say(); // Tom
// 查找链:p → Person.prototype → Object.prototype → null
ASCII 图示:
⚠️每个对象都有 __proto__
,函数才有 prototype
「Q18: class 是语法糖吗?」
A18: 是!class 内部还是基于原型链实现,只是更清晰 🎀
🧬如何实现继承?
- ES6 class 继承(推荐)
js
class Animal { constructor(name) { this.name = name; } }
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
}
- 寄生组合继承(手写)
js
function create(proto) {
function F() {}
F.prototype = proto;
return new F();
}
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = create(Animal.prototype);
Dog.prototype.constructor = Dog;
「Q19: 为什么不用 Dog.prototype = new Animal()
?」
A19: 会执行父构造函数,可能产生无用属性,且不易传参 🤯