🔥99%人只知WebP小,第3个特性却翻车?💥

1,085 阅读4分钟

🌟WebP 是什么?

WebP 是 Google 推出的一种现代图片格式,支持有损/无损压缩、透明通道(alpha)、动画,目标是“比 JPG 小 30%,比 PNG 小 80%”。

✅ 优势:

  • 体积小 → 加载快 → LCP 提升
  • 支持透明(PNG 级)
  • 支持动图(GIF 替代)
  • 渐进式加载

❌ 缺点:

  • iOS Safari < 14 不支持(2020年前)
  • 解码耗 CPU(低端机卡顿)
  • 编码慢(构建时压力大)

🧩 如何判断是否支持 WebP?

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 图片优化流程」架构图
%% 主题:图片格式双生成 + CDN 智能分发 + 性能优化
%% 适配:深色 / 浅色模式自适应
%% 图标:FontAwesome 6.5
%% =========================================================

%% 1. 全局样式
%% 2. 构建阶段
%% 3. CDN 分发
%% 4. 回退机制
%% 5. 性能优化
%% 6. 结果展示

flowchart LR
    classDef default fill:#f9fafb,stroke:#e5e7eb,color:#1f2937,stroke-width:1px,rx:8px,ry:8px
    classDef process fill:#0ea5e9,stroke:#0284c7,color:#fff,stroke-width:2px,rx:8px,ry:8px
    classDef emphasis fill:#f59e0b,stroke:#d97706,color:#fff,stroke-width:2px,rx:8px,ry:8px
    classDef result fill:#10b981,stroke:#059669,color:#fff,stroke-width:2px,rx:8px,ry:8px

    %% ========== 2. 构建阶段 ==========
    subgraph 构建阶段
        A["<i class='fa fa-cogs'></i><br><b>双格式生成</b><br><small>WebP + JPG</small>"]:::process
    end

    %% ========== 3. CDN 分发 ==========
    subgraph CDN 智能分发
        direction LR
        B["<i class='fa fa-cloud'></i><br><b>CDN 边缘节点</b>"]:::process
        B --> C["<i class='fa fa-code'></i><br><b>检测请求头</b><br><small>Accept: image/webp</small>"]
        C --> D{是否支持 WebP?}
    end

    %% ========== 4. 回退机制 ==========
    subgraph 兼容性处理
        direction TB
        D -->|是| E["<i class='fa fa-file-image-o'></i><br><b>返回 WebP</b>"]:::result
        D -->|否| F["<i class='fa fa-file-image-o'></i><br><b>返回 JPG</b><br><small>自动回退</small>"]:::result
    end

    %% ========== 5. 性能优化 ==========
    subgraph 前端优化
        G["<i class='fa fa-bolt'></i><br><b>图片懒加载</b>"]:::process
        H["<i class='fa fa-database'></i><br><b>WebP 检测缓存</b>"]:::process
        G --> H
    end

    %% ========== 6. 结果展示 ==========
    I["<i class='fa fa-tachometer-alt'></i><br><b>首屏提速 35%</b><br><small>性能指标</small>"]:::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> 标签优雅降级:

<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

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 行)

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,可用:

  1. polyfillwhatwg-fetch
  2. 降级到 XMLHttpRequest
  3. 构建时转换(Babel + polyfill)
// 手动封装兼容层
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需服务端配合
JSONPscript 标签绕过只能 GET
代理本地启服务转发请求构建时/开发用
postMessageiframe 通信复杂,需双方配合

🔐CORS 如何设置?

服务端设置 header:

Access-Control-Allow-Origin: https://a.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true

前端发请求:

fetch('http://b.com/api', {
  credentials: 'include' // 带 cookie
})

⚠️注意:*credentials: true 不能共存!

「Q16: 预检请求(preflight)什么时候触发?」
A16: 当请求是“非简单请求”时(如 PUT、带自定义 header)先发 OPTIONS 💣


❓JSONP 为什么不支持 POST?

因为 JSONP 是靠 <script src="..."> 实现的,而 script 标签只支持 GET 请求

<script src="http://b.com/api?callback=fn"></script>

服务端返回:fn({"data": 1}) → 执行全局函数

「Q17: JSONP 有安全风险吗?」
A17: 有!XSS 攻击,只能用于可信接口,且需校验 referer 🔐


🔗原型链是什么?

JavaScript 通过 __proto__prototype 实现继承。

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 图示:

%% =========================================================
%% 专业级「JavaScript 原型链」可视化图
%% 主题:对象原型关系 & __proto__ 链
%% 适配:深色 / 浅色模式自适应
%% 图标:FontAwesome 6.5
%% =========================================================

%% 1. 全局样式
%% 2. 节点语义
%% 3. 关键连线
%% 4. 原型链高亮
%% 5. 动效提示

flowchart TB
    classDef instance     fill:#f59e0b,stroke:#d97706,color:#fff,stroke-width:2px,rx:8px,ry:8px
    classDef prototype    fill:#8b5cf6,stroke:#7c3aed,color:#fff,stroke-width:2px,rx:8px,ry:8px
    classDef builtin      fill:#0ea5e9,stroke:#0284c7,color:#fff,stroke-width:2px,rx:8px,ry:8px
    classDef nullClass    fill:#9ca3af,stroke:#6b7280,color:#fff,stroke-width:2px,rx:8px,ry:8px
    classDef function     fill:#10b981,stroke:#059669,color:#fff,stroke-width:2px,rx:8px,ry:8px

    %% ========== 2. 节点定义 ==========
    P["<i class='fa fa-user'></i> p (实例)"]:::instance
    PP["<i class='fa fa-dna'></i> Person.prototype"]:::prototype
    PS["<i class='fa fa-cog'></i> say()"]:::function

    O["<i class='fa fa-cube'></i> Object.prototype"]:::builtin
    OS["<i class='fa fa-cog'></i> toString()"]:::function

    N["<i class='fa fa-ban'></i> null"]:::nullClass

    %% ========== 3. 关键连线 ==========
    P -.->|__proto__| PP
    PP --> PS
    PP -.->|__proto__| O
    O --> OS
    O -.->|__proto__| N

    %% ========== 4. 原型链高亮 ==========
    linkStyle 0 stroke:#f59e0b,stroke-width:3px
    linkStyle 2 stroke:#8b5cf6,stroke-width:3px
    linkStyle 4 stroke:#0ea5e9,stroke-width:3px

    %% ========== 5. 动效提示 ==========
    %% 部署时可通过 CSS 实现:
    %% .node[id*="P"]:hover { transform: scale(1.05); }
    %% .edgePath[id*="P-PP"]:hover { stroke-width: 4px; }

⚠️每个对象都有 __proto__,函数才有 prototype

「Q18: class 是语法糖吗?」
A18: 是!class 内部还是基于原型链实现,只是更清晰 🎀


🧬如何实现继承?

  1. ES6 class 继承(推荐)
class Animal { constructor(name) { this.name = name; } }
class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
}
  1. 寄生组合继承(手写)
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: 会执行父构造函数,可能产生无用属性,且不易传参 🤯