普通视图

发现新文章,点击刷新页面。
昨天以前首页

基础 | HTML语义、CSS3新特性、浏览器存储、this、防抖节流、重绘回流、date排序、calc

作者 前端微白
2025年8月13日 20:50

1. HTML 语义化标签的作用

graph TD
    A[语义化标签] --> B[SEO]
    A --> C[可访问性]
    A --> D[代码可读性]
    A --> E[自动化工具]

⚡这一步暗藏BUG?——用 <div class="header"> 也能做头部,但屏幕阅读器会迷路!

Q1: 为什么 <article><div> 更适合博客正文?
A1: <article> 自带独立内容含义,RSS/爬虫可直接识别,SEO 权重↑。

Q2: 🤯你以为懂了?那 <section><article> 能互相嵌套吗?
A2: 可以,但语义要自洽:<section> 表章节,<article> 表完整故事,别套娃到逻辑混乱。


2. 举例 CSS3 新特性

  • 布局:Flexbox、Grid
  • 视觉:border-radius、box-shadow、linear-gradient
  • 动画:transition、@keyframes、transform: translateZ(0) 硬件加速
  • 响应式:@media、clamp()

Q3: Grid 和 Flexbox 何时一起用?
A3: 外层 Grid 做二维骨架,内层 Flexbox 做一维对齐,电商商品列表常用。


3. 浏览器存储方案对比

特性 cookie localStorage sessionStorage
大小 ~4KB ~5MB ~5MB
生命周期 可设过期 永久 标签页关闭
随请求携带
适用场景 登录态 主题/缓存 表单草稿

Q4: 如何防止 localStorage 被 XSS 窃取?
A4: 存敏感信息前做加密(如 AES),并设置 Content-Security-Policy 禁止内联脚本。


4. JS 变量提升

console.log(a); // undefined(声明提升,赋值不提升)
var a = 1;

Q5: let/const 真的不提升吗?
A5: 也提升,但存在「暂时性死区」,在声明前访问直接抛 ReferenceError。


5. this 关键字 & 箭头函数

  • 普通函数:运行时绑定,谁调用指向谁
  • 箭头函数:词法作用域,定义时捕获外层 this
  • call/apply/bind:显式绑定,区别只在传参方式
const obj = { x: 1 };
function show() { console.log(this.x); }
show.call(obj);        // 1
show.apply(obj, []);   // 1
const bound = show.bind(obj);
bound();               // 1

Q6: 箭头函数能用 call 改 this 吗?
A6: 不能,箭头函数 this 固化,call/apply/bind 无效。


6. typeof vs instanceof

typeof []          // "object"(数组也是对象)
[] instanceof Array // true

Q7: 🤯如何准确判断 NaN?
A7: Number.isNaN(NaN),因为 typeof NaN === 'number'


7. 防抖 vs 节流

// 防抖:停止触发后 wait 毫秒执行
const debounce = (fn, wait) => {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), wait);
  };
};

// 节流:每 wait 毫秒最多执行一次
const throttle = (fn, wait) => {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last > wait) {
      last = now;
      fn.apply(this, args);
    }
  };
};

Q8: 搜索框输入用哪个?
A8: 防抖,避免每敲一次就请求。


8. 重绘 vs 回流

  • 回流(reflow):几何变化,如宽高、位置
  • 重绘(repaint):外观变化,如颜色、阴影
    性能优化:读写分离、transform 合成层、避免 table 布局

Q9: 如何强制触发一次回流?
A9: 读取 offsetHeightgetComputedStyle 等会 flush 队列。


9. 代码题:表格 date 字段正序/倒序

<table id="t">
  <thead><tr><th onclick="sortTable()">Date</th></tr></thead>
  <tbody>
    <tr><td>2023-10-01</td></tr>
    <tr><td>2022-05-20</td></tr>
  </tbody>
</table>

<script>
let asc = true;
function sortTable() {
  const tbody = t.querySelector('tbody');
  const rows = [...tbody.rows].sort((a, b) => {
    const d1 = new Date(a.cells[0].textContent);
    const d2 = new Date(b.cells[0].textContent);
    return asc ? d1 - d2 : d2 - d1;
  });
  asc = !asc;
  rows.forEach(r => tbody.appendChild(r)); // 复用节点,减少回流
}
</script>

10. 代码题:calc 最大乘积拆分

