手写 URL 解析工具函数

手写 URL 解析工具函数

背景

在日常开发中,经常遇到一些需要解析路由参数的场景,这个需求就属于一看就会,一写就废的题目,接下来实现一个解析函数

思路梳理

需要先梳理一下完整的 URL 由哪些部分组成

  1. protocol,比如 http,https,ws,wss
  2. host,比如 localhost、localhost:3000
  3. port,比如 3000
  4. pathname,比如 /test/list
  5. query,比如 ?id=18298
  6. hash,比如 #comment

可以初步观察到一个规律,每一个部分都有其独特的开头标识,比如 query 以问号开头,hash 以井号开头,这样看可能还不明显,先给出本次的用例

const a = "http://baidu.com?query=edu&id=12897#comments";
const b = "http://baidu.com/search?query=edu&id=12897#comments";
const c = "http://baidu.com/search/list?query=edu&id=12897#comments";
const d = "http://baidu.com:8080/search/list?query=edu&id=12897#comments";
const e = "http://baidu.com#comments";
const f = "http://baidu.com?query=edu#comments";
const g = "baidu.com?query=edu#comments";
const arr = [a, b, c, d, e, f, g];

因为有些部分不一定存在,比如 port,query,pathname,hash,所以初步思路是,从前往后解析,每完成一部分的解析,就剔除掉这部分内容

代码实现

先搭建一下初步的框架

const analysisUrl = (url) => {const res = {protocol: "",host: "",port: "",pathname: "",query: "",hash: "",};// ...return res
}

然后第一步是对协议的解析,比较简单,对 url 进行切割,然后赋值,代码如下

if (protocolIndex > -1) {res.protocol = url.slice(0, protocolIndex).toLowerCase();url = url.slice(protocolIndex + 3);}

接下来是比较麻烦的地方,也就是对于 host,port,pathname,query,hash 的切割,因为有些部分可有可无,这样会导致分隔符不同,但是整体思路是从前往后解析,所以,首要任务是,先切割出 host,那么就需要计算出切割结束的下标,由于协议部分已经被移除,所以切割开始下标为 0,无需计算,host 切割结束代码如下

 const slashIndex = url.indexOf("/");const queryIndex = url.indexOf("?");const hashIndex = url.indexOf("#");const hostEnd = Math.min(slashIndex === -1 ? Infinity : slashIndex,queryIndex === -1 ? Infinity : queryIndex,hashIndex === -1 ? Infinity : hashIndex);// 解析 hostif (hostEnd !== Infinity) {res.host = url.slice(0, hostEnd);url = url.slice(hostEnd);} else {res.host = url;url = "";}

该如何理解呢,从上面的用例中可以看到,从 host 开始,最先出现的就是 pathname(port 后续单独分割),然后是 query,最后是 hash,所以可以写出第四行的判断,将这三个分隔符的索引取最小值,无论每个部分存在与否,这个结果一定是 host 的结尾下标,所以可以先分割出 host,顺便计算得出 port,代码如下

 const portIndex = res.host.indexOf(":");if (portIndex > -1) {res.port = res.host.slice(portIndex + 1);}

接下来就是 pathname 的解析,还是一样的套路,此时需要判断 query 和 hash 的分隔符,也就是问号和井号,代码如下

if (url.startsWith("/")) {const queryIndex = url.indexOf("?");const hashIndex = url.indexOf("#");const pathEnd = Math.min(queryIndex === -1 ? Infinity : queryIndex,hashIndex === -1 ? Infinity : hashIndex);if (pathEnd !== Infinity) {res.pathname = url.slice(0, pathEnd);url = url.slice(pathEnd);} else {res.pathname = url;url = "";}}

每次解析完成后,都要记得更新 url 的值,防止对后续的解析产生干扰,接下来是 query 的解析,因为 query 之后,只会存在 hash,所以这次只需要判断当前 url 是否包含井号,代码如下

 if (url.startsWith("?")) {const hashIndex = url.indexOf("#");const queryEnd = hashIndex !== -1 ? hashIndex : url.length ;res.query = url.slice(0, queryEnd);url = url.slice(queryEnd);}

