布莱克的充电站

世界上只有一种真正的英雄主义,就是在认清生活的真相之后依然热爱生活——罗曼·罗兰

展开
CDN加速
什么是CDN CDN(Content Delivery Network,内容分发网络) ,本质上是一个由遍布全球的边缘服务器节点组成的分布式网络。它的核心作用是: 将网站内容缓存到离用户最近的服务器上,让用户就近获取数据 。 工作流程: 当用户访问一个使用了 CDN 的网站时,流程是这样的: 用户请求资源:浏览器向网站域名发起请求 智能调度:CDN 的 DNS 系统判断用户的地理位置和网络状况 就近分配:系统将请求指向离用户最近的边缘节点 缓存响应:如果节点有缓存 → 直接返回(缓存命中)如果节点无缓存 → 从源站获取 → 缓存到节点 → 返回用户 关键指标 :良好的 CDN 服务,缓存命中率应保持在 85% 以上 资源类型 适合程度 原因 CSS/JS 文件 ⭐⭐⭐⭐⭐ 体积大、变更频率低、缓存收益高 图片/字体 ⭐⭐⭐⭐⭐ 不常变更、适合长期缓存 第三方库 (Vue/React) ⭐⭐⭐⭐⭐ 跨项目可共享缓存,命中率高 HTML 文件 ⭐⭐ 更新频繁、缓存策略难控制 API 响应 ⭐ 动态内容、不宜缓存 什么资源适合放 CDN? 资源类型 适合程度 原因 CSS/JS 文件 ⭐⭐⭐⭐⭐ 体积大、变更频率低、缓存收益高 图片/字体 ⭐⭐⭐⭐⭐ 不常变更、适合长期缓存 第三方库 (Vue/React) ⭐⭐⭐⭐⭐ 跨项目可共享缓存,命中率高 HTML 文件 ⭐⭐ 更新频繁、缓存策略难控制 API 响应 ⭐ 动态内容、不宜缓存 待补充
布莱克
2026-05-18 18:19性能
发布-订阅模式
什么是发布订阅者模式 发布订阅者模式是一种 消息通信模式 ,它定义了对象间的一种 一对多的依赖关系 。当一个对象(发布者)的状态发生变化时,所有依赖于它的对象(订阅者)都会得到通知并自动更新 与观察者模式的区别 特性 观察者模式 发布订阅模式 耦合度 观察者知道被观察者存在 发布者和订阅者完全解耦 中间层 无 有事件通道/调度中心 通信方式 同步 通常异步 灵活性 较低 较高 核心组成: 事件中心:维护事件与回调函数的映射关系 发布者:触发事件 订阅者:监听事件并执行回调 待补充
布莱克
2026-05-15 16:54设计模式已编辑
React 和 Vue 对比
Vue 声明式 + 响应式编程 + 可变数据 Vue的核心心智模型是: 你修改数据,UI自动更新 // Vue的编程方式 const state = reactive({ count: 0 }) state.count++ // 直接修改,UI自动响应 // 你关注的是:修改数据这个动作 // 框架帮你处理:数据变了 -> 重新渲染哪些部分 精准订阅 (代理 + 响应追踪) 内部用 Proxy 拦截 data 的 getter/setter。 修改数据时,Vue 知道具体哪个组件、哪个属性依赖了这个数据。 更新时:只重新渲染依赖该数据的那个最小组件(非常精准)。 React 声明式 + 函数式编程 + 不可变数据 React的核心心智模型是: UI = f(state) ,即UI是状态的纯函数输出。 // React的编程方式 const [count, setCount] = useState(0) setCount(count + 1) // 你告诉React:新状态是什么 // React内部做的是: // 旧状态 -> 新状态 -> 重新执行整个组件函数 -> 生成新UI 重新执行整个组件函数 没有任何 Proxy 或依赖追踪。 setState 触发后,React 不知道该组件里哪些地方用了这个 state。 解决方案:重新执行整个组件函数,重新计算所有 JSX。 对比:React 的更新粒度是组件级别(虽然最终 diff 算法能复用大部分 DOM,但函数会从头跑一遍) Vue 的更新流程 简单、同步、确定性 修改数据 → 触发 setter 通知所有依赖的 Watcher 将 Watcher 放入微任务队列(Promise.then) 同一事件循环中批量执行 Watcher 每个 Watcher 执行 beforeUpdate → 生成新虚拟 DOM → diff → 更新真实 DOM 整个过程不可中断 React 的更新流程 Fiber + 优先级 + 可中断 setState → 创建更新对象,进入调度阶段(Scheduler) 根据优先级(用户点击 > 动画 > 网络请求)安排任务 协调阶段(Reconciliation):可中断:高优先级任务(如输入框事件)可以打断低优先级的更新低优先级更新可能被丢弃,等空闲时重新执行使用 双缓存 Fiber 树(current 树显示界面,workInProgress 树在内存中构建) 提交阶段(Commit):同步、不可中断将 workInProgress 树的变化应用到真实 DOM双树切换(新的 current 树指向 workInProgress) Vue的Dom更新是异步批量更新,react呢,每个setState都会触发更新吗? 在 React 中,多个 setState 默认情况下不会导致每个都触发一次组件函数重新执行,而是会合并成一次重新执行 React 在合成事件中批量更新(一次渲染),在异步代码中老版本会多次渲染,React 18 后基本都能批量 如何理解react的合成事件? 合成事件是 React 自己实现的一套跨浏览器的原生事件包装层。它并不是直接绑定到具体的 DOM 元素上,而是统一绑定在根容器上,采用事件代理机制 React 在组件渲染时,不会给每个 button 单独绑定 click 事件 React 在根容器(如 document 或 root 节点)上只绑定一个事件监听器 当用户点击按钮,真实 DOM 事件冒泡到根容器 React 根据事件的 target 找到对应的虚拟 DOM 和组件 React 创建合成事件对象(SyntheticEvent)并调用你写的 handleClick 什么是Fiber 待学习 Hook 让你从一个普通函数(组件函数)内部,“伸出一个钩子”去 钩住 React的核心功能(状态、副作用、上下文等),然后把这些功能“拉”到你的组件里使用 function MyComponent() { // 用 useState 这个“钩子”,钩住 React 内部的状态管理 const [count, setCount] = useState(0) // 用 useEffect 这个“钩子”,钩住 React 的渲染生命周期 useEffect(() => { document.title = `点击了${count}次` }, [count]) return <button onClick={() => setCount(count+1)}>点我</button> } React Hook 作用 Vue 3 对应 useState 声明响应式状态 ref / reactive useEffect 处理副作用(DOM操作、请求、定时器) watchEffect / watch useContext 获取跨层级数据 inject useRef 保存一个不触发重新渲染的值 / DOM引用 ref (同名的,注意区分) useMemo 缓存计算结果 computed useCallback 缓存函数引用 没有直接对应,但Vue不需要(因为函数不会无故重新创建) useReducer 复杂状态逻辑(类似Redux) 通常用 reactive + 方法
布莱克
2026-05-15 10:48React已编辑
CommonJS规范 ESM规范
require: CommonJS规范,最初用于Node.js环境 import: ES6模块规范,JavaScript官方标准 维度 require import 规范 CommonJS ES Module 加载时机 运行时 (代码执行到才加载) 编译时 (代码执行前就解析) 位置限制 任何位置 必须在模块顶部 动态路径 ✅ 支持 ❌ 不支持(需 import()) 值的性质 值的拷贝 实时只读引用 Tree Shaking ❌ 不支持 ✅ 支持 // require - CommonJS 规范 const module = require('./module') const { fn1, fn2 } = require('./module') // import - ES Module 规范 import module from './module' import { fn1, fn2 } from './module' 加载时机: require是运行时加载,代码执行到那一行才加载 import是编译时加载,代码执行前就已经加载解析 // require - 运行时加载 console.log('start') const math = require('./math') // 执行到这里才会加载 console.log(math.add(1, 2)) // import - 静态加载(编译时) import { add } from './math' // 代码执行前就已完成加载和解析 console.log('start') console.log(add(1, 2)) require的优势和不足: ✅ 优势: // 1. 真正的动态加载,节省资源 if (feature.isEnabled) { const feature = require('./feature') // 不启用就不加载 } // 2. 适合大型应用的分层加载 const middleware = [] if (env === 'dev') { middleware.push(require('./dev-tools')) } // 3. 配置文件的天然支持 const config = require(`./config/${NODE_ENV}.json`) // 4. 运行时修改模块(热替换) delete require.cache[moduleId] const newModule = require(moduleId) // 重新加载 ❌ 不足: // 1. 无法 Tree Shaking // 即使只用到 one 函数,整个 utils 都会打包 const utils = require('./utils') utils.one() // 2. 同步阻塞 const hugeLib = require('./huge-lib') // 卡住直到加载完成 // 3. 无法静态分析 // 打包工具不知道哪些模块被使用了 // 导致代码分割困难 // 4. 循环依赖可能产生不完整对象 // a.js const b = require('./b') module.exports = { value: 1, b } // 此时 b 可能还是空对象 import的优势和不足: ✅ 优势: // 1. Tree Shaking - 只打包使用的代码 import { debounce } from 'lodash-es' // 只会打包 debounce,其他函数被 tree-shake 掉 // 2. 异步加载(无需额外配置) const module = await import('./heavy-module') // 3. 更容易做代码分割 // Webpack/Vite 能根据 import 自动分割 chunk const AdminPage = lazy(() => import('./admin/AdminPage')) // 4. 更好的静态分析 // IDE 能准确知道依赖关系,提供智能提示 // 可以做类型推导、自动导入等高级特性 // 5. 更容易实现变量绑定(实时引用) // 导出的是引用,不是值拷贝 ❌ 不足: // 1. 动态能力弱(需要特殊语法) // 不能直接写变量路径 import('./locale/' + lang + '.json') // 能工作但有限制 // 打包工具会生成多个 chunk,可能不是你想要的 // 2. 严格的循环依赖检测(可能误杀合法场景) // a.mjs import { b } from './b.mjs' export const a = () => b() // 3. 加载时机固定 // 即使后面没用到,也会加载执行 import hugeLib from './huge-lib' // 一定会加载 // 4. 需要在模块顶层使用 if (true) { import { x } from './x' // ❌ 语法错误 } // 只能用 import() 动态导入,但那是异步的 为什么CommonJS不能实现摇树优化: Tree Shaking 的本质是 编译时静态分析 ,删除未使用的代码。但 CommonJS 的模块结构是 动态的 require 是 运行时加载 ,代码执行到 require 语句时才去读取、解析、执行模块。这给了它很大的灵活性——可以在任何位置调用,可以使用动态路径,可以根据条件加载。但打包工具无法做静态分析, 不支持 Tree Shaking ,因为模块的导出结构可能在运行时发生变化。
布莱克
2026-05-11 16:36JavaScript
Vue组件通信
Vue2组件通信方式: Props / $emit (父子组件通信) // 父组件 <template> <Child :message="parentMsg" @childEvent="handleEvent" /> </template> <script> export default { data() { return { parentMsg: 'Hello from parent' } }, methods: { handleEvent(data) { console.log('收到子组件事件:', data) } } } </script> // 子组件 <script> export default { props: ['message'], methods: { sendToParent() { this.$emit('childEvent', '数据来自子组件') } } } </script> provide / inject (跨层级传递) //provide/inject 默认不是响应式的,需要传递响应式对象才能实现响应式。 // 祖先组件 export default { provide() { return { userInfo: this.userInfo, updateUser: this.updateUser } }, data() { return { userInfo: { name: '张三' } } }, methods: { updateUser(name) { this.userInfo.name = name } } } // 后代组件 export default { inject: ['userInfo', 'updateUser'], mounted() { console.log(this.userInfo) this.updateUser('李四') } } Event Bus (事件总线) // EventBus 对象本质上就是一个普通的 Vue 实例,没有 template等等,但拥有 Vue 实例的所有能力 // event-bus.js import Vue from 'vue' export const EventBus = new Vue() // 组件 A(发送事件) import { EventBus } from './event-bus' EventBus.$emit('custom-event', { data: 'some data' }) // 组件 B(监听事件) import { EventBus } from './event-bus' EventBus.$on('custom-event', (payload) => { console.log(payload) }) // 监听事件组件销毁前移除监听 beforeDestroy() { EventBus.$off('custom-event') } 为什么 EventBus 能跨组件通信? 因为 A 和 B 导入的是同一个对象(单例模式) 所以它们操作的是同一个事件中心 EventBus 本质上是一个 全局的发布-订阅对象 ,它会持久存在(除非页面刷新或关闭)。当一个组件监听了一个事件后,EventBus 内部会持有该监听函数的引用。 发送事件的组件:只是调用 $emit 触发事件,EventBus 不持有该组件的任何引用,组件销毁不影响 EventBus 监听事件的组件:通过 $on 将回调函数注册到 EventBus 上,EventBus 持有这个回调函数的引用,如果回调函数中访问了组件的数据,就会形成引用链 如果不移除会有什么后果? 内存泄漏 当组件 B 被销毁(如路由切换、v-if 隐藏)后: 组件 B 虽然从 DOM 中移除了 但 EventBus 仍然持有回调函数的引用 回调函数通过闭包持有组件 B 的 this 引用 组件 B 及其所有数据(包括 hugeData)永远不会被垃圾回收 重复监听导致逻辑错误 //用户反复进入/离开组件多次 // 组件 mounted() { EventBus.$on('update-count', () => { this.count++ // 每次进入组件都会执行多次 }) } // 场景:用户进入页面 → 离开 → 重新进入 → 点击按钮触发事件 // 点击一次,count 会增加 3 次(因为注册了 3 个监听器) 组件销毁后仍然执行逻辑 Vuex (全局状态管理) // store.js export default new Vuex.Store({ state: { count: 0 }, mutations: { increment(state) { state.count++ } }, actions: { incrementAsync({ commit }) { setTimeout(() => commit('increment'), 1000) } }, getters: { doubleCount: state => state.count * 2 } }) // 组件中使用 this.$store.commit('increment') this.$store.dispatch('incrementAsync') this.$store.getters.doubleCount Vue3组件通信: Props / $emit (变化较小) <!-- 子组件 Child.vue --> <script setup> // 定义 props const props = defineProps({ message: String, count: Number }) // 定义 emits const emit = defineEmits(['update', 'childEvent']) const sendToParent = () => { emit('childEvent', '数据来自子组件') } </script> <!-- 父组件 --> <template> <Child :message="msg" @childEvent="handleEvent" /> </template> <script setup> import { ref } from 'vue' import Child from './Child.vue' const msg = ref('Hello from parent') const handleEvent = (data) => { console.log(data) } </script> 使用 mitt 实现 Event Bus Vue 3 专注于组件核心功能,EventBus 属于“模式”而非“核心功能” // event-bus.js import mitt from 'mitt' export const emitter = mitt() // 组件 A(发送事件) import { emitter } from './event-bus' emitter.emit('custom-event', { data: 'some data' }) // 组件 B(监听事件) import { emitter } from './event-bus' import { onUnmounted } from 'vue' const handler = (payload) => { console.log(payload) } emitter.on('custom-event', handler) // 组件卸载时移除监听 onUnmounted(() => { emitter.off('custom-event', handler) }) Vuex 4 / Pinia (状态管理) // stores/user.js import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ name: '张三', age: 18 }), getters: { adult: (state) => state.age >= 18 }, actions: { updateName(name) { this.name = name } } }) // 组件中使用 <script setup> import { useUserStore } from '@/stores/user' import { storeToRefs } from 'pinia' const userStore = useUserStore() // 使用 storeToRefs 保持响应性 const { name, age, adult } = storeToRefs(userStore) const { updateName } = userStore // 直接调用 action updateName('李四') </script>
布莱克
2026-05-10 15:49Vue已编辑
Nuxt 的 “SSR渲染”
什么是 SSR? 传统 SPA 的两大痛点 传统的单页应用(如纯 Vue/React)采用客户端渲染(CSR),浏览器下载空的 HTML 骨架,然后用 JavaScript 动态填充内容。这导致了两个核心问题: 问题一:首屏白屏时间过长 <!-- 传统 SPA 的初始 HTML --> <!DOCTYPE html> <html> <head><title>Loading...</title></head> <body> <div id="app"></div> <!-- 空空如也! --> <script src="/js/chunk-vendors.js"></script> <script src="/js/app.js"></script> </body> </html> 用户需要等待 JS 下载、解析、执行完成后才能看到内容,在弱网环境下体验极差。 问题二:SEO 不友好 搜索引擎爬虫抓取页面时,看到的是空白的 HTML,无法获取页面的真实内容,导致排名低下。 SSR 如何解决这些问题? 服务端渲染(Server-Side Rendering)在服务器上直接生成完整的 HTML 字符串返回给浏览器: 带来的好处: ✅ 用户立即看到内容(无需等待 JS) ✅ 搜索引擎能抓取到完整内容 ✅ 社交媒体分享时能正确显示标题、描述和图片 Nuxt SSR 的核心原理 Nuxt 实现了一种 "同构渲染"(混合渲染)(Universal Rendering) 策略:同一份 Vue 组件代码,既能在服务端运行生成 HTML,又能在客户端运行实现交互。 首次加载:完整的 SSR 流程 服务器接收请求后,执行页面的所有异步操作(useFetch、useAsyncData) 数据获取完成后,将 Vue 组件渲染成 HTML 字符串 将初始状态序列化后嵌入 window.__NUXT__ 返回完整 HTML 给浏览器 后续导航:纯客户端渲染 哪些场景会触发 SSR? 操作方式 渲染模式 是否执行 SSR Network 中能看到 API 请求? 直接在地址栏输入 URL SSR ✅ 是 ❌ 看不到 刷新页面(F5 / Cmd+R) SSR ✅ 是 ❌ 看不到 从外部链接跳转(Google 搜索) SSR ✅ 是 ❌ 看不到 新标签页打开链接 SSR ✅ 是 ❌ 看不到 点击 <NuxtLink> CSR ❌ 否 ✅ 看得到 调用 navigateTo() CSR ❌ 否 ✅ 看得到 调用 router.push() CSR ❌ 否 ✅ 看得到 每次直接访问(刷新、新标签页、外部链接)都是 SSR,只有客户端路由跳转才是 CSR Nuxt SSR 的适用场景 场景 为什么适合 SSR 内容型网站(博客、文档、新闻) SEO 要求高,首屏加载速度重要 电商网站 产品页面需要被搜索引擎索引 营销落地页 需要快速展示内容,社交媒体分享需要正确预览 企业官网 品牌形象需要好的 SEO 表现
布莱克
2026-05-08 18:53Nuxt
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 9
assistant