function calc(n) {
  if (n < 2) return [];
  const res = [];
  let k = 2;
  while (n >= k) {
    res.push(k);
    n -= k++;
  }
  // 把余数均匀加到后面几项,避免 1
  for (let i = res.length - 1; n > 0; i--) {
    res[i]++;
    n--;
  }
  return res;
}
console.log(calc(10)); // [3,3,4] 3*3*4=36 最大

Q10: 边界:n=2 时返回 [2] 还是 [1,1]?
A10: [2],乘积更大且不含 1。

模块联邦(Module Federation)微前端方案

作者 前端微白
2025年8月12日 20:14

问题场景:巨石应用的拆分与协作难题

在你接手的一个大型企业管理系统项目中,前端代码库已经膨胀到难以维护的程度。多个业务团队并行开发,却因为代码耦合严重,经常出现“改A功能影响B功能”的问题。你迫切需要一种方案,既能将系统拆分成独立的子应用,又能实现模块间的高效共享与协作。

传统的iframe隔离方案虽然简单,但存在样式、通信、路由等多重问题。而single-spa、qiankun等方案虽然提供了生命周期管理,但在模块共享上仍显笨重。这时,Webpack 5的Module Federation(模块联邦)进入了我们的视野。它承诺在运行时实现模块的动态共享,这正是我们解决“巨石应用”痛点的关键。

解决方案:Module Federation 实战应用

采用Module Federation来重构系统。目标是将系统拆分为“主应用”和多个“子应用”,并通过Module Federation实现公共组件和业务模块的共享。

项目结构设计

project-root/
├── host-app/          // 主应用
├── remote-app-a/       // 子应用A
├── remote-app-b/       // 子应用B
└── shared-lib/         // 共享库

主应用配置 (host-app/webpack.config.js)

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');

module.exports = {
  // 🔍 核心配置:定义当前应用为"Host"
  plugins: [
    new ModuleFederationPlugin({
      name: 'host_app',
      filename: 'remoteEntry.js',
      // 🔍 声明需要从远程应用消费的模块
      remotes: {
        'remote_app_a': 'remote_app_a@http://localhost:3001/remoteEntry.js',
        'remote_app_b': 'remote_app_b@http://localhost:3002/remoteEntry.js',
      },
      // 🔍 共享依赖,避免重复加载
      shared: {
        'react': { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
      }
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    })
  ]
};

逐行解析

  • name: 定义当前应用的全局唯一名称,供其他应用引用。
  • filename: 生成的远程入口文件名,其他应用通过此文件加载本应用模块。
  • remotes: 声明需要从哪些远程应用加载模块,格式为别名: 全局名@远程地址
  • shared: 声明共享依赖,singleton: true确保只有一个实例,避免版本冲突。

子应用A配置 (remote-app-a/webpack.config.js)

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');

module.exports = {
  // 🔍 核心配置:定义当前应用为"Remote"
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote_app_a',
      filename: 'remoteEntry.js',
      // 🔍 暴露本应用可供其他应用消费的模块
      exposes: {
        './Button': './src/components/Button',
        './UserCard': './src/features/user/UserCard'
      },
      shared: {
        'react': { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
      }
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    })
  ]
};
  • exposes: 定义本应用对外暴露的模块,键为外部引用的路径,值为本应用内部模块路径。

主应用中消费远程模块 (host-app/src/App.js)

// 🔍 动态导入远程模块
const RemoteButton = React.lazy(() => import('remote_app_a/Button'));
const RemoteUserCard = React.lazy(() => import('remote_app_b/UserCard'));

function App() {
  return (
    <div>
      <h1>主应用</h1>
      {/* 🔍 使用远程组件 */}
      <React.Suspense fallback="Loading...">
        <RemoteButton />
        <RemoteUserCard />
      </React.Suspense>
    </div>
  );
}
  • 使用React.lazyimport()实现远程模块的按需加载。
  • React.Suspense提供加载状态的fallback。

原理剖析:Module Federation 的底层机制

表面用法:配置驱动的模块共享

Module Federation的核心是通过Webpack插件配置,声明应用是“Host”还是“Remote”,以及需要共享或消费哪些模块。这种声明式的方式极大地简化了开发者的操作。

底层机制:运行时的模块解析与加载

Module Federation的魔力在于其运行时机制。当主应用启动时,它会根据remotes配置,通过<script>标签动态加载远程应用的remoteEntry.js文件。这个文件包含了远程应用的模块映射表。

