手写 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,一经查实,立即删除!

相关文章

uniapp 小程序 周选择器

这里贴出来的是子组件的代码&#xff0c;父组件只是打开了一下popup // 打开了一下popup $refs.popup.open(bottom)如果不想用子组件的话&#xff0c;直接打开popup就可以用<template><uni-popup ref"popup" type"bottom" background-color&quo…

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

此笔记来自于黑马程序员&#xff0c;pink老师yyds 复习&#xff1a; splice() 方法用于添加或删除数组中的元素。 注意&#xff1a; 这种方法会改变原始数组。 删除数组&#xff1a; splice(起始位置&#xff0c; 删除的个数) 比如&#xff1a;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;这就引出了缓…

doris使用使用broker从HDFS导入数据

前提&#xff1a;doris使用broker导入需要单独部署broker&#xff0c;我这边看着部署教程都是不带broker部署部分的。 1.建表 我测试环境的hive数据是用时间分区&#xff0c;在导入时总是报错 type:ETL_QUALITY_UNSATISFIED; msg:quality not good enough to cancel 使用SH…

Spring Boot实战:SSO和OAuth2.0

SSO和OAuth2.0 SSO&#xff08;单点登录&#xff09;和OAuth 2.0 是两个在认证和授权场景中常用的技术概念。它们的应用场景、目标和工作机制不同&#xff0c;但在一些方面也有联系。以下是它们的区别和联系的详细分析。 一、SSO&#xff08;Single Sign-On&#xff09; 1.1…

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

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

[QUIC] 版本协商

QUIC 兼容的版本协商 QUIC的核心规范中没有定义一个完整的版本协商机制,只是提供了一个拒绝客户端使用的版本的方法。 这里我们定义一种新的机制来让服务器端和客户端进行版本协商。并且如果客户端选择的版本和最终协商出来的版本之间的第一组包结构是互相兼容的,这里的协商…

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…

Redis 初学者指南

Redis 初学者指南 1. 什么是 Redis&#xff1f;2. Redis 的基本概念3. 安装 Redis3.1 使用 Docker 安装3.2 从源码编译安装 4. 基本操作4.1 启动 Redis 服务4.2 连接 Redis 客户端4.3 常用命令 5. Redis 的数据结构5.1 字符串5.2 列表5.3 集合5.4 散列5.5 有序集合 6. 高级特性…

如何封装一个axios,封装axios有哪些好处

什么是Axios Axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;用于在浏览器和 Node.js 中发送异步网络请求。它简化了发送 GET、POST、PUT、DELETE 等请求的过程&#xff0c;并且支持请求拦截、响应拦截、取消请求和自动处理 JSON 数据等功能。 为什么要封装Axios 封装…

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…