C# 解析 URL URI 中的参数

C# 解析 URL URI 中的参数

文章目录

完整代码

csharp 复制代码
namespace System
{
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Text;

    /// <summary>
    /// 函数<see cref="GetQueryDictionary(string, bool)"/> 和 函数<see cref="GetQueryCollection(string, bool)"/><br />
    /// 支持解析示例:<br />
    /// <![CDATA[    https://www.google.com/index?page=1&lang=eng    ]]> <br />
    /// <![CDATA[    https://www.google.com/    ]]> <br />
    /// <![CDATA[    https://www.google.com/index?    ]]> <br />
    /// <![CDATA[    https://www.google.com/index?page=title=index=1&lang=&chang&char=?&id=123    ]]> <br />
    /// </summary>
    public static class UrlHelper
    {
        #region Test 测试示例。

        /// <summary>
        /// 使用示例。
        /// </summary>
        [System.Diagnostics.Conditional("DEBUG")]
        public static void Test()
        {
            TestGetQueryNormal();
            TestGetQuerySpecial();
            TestGetUrlStringNormal();
        }

        /// <summary>
        /// 常见的使用示例。解析URL中的参数。
        /// </summary>
        [System.Diagnostics.Conditional("DEBUG")]
        public static void TestGetQueryNormal()
        {
            string urlTest1 = "https://www.google.com/index?page=1&lang=eng";

            string dictionaryBaseUrlTest1;
            var dictionaryTest1 = GetQueryDictionary(urlTest1, out dictionaryBaseUrlTest1);
            System.Diagnostics.Debug.Assert(dictionaryBaseUrlTest1 == "https://www.google.com/index" &&
                dictionaryTest1["page"] == "1" && dictionaryTest1["lang"] == "eng",
                "GetQueryDictionary Test1");
            System.Diagnostics.Debug.Assert(GetUrlString(dictionaryBaseUrlTest1, dictionaryTest1) == urlTest1,
                "GetUrlString by Dictionary Test1");

            string collectionBaseUrlTest1;
            var collectionTest1 = GetQueryCollection(urlTest1, out collectionBaseUrlTest1);
            System.Diagnostics.Debug.Assert(collectionBaseUrlTest1 == "https://www.google.com/index" &&
                collectionTest1["page"] == "1" && collectionTest1["lang"] == "eng",
                "GetQueryCollection Test1");
            System.Diagnostics.Debug.Assert(GetUrlString(collectionBaseUrlTest1, collectionTest1) == urlTest1,
                "GetUrlString by Collection Test1");

            string urlTest2 = "https://www.google.com/";

            string dictionaryBaseUrlTest2;
            GetQueryDictionary(urlTest2, out dictionaryBaseUrlTest2);
            System.Diagnostics.Debug.Assert(dictionaryBaseUrlTest2 == "https://www.google.com/",
                "GetQueryDictionary Test2");
            string collectionBaseUrlTest2;
            GetQueryCollection(urlTest2, out collectionBaseUrlTest2);
            System.Diagnostics.Debug.Assert(collectionBaseUrlTest2 == "https://www.google.com/",
                "GetQueryCollection Test2");

            string urlTest3 = "https://www.google.com/index?";

            string dictionaryBaseUrlTest3;
            GetQueryDictionary(urlTest3, out dictionaryBaseUrlTest3);
            System.Diagnostics.Debug.Assert(dictionaryBaseUrlTest3 == "https://www.google.com/index",
                "GetQueryDictionary Test3");

            string collectionBaseUrlTest3;
            GetQueryCollection(urlTest3, out collectionBaseUrlTest3);
            System.Diagnostics.Debug.Assert(collectionBaseUrlTest3 == "https://www.google.com/index",
                "GetQueryCollection Test4");

        }