当主应用尝试import('remote_app_a/Button')时,Module Federation的运行时会:

  1. 查找remote_app_a对应的远程地址。
  2. 加载并解析remoteEntry.js,获取Button模块的内部路径。
  3. 通过JSONP或其他方式,从远程应用加载Button模块的实际代码。
  4. 执行代码并返回模块导出。

这个过程对开发者是透明的,但理解其机制有助于我们进行性能优化和问题排查。

设计哲学:去中心化的模块管理

Module Federation的设计哲学是“去中心化”。它不依赖于一个中央的包管理器,而是让每个应用都成为独立的模块提供者和消费者。这种设计使得团队可以独立开发、部署和更新自己的应用,同时又能轻松地共享代码。

应用扩展:与其他方案的对比

特性/方案 Module Federation (Webpack 5) qiankun (single-spa) iframe
模块共享 运行时动态共享 构建时静态共享 隔离,无法共享
通信机制 ES Module标准 Custom Events postMessage
样式隔离 需自行处理 沙箱隔离 原生隔离
部署方式 独立部署 独立部署 独立部署
学习成本 中等 (需理解Webpack)
适用场景 复杂、高频共享的大型应用 中大型应用 简单隔离场景

Module Federation在模块共享上具有明显优势,尤其适合需要跨应用复用大量组件和逻辑的场景。

实用价值强化

可复用配置片段

// config/moduleFederationShared.js
// 🔍 通用共享依赖配置,适配不同环境
const sharedDependencies = {
  react: { singleton: true, requiredVersion: '^18.0.0' },
  'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
  // ... 其他共享库
};

// 根据环境调整共享策略
if (process.env.NODE_ENV === 'production') {
  // 生产环境可以更激进地共享,减少bundle size
  Object.keys(sharedDependencies).forEach(key => {
    sharedDependencies[key].eager = true; // 立即加载共享依赖
  });
}

module.exports = sharedDependencies;

环境适配说明

  • 开发环境:可以设置sharedeager: false,实现按需加载,加快构建速度。
  • 生产环境:建议设置eager: true,提前加载共享依赖,优化首屏性能。

变体场景实现思路

  1. 跨框架共享:Module Federation不仅支持React/Vue等同构框架间的共享,也支持跨框架共享。例如,可以将一个通用的UI组件库(如Ant Design)打包成一个独立的“组件Remote”,供React和Vue应用同时消费。实现时需注意运行时适配和样式隔离。

  2. 服务端渲染(SSR)集成:在需要SSR的场景下,Module Federation的客户端动态加载机制会带来挑战。可以采用“构建时预加载 + 运行时fallback”的策略。在服务端渲染阶段,通过Node.js的import()预加载关键远程模块;在客户端,保留原有的动态加载逻辑以处理非关键路径。

  3. 版本化部署与灰度发布:Module Federation可以轻松支持微前端的版本化部署。通过为不同的remoteEntry.js文件设置不同的URL(如包含版本号或环境标识),主应用可以按需加载不同版本的子应用。结合网关或CDN的路由规则,可以实现灰度发布和A/B测试。

基础 | 🔥6种声明方式全解⚠️

作者 前端微白
2025年8月11日 20:05

一、ES6 的6种变量声明方式(含实战陷阱与追问链)

ES6(ECMAScript 2015)带来了语言层面的巨大升级,其中变量声明机制是开发者最直观感受到的变革。它不再局限于 var,而是引入了更安全、更语义化的多种声明方式。

我们先看一张核心对比图:

graph LR
    A[变量声明] --> B[var]
    A --> C[let]
    A --> D[const]
    A --> E[function]
    A --> F[class]
    A --> G[import]
    
    style B fill:#f96,stroke:#333
    style C fill:#6f9,stroke:#333
    style D fill:#69f,stroke:#333
    style E fill:#ff6,stroke:#333
    style F fill:#6ff,stroke:#333
    style G fill:#f6f,stroke:#333

⚡图:ES6 六大声明方式全景图
⚠️这一步暗藏变量提升陷阱?

1. var —— 被时代淘汰但仍在运行时存在

  • 函数作用域
  • 存在变量提升(hoisting)
  • 可重复声明
  • 不推荐在现代项目中使用
console.log(a); // undefined(不是报错!)
var a = 1;

