手写 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.rhkb.cn/news/464949.html

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

相关文章

C++ | Leetcode C++题解之第540题有序数组中的单一元素

题目&#xff1a; 题解&#xff1a; class Solution { public:int singleNonDuplicate(vector<int>& nums) {int low 0, high nums.size() - 1;while (low < high) {int mid (high - low) / 2 low;mid - mid & 1;if (nums[mid] nums[mid 1]) {low mid…

Python练习7

Python日常练习 题目&#xff1a; 编写程序&#xff0c;输出由1、2、3、4这四个数字组成的每位数都不相同的所有三位数 要求&#xff1a; 每个数字用换行隔开 --------------------------------------------------------- 注意&#xff1a; 部分源程序给出如下。请勿改动…

RK3568开发板静态IP地址配置

1. 连接SSH MYD-LR3568 开发板设置了静态 eth0:1 192.168.0.10 和 eth1:1 192.168.1.10&#xff0c;在没有串口时调试开发板&#xff0c;可以用工具 SSH 登陆到开发板。 首先需要用一根网线直连电脑和开发板&#xff0c;或者通过路由器连接到开发板&#xff0c;将电脑 IP 手动设…

MySQL45讲 第八讲 事务到底是隔离的还是不隔离的?

文章目录 MySQL45讲 第八讲 事务到底是隔离的还是不隔离的&#xff1f;MVCC 实现原理事务 ID 与数据版本一致性视图 总结 MySQL45讲 第八讲 事务到底是隔离的还是不隔离的&#xff1f; 在 MySQL 的事务处理中&#xff0c;事务隔离级别与数据一致性是至关重要的概念。可重复读隔…

【工具变量】中国制造2025试点城市数据集(2000-2023年)

数据简介&#xff1a;《中国制造2025》是中国ZF于2015年5月8日印发的一项战略规划&#xff0c;旨在加快制造业的转型升级&#xff0c;提升制造业的质量和效益&#xff0c;实现从制造大国向制造强国的转变。该规划是中国实施制造强国战略的第一个十年行动纲领&#xff0c;明确提…

任务中心全新升级,新增分享接口文档功能,MeterSphere开源持续测试工具v3.4版本发布

2024年11月5日&#xff0c;MeterSphere开源持续测试工具正式发布v3.4版本。 在这一版本中&#xff0c;系统设置方面&#xff0c;任务中心支持实时查看系统即时任务与系统后台任务&#xff1b;接口测试方面&#xff0c;新增接口文档分享功能、接口场景导入导出功能&#xff0c;…

CUDA下载和安装

CUDA下载和安装 前言下载安装后续添加参考链接 前言 由于我需要运行的代码与我当前的CUDA版本不兼容,所以我现在需要进行CUDA的更新,下载一个低版本的CUDA以匹配我的Pytorch 下载 CUDA下载地址:CUDA下载链接 选择适合自己的版本 由于我是要运行一个开源项目,我选择对应的CU…

Multimodal Reasoning with Multimodal Knowledge Graph

摘要 大型语言模型&#xff08;llm&#xff09;的多模态推理常常存在幻觉和llm中存在缺陷或过时的知识。一些方法试图通过使用文本知识图来缓解这些问题&#xff0c;但其单一的知识形态限制了全面的跨模态理解。本文提出了多模态推理与多模态知识图&#xff08;MR-MKG&#xf…

Git代码托管(三)可视化工具操作(1)

常见的可视化操作工具有 一、官方网页 如码云、gitlab&#xff0c;自带了常见的git操作。 以码云为例&#xff1a; 1、创建分支&#xff1a; 进入分支目录&#xff0c;点击 新建分支 按钮&#xff0c; 在弹出框中输入新分支名称&#xff0c;点击确定即可一键创建分支&…

go中Println和Printf的区别

Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 go中Println和Printf的区别 package mainimport ( "fmt" )//TIP To run your code, right-click the c…

项目审核系统 ---(连接数据库---项目模拟)