最后来到了 hash 的解析,如果走到这里,而且 url 依然不为空,那么就可以直接得到 hash,代码如下

 if (url.startsWith("#")) {res.hash = url.slice(0);}

到这里,就已经实现了一个包含核心解析逻辑的工具函数,如果在生产环境使用,还需要添加一个特殊情况的校验、处理,完整代码如下

const analysisUrl = (url) => {const res = {protocol: "",host: "",port: "",pathname: "",query: "",hash: "",};const protocolIndex = url.indexOf("://");if (protocolIndex > -1) {res.protocol = url.slice(0, protocolIndex).toLowerCase();url = url.slice(protocolIndex + 3);}const slashIndex = url.indexOf("/");const queryIndex = url.indexOf("?");const hashIndex = url.indexOf("#");const hostEnd = Math.min(slashIndex === -1 ? Infinity : slashIndex,queryIndex === -1 ? Infinity : queryIndex,hashIndex === -1 ? Infinity : hashIndex);// 解析 hostif (hostEnd !== Infinity) {res.host = url.slice(0, hostEnd);url = url.slice(hostEnd);} else {res.host = url;url = "";}// 从 host 中解析端口const portIndex = res.host.indexOf(":");if (portIndex > -1) {res.port = res.host.slice(portIndex + 1);}// 解析 pathnameif (url.startsWith("/")) {const queryIndex = url.indexOf("?");const hashIndex = url.indexOf("#");const pathEnd = Math.min(queryIndex === -1 ? Infinity : queryIndex,hashIndex === -1 ? Infinity : hashIndex);if (pathEnd !== Infinity) {res.pathname = url.slice(0, pathEnd);url = url.slice(pathEnd);} else {res.pathname = url;url = "";}}// 解析 queryif (url.startsWith("?")) {const hashIndex = url.indexOf("#");const queryEnd = hashIndex !== -1 ? hashIndex : url.length ;res.query = url.slice(0, queryEnd);url = url.slice(queryEnd);}// 解析锚点if (url.startsWith("#")) {res.hash = url.slice(0);}return res;
};
const a = "http://baidu.com?query=edu&id=12897#comments";
const b = "http://baidu.com/search?query=edu&id=12897#comments";
const c = "http://baidu.com/search/list?query=edu&id=12897#comments";
const d = "http://baidu.com:8080/search/list?query=edu&id=12897#comments";
const e = "http://baidu.com#comments";
const f = "http://baidu.com?query=edu#comments";
const g = "baidu.com?query=edu#comments";
const arr = [a, b, c, d, e, f, g];
arr.map(analysisUrl);
其他方案

解析 url 当然不止这一种方案,如果追求极致的代码简洁程度,可以使用正则,不过这种方式在面试中,不一定可以一次写对,但是可以证明你的正则能力,代码如下

function parseURL(url) {// 正则表达式匹配 URL 的各个部分const regex = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;const matches = regex.exec(url);if (!matches) {throw new Error('Invalid URL');}const result = {protocol: matches[2] ? matches[2].toLowerCase() : null, // 协议host: matches[4] || null,                               // 主机和端口hostname: matches[4] ? matches[4].split(':')[0] : null, // 主机名port: matches[4] ? (matches[4].split(':')[1] || null) : null, // 端口pathname: matches[5] || '/',                            // 路径search: matches[6] || '',                               // 查询字符串hash: matches[8] || '',                                 // 片段标识符origin: matches[2] && matches[4] ? `${matches[2]}://${matches[4]}` : null // 原始地址};return result;
}

还有一种借助 a 标签来实现的,也是一种思路,但是局限于环境,代码如下