        /// <summary>
        /// 不常见的使用示例。解析URL中的参数。
        /// </summary>
        [System.Diagnostics.Conditional("DEBUG")]
        public static void TestGetQuerySpecial()
        {
            string urlSpecial4 = "https://www.google.com/index?page=title=index=1&lang=&chang&char=?&id=123";

            string dictionaryBaseUrlSpecial4;
            var dictionarySpecial4 = GetQueryDictionary(urlSpecial4, out dictionaryBaseUrlSpecial4);
            System.Diagnostics.Debug.Assert(dictionaryBaseUrlSpecial4 == "https://www.google.com/index" &&
                dictionarySpecial4["page"] == "title=index=1" && dictionarySpecial4["lang"] == "" &&
                dictionarySpecial4["chang"] == null && dictionarySpecial4["char"] == "?" && dictionarySpecial4["id"] == "123",
                "GetQueryDictionary UrlSpecial4");

            string collectionBaseUrlSpecial4;
            var collectionSpecial4 = GetQueryCollection(urlSpecial4, out collectionBaseUrlSpecial4);
            System.Diagnostics.Debug.Assert(collectionBaseUrlSpecial4 == "https://www.google.com/index" &&
                collectionSpecial4["page"] == "title=index=1" && collectionSpecial4["lang"] == "" &&
                collectionSpecial4["chang"] == null && collectionSpecial4["char"] == "?" && collectionSpecial4["id"] == "123",
                "GetQueryCollection UrlSpecial4");

            string urlSpecial5 = "https://www.google.com/index?page=1&&lang=eng";

            string dictionaryBaseUrlSpecial5;
            var dictionarySpecial5 = GetQueryDictionary(urlSpecial5, out dictionaryBaseUrlSpecial5);
            System.Diagnostics.Debug.Assert(dictionaryBaseUrlSpecial5 == "https://www.google.com/index" &&
                dictionarySpecial5["page"] == "1" && dictionarySpecial5["lang"] == "eng",
                "GetQueryDictionary UrlSpecial5");

            string collectionBaseUrlSpecial5;
            var collectionSpecial5 = GetQueryCollection(urlSpecial5, out collectionBaseUrlSpecial5);
            System.Diagnostics.Debug.Assert(collectionBaseUrlSpecial5 == "https://www.google.com/index" &&
                collectionSpecial5["page"] == "1" && collectionSpecial5["lang"] == "eng",
                "GetQueryCollection UrlSpecial5");
        }

        /// <summary>
        /// 常见的使用示例。拼接URL的<paramref name="baseUrl"/>和URL中的参数。
        /// </summary>
        [System.Diagnostics.Conditional("DEBUG")]
        public static void TestGetUrlStringNormal()
        {
            string baseUrl1 = "https://www.google.com/index?page=1&lang=eng";

            System.Diagnostics.Debug.Assert(GetUrlString(baseUrl1, new Dictionary<string, string>()
            {
                ["TestKey1"] = "TestValue1",
                ["TestKey2"] = "TestValue2",
            }) == "https://www.google.com/index?page=1&lang=eng&TestKey1=TestValue1&TestKey2=TestValue2",
                "GetUrlString by Dictionary BaseUrl1");

            System.Diagnostics.Debug.Assert(GetUrlString(baseUrl1, new NameValueCollection()
            {
                ["TestKey1"] = "TestValue1",
                ["TestKey2"] = "TestValue2",
            }) == "https://www.google.com/index?page=1&lang=eng&TestKey1=TestValue1&TestKey2=TestValue2",
                "GetUrlString by Collection BaseUrl1");

            string baseUrl2 = "https://www.google.com/index";

            System.Diagnostics.Debug.Assert(GetUrlString(baseUrl2, new Dictionary<string, string>()
            {
                ["TestKey1"] = "TestValue1",
                ["TestKey2"] = "TestValue2",
            }) == "https://www.google.com/index?TestKey1=TestValue1&TestKey2=TestValue2",
                "GetUrlString by Dictionary BaseUrl2");

            System.Diagnostics.Debug.Assert(GetUrlString(baseUrl2, new NameValueCollection()
            {
                ["TestKey1"] = "TestValue1",
                ["TestKey2"] = "TestValue2",
            }) == "https://www.google.com/index?TestKey1=TestValue1&TestKey2=TestValue2",
                "GetUrlString by Collection BaseUrl2");
        }

        #endregion Test 测试示例。

        /// <summary>
        /// 解析URL中的参数。会覆盖重复键的值。<br />
        /// 注意:在<see cref="Dictionary{TKey, TValue}"/>中,
        /// 通过<see cref="Dictionary{TKey, TValue}"/>的<![CDATA[    this[TKey key]    ]]>,
        /// 直接读取不存在的键值对时,会抛出异常。<br />
        /// </summary>
        /// <param name="url"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public static Dictionary<string, string> GetQueryDictionary(string url, bool ignoreCase = false)
        {
            string baseUrl;
            return GetQueryDictionary(url, out baseUrl, ignoreCase);
        }

