meta 标签

概览 meta标签一般放在整个html页面的head部分,在MDN中对他这样定义: meta是文档级元数据元素,用来表示那些不能由其它 HTML 元相关元素(<base>、<link>, <script>、<style>或 <title>)之一表示的任何元数据。 是不是感觉看起来很抽象?说白了就是为了传达信息。 先看看meta 元素定义的元数据的类型: 如果设置了 name属性,meta 元素提供的是文档级别的元数据,应用于整个页面。 如果设置了 http-equiv属性,meta 元素则是编译指令,提供的信息与类似命名的 HTTP 头部相同。 如果设置了 charset属性,meta 元素是一个字符集声明,告诉文档使用哪种字符编码。 如果设置了 itemprop 属性,meta 元素提供用户定义的元数据。 name属性 name和content一起使用,前者表示要表示的元数据的名称,后者是元数据的值。 author 用来表示网页的作者的名字,例如某个组织或者机构。 <meta name="author" content="aaa@mail.abc.com"> description 是一段简短而精确的、对页面内容的描述。以头条和taobao的description标签为例: keywords 与页面内容相关的关键词,使用逗号分隔。某些搜索引擎在遇到这些关键字时,会用这些关键字对文档进行分类。 还是以头条和taobao为例 viewpoint 为 viewport(视口)的初始大小提供指示。目前仅用于移动设备。 可能你也发现了,我们在vscode中自动生成html的代码片段时,会自动生成: <meta name="viewport" content="width=device-width, initial-scale=1.0"> width用来设置 viewport 的宽度为设备宽度; initial-scale为设备宽度与 viewport 大小之间的缩放比例。 robots 表示爬虫对此页面的处理行为,或者说,应当遵守的规则,是用来做搜索引擎抓取的。 它的content可以为: all:搜索引擎将索引此网页,并继续通过此网页的链接索引文件将被检索 none:搜索引擎讲忽略此网页 index:搜索引擎索引此网页 follow:搜索引擎继续通过此网页的链接索引搜索其它的网页 renderer 用来指定双核浏览器的渲染方式,比如360浏览器,我们可以通过这个设置来指定360浏览器的渲染方式 <meta name="renderer" content="webkit"> //默认webkit内核 <meta name="renderer" content="ie-comp"> //默认IE兼容模式 <meta name="renderer" content="ie-stand"> //默认IE标准模式 http-equiv http-equiv也是和content一起使用,前者表示要表示的元数据的名称,后者是元数据的值。 http-equiv 所有允许的值都是特定 HTTP 头部的名称, ...

November 5, 2024

position