本章主要是查询方法和修改方法 编写查询方法&#xff0c;查询所有项目审核信息并返回查询结果&#xff0c;需实现分页功能&#xff0c;注意必要的异常处理。编写查询方法&#xff0c;根据项目编号查询指定项目的审核信息&#xff0c;注意必要的异常处理。编写修改方法&#xf…

(十三)JavaWeb后端开发——MySQL2

目录 1.DQL数据查询语言 1.1基本查询 1.2条件查询 where关键字 1.3分组查询 1.4排序查询 1.5分页查询 2.多表设计 3.多表查询——联查 4.多表查询——子查询​ 5.MySQL 事务 6.MySQL 索引 1.DQL数据查询语言 分为五大基本查询语法 1.1基本查询 -- 查询特定字段 s…

【STL栈和队列】:高效数据结构的应用秘籍

前言&#xff1a; C 标准模板库&#xff08;STL&#xff09;为我们提供了多种容器&#xff0c;其中 stack&#xff08;栈&#xff09;和 queue&#xff08;队列&#xff09;是非常常用的两种容器。 根据之前C语言实现的栈和队列&#xff0c;&#xff08;如有遗忘&#xff0c;…

LWIP通信协议UDP发送、接收源码解析

1.UDP发送函数比较简短&#xff0c;带操作系统和裸机一样。以下是udp_sendto源码解析&#xff1b; 2.LWIP源码UDP接收数据 2.1.UDP带操作系统接收数据&#xff0c;以下是源码解析&#xff1b; 2.2.UDP裸机接收数据&#xff0c;以下是源码解析

小菜家教平台:基于SpringBoot+Vue打造一站式学习管理系统

前言 现在已经学习了很多与Java相关的知识&#xff0c;但是迟迟没有进行一个完整的实践&#xff08;之前这个项目开发到一半&#xff0c;很多东西没学搁置了&#xff0c;同时原先的项目中也有很多的问题&#xff09;&#xff0c;所以现在准备从零开始做一个基于SpringBootVue的…

【优选算法 — 双指针】双指针小专题

和为 s 的两个数 和为s的两个数 题目描述 解法一&#xff1a;暴力枚举 暴力枚举&#xff0c;先固定一个数&#xff0c;然后让这个数和另一个数匹配相加&#xff0c; 如果当前的数 所有剩余的数 target&#xff0c;则返回这两个数&#xff0c;否则固定下一个数&#…

轻松理解操作系统 - 轻松了解 inode 是如何管理文件的

Linux 由于其开源、比较稳定等特点统治了服务端领域。也因此&#xff0c;学习Linux 系统相关知识在后端开发等岗位中变得越来越重要&#xff0c;甚至可以说是必不可少的。 因为它的广泛应用&#xff0c;所以在程序员的日常工作和面试中&#xff0c;它都是经常出现的。它的开源特…

Vue(JavaScript)读取csv表格并求某一列之和(大浮点数处理: decimal.js)

文章目录 想要读这个表格&#xff0c;并且求第二列所有价格的和方法一&#xff1a;通过添加文件输入元素上传csv完整&#xff08;正确&#xff09;代码之前的错误部分因为价格是小数&#xff0c;所以下面的代码出错。如果把parseFloat改成parseInt&#xff0c;那么求和没有意义…

微信小程序-事件总线

一.事件总线的概念和作用 事件总线是对发布-订阅模式的一种实现&#xff0c;是一种集中式事件处理机制&#xff0c;允许不同组件之间进行彼此通信&#xff0c;常用于两个非父子组件和兄弟组件之间的通讯。 在日常开发过程中&#xff0c;我们可以使用第三方的发布订阅 JS 包来实…

成都郝蓉宜恺文化传媒:引领大数据应用新篇章

在信息化浪潮汹涌的今天&#xff0c;大数据被誉为新时代的“石油”&#xff0c;正在以前所未有的速度改变着我们的生活和工作方式。成都郝蓉宜恺文化传媒&#xff0c;作为大数据领域的领军企业&#xff0c;始终站在创新的前沿&#xff0c;引领着大数据应用的新篇章。 作为大数…