外观模式

定义 外观模式(Facade Pattern)是一种结构型设计模式,它为子系统中的一组接口提供一个统一的高层接口。外观模式定义了一个高层接口,使得子系统更加容易使用。 外观模式的核心思想是通过引入一个外观类,将复杂子系统的内部复杂性隐藏起来,只暴露一个简单的接口给客户端。 为什么需要外观模式 外观模式要解决的核心问题是:简化复杂子系统的使用,为客户端提供一个更简单的接口。 问题场景:假设我们正在开发一个视频播放功能,需要处理音频、视频、字幕等多个子系统。 直接使用各个子系统: class VideoPlayerController { func playVideo(url: URL) { // 1. 初始化音频引擎 let audioEngine = AudioEngine() audioEngine.configure(sampleRate: 44100) audioEngine.setOutputDevice(.speaker) // 2. 初始化视频解码器 let videoDecoder = VideoDecoder() videoDecoder.setCodec(.h264) videoDecoder.configureHardwareAcceleration(true) // 3. 初始化渲染器 let renderer = VideoRenderer() renderer.setResolution(1920, 1080) renderer.setFrameRate(30) // 4. 加载字幕 let subtitleLoader = SubtitleLoader() let subtitles = subtitleLoader.load(from: url) // 5. 同步各个组件 let synchronizer = AVSynchronizer() synchronizer.link(audio: audioEngine, video: videoDecoder) // 6. 开始播放 let data = loadVideoData(url) let decodedVideo = videoDecoder.decode(data) renderer.render(decodedVideo) audioEngine.play() // 还要处理暂停、seek、音量调节... } } 这种方式有什么问题? ...

May 2, 2026

迪米特法则

什么是迪米特法则? 迪米特法则(Law of Demeter, LoD)也称为最少知识原则(Least Knowledge Principle, LKP),由 Ian Holland 在 1987 年提出。 Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. 每个单元应该只对与其紧密相关的单元有有限的了解。 更通俗的表述是:只和直接朋友交流,不要和陌生人说话。 核心思想 什么是"直接朋友"? 一个对象的"直接朋友"包括: 当前对象本身(this/self) 作为参数传入的对象 当前对象创建的对象 当前对象的成员变量 全局对象(如单例,技术上可访问,但应谨慎依赖) class Person { let wallet: Wallet // 成员变量 - 直接朋友 func buyItem(shop: Shop, item: Item) { // 参数 - 直接朋友 let receipt = Receipt() // 自己创建的对象 - 直接朋友 // 以下都是与直接朋友交流 let money = wallet.getMoney() shop.sell(item: item) receipt.record(item: item) } } 什么是"陌生人"? 如果需要通过多层对象去访问另一个对象,那个被间接访问的对象就是"陌生人"。 ...

May 2, 2026

前端构建工具详解

谈到构建工具,大家首先想到的肯定就是 Webpack 以及现在最🔥的 Vite。 Webpack,功能强大,生态丰富,从面世到今天,一直是很受大家欢迎;Vite 采用 unbundle 构建模式,带来了极致的开发体验,给开发人员以新的选择。 在这两个构建工具之外,还有其他的构建工具,如和 Webpack、Vite 类似的 Rollup、Parcel、Esbuild,自动化构建工具 grunt、gulp,以及更加久远的 YUI Tool。 这些工具的存在,构成了前端构建工具的发展史。 YUI Tool + Ant YUI tool 是 07 年左右出现的一个构建工具,功能比较简单,用于压缩混淆 css 和 js 代码,需要配合 java 的 Ant 使用。 当时 web 应用开发主要采用 JSP,还不像现在这样前后端分离,通常是由 java 开发人员来编写 js、css 代码,前端代码都是和后端 java 代码放在一起的。因此前端代码的压缩混淆也就基于 java 实现了。 Grunt / Gulp Grunt / Gulp 都是运行在 node 环境上的自动化工具。 在开发过程中,我们可以将一些常见操作如解析 html、es6 代码转换为 es5、less / sass 代码转换为 css 代码、代码检查、代码压缩、代码混淆配置成一系列任务,然后通过 Grunt / Gulp 自动执行这些任务。 Grunt 和 Gulp 的不同点: ...

August 7, 2025

http状态码

一、HTTP 状态码概述 1. 概念 当我们在浏览器输入URL并按下Enter键时,浏览器就会向站点的服务器发送一个HTTP请求,服务器接收并处理请求,然后将相关资源和HTTP标头一起返回。可以在浏览器的Network中查看 HTTP 的请求状态码: 维基百科中对HTTP状态码的解释: HTTP状态码(HTTP Status Code)是用以表示网页服务器超文本传输协议响应状态的3位数字代码。它由 RFC 2616 规范定义的,并得到 RFC 2518、RFC 2817、RFC 2295、RFC 2774 与 RFC 4918 等规范扩展。所有状态码被分为五类,状态码的第一个数字代表了响应的五种状态之一。所示的消息短语是典型的,但是可以提供任何可读取的替代方案。 除非另有说明,状态码是HTTP/1.1标准(RFC 7231)的一部分。 HTTP 状态代码是从 Web 服务器发送的 3 位代码(如 200 OK 或 404 Not Found),用于让我们和搜索引擎知道请求中是否存在任何错误或服务器尝试处理请求时是否存在任何问题。 2. 分类 应用通常就是客户端向服务器发出请求,服务器做出响应。状态码就是让我们知道 HTTP 请求是成功、失败还是其他。HTTP状态码通常分为五类: 类别 定义 描述 1xx Informational(信息性状态码) 接受的请求正在处理 2xx Success(成功状态码) 请求正常处理完毕 3xx Redirection(重定向状态码) 需要进行附加操作来完成请求 4xx Client Error (客户端错误状态码) 服务器无法处理请求 5xx Server Error(服务器错误状态码) 服务器处理请求出错 3. 重要性 HTTP 状态代码对于诊断应用问题很重要,例如网络服务器是否无法正常工作且无法提供页面等。快速发现这些问题对于开发人员和搜索引擎提供良好的体验非常重要。那为什么 HTTP 状态代码和错误对搜索引擎优化 (SEO) 很重要呢? ...

July 28, 2025

React 生命周期

简介 React从v16.3的版本开始, 对生命周期的钩子进行了渐进式的调整,分别废弃和新增了一些生命周期的钩子函数,本文会从以下四点开始讲解: 新旧生命周期函数的对比 分析为什么要废弃旧的钩子函数 详解新的生命周期使用场景 实例代码演示并进行总结 新旧生命周期对比 一个完整的React组件生命周期会依次调用如下钩子: old lifecycle 挂载 constructor componentWillMount render componentDidMount 更新 componentWillReceiveProps shouldComponentUpdate componentWillUpdate render componentDidUpdate 卸载 componentWillUnmount new lifecycle 挂载 constructor getDerivedStateFromProps render componentDidMount 更新 getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate componentDidUpdate 卸载 componentWillUnmount 从以上生命周期的对比,我们不难看出,React从v16.3开始废弃 componentWillMount componentWillReceiveProps componentWillUpdate 三个钩子函数 分析废弃原因 Facebook花了两年多的时间搞出了React Fiber, 因为在v15的版本,更新过程是同步的,往往一个主线程长时间被占用,会导致页面性能问题 而 React Fiber的机制: 利用浏览器 requestIdleCallback 将可中断的任务进行分片处理,每一个小片的运行时间很短,这样唯一的线程就不会被独占 需要详细了解可以点下面链接 Morgan大佬 - 知乎 那么React Fiber会对生命周期带来什么影响吗? 因为React Fiber Reconciliation 这个过程有可能暂停然后继续执行,所以挂载和更新之前的生命周期钩子就有可能不执行或者多次执行; 目前React为这几个生命周期钩子提供了别名,分别是: UNSAFE_componentWillMount UNSAFE_componentWillReceiveProps UNSAFE_componentWillUpdate React17将只提供别名,取个别名的目的就是恶心你,不让你使用。 ...

December 17, 2024

浏览器的存储机制

浏览器的存储机制涵盖多种技术,目的是满足 Web 应用在不同场景下的数据持久化、缓存和离线访问需求。它们各自具有不同的存储容量、生命周期、安全策略和访问方式,理解这些机制对于构建高效、安全的前端应用至关重要。 一、浏览器存储机制分类 1. Cookie 最早期的存储机制,主要用于会话管理和身份认证。 容量限制:单个约 4KB,总数有限制; 生命周期:可设置过期时间或为会话级; 访问方式:由浏览器自动在 HTTP 请求头中携带,也能通过 JS 访问(除非设置 HttpOnly); 安全性:可设置 HttpOnly、Secure、SameSite 防范安全风险。 2. Web Storage(LocalStorage 和 SessionStorage) HTML5 新增的简单键值对存储方案。 LocalStorage:永久存储,关闭浏览器数据仍保留,同源可访问; SessionStorage:页面会话存储,关闭标签页即清空; 容量:一般为 5~10MB; 访问方式:同步 API,容易使用但可能阻塞主线程。 3. IndexedDB 浏览器内置的底层结构化数据库,支持事务和索引,适合复杂和大容量数据存储。 容量:远大于 LocalStorage,通常以设备剩余空间为限; 访问方式:异步 API,支持复杂操作; 用途:离线应用、大数据缓存、文件存储等。 4. Cache Storage 由 Service Worker 管理的请求和响应缓存,用于离线和加速访问。 容量:较大,依赖设备和浏览器策略; 访问方式:异步 Promise API; 用途:缓存静态资源、接口响应,实现离线支持。 5. Service Worker 虽然不是存储机制本身,但作为浏览器后台代理进程,管理 Cache Storage,实现网络请求拦截和缓存策略,是现代离线应用的核心。 生命周期独立于页面,可在后台运行; 控制页面的网络请求,提供离线能力和资源预缓存; 可结合 Cache Storage 和 IndexedDB 进行数据管理。 二、存储机制的生命周期与访问作用域 Cookie:基于域和路径,可设置 HttpOnly、Secure 限制访问; LocalStorage/SessionStorage:基于同源策略,SessionStorage 进一步限定于标签页会话; IndexedDB 和 Cache Storage:基于同源,支持版本控制和升级; Service Worker:独立于页面,能控制同源下的所有相关页面。 三、容量限制与性能影响 Cookie 容量最小,且会随每次请求自动发送,影响网络性能; LocalStorage/SessionStorage 适合轻量存储,容量适中; IndexedDB 和 Cache Storage 容量大,适合海量数据和文件缓存; Service Worker 通过异步调度,避免阻塞主线程。 四、安全与隐私考虑 Cookie 可被服务器访问,设置 HttpOnly 避免脚本窃取; Web Storage、IndexedDB 只能由同源脚本访问,防止跨站数据泄漏; Service Worker 需 HTTPS 环境,避免被恶意注入; 用户隐私模式下,存储行为可能受限,数据不保证持久。 五、应用场景及选择建议 需要与服务器频繁交互、会话维持时用 Cookie; 存储简单配置、少量数据用 LocalStorage; 页面会话临时数据用 SessionStorage; 大规模结构化数据和离线存储用 IndexedDB; 静态资源及请求缓存用 Cache Storage,配合 Service Worker 提升离线体验和性能; Service Worker 负责管理缓存和拦截网络请求,实现 PWA 功能。 常见考点 面试考察点通常围绕存储类型、特点、使用场景、容量限制、安全性和生命周期展开: ...

December 17, 2024

内存空间

var a = 20; var b = 'abc'; var c = true; var d = { m: 20 } 因为JavaScript具有自动垃圾回收机制,所以对于前端开发来说,内存空间并不是一个经常被提及的概念,很容易被大家忽视。特别是很多不是计算机专业的朋友在进入到前端之后,会对内存空间的认知比较模糊,甚至有些人干脆就是一无所知。 当然也包括我自己。在很长一段时间里认为内存空间的概念在JS的学习中并不是那么重要。可是后我当我回过头来重新整理JS基础时,发现由于对它们的模糊认知,导致了很多东西我都理解得并不明白。比如最基本的引用数据类型和引用传递到底是怎么回事儿?比如浅复制与深复制有什么不同?还有闭包,原型等等。 因此后来我才渐渐明白,想要对JS的理解更加深刻,就必须对内存空间有一个清晰的认知。 在学习内存空间之前,我们需要对三种数据结构有一个直观的认知。他们分别是堆(heap),栈(stack)与队列(queue)。 一、栈数据结构 与C/C++不同,JavaScript中并没有严格意义上区分栈内存与堆内存。因此我们可以简单粗暴的理解为JavaScript的所有数据都保存在堆内存中。但是在某些场景,我们仍然需要基于堆栈数据结构的思维来实现一些功能,比如JavaScript的执行上下文(关于执行上下文我会在下一篇文章中总结)。执行上下文的执行顺序借用了栈数据结构的存取方式。(也就是后面我们会经常提到的函数调用栈)。因此理解栈数据结构的原理与特点十分重要。 要简单理解栈的存取方式,我们可以通过类比乒乓球盒子来分析。如下图左侧。 这种乒乓球的存放方式与栈中存取数据的方式如出一辙。处于盒子中最顶层的乒乓球5,它一定是最后被放进去,但可以最先被使用。而我们想要使用底层的乒乓球1,就必须将上面的4个乒乓球取出来,让乒乓球1处于盒子顶层。这就是栈空间先进后出,后进先出的特点。图中已经详细的表明了栈空间的存储原理。 二、堆数据结构 堆数据结构是一种树状结构。它的存取数据的方式,则与书架与书非常相似。 书虽然也整齐的存放在书架上,但是我们只要知道书的名字,我们就可以很方便的取出我们想要的书,而不用像从乒乓球盒子里取乒乓一样,非得将上面的所有乒乓球拿出来才能取到中间的某一个乒乓球。好比在JSON格式的数据中,我们存储的key-value是可以无序的,因为顺序的不同并不影响我们的使用,我们只需要关心书的名字。 三、队列 在JavaScript中,理解队列数据结构的目的主要是为了清晰的明白事件循环(Event Loop)的机制到底是怎么回事。在后续的章节中我会详细分析事件循环机制。 队列是一种先进先出(FIFO)的数据结构。正如排队过安检一样,排在队伍前面的人一定是最先过检的人。用以下的图示可以清楚的理解队列的原理。 四、变量对象与基础数据类型 JavaScript的执行上下文生成之后,会创建一个叫做变量对象的特殊对象(具体会在下一篇文章与执行上下文一起总结),JavaScript的基础数据类型往往都会保存在变量对象中。 严格意义上来说,变量对象也是存放于堆内存中,但是由于变量对象的特殊职能,我们在理解时仍然需要将其于堆内存区分开来。 基础数据类型都是一些简单的数据段,JavaScript中有5中基础数据类型,分别是Undefined、Null、Boolean、Number、String。基础数据类型都是按值访问,因为我们可以直接操作保存在变量中的实际的值。 ES6中新加了一种基础数据类型Symbol,可以先不用考虑他 五、引用数据类型与堆内存 与其他语言不同,JS的引用数据类型,比如数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以理解为保存在变量对象中的一个地址,该地址与堆内存的实际值相关联。 为了更好的搞懂变量对象与堆内存,我们可以结合以下例子与图解进行理解。 var a1 = 0; // 变量对象 var a2 = 'this is string'; // 变量对象 var a3 = null; // 变量对象 var b = { m: 20 }; // 变量b存在于变量对象中,{m: 20} 作为对象存在于堆内存中 var c = [1, 2, 3]; // 变量c存在于变量对象中,[1, 2, 3] 作为对象存在于堆内存中 因此当我们要访问堆内存中的引用数据类型时,实际上我们首先是从变量对象中获取了该对象的地址引用(或者地址指针),然后再从堆内存中取得我们需要的数据。 理解了JS的内存空间,我们就可以借助内存空间的特性来验证一下引用类型的一些特点了。 在前端面试中我们常常会遇到这样一个类似的题目 // demo01.js var a = 20; var b = a; b = 30; // 这时a的值是多少? // demo02.js var m = { a: 10, b: 20 } var n = m; n.a = 15; // 这时m.a的值是多少 在变量对象中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a执行之后,a与b虽然值都等于20,但是他们其实已经是相互独立互不影响的值了。具体如图。所以我们修改了b的值以后,a的值并不会发生变化。 ...

November 6, 2024

HTML 元素分类

根据标签的功能和表现,HTML 标签主要分为以下几类: 1. 块级元素(Block-level Elements) 块级元素通常独占一行,并且可以包含其他块级元素或内联元素,能够扩展至父容器的全部宽度。 特点: 独占一行,默认情况下会在前后产生换行。 宽度默认是父元素的 100%。 可以设置 width、height、margin、padding 等布局属性。 常见的块级元素: <div>:无特定语义的容器,常用于布局。 <p>:段落标签。 <h1> 到 <h6>:标题标签,按重要性递减。 <ul>、<ol>、<li>:无序、有序列表及其项。 <header>、<footer>、<section>、<article>:HTML5 语义化标签,分别表示页头、页尾、内容区块等。 示例: <div> <h1>标题</h1> <p>这是一个段落。</p> </div> 2. 内联元素(Inline Elements) 内联元素与其他元素排列在同一行,且只占用其内容所需的宽度。它们不会独占一行,不能包含块级元素。 特点: 与相邻的内联元素排列在同一行。 仅占据自身内容的宽度,不会在前后产生换行。 不能设置 width、height(无效),但可以设置 padding 和 margin,只影响水平方向。 常见的内联元素: <span>:无特定语义的容器,常用于局部样式。 <a>:超链接。 <strong>:表示强调的文本,通常加粗显示。 <em>:表示强调的文本,通常斜体显示。 <img>:图像标签。 <input>:表单输入元素。 示例: <p>这是一个段落,<a href="#">点击这里</a> 了解更多。</p> 3. 内联块元素(Inline-block Elements) 内联块元素是同时具备内联元素和块级元素特性的元素。它们在布局上是内联的,但表现为块状元素,因此可以设置宽高。 特点: 与其他元素排列在同一行,但可设置宽度和高度。 不会自动换行。 常见的内联块元素: <img>:虽然图像显示在行内,但可以设置 width 和 height。 <button>:按钮。 <input type="text">:文本输入框。 CSS 设置为 display: inline-block 的元素。 示例: <span style="display: inline-block; width: 100px; height: 100px; background: red;"></span> 4. 自闭合元素(Self-closing Elements) 自闭合元素是没有闭合标签的元素,因为它们没有任何内容。这类标签通常用于插入内容或进行控制,而不包含其他内容。 ...

October 15, 2024

优先级

在前端面试中,经常会遇到CSS选择器的问题三连: 说一下CSS的选择器有哪些 优先级是怎样的 权重计算方式 今天就来给大家介绍选择器的优先级和权重计算方式。 优先级是什么?有什么用? 先提出一个问题:如果一个元素通过多种方式设置了字体大小,那么浏览器在最后渲染时会使用哪个属性值呢? 其实,浏览器会通过优先级来判断哪些属性值与一个元素最为相关,从而在该元素上应用这些属性值。优先级是基于不同种类选择器组成的匹配规则。 换种说法,优先级就是分配给指定的 CSS 声明的一个权重,它由 匹配的选择器 中的 每一种选择器类型的数值 决定。 总结一下: 权重决定了你css规则怎样被浏览器解析直到生效,css权重关系到你的css规则是怎样显示的。 当很多的样式被应用到某一个元素上时,权重决定哪种样式生效。 每个选择器都有自己的权重。你的每条css规则,都包含一个权重级别。 这个级别是由不同的选择器加权计算的,通过权重,不同的样式最终会作用到你的网页中 。 如果两个选择器同时作用到一个元素上,权重高者生效。 权重的计算规则 接着介绍权重的计算规则: 第一优先级:!important会覆盖页面内任何位置的元素样式 内联样式,如style="color: green",权值为1000 ID选择器,如#app,权值为0100 类、伪类、属性选择器,如.foo, :first-child, div[class="foo"],权值为0010 标签、伪元素选择器,如div::first-line,权值为0001 通配符、子类选择器、兄弟选择器,如*, >, +,权值为0000 继承的样式没有权值 权值计算公式: 权值 = 第一等级选择器 x 个数,第二等级选择器 x 个数,第三等级选择器 x 个数,第四等级选择器 x 个数 比如: 选择器 千位 百位 十位 个位 优先级 h1 0 0 0 1 0001 h1 + p::first-letter 0 0 0 3 0003 li > a[href*=“en-US”] > .inline-warning 0 0 2 2 0022 #identifier 0 1 0 0 0100 内联样式 1 0 0 0 1000 权重的比较规则 当两个权值进行比较的时候,是从高到低逐级将等级位上的权重值(如 权值 1,0,0,0 对应–> 第一等级权重值,第二等级权重值,第三等级权重值,第四等级权重值)来进行比较的,而不是简单的 1000个数 + 100个数 + 10个数 + 1个数 的总和来进行比较的,换句话说,低等级的选择器,个数再多也不会越等级超过高等级的选择器的优先级的。 ...

October 10, 2024

计算属性

前言 Vue 的计算属性(computed)是 Vue.js 中非常重要的一个概念。它允许你在 Vue 组件中定义一些计算逻辑,这些逻辑会依赖于 Vue 的响应式数据,并且具有缓存机制,只有在依赖数据发生改变时才会重新计算,从而提高了性能。 计算属性的主要作用包括: 数据转换 数据过滤 响应式依赖 缓存机制 简化模板 在本篇文章中,我们将详细探寻如何利用计算属性优化 Vue 组件的数据处理和渲染逻辑,深入了解 Vue 计算属性的精髓。 一. 认识计算属性 1. 什么是计算属性 Vue.js 中的计算属性(computed properties)是 Vue 实例中非常重要的一个特性。它们用于对 Vue 实例的数据进行动态计算,且具有缓存机制,只有在相关依赖发生改变时才会重新计算。这种特性使得计算属性非常适合用于处理模板中的逻辑。 2. 计算属性的定义 计算属性是在 Vue 实例的computed选项中定义的,可以是一个函数或具有get和set方法的对象。函数形式的计算属性会在调用时被执行,而对象形式的计算属性则可以提供自定义的get和set方法。 computed: { // 函数形式的计算属性 reversedMessage: function () { return this.message.split('').reverse().join(''); }, // 对象形式的计算属性 fullName: { get: function () { return this.firstName + ' ' + this.lastName; }, set: function (newValue) { var names = newValue.split(' '); this.firstName = names[0]; this.lastName = names[names.length - 1]; } } } 3. 计算属性的依赖 计算属性可以依赖于 Vue 实例的响应式数据。当依赖数据发生改变时,相关的计算属性会重新计算。 ...

October 4, 2024