CSS 中 position 属性用于指定元素的定位方法的类型(static、relative、absolute、fixed、sticky)。 静态定位 position: static 此属性未 HTML 元素默认定位,一个元素没有以任何特殊的方式定位,它总是按照页面的正常流程定位。 在此属性下,属性值 top、left、right、bottom 和 z-index 对HTML元素没有影响。 <div class="parent"> <div class="child"></div> </div> <div class="sibling"></div> <style type="text/css"> .parent { width: 480px; height: 320px; background-color: #008800; } .child { width: 240px; height: 160px; background-color: #ff0000; } .sibling { width: 240px; height: 160px; background-color: #0000ff; } </style> 效果如图: 为什么使用它呢?将元素设置为 position: static 的唯一原因是强制删除应用于无法控制的元素上的某些定位。 相对定位 position: relative 此属性相对于其正常位置的,在不改变布局的情况下根据元素的当前位置定位元素。 ...

October 8, 2024

生命周期

vue3生命周期 在Vue中,生命周期指的是Vue实例从创建到销毁的过程。在这个过程中,Vue实例会经历一系列的阶段,包括数据观测、模板编译、挂载DOM、更新DOM、卸载等等。在这些阶段,Vue提供了一些生命周期钩子函数,让我们可以在特定的时机执行某些代码。 为什么需要生命周期 生命周期钩子函数为我们提供了在Vue实例生命周期不同阶段执行代码的能力。这对于非响应式的操作非常有用,例如直接操作DOM、添加事件监听器或者创建定时器等。这些操作如果在数据变化时执行可能会导致不必要的性能开销,而生命周期钩子函数让我们可以更合理地安排这些操作的执行时机。一些常用的库,例如react,webpack都提供了生命周期的钩子,方便开发者理解整个运行流程。 Vue3 有哪些生命周期,分别在什么时候调用? Vue3的生命周期钩子函数主要包括: beforeCreate: 创建实例之前调用,此时实例的数据观测、事件等还未初始化。 created: 创建实例后调用,此时实例的数据观测、事件等已经初始化完成。 beforeMount: 挂载实例之前调用,此时模板已经编译完成,但是还未挂载到DOM。 mounted: 挂载实例后调用,此时实例已经挂载到DOM,可以进行DOM操作。 beforeUpdate: 数据更新之前调用,此时可以进行状态的最后更改。 updated: 数据更新后调用,此时DOM已经完成更新,可以进行DOM操作。 beforeUnmount: 卸载实例之前调用,此时实例仍然完全可用。 unmounted: 卸载实例后调用,此时实例已经完全卸载。 生命周期类型的枚举位于packages/runtime-core/src/enums.ts目录下: export const enum LifecycleHooks { BEFORE_CREATE = 'bc', // 创建之前 CREATED = 'c', // 创建 BEFORE_MOUNT = 'bm', // 挂载之前 MOUNTED = 'm', // 挂载 BEFORE_UPDATE = 'bu', // 更新之前 UPDATED = 'u', // 更新 BEFORE_UNMOUNT = 'bum', // 卸载之前 UNMOUNTED = 'um', // 卸载之后 DEACTIVATED = 'da', // 被 keep-alive 缓存的组件停用时调用。 ACTIVATED = 'a', // 被 keep-alive 缓存的组件激活时调用。 RENDER_TRIGGERED = 'rtg', // 依赖被收集时调用 RENDER_TRACKED = 'rtc', // 依赖被触发时调用 ERROR_CAPTURED = 'ec', // 错误捕获 SERVER_PREFETCH = 'sp' } 上面生命周期可以用一张图来表示: 选项式和组合式生命周期的区别 在Vue3中,我们可以使用选项式API和组合式API来定义组件。这两种API对应的生命周期钩子函数有一些区别。 选项式API的生命周期钩子函数定义在组件选项对象的methods中,例如: export default { methods: { created() { // do something } } } 组合式API的生命周期钩子函数定义在setup函数中,需要使用import从vue中引入,例如: import { onMounted } from 'vue'; ​ export default { setup() { onMounted(() => { // do something }); } } 需要注意的是,在组合式API中没有beforecate和created钩子,因为他们本身被setup这个函数替代了,直接把相关逻辑写在setup中就行了 ...

October 4, 2024

export/import

export,import 是什么 我们知道,JS 模块导入导出,使用 import , export 这两个关键字 export 用于对外输出本模块 import 用于导入模块 也就是说使用 export 导出一个模块之后,其它文件就可以使用 import 导入相应的模块了 下面我们具体看看, import 和 export 到底怎么用?怎么导出模块(比如变量,函数,类,对象等) 1 导出单个变量 //a.js 导出一个变量,语法如下 export var site = "www.helloworld.net" //b.js 中使用import 导入上面的变量 import { site } from "/.a.js" //路径根据你的实际情况填写 console.log(site) //输出: www.helloworld.net 2 导出多个变量 上面的例子是导出单个变量,那么如何导出多个变量呢 //a.js 中定义两个变量,并导出 var siteUrl="www.helloworld.net" var siteName="helloworld开发者社区" //将上面的变量导出 export { siteUrl ,siteName } // b.js 中使用这两个变量 import { siteUrl , siteName } from "/.a.js" //路径根据你的实际情况填写 console.log(siteUrl) //输出: www.helloworld.net console.log(siteName) //输出: helloworld开发者社区 ``` ### 3 导出函数 导出函数和导出变量一样,需要添加`{ }` ````js //a.js 中定义并导出一个函数 function sum(a, b) { return a + b } //将函数sum导出 export { sum } //b.js 中导入函数并使用 import { sum } from "/.a.js" //路径根据你的实际情况填写 console.log( sum(4,6) ) //输出: 10 ``` ### 4 导出对象 js中一切皆对象,所以对象一定是可以导出的,并且有两种写法 #### 4.1 第一种写法 使用 `export default` 关键字导出,如下 ````js //a.js 中,定义对象并导出, 注意,使用export default 这两个关键字导出一个对象 export default { siteUrl:'www.helloworld.net', siteName:'helloworld开发者社区' } //b.js 中导入并使用 import obj from './a.js' //路径根据你的实际情况填写 console.log(obj.siteUrl) //输出:www.helloworld.net console.log(obj.siteName) //输出:helloworld开发者社区 ``` #### 4.2 第二种写法 同样是使用`export default` 关键字,如下 ````js //a.js 中定义对象,并在最后导出 var obj = { siteUrl:'www.helloworld.net', siteName:'helloworld开发者社区' } export default obj //导出对象obj //b.js 中导入并使用 import obj from './a.js' //路径根据你的实际情况填写 console.log(obj.siteUrl) //输出:www.helloworld.net console.log(obj.siteName) //输出:helloworld开发者社区 ``` ### 5 导出类 导出类与上面的导出对象类似,同样是用 `export default` 关键字,同样有两种写法 #### 5.1 第一种写法 ````js //a.js 中定义一个类并直接导出 export default class Person { //类的属性 site = "www.helloworld.net" //类的方法 show(){ console.log(this.site) } } //b.js 中导入并使用 //导入类 import Person from './a.js' //创建类的一个对象person let person = new Person() //调用类的方法 person.show() //输出:www.helloworld.net ``` #### 5.2 第二种写法 ````js //a.js 中定义一个类,最后导出 class Person { //类的属性 site = "www.helloworld.net" //类的方法 show(){ console.log(this.site) } } //导出这个类 export default Person //b.js 中导入并使用 //导入类 import Person from './a.js' //创建类的一个对象person let person = new Person() //调用类的方法 person.show() //输出:www.helloworld.net ``` ### 小结 下面我们简单总结一下 `export`与`export default`的区别 * export与export default均可用于导出常量、函数、文件、模块等 * 可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用 * export default后面不能跟const或let的关键词 * export、import可以有多个,export default仅有一个。 * 通过export方式导出,在导入时要加 { },export default则不需要 * export具名导出xxx ,export default匿名。区别在于导入的时候,export需要一样的名称才能匹配,后者无论取什么名都可以。 * 模块化管理中一个文件就是一个模块,export可以导出多个方法和变量,export default只能导出当前模块,一个js文件中只支持出现一个 对于 `import` ,`export` , `export default` ,他们的用法上面的例子已经很详细的列出了,忘记的时候,可以当作参考看看 最重要的还是要明白为什么要这么写,实在不明白记住就行了。 常见考点 1. export 的基本使用 export 用于导出模块中的内容(如变量、函数、类等),使得其他模块能够导入这些内容。 ...

October 2, 2024

文件上传安全

文件上传漏洞简介 原理 攻击者可以上传一个与网站脚本语言相对应的恶意代码动态脚本到服务器上,然后访问这些恶意脚本中包含的恶意代码,从而获得了执行服务器端命令的能力,进一步影响服务器安全。 危害 文件上传漏洞最直接的威胁就是上传任意文件,包括恶意脚本、可执行程序等。 如果Web 服务器所保存上传文件的可写目录具有执行权限,那么就可以直接上传后门文件,导致网站沦陷。 如果攻击者通过其他漏洞进行提权操纵,拿到系统管理权限,那么直接导致服务器沦陷。 同服务器下的其他网站无一幸免,均会被攻击者控制。 利用 对于利用,我们只需要有个文件上传的点,以及我们知道我们所传的文件所在目录并且上传的恶意文件能够被web容器解析执行,便可能存在文件上传漏洞。 实战 说是实战但是因为不能随便入侵别人的机器(有概率获取银手镯一套),这里给大家推荐一个专门用来练习文件上传漏洞的不同利用方法的靶场。 github.com/c0ny1/uploa… 可以看到里面有很多关卡,对于环境的搭建,这里我推荐用phpstudy去进行一键搭建,比较适合入门的萌新,具体搭建方法不是本文的重点,于是我们跳过,接下来给大家带来文件上传漏洞一些常见的绕过方法。 客户端检测绕过 有的客户端会存在客户端校验,校验上传文件的后缀名,当我们传入php木马时可能会因为类型不同被禁止,此时我们要怎么进行上传一句话木马呢,这里给大家提供两个思路,第一个我们可以禁用浏览器的JS脚本,第二种方法便是使用渗透中常用工具burpsuit进行抓包,然后修改文件后缀为php即可绕过,因为他只是做了一个前端的简单验证。 修改MIME 类型 有的服务器端将会对上传文件content-type类型进行检查以此来防范恶意文件的上传,这时我们打开burp suite将php文件上传,因为content-type的类型不是服务器认可的类型,那么我们就可以使用burpsuite进行修改该文件类型,并将该文件进行上传。 常见的content-type对应类型如下: 00截断绕过 运用此漏洞需要满足两个条件: 1.php版本小于5.3.4 2.php的magic_quotes_gpc为OFF状态 0x开头表示16进制,0在十六进制中是00, 0x00就是%00解码成的16进制,具体运用方法如下,假如我们上传一个一句话木马: 攻击者修改了path以后的拼接结果为:uploads/XINO.php%00/20190818.php,移动文件的时候会将文件保存为:uploads/XINO.php,以此我们便可以绕过检测来上传一句话木马。 .htaccess文件攻击 .htaccess文件(或者"分布式配置文件"),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。 简单来说该文件的作用就是根据里面的内容解析为我们想要的类型,这么说可能很难理解,比如我上传一个图片马(JPG文件里插入了一句话木马),因为网站对我们的上传类型做了限制,我们上传不了PHP文件,我们先上传一个JPG的图片马,然后上传 .htaccess文件, 将图片解析为php语言,来达到getshell的目的。具体的文件内的内容为: <FilesMatch "evil.gif"> SetHandler application/x-httpd-php#在当前目录下,如果匹配到evil.gif文件,则被解析成PHP代码执行 AddHandler php5-script .gif #在当前目录下,如果匹配到evil.gif文件,则被解析成PHP代码执行 </FilesMatch> 当然不止这一种,还能如下写法: AddHandler php5-script .jpg AddType application/x-httpd-php .jpg Sethandler application/x-httpd-php 文件头检测 当我们上传一些文件时,有些拦截会检测我们文件里的具体内容,如果匹配了正确的文件头则会通过检测,反之则会拦截我们的文件,针对如此我们可以利用在文件前写入适当文件类型的文件头来绕过。常用的文件头如下: JPG:FF D8 FF E0 00 10 4A 46 49 46 GIF :47 49 46 38 39 61 (GIF89a) ...

October 2, 2024

作用域链与闭包

初学JavaScript的时候,我在学习闭包上,走了很多弯路。而这次重新回过头来对基础知识进行梳理,要讲清楚闭包,也是一个非常大的挑战。 闭包有多重要?如果你是初入前端的朋友,我没有办法直观的告诉你闭包在实际开发中的无处不在,但是我可以告诉你,前端面试,必问闭包。面试官们常常用对闭包的了解程度来判定面试者的基础水平,保守估计,10个前端面试者,至少5个都死在闭包上。 可是为什么,闭包如此重要,还是有那么多人没有搞清楚呢?是因为大家不愿意学习吗?还真不是,而是我们通过搜索找到的大部分讲解闭包的中文文章,都没有清晰明了的把闭包讲解清楚。要么浅尝辄止,要么高深莫测,要么干脆就直接乱说一通。包括我自己曾经也写过一篇关于闭包的总结,回头一看,不忍直视[捂脸]。 因此本文的目的就在于,能够清晰明了得把闭包说清楚,让读者朋友们看了之后,就把闭包给彻底学会了,而不是似懂非懂。 一、作用域与作用域链 在详细讲解作用域链之前,我默认你已经大概明白了JavaScript中的下面这些重要概念。这些概念将会非常有帮助。 基础数据类型与引用数据类型 内存空间 垃圾回收机制 执行上下文 变量对象与活动对象 如果你暂时还没有明白,可以去看本系列的前三篇文章,本文文末有目录链接。为了讲解闭包,已经为大家做好了基础知识的铺垫哦。 作用域 在JavaScript中,我们可以将作用域定义为一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。 这里的标识符,指的是变量名或者函数名 JavaScript中只有全局作用域与函数作用域(因为eval我们平时开发中几乎不会用到它,这里不讨论)。 作用域与执行上下文是完全不同的两个概念。我知道很多人会混淆他们,但是一定要仔细区分。 JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段与代码执行阶段。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定。执行阶段由引擎完成,主要任务是执行可执行代码,执行上下文在这个阶段创建。 作用域链 回顾一下,我们分析过的执行上下文的生命周期,如下图: 我们知道函数在调用激活时,会开始创建对应的执行上下文,在执行上下文生成的过程中,变量对象,作用域链,以及this的值会分别被确定。之前一篇文章我们详细说明了变量对象,而这里,我们将详细说明作用域链。 作用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。 为了帮助大家理解作用域链,我我们先结合一个例子,以及相应的图示来说明。 var a = 20; function test() { var b = a + 10; function innerTest() { var c = 10; return b + c; } return innerTest(); } test(); 在上面的例子中,全局,函数test,函数innerTest的执行上下文先后创建。我们设定他们的变量对象分别为VO(global),VO(test), VO(innerTest)。而innerTest的作用域链,则同时包含了这三个变量对象,所以innerTest的执行上下文可如下表示。 innerTestEC = { VO: {...}, // 变量对象 scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链 } 我们可以直接用一个数组来表示作用域链,数组的第一项scopeChain[0]为作用域链的最前端,而数组的最后一项,为作用域链的最末端,所有的最末端都为全局变量对象。 很多人会误解为当前作用域与上层作用域为包含关系,但其实并不是。以最前端为起点,最末端为终点的单方向通道我认为是更加贴切的形容。如图。 注意,因为变量对象在执行上下文进入执行阶段时,就变成了活动对象,这一点在上一篇文章中已经讲过,因此图中使用了AO来表示。Active Object 是的,作用域链是由一系列变量对象组成,我们可以在这个单向通道中,查询变量对象中的标识符,这样就可以访问到上一层作用域中的变量了。 二、闭包 对于那些有一点 JavaScript 使用经验但从未真正理解闭包概念的人来说,理解闭包可以看作是某种意义上的重生,突破闭包的瓶颈可以使你功力大增。 闭包是一种特殊的对象。 它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。 当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。 在大多数理解中,包括许多著名的书籍,文章里都以函数B的名字代指这里生成的闭包。而在chrome中,则以执行上下文A的函数名代指闭包。 因此我们只需要知道,一个闭包对象,由A、B共同组成,在以后的篇幅中,我将以chrome的标准来称呼。 // demo01 function foo() { var a = 20; var b = 30; function bar() { return a + b; } return bar; } var bar = foo(); bar(); 上面的例子,首先有执行上下文foo,在foo中定义了函数bar,而通过对外返回bar的方式让bar得以执行。当bar执行时,访问了foo内部的变量a,b。因此这个时候闭包产生。 ...

October 1, 2024

iOS中import详解

源码版本说明:本文涉及的源码基于 LLVM/Clang 23.0.0 开发版本(llvm-project main分支,commit: 301c0d91b558,2026-01-16)。不同版本的实现细节可能略有差异,但核心机制保持一致。 在Objective-C开发中,头文件引用是日常开发中最基础的操作之一。本文将从Clang编译器的角度,深入讲解iOS中各种import方式的区别、底层查找原理以及最佳实践。 引用方式 语法示例 适用场景 #include #include "Header.h" C/C++传统方式 #import #import "Header.h" Objective-C项目内部引用 #import #import <Framework/Header.h> 系统/第三方Framework @import @import Foundation; Clang Modules方式 PCH Prefix Header文件 预编译公共头文件 #include vs #import #include的工作原理 #include是C/C++中的头文件引用方式,它的工作原理非常简单:预处理器会将目标头文件的内容原封不动地复制粘贴到当前文件中。 这种方式存在一个问题:重复引用。如果多个文件都include了同一个头文件,或者存在循环引用,会导致编译错误。传统的解决方案是使用Include Guard: // Header.h #ifndef HEADER_H #define HEADER_H // 头文件内容 #endif 现代编译器还支持#pragma once指令,功能相同但更简洁: // Header.h #pragma once // 头文件内容 #import的增强 #import是Objective-C对#include的封装和增强。它在#include的基础上增加了一层判重逻辑,自动防止同一个头文件被重复引用。 // BClass.m #import "AClass.h" #import "AClass.h" // 第二次import会被自动忽略 双引号 vs 尖括号 在日常开发中,我们经常会看到两种不同的引用方式: #import "MyClass.h" // 双引号形式 #import <UIKit/UIKit.h> // 尖括号形式 它们的核心区别在于搜索路径的范围不同: ...

June 8, 2026

Prompt Engineering

大语言模型(LLM)的能力边界很大程度上取决于你怎么跟它说话。同样的模型,一个精心设计的Prompt可以得到专业级的回答,一个随意的Prompt可能只得到平庸甚至错误的结果。Prompt Engineering就是研究如何有效地与LLM沟通的技术。 一、Prompt的基本构成 1.1 一个Prompt的解剖 一个高质量的Prompt通常包含以下要素: graph TD P["完整的Prompt"] --> R["角色Role"] P --> C["上下文Context"] P --> I["指令Instruction"] P --> E["示例Examples"] P --> F["格式要求Format"] P --> CO["约束Constraints"] 要素 作用 示例 角色 设定模型的身份和专业背景 “你是一位有10年经验的iOS架构师” 上下文 提供背景信息 “我们的项目使用MVVM架构,Swift语言” 指令 明确说明任务 “请review以下代码并指出问题” 示例 展示期望的输入输出格式 给出一个输入-输出样例 格式要求 指定输出的结构 “以Markdown表格形式输出” 约束 限制条件 “不超过500字"“只使用Swift标准库” 1.2 System Prompt vs User Prompt 在API调用中,消息分为不同的角色: { "messages": [ { "role": "system", "content": "你是一位资深iOS开发工程师,擅长性能优化。回答时给出具体的代码示例。" }, { "role": "user", "content": "如何优化UITableView的滚动性能?" } ] } System Prompt:设定模型的整体行为、角色和约束,贯穿整个对话 User Prompt:具体的问题或指令 Assistant:模型的历史回复,提供对话上下文 二、核心Prompt技术 2.1 Zero-Shot Prompting 直接给指令,不提供任何示例: ...

May 2, 2026

策略模式

定义 策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法独立于使用它的调用者而变化。 策略模式的核心思想是:将算法的定义与使用分离,通过组合而非继承来实现算法的切换。 为什么需要策略模式 在实际开发中,我们经常会遇到这样的场景:同一个功能有多种实现方式,而且这些实现方式需要根据不同条件进行切换。 问题场景:假设我们正在开发一个电商App的支付功能,需要支持信用卡、Apple Pay、支付宝等多种支付方式。 最直接的实现方式可能是这样: func processPayment(type: String, amount: Double) { if type == "creditCard" { // 信用卡支付逻辑(可能有几十行代码) print("Processing credit card payment...") } else if type == "applePay" { // Apple Pay支付逻辑 print("Processing Apple Pay...") } else if type == "alipay" { // 支付宝支付逻辑 print("Processing Alipay...") } // 后续可能还要添加更多支付方式... } 这种实现方式存在几个明显的问题: 违反开闭原则:每次添加新的支付方式,都需要修改这个函数,增加新的分支 代码臃肿:随着支付方式增多,函数会变得越来越长,难以维护 测试困难:所有支付逻辑耦合在一起,难以单独测试某种支付方式 复用性差:如果其他地方也需要使用某种支付逻辑,只能复制代码 策略模式的解决思路: 策略模式将每种支付方式抽取为独立的类(策略),它们都实现相同的接口。调用方只需要持有策略接口的引用,不需要知道具体是哪种实现。 这样做的好处是: 新增支付方式:只需要新建一个策略类,无需修改现有代码 代码清晰:每种支付逻辑独立封装,职责单一 易于测试:可以对每种策略单独进行单元测试 运行时切换:用户可以随时切换支付方式,系统只需要替换策略对象 简单来说,策略模式就是把「做什么」和「怎么做」分离开来——调用方只关心「做什么」,具体「怎么做」由不同的策略类来决定。 模式结构 classDiagram class Context { -strategy: Strategy +setStrategy(strategy: Strategy) +executeStrategy() } class Strategy { <<interface>> +execute() } class ConcreteStrategyA { +execute() } class ConcreteStrategyB { +execute() } class ConcreteStrategyC { +execute() } Context o-- Strategy Strategy <|.. ConcreteStrategyA Strategy <|.. ConcreteStrategyB Strategy <|.. ConcreteStrategyC 角色说明 Strategy(策略接口):定义所有支持的算法的公共接口 ConcreteStrategy(具体策略):实现Strategy接口的具体算法 Context(上下文):持有Strategy的引用,负责调用策略方法 iOS中的实现 基础实现 // 策略协议 protocol PaymentStrategy { func pay(amount: Double) -> Bool var name: String { get } } // 具体策略 - 信用卡支付 class CreditCardPayment: PaymentStrategy { private let cardNumber: String private let cvv: String var name: String { "Credit Card" } init(cardNumber: String, cvv: String) { self.cardNumber = cardNumber self.cvv = cvv } func pay(amount: Double) -> Bool { print("Paying \(amount) using Credit Card ending with \(cardNumber.suffix(4))") // 实际支付逻辑 return true } } // 具体策略 - Apple Pay class ApplePayPayment: PaymentStrategy { var name: String { "Apple Pay" } func pay(amount: Double) -> Bool { print("Paying \(amount) using Apple Pay") // 调用Apple Pay SDK return true } } // 上下文 class PaymentContext { private var strategy: PaymentStrategy init(strategy: PaymentStrategy) { self.strategy = strategy } func setStrategy(_ strategy: PaymentStrategy) { self.strategy = strategy } func checkout(amount: Double) -> Bool { print("Processing payment with \(strategy.name)...") return strategy.pay(amount: amount) } } // 使用 let creditCard = CreditCardPayment(cardNumber: "1234567890123456", cvv: "123") let context = PaymentContext(strategy: creditCard) context.checkout(amount: 99.99) // 切换支付方式 let applePay = ApplePayPayment() context.setStrategy(applePay) context.checkout(amount: 99.99) 使用闭包简化策略 对于简单的策略,可以使用闭包代替完整的类: ...

May 2, 2026

包管理

现如今,前端开发的同学已经离不开 npm 这个包管理工具,其优秀的包版本管理机制承载了整个繁荣发展的NodeJS社区,理解其内部机制非常有利于加深我们对模块开发的理解、各项前端工程化的配置以加快我们排查问题(相信不少同学收到过各种依赖问题的困扰)的速度。 本文从三个角度:package.json、版本管理、依赖安装结合具体实例对 npm 的包管理机制进行了详细分析。 一、剖析 package.json 在 Node.js 中,模块是一个库或框架,也是一个 Node.js 项目。Node.js 项目遵循模块化的架构,当我们创建了一个 Node.js 项目,意味着创建了一个模块,这个模块必须有一个描述文件,即 package.json。它是我们最常见的配置文件,但是它里面的配置你真的有详细了解过吗?配置一个合理的 package.json 文件直接决定着我们项目的质量,所以首先带大家分析下 package.json 的各项详细配置。 1.1 必备属性 package.json 中有非常多的属性,其中必须填写的只有两个:name 和 version ,这两个属性组成一个 npm 模块的唯一标识。 npm包命名规则 name 即模块名称,其命名时需要遵循官方的一些规范和建议: 包名会成为模块url、命令行中的一个参数或者一个文件夹名称,任何非url安全的字符在包名中都不能使用,可以使用 validate-npm-package-name 包来检测包名是否合法。 语义化包名,可以帮助开发者更快的找到需要的包,并且避免意外获取错误的包。 若包名称中存在一些符号,将符号去除后不得与现有包名重复 例如:由于react-native已经存在,react.native、reactnative都不可以再创建。 如果你的包名与现有的包名太相近导致你不能发布这个包,那么推荐将这个包发布到你的作用域下。 例如:用户名 conard,那么作用域为 @conard,发布的包可以是@conard/react。 查看包是否被占用 name 是一个包的唯一标识,不得和其他包名重复,我们可以执行 npm view packageName 查看包是否被占用,并可以查看它的一些基本信息: 若包名称从未被使用过,则会抛出 404 错误: 另外,你还可以去 https://www.npmjs.com/ 查询更多更详细的包信息。 1.2描述信息 基本描述 { "description": "An enterprise-class UI design language and React components implementation", "keywords": [ "ant", "component", "components", "design", "framework", "frontend", "react", "react-component", "ui" ] } description用于添加模块的的描述信息,方便别人了解你的模块。 ...

August 7, 2025