2. let —— 块级作用域新标准

  • 块级作用域({} 内有效)
  • 不存在“暂时性死区”外的访问
  • 不可重复声明
  • 推荐用于可变变量
{
  let b = 2;
  console.log(b); // 2
}
console.log(b); // ReferenceError!

3. const —— 常量声明(但≠不可变!)

  • 块级作用域
  • 必须初始化
  • 引用地址不可变,但对象属性可改
const obj = { name: 'ES6' };
obj.name = 'Modern JS'; // ✅ 合法
obj = {}; // ❌ 报错:Assignment to constant variable.

4. function —— 函数声明(也是ES6前就有的)

  • 提升且初始化
  • 仅在定义的作用域内有效
  • let/const 冲突时会抛错
function foo() { return 1; }

5. class —— 语法糖,本质仍是函数

  • 必须先定义后使用(TDZ)
  • 不会被提升
  • class 声明创建一个不可重新赋值的常量
class Person {
  constructor(name) {
    this.name = name;
  }
}
const p = new Person('Tom');

6. import —— 模块导入即声明

  • 静态分析,编译时绑定
  • 所有 import 自动提升到文件顶部
  • 绑定是只读引用,不是拷贝
import { useState } from 'react'; // 声明了一个只读绑定

📌 主流共识:letconst 是现代 JS 开发的默认选择,优先使用 const,仅当需要重新赋值时用 let


「Q1: varlet 最大区别是什么?💥」
A1: var 是函数作用域且会变量提升并初始化为 undefinedlet 是块级作用域,存在暂时性死区(TDZ),在声明前访问会报错。

「Q2: const 定义的对象为什么还能修改属性?🤯」
A2: const 保证的是引用地址不变,而非对象内部不可变。要真正冻结对象需用 Object.freeze(obj)

「Q3: import 是不是也受 TDZ 影响?」
A3: 是的!虽然 import 会被提升,但在模块执行前无法访问,属于 TDZ 范畴。比如动态 import() 可以规避。

「Q4: class 声明会不会被提升?」
A4: class 声明存在 TDZ,不会被提升。typeof MyClass 在声明前会报错,不像 function 可以提前使用。

「Q5: 为什么 ES6 不直接废弃 var?」
A5: 为了向后兼容。大量旧代码依赖 var 行为,直接移除会导致生态崩溃。但规范鼓励使用 let/const


二、CSS 元素垂直居中的 7 种方式

垂直居中是前端永恒话题。我们先上一张布局方式演进图:

+------------------+     +------------------+
|   display:table  |     | position + 负margin |
+------------------+     +------------------+
          |                        |
          v                        v
+------------------+     +------------------+
|  flex: center!!  |<--->|    grid layout   |
+------------------+     +------------------+
          |
          v
+------------------+
| transform:translate(-50%) |
+------------------+

⚡图:CSS 垂直居中方法演进路径
⚠️移动端慎用负 margin?

方法 1:Flexbox(推荐 ✅)

.container {
  display: flex;
  justify-content: center; /* 水平 */
  align-items: center;     /* 垂直 */
  height: 100vh;
}

方法 2:Grid(现代方案)

.container {
  display: grid;
  place-items: center; /* 水平+垂直 */
  height: 100vh;
}

方法 3:绝对定位 + transform

.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

方法 4:绝对定位 + margin auto

.container {
  position: relative;
  height: 300px;
}
.child {
  position: absolute;
  top: 0; bottom: 0;
  left: 0; right: 0;
  margin: auto;
  width: 100px;
  height: 50px;
}

方法 5:table-cell(老派但兼容好)

.container {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
  width: 300px;
  height: 300px;
}

方法 6:line-height(仅限单行文本)

.box {
  height: 100px;
  line-height: 100px;
  text-align: center;
}

方法 7:padding 调整(固定高度可用)

.box {
  padding: 50px 0;
  height: 200px;
  box-sizing: border-box;
}

「Q1: Flex 居中会不会影响子元素布局?💥」
A1: 不会,align-itemsjustify-content 只控制主轴和交叉轴对齐,子元素仍可自由布局。

「Q2: transform: translate 会导致模糊?🤯」
A2: 是的!非整数像素位移可能引起亚像素渲染模糊。可加 will-change: transform 或强制 GPU 加速缓解。

「Q3: Grid 和 Flex 如何选?」
A3: Grid 适合二维布局(行列都复杂),Flex 适合一维(主轴方向)。居中场景两者皆可,优先 Flex。