        /// <summary>
        /// 解析URL中的参数。会覆盖重复键的值。<br />
        /// 注意:在<see cref="Dictionary{TKey, TValue}"/>中,
        /// 通过<see cref="Dictionary{TKey, TValue}"/>的<![CDATA[    this[TKey key]    ]]>,
        /// 直接读取不存在的键值对时,会抛出异常。<br />
        /// </summary>
        /// <param name="url"></param>
        /// <param name="baseUrl"> URL中符号"?"的前面部分。</param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public static Dictionary<string, string> GetQueryDictionary(string url, out string baseUrl, bool ignoreCase = false)
        {
            StringComparer comparer;
            if (ignoreCase)
            {
                comparer = StringComparer.InvariantCultureIgnoreCase;
            }
            else
            {
                comparer = StringComparer.InvariantCulture;
            }
            return GetQueryDictionary(url, out baseUrl, comparer);
        }

        /// <summary>
        /// 解析URL中的参数。会覆盖重复键的值。<br />
        /// 注意:在<see cref="Dictionary{TKey, TValue}"/>中,
        /// 通过<see cref="Dictionary{TKey, TValue}"/>的<![CDATA[    this[TKey key]    ]]>,
        /// 直接读取不存在的键值对时,会抛出异常。<br />
        /// </summary>
        /// <param name="url"></param>
        /// <param name="baseUrl"> URL中符号"?"的前面部分。</param>
        /// <param name="comparer"></param>
        /// <returns></returns>
        public static Dictionary<string, string> GetQueryDictionary(string url, out string baseUrl, StringComparer comparer)
        {
            baseUrl = null;

            // 第一个"?"符号的下标。
            // 用于支持,参数中包括"?"符号的URL。
            int indexQuestionMark = url.IndexOf('?');
            int countQuery = url.Length - indexQuestionMark - 1;

            Dictionary<string, string> info = null;

            // 如果URL中包括有效的参数。
            if (indexQuestionMark > -1 && countQuery > 0)
            {
                string queryString = url.Substring(indexQuestionMark + 1, countQuery);

                // 为空的键值对没有意义,所以,舍弃为空的键值对。
                string[] keyAndValuePairs = queryString.Split(new char[] { '&' }/*, StringSplitOptions.RemoveEmptyEntries*/);
                info = new Dictionary<string, string>(keyAndValuePairs.Length + 1, comparer);
                foreach (var pair in keyAndValuePairs)
                {
                    // 第一个"="符号的下标。
                    // 用于支持,value中包括"="符号的参数。
                    int indexEquals = pair.IndexOf('=');

                    // 如果包含"="符号。
                    if (indexEquals > -1)
                    {
                        string key = pair.Substring(0, indexEquals);
                        key = Uri.UnescapeDataString(key);

                        int countValue = pair.Length - indexEquals - 1;
                        // 避免"="符号后面没有内容时,indexEquals + 1超出数组的有效索引。
                        string value = (countValue == 0) ? string.Empty : pair.Substring(indexEquals + 1, countValue);
                        value = Uri.UnescapeDataString(value);

                        info[key] = value;
                    }
                    // 用关键字保存特殊参数。
                    else
                    {
                        info[pair] = null;
                    }
                }
            }
            info = info ?? new Dictionary<string, string>(1, comparer);

            if (indexQuestionMark < 0 || indexQuestionMark > url.Length)
            {
                indexQuestionMark = url.Length;
            }
            // URL中符号"?"的前面部分。
            baseUrl = url.Substring(0, indexQuestionMark);
            return info;
        }

        /// <summary>
        /// 解析URL中的参数。支持重复键。<br />
        /// 注意:在<see cref="NameValueCollection"/>中,直接读取不存在的键值对时,返回 null ,不会抛出异常。<br />
        /// </summary>
        /// <param name="url"></param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public static NameValueCollection GetQueryCollection(string url, bool ignoreCase = false)
        {
            string baseUrl;
            return GetQueryCollection(url, out baseUrl, ignoreCase);
        }