function parseURL(url) {const parser = document.createElement('a');parser.href = url;const result = {protocol: parser.protocol,       // 协议,例如 "http:"host: parser.host,               // 主机和端口,例如 "zhaowa.com:9000"hostname: parser.hostname,       // 主机名,例如 "zhaowa.com"port: parser.port,               // 端口,例如 "9000"pathname: parser.pathname,       // 路径,例如 "/search/index"search: parser.search,           // 查询字符串,例如 "?query=edu"hash: parser.hash,               // 片段标识符,例如 "#comment"origin: parser.origin            // 原始地址,例如 "http://zhaowa.com:9000"};return result;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/59266.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js WebAPI黑马笔记(万字速通)

此笔记来自于黑马程序员,pink老师yyds 复习: splice() 方法用于添加或删除数组中的元素。 注意: 这种方法会改变原始数组。 删除数组: splice(起始位置, 删除的个数) 比如:1 let arr [red, green, b…

【Pikachu靶场:XSS系列】xss之过滤,xss之htmlspecialchars,xss之herf输出,xss之js输出通关啦

一、xss之过滤 <svg onloadalert("过关啦")> 二、xss之htmlspecialchars javascript:alert(123) 原理&#xff1a;输入测试文本为herf的属性值和内容值&#xff0c;所以转换思路直接变为js代码OK了 三、xss之href输出 JavaScript:alert(假客套) 原理&#x…

JS装备智能化储备管理体系优化改革

现代化的JS仓储管理方案&#xff0c;通过整合先进的RFID技术与三维模拟技术&#xff0c;为JS物流领域开创了新颖的改革浪潮。以下是对这两项尖端技术融合并用于战备物资管理的应用概述&#xff1a; 一、RFID技术在JS物资管理中的实践 RFID技术依靠无线电波实现无需直接接触的数…

缓存淘汰策略:Redis中的内存管理艺术

在现代应用架构中&#xff0c;缓存是提升性能的关键组件。 Redis&#xff0c;作为一个高性能的键值存储系统&#xff0c;因其快速的数据访问能力而被广泛使用。然而&#xff0c;由于物理内存的限制&#xff0c;Redis必须在存储空间和性能之间找到平衡&#xff0c;这就引出了缓…

AUTOSAR COM 与 LargeDataCOM 模块解析及 C++ 实现示例

AUTOSAR COM 和 LargeDataCOM 模块在功能和使用场景上有一些显著的区别。以下是它们的主要区别及具体的应用示例,最后用 C++ 源代码来解析说明。 AUTOSAR COM 模块 • 功能:主要用于处理标准大小的信号和 I-PDU(协议数据单元),提供了信号打包、解包、数据传输和接收等功能…

JavaWeb复习

在网络应用程序中有两种基本的结构&#xff0c;即C/S和B/S&#xff0c;对于c/s程序分为客户机和服务器两层&#xff0c;把应用软件按照在客户机端(通常由客户端维护困难)&#xff0c;通过网络与服务器进行相互通信。B/S结构却不用通知客户端安装某个软件&#xff0c;内容修改了…

qt获取本机IP和定位

前言&#xff1a; 在写一个天气预报模块时&#xff0c;需要一个定位功能&#xff0c;在网上翻来翻去才找着&#xff0c;放在这里留着回顾下&#xff0c;也帮下有需要的人 正文&#xff1a; 一开始我想着直接调用百度地图的API来定位&#xff0c; 然后我就想先获取本机IP的方…

python爬取旅游攻略(1)

参考网址&#xff1a; https://blog.csdn.net/m0_61981943/article/details/131262987 导入相关库&#xff0c;用get请求方式请求网页方式&#xff1a; import requests import parsel import csv import time import random url fhttps://travel.qunar.com/travelbook/list.…

Oracle OCP认证考试考点详解082系列12

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 56. 第56题&#xff1a; 题目 解析及答案&#xff1a; 关于企业管理器&#xff08;EM&#xff09;Express&#xff0c;以下哪两个陈述是…

Postgresql源码(137)执行器参数传递与使用

参考 《Postgresql源码&#xff08;127&#xff09;投影ExecProject的表达式执行分析》 0 总结速查 prepare p_04(int,int) as select b from tbl_01 where a $1 and b $2为例。 custom计划中&#xff0c;在表达式计算中使用参数的值&#xff0c;因为custom计划会带参数值&…

SPI通信详解-学习笔记

参考原文地址 SPI&#xff1a;高速、全双工&#xff0c;同步、通信总线 SPI主从模式 SPI分为主、从两种模式&#xff0c;一个SPI通讯系统需要包含一个&#xff08;且只能是一个&#xff09;主设备&#xff0c;一个或多个从设备。提供时钟的为主设备&#xff08;Master&#xff…

Day102漏洞发现-漏扫项目篇Poc开发Yaml语法插件一键生成匹配结果交互提取

知识点&#xff1a; 1、Nuclei-Poc开发-环境配置&编写流程 2、Nuclei-Poc开发-Yaml语法&匹配提取 3、Nuclei-Poc开发-BurpSuite一键生成插件 Nuclei-Poc开发-环境配置&编写流程 1、开发环境&#xff1a;VscodeYaml插件 Visual Studio Code - Code Editing. R…

Node.js 入门指南:从零开始构建全栈应用

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;node.js篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来node.js篇专栏内容:node.js-入门指南&#xff1a;从零开始构建全栈应用 前言 大家好&#xff0c;我是青山。作…

WordPress网站添加嵌入B站视频,自适应屏幕大小,取消自动播放

结合bv号 改成以下嵌入式代码&#xff08;自适应屏幕大小,取消自动播放&#xff09; <iframe style"width: 100%; aspect-ratio: 16/9;" src"//player.bilibili.com/player.html?isOutsidetrue&bvidBV13CSVYREpr&p1&autoplay0" scrolling…

大模型应用系列:Query 变换的示例浅析

【引】NLP中的经典组件在大模型应用中还有效么&#xff1f;大模型对自然语言处理中的典型任务有什么影响么&#xff1f; RAG应用通过分割文档、嵌入向量化并检索高语义相似性的块来响应用户问题&#xff0c;但面临文档块不相关、用户用词不当及结构化查询需求等问题。若RAG无法…

【Oracle】空格单字符通配符查询匹配失败

问题 在进行模糊查询的时候&#xff0c;通过全局任意字符串匹配出含有两个字刘姓的人&#xff0c;但是通过刘_不能匹配出结果。 解决 检查后发现&#xff0c;姓名中包含空格 SELECT * FROM student WHERE TRIM(sname) LIKE 刘_;第一种解决方案就是查询的时候进行去空格处理&a…

讲讲⾼并发的原则?

大家好&#xff0c;我是锋哥。今天分享关于【讲讲⾼并发的原则&#xff1f;】面试题。希望对大家有帮助&#xff1b; 讲讲⾼并发的原则&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 高并发是指系统在同一时间内能够处理大量请求的能力。要有效地管理…

鸿蒙进阶-AlphabetIndexer组件

大家好&#xff0c;这里是鸿蒙开天组&#xff0c;今天我们来学习AlphabetIndexer组件&#xff0c;喜欢就点点关注吧&#xff01; 通过 AlphabetIndexer 组件可以与容器组件结合&#xff0c;实现导航联动&#xff0c;以及快速定位的效果 核心用法 AlphabetIndexer不是容器组件…

ubuntu交叉编译expat库给arm平台使用

1.下载expat库源码: https://github.com/libexpat/libexpat/release?page=2 wget https://github.com/libexpat/libexpat/release/download/R_2_3_0/expat-2.3.0.tar.bz2 下载成功: 2.解压expat库,并进入解压后的目录: tar xjf expat-2.3.0.tar.bz2 cd expat-2.3.0 <…

【系统面试篇】进程和线程类(1)(笔记)——区别、通讯方式、同步、互斥、锁分类

目录 一、问题综述 1. 进程和线程的区别&#xff1f; 2. 进程的状态有哪些&#xff1f; 3. 进程之间的通信方式? &#xff08;1&#xff09;管道 &#xff08;2&#xff09;消息队列 &#xff08;3&#xff09;共享内存 &#xff08;4&#xff09;信号量 &#xff08…