「Q4: 为什么不用 vertical-align: middle?」
A4: 因为它只对 inlinetable-cell 元素有效,块级元素无效,常被误解。

「Q5: 移动端适配居中要注意什么?🌍」
A5: 避免固定高度,优先使用 Flex 或 Grid,结合 vh 单位,并处理 iOS 安全区(env() 函数)。


三、Flex 布局的三大伸缩属性详解(含权重计算)

Flex 布局核心在于三个伸缩属性:

classDiagram
    class FlexItem {
        +flex-grow: number
        +flex-shrink: number
        +flex-basis: length | auto
    }
    
    FlexItem : flex = grow shrink basis

⚡图:Flex 伸缩三属性关系
⚠️默认值组合有坑?

1. flex-grow:剩余空间分配权重

  • 默认 0
  • 数值越大,分得越多
  • 不会缩小已有内容
.item1 { flex-grow: 1; }
.item2 { flex-grow: 2; } /* 分得两倍空间 */

2. flex-shrink:溢出时压缩比例

  • 默认 1
  • 设为 0 表示不压缩
  • 压缩量 = (自身大小 × shrink) / 总权重
.item { flex-shrink: 0; } /* 固定宽度,不压缩 */

3. flex-basis:分配前的初始大小

  • 类似 width,但优先级更高
  • 可设为 auto(按内容)、0、具体值
.item { flex-basis: 100px; }

简写 flex 的常见组合:

写法 等价于
flex: 1 1 1 0%1 1 0px(浏览器差异)
flex: auto 1 1 auto
flex: none 0 0 auto(不伸不缩)

💡 实战建议:用 flex: 1 快速填满剩余空间,flex: none 防止压缩。

「Q1: flex: 1 到底等于 1 1 0 还是 1 1 auto?💥」
A1: 规范定义为 1 1 0,但某些浏览器实现为 0%。为明确行为,建议写全。

「Q2: flex-basis: 0width: 0 一样吗?🤯」
A2: 不同!flex-basis 参与 flex 计算,width 在 flex 中可能被覆盖。flex-basis: 0 更适合均分。

「Q3: 多个 item 设置 flex-grow 如何计算?」
A3: 按权重比例分配剩余空间。如 grow=1 和 grow=2,则比例 1:2。

「Q4: flex-shrink 为负数会怎样?」
A4: 无效!flex-shrink 必须是非负数,负值会被忽略或转为 0。

「Q5: 为什么有时 flex: 1 不占满?」
A5: 可能父容器无固定宽度,或子元素有 min-width 限制。检查 min-width: auto 是否干扰。


四、LeetCode 开平方根题(第69题)—— 二分法 vs 牛顿法

题目:实现 int sqrt(int x),返回 √x 的整数部分。

例:输入 8 → 输出 2(√8 ≈ 2.828)

方法 1:二分查找(推荐 ✅)

function mySqrt(x) {
  if (x < 2) return x;
  let left = 1, right = Math.floor(x / 2);
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    const square = mid * mid;
    if (square === x) return mid;
    if (square < x) {
      left = mid + 1;
    } else {
      right = mid - 1;
    }
  }
  return right; // 最大满足 mid*mid <= x 的值
}

方法 2:牛顿迭代法(更快收敛)

function mySqrt(x) {
  if (x < 2) return x;
  let r = x;
  while (r * r > x) {
    r = Math.floor((r + x / r) / 2); // 牛顿公式: r = (r + x/r)/2
  }
  return r;
}

时间复杂度:二分 O(log x),牛顿法接近 O(log log x)

「Q1: 为什么 right = x / 2?💥」
A1: 因为当 x ≥ 4 时,√x ≤ x/2。边界优化可减少搜索范围。

「Q2: 牛顿法会不会死循环?🤯」
A2: 不会,因为每次迭代都更接近真实值,且整数除法会收敛。但需用 Math.floor 控制精度。

「Q3: 能不能用 Math.sqrt?」
A3: 面试中通常要求手动实现。若允许,直接 Math.floor(Math.sqrt(x))

「Q4: 浮点数开方怎么做?」
A4: 扩展牛顿法,设置精度阈值(如 1e-6),直到 |r*r - x| < eps

「Q5: 大数溢出怎么处理?」
A5: 用 BigInt 或将比较改为 mid <= x / mid 避免乘法溢出。

❌
❌