        /// <summary>
        /// 解析URL中的参数。支持重复键。<br />
        /// 注意:在<see cref="NameValueCollection"/>中,直接读取不存在的键值对时,返回 null ,不会抛出异常。<br />
        /// </summary>
        /// <param name="url"></param>
        /// <param name="baseUrl"> URL中符号"?"的前面部分。</param>
        /// <param name="ignoreCase"></param>
        /// <returns></returns>
        public static NameValueCollection GetQueryCollection(string url, out string baseUrl, bool ignoreCase = false)
        {
            StringComparer comparer;
            if (ignoreCase)
            {
                comparer = StringComparer.InvariantCultureIgnoreCase;
            }
            else
            {
                comparer = StringComparer.InvariantCulture;
            }
            return GetQueryCollection(url, out baseUrl, comparer);
        }

        /// <summary>
        /// 解析URL中的参数。支持重复键。<br />
        /// 注意:在<see cref="NameValueCollection"/>中,直接读取不存在的键值对时,返回 null ,不会抛出异常。<br />
        /// </summary>
        /// <param name="url"></param>
        /// <param name="baseUrl"> URL中符号"?"的前面部分。</param>
        /// <param name="comparer"></param>
        /// <returns></returns>
        public static NameValueCollection GetQueryCollection(string url, out string baseUrl, StringComparer comparer)
        {

            // 第一个"?"符号的下标。
            // 用于支持,参数中包括"?"符号的URL。
            int indexQuestionMark = url.IndexOf('?');
            int countQuery = url.Length - indexQuestionMark - 1;

            NameValueCollection info = null;

            // 如果URL中包括有效的参数。
            if (indexQuestionMark > -1 && countQuery > 0)
            {
                string queryString = url.Substring(indexQuestionMark + 1, countQuery);

                // 为空的键值对没有意义,所以,舍弃为空的键值对。
                string[] keyAndValuePairs = queryString.Split(new char[] { '&' }/*, StringSplitOptions.RemoveEmptyEntries*/);
                info = new NameValueCollection(keyAndValuePairs.Length + 1, comparer);
                foreach (var pair in keyAndValuePairs)
                {
                    // 第一个"="符号的下标。
                    // 用于支持,value中包括"="符号的参数。
                    int indexEquals = pair.IndexOf('=');

                    // 如果包含"="符号。
                    if (indexEquals > -1)
                    {
                        string key = pair.Substring(0, indexEquals);
                        key = Uri.UnescapeDataString(key);

                        int countValue = pair.Length - indexEquals - 1;
                        // 避免"="符号后面没有内容时,indexEquals + 1超出数组的有效索引。
                        string value = (countValue == 0) ? string.Empty : pair.Substring(indexEquals + 1, countValue);
                        value = Uri.UnescapeDataString(value);

                        info.Add(key, value);
                    }
                    // 用关键字保存特殊参数。
                    else
                    {
                        info.Add(pair, null);
                    }
                }
            }
            info = info ?? new NameValueCollection(1, comparer);

            if (indexQuestionMark < 0 || indexQuestionMark > url.Length)
            {
                indexQuestionMark = url.Length;
            }
            // URL中符号"?"的前面部分。
            baseUrl = url.Substring(0, indexQuestionMark);
            return info;
        }

        /// <summary>
        /// 拼接URL的<paramref name="baseUrl"/>(可以是URL中符号"?"的前面部分,也可以是已经包含参数的URL)和URL中的参数。
        /// </summary>
        /// <param name="baseUrl"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static string GetUrlString(string baseUrl, ICollection<KeyValuePair<string, string>> parameters)
        {
            if (parameters != null && parameters.Count > 0)
            {
                var indexEndSplit = baseUrl.LastIndexOf('/');
                if (indexEndSplit < 0)
                {
                    indexEndSplit = 0;
                }
                StringBuilder builder = new StringBuilder(baseUrl.Length + 128 + 1);
                var indexQuestionMark = baseUrl.IndexOf('?', indexEndSplit);
                if (indexQuestionMark < 0)
                {
                    builder.Append(baseUrl);
                    bool hasQueryItem = false;
                    bool addQuestionMark = true;
                    GetUrlQueryStringCore(parameters, builder, hasQueryItem, addQuestionMark);
                }
                else
                {
                    builder.Append(baseUrl);
                    bool hasQueryItem = indexQuestionMark < baseUrl.Length - 1;
                    bool addQuestionMark = false;
                    GetUrlQueryStringCore(parameters, builder, hasQueryItem, addQuestionMark);
                }
                return builder.ToString();
            }
            return baseUrl;
        }

        /// <summary>
        /// 拼接URL的<paramref name="baseUrl"/>(可以是URL中符号"?"的前面部分,也可以是已经包含参数的URL)和URL中的参数。
        /// </summary>
        /// <param name="baseUrl"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static string GetUrlString(string baseUrl, NameValueCollection parameters)
        {
            if (parameters != null && parameters.Count > 0)
            {
                var indexEndSplit = baseUrl.LastIndexOf('/');
                if (indexEndSplit < 0)
                {
                    indexEndSplit = 0;
                }
                StringBuilder builder = new StringBuilder(baseUrl.Length + 128 + 1);
                var indexQuestionMark = baseUrl.IndexOf('?', indexEndSplit);
                if (indexQuestionMark < 0)
                {
                    builder.Append(baseUrl);
                    bool hasQueryItem = false;
                    bool addQuestionMark = true;
                    GetUrlQueryStringCore(parameters, builder, hasQueryItem, addQuestionMark);
                }
                else
                {
                    builder.Append(baseUrl);
                    bool hasQueryItem = indexQuestionMark < baseUrl.Length - 1;
                    bool addQuestionMark = false;
                    GetUrlQueryStringCore(parameters, builder, hasQueryItem, addQuestionMark);
                }
                return builder.ToString();
            }
            return baseUrl;
        }

        public static string GetUrlQueryString(ICollection<KeyValuePair<string, string>> parameters, bool addQuestionMark)
        {
            StringBuilder builder = new StringBuilder(128);
            bool hasQueryItem = false;
            GetUrlQueryStringCore(parameters, builder, hasQueryItem, addQuestionMark);
            return builder.ToString();
        }

        public static string GetUrlQueryString(NameValueCollection parameters, bool addQuestionMark)
        {
            StringBuilder builder = new StringBuilder(128);
            bool hasQueryItem = false;
            GetUrlQueryStringCore(parameters, builder, hasQueryItem, addQuestionMark);
            return builder.ToString();
        }

        private static void GetUrlQueryStringCore(ICollection<KeyValuePair<string, string>> parameters, StringBuilder builder, bool hasQueryItem, bool addQuestionMark)
        {
            foreach (KeyValuePair<string, string> item in parameters)
            {
                GetUrlQueryStringForAddParameterBefore(builder, ref hasQueryItem, addQuestionMark);
                builder.Append(Uri.EscapeDataString(item.Key));
                if (item.Value != null)
                {
                    builder.Append("=");
                    builder.Append(Uri.EscapeDataString(item.Value));
                }
            }
        }

        private static void GetUrlQueryStringCore(NameValueCollection parameters, StringBuilder builder, bool hasQueryItem, bool addQuestionMark)
        {
            foreach (object itemKeyObj in parameters.Keys)
            {
                string itemKey = itemKeyObj?.ToString();
                if (itemKey != null)
                {
                    var itemValues = parameters.GetValues(itemKey);
                    if (itemValues == null)
                    {
                        GetUrlQueryStringForAddParameterBefore(builder, ref hasQueryItem, addQuestionMark);
                        builder.Append(Uri.EscapeDataString(itemKey));
                    }
                    else
                    {
                        foreach (var itemValue in itemValues)
                        {
                            GetUrlQueryStringForAddParameterBefore(builder, ref hasQueryItem, addQuestionMark);
                            builder.Append(Uri.EscapeDataString(itemKey));
                            if (itemValue != null)
                            {
                                builder.Append("=");
                                builder.Append(Uri.EscapeDataString(itemValue));
                            }
                        }
                    }
                }
            }
        }

        private static void GetUrlQueryStringForAddParameterBefore(StringBuilder builder, ref bool hasQueryItem, bool addQuestionMark)
        {
            if (hasQueryItem)
            {
                builder.Append("&");
            }
            else
            {
                hasQueryItem = true;
                if (addQuestionMark)
                {
                    builder.Append('?');
                }
            }
        }

    }
}

概述

核心功能概述

  1. 查询参数解析

    • GetQueryDictionary():将URL查询参数解析为字典(键值唯一,后值覆盖前值)
    • GetQueryCollection():将URL查询参数解析为集合(支持多值键)
  2. URL构建

    • GetUrlString():将基础URL与参数集合/字典拼接成完整URL
  3. 特殊支持

    • 处理含特殊字符的键值(如?=
    • 支持无值参数(如&key
    • 处理参数中出现的等号和问号

查询参数解析方法对比

特性 GetQueryDictionary GetQueryCollection
返回值类型 Dictionary<string, string> NameValueCollection
键唯一性 ✔️(后值覆盖前值) ✖️(支持多值键)
读取不存在键 抛出异常 返回null
参数格式 键=值(值中可含=) 键=值(值中可含=)
无值参数处理 键→null 键→null

核心方法实现解析

1. 查询参数解析逻辑

csharp 复制代码
// 解析字典示例
int indexQuestionMark = url.IndexOf('?');
if (indexQuestionMark > -1)
{
    string queryString = url.Substring(indexQuestionMark + 1);
    string[] pairs = queryString.Split('&');
    foreach (var pair in pairs)
    {
        int indexEquals = pair.IndexOf('=');
        if (indexEquals > -1)
        {
            string key = Uri.UnescapeDataString(pair.Substring(0, indexEquals));
            string value = Uri.UnescapeDataString(pair.Substring(indexEquals + 1));
            dict[key] = value; // 字典直接赋值(覆盖)
        }
        else
        {
            dict[pair] = null; // 无值参数处理
        }
    }
}
baseUrl = url.Substring(0, indexQuestionMark);

2. URL构建逻辑

csharp 复制代码
// URL拼接核心逻辑
private static void GetUrlQueryStringCore(NameValueCollection parameters, StringBuilder builder)
{
    foreach (string key in parameters.Keys)
    {
        string[] values = parameters.GetValues(key);
        foreach (string value in values)
        {
            // 添加分隔符(?或&)
            if (builder.Length > baseUrlLength) builder.Append('&');
            else if (needQuestionMark) builder.Append('?');
            
            builder.Append(Uri.EscapeDataString(key));
            if (value != null)
            {
                builder.Append('=');
                builder.Append(Uri.EscapeDataString(value));
            }
        }
    }
}

特殊场景处理说明

  1. 值中含等号

    使用首次出现的=分割键值:

    csharp 复制代码
    // 示例参数:title=index=1
    // 解析结果:key="title", value="index=1"
    int indexEquals = pair.IndexOf('=');
  2. 无值参数

    被解析为key → null

    csharp 复制代码
    // 示例URL:https://site.com?key1&key2=val
    // 解析结果:key1=null, key2="val"
  3. 问号处理

    仅识别第一个?作为查询起始:

    csharp 复制代码
    // 示例:https://site.com?param=?
    // 正确解析:param="?"
    int indexQuestionMark = url.IndexOf('?');

测试用例验证

markdown 复制代码
测试场景:
1. 标准URL解析
   ✓ https://site.com?k1=v1&k2=v2 → k1="v1", k2="v2"
   
2. 特殊字符处理
   ✓ https://site.com?key=? → key="?"
   ✓ https://site.com?k=1=2=3 → k="1=2=3"
   
3. 边界情况
   ✓ https://site.com? (空参数) → 空集合
   ✓ https://site.com (无参数) → 空集合
   
4. URL重建验证
   ✓ 解析后重建URL应与原始URL一致

使用注意事项

  1. 字典读取风险

    csharp 复制代码
    // 安全读取方式
    if (dict.TryGetValue("key", out var value)) { ... }
  2. 编码规范

    • 使用Uri.EscapeDataString处理特殊字符
    • 使用Uri.UnescapeDataString反向解码
相关推荐
程序员阿超的博客20 分钟前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 24522 分钟前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇5 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖6 小时前
http的缓存问题
前端·javascript·http
小小小小宇6 小时前
请求竞态问题统一封装
前端
loriloy6 小时前
前端资源帖
前端
源码超级联盟6 小时前
display的block和inline-block有什么区别
前端
GISer_Jing6 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js
让梦想疯狂6 小时前
开源、免费、美观的 Vue 后台管理系统模板
前端·javascript·vue.js
海云前端6 小时前
前端写简历有个很大的误区,就是夸张自己做过的东西。
前端