您可能在不知不觉中以不安全的方式编写 URL
你能发现这段代码中的错误吗?
const url = `https://builder.io/api/v2/content
?model=${model}&locale=${locale}?query.text=${text}`
const res = await fetch(url)
至少有三个!
我们将在下面分解它们:
常见问题 #1:不正确的分隔符
哎呀!这当然是一个新手错误,但很容易错过,即使经过 10 年的 JS 开发,我也在我自己的代码中发现了这个错误。
根据我的经验,一个常见的罪魁祸首是在编辑或移动代码之后。例如,您有一个结构正确的 URL,然后将一个片段从一个片段复制到另一个片段,然后错过了参数分隔符的错误排序。
连接时也会发生这种情况。例如:
url = url '?foo=bar'
但是等等,原来的url
可能有一个查询参数。好的,所以这应该是:
url = url '&foo=bar'
但是等等,如果原来url
没有查询参数那么现在这是错误的。啊。
常见问题 #2:忘记编码
啊。model
并且locale
可能不需要编码,因为它们是 URL 安全值,但我并没有停下来思考text
可以是所有类型的文本,包括空格和特殊字符,这会给我们带来问题。
因此,也许我们会矫枉过正,让事情变得更加安全:
const url = `https://builder.io/api/v2/content
?model=${
encodeURIComponent(model)
}&locale=${
encodeURIComponent(locale)
}&query.text=${
encodeURIComponent(text)
}`
但事情感觉有点......丑陋。
常见问题 #3:意外的空白字符
钱币。为了将这个长 URL 分成多行,我们不小心在 URL 中包含了换行符和额外的空格,这将导致无法按预期进行抓取。
我们现在可以正确地分解字符串,但我们变得更加混乱和难以阅读:
const url = `https://builder.io/api/v2/content`
`?model=${
encodeURIComponent(model)
}&locale=${
encodeURIComponent(locale)
}&query.text=${
encodeURIComponent(text)
}`
仅仅为了使构建一个 URL 正确就需要很多。下一次我们是否会记住这一切,尤其是在截止日期即将到来且我们需要尽快发布新功能或修复时?
一定有更好的方法。
URL
营救的构造函数
解决上述挑战的更清洁、更安全的解决方案是使用URL 构造函数:
const url = new URL('https://builder.io/api/v2/content')
url.searchParams.set('model', model)
url.searchParams.set('locale', locale)
url.searchParams.set('text', text)
const res = await fetch(url.toString())
这为我们解决了几件事:
- 分隔符总是正确的(
?
对于第一个参数,以及之后的参数)。 - 所有参数都自动编码。
- 为长 URL 跨多行时没有额外空白字符的风险。
修改网址
对于我们正在修改 URL 但不知道当前状态的情况,它也非常有用。
例如,而不是有这个问题:
url = (url.includes('?') ? '&' : '?') 'foo=bar'
我们可以改为:
// Assuming `url` is a URL
url.searchParams.set('foo', 'bar')
// Or if URL is a string
const structuredUrl = new URL(url)
structuredUrl.searchParams.set('foo', 'bar')
url = structuredUrl.toString()
同样,你也可以写URL的其他部分:
const url = new URL('https://builder.io')
url.pathname = '/blog' // Update the path
url.hash = '#featured' // Update the hash
url.host = 'www.builder.io' // Update the host
url.toString() // https://www.builder.io/blog#featured
读取 URL 值
现在,“我只想在没有库的情况下从当前 URL 读取查询参数”这个由来已久的问题得到了解决。
const pageParam = new URL(location.href).searchParams.get('page')
或者例如更新当前 URL:
const url = new URL(location.href)
const currentPage = Number(url.searchParams.get('page'))
url.searchParams.set('page', String(currentPage 1))
location.href = url.toString()
但这不仅限于浏览器。它也可以在 Node.js 中使用
const http = require('node:http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `https://${req.headers.host}`)
// Read path, query, etc...
});
以及 Deno:
import { serve } from "https://deno.land/std/http/mod.ts";
async function reqHandler(req: Request) {
const url = new URL(req.url)
// Read path, query, etc...
return new Response();
}
serve(reqHandler, { port: 8000 });
要知道的 URL 属性
URL 实例支持您已经在浏览器中使用的所有属性,例如 onwindow.location
或 anchor 元素,所有这些您都可以读写:
const url = new URL('https://builder.io/blog?page=1');
url.protocol // https:
url.host // builder.io
url.pathname // /blog
url.search // ?page=1
url.href // https://builder.io/blog?page=1
url.origin // https://builder.io
url.searchParams.get('page') // 1
或者,一目了然:
URLSearchParams方法要知道
该URLSearchParams
对象可在URL
实例上访问,url.searchParams
支持许多方便的方法:
searchParams.has(name)
检查搜索参数是否包含给定名称:
url.searchParams.has('page') // true
searchParams.get(name)
获取给定参数的值:
url.searchParams.get('page') // '1'
searchParams.getAll(name)
获取为参数提供的所有值。如果您允许同名的多个值,这很方便,例如&page=1&page=2
:
url.searchParams.getAll('page') // ['1']
searchParams.set(name, value)
设置参数的值:
url.searchParams.set('page', '1')
searchParams.append(name, value)
附加一个参数——如果你可能多次支持同一个参数,这很有用,比如&page=1&page=2
:
url.searchParams.append('page', '2')
searchParams.delete(name)
从 URL 中完全删除一个参数:
url.searchParams.delete('page')
陷阱
要知道的一大陷阱是传递给 URL 构造函数的所有 URL 都必须是绝对的。
例如,这将引发错误:
new URL('/blog') // ERROR!
您可以通过提供原点作为第二个参数来解决这个问题,如下所示:
new URL('/blog', 'https://builder.io')
或者,如果您真的只需要使用 URL 部分, 如果您只需要使用相对 URL 的查询参数,则可以直接使用URLSearchParams :
const params = new URLSearchParams('page=1')
params.set('page=2')
params.toString()
URLSearchParams
还有一个优点,那就是它也可以将键值对的对象作为其输入:
const params = new URLSearchParams({
page: 1,
text: 'foobar',
})
params.set('page=2')
params.toString()
浏览器和运行时支持
new URL
支持所有现代浏览器,以及 Node.js 和 Deno!(来源)