From e76335bbe8de05f07da36175029b98ff916b051f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 5 Sep 2021 11:07:33 +0800 Subject: [PATCH 01/31] refactor: edit theme --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 64e1bf1..7dd0ade 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,9 @@ "license": "Creative Commons Attribution-ShareAlike License", "devDependencies": {}, "dependencies": { - "gh-pages": "latest", + "gh-pages": "^3.2.3", "husky": "^4.3.8", - "loppo": "latest", - "loppo-theme-wangdoc": "latest" + "loppo": "^0.6.24", + "loppo-theme-wangdoc": "^0.6.1" } } From b3fe8e89873853a53ef1bace61a91abbb86aa5dc Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2022年4月10日 12:13:00 +0800 Subject: [PATCH 02/31] docs(fetch): edit keepalive --- docs/fetch.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/fetch.md b/docs/fetch.md index 740c2fc..0200484 100644 --- a/docs/fetch.md +++ b/docs/fetch.md @@ -489,7 +489,12 @@ fetch('http://another.com', { window.onunload = function() { fetch('/analytics', { method: 'POST', - body: "statistics", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + some: 'data' + }), keepalive: true }); }; From f39deeb6b120df6fd8156b3ae46388e46e846c0c Mon Sep 17 00:00:00 2001 From: Noah <284516433@qq.com> Date: 2022年9月26日 16:52:38 +0800 Subject: [PATCH 03/31] docs: fix typo in service-worker.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 跨于 -> 跨域 --- docs/service-worker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/service-worker.md b/docs/service-worker.md index faa8534..a9d8718 100644 --- a/docs/service-worker.md +++ b/docs/service-worker.md @@ -53,7 +53,7 @@ navigator.serviceWorker.register('sw.js'.then(() => { }) ``` -上面代码的`sw.js`就是需要浏览器注册的 service worker 脚本。注意,这个脚本必须与当前网址同域,service worker 不支持跨与脚本。另外,`sw.js`必须是从 HTTPS 协议加载的。 +上面代码的`sw.js`就是需要浏览器注册的 service worker 脚本。注意,这个脚本必须与当前网址同域,service worker 不支持跨域脚本。另外,`sw.js`必须是从 HTTPS 协议加载的。 默认情况下,Service worker 只对根目录`/`生效,如果要改变生效范围,可以运行下面的代码。 From d00bd368da7372277cf4db87ca02e4fb865c6a63 Mon Sep 17 00:00:00 2001 From: fwqaaq Date: Sat, 4 Feb 2023 21:15:48 +0800 Subject: [PATCH 04/31] chore: fix translation about cross-origin --- docs/fetch.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/fetch.md b/docs/fetch.md index 0200484..b98d6f6 100644 --- a/docs/fetch.md +++ b/docs/fetch.md @@ -96,9 +96,9 @@ async function fetchText() { `Response.type`属性返回请求的类型。可能的值如下: - `basic`:普通请求,即同源请求。 -- `cors`:跨域请求。 +- `cors`:跨源请求。 - `error`:网络错误,主要用于 Service Worker。 -- `opaque`:如果`fetch()`请求的`type`属性设为`no-cors`,就会返回这个值,详见请求部分。表示发出的是简单的跨域请求,类似`
`表单的那种跨域请求。 +- `opaque`:如果`fetch()`请求的`type`属性设为`no-cors`,就会返回这个值,详见请求部分。表示发出的是简单的跨源请求,类似``表单的那种跨源请求。 - `opaqueredirect`:如果`fetch()`请求的`redirect`属性设为`manual`,就会返回这个值,详见请求部分。 **Response.redirected** @@ -455,19 +455,19 @@ const response = fetch(url, { `mode`属性指定请求的模式。可能的取值如下: -- `cors`:默认值,允许跨域请求。 +- `cors`:默认值,允许跨源请求。 - `same-origin`:只允许同源请求。 -- `no-cors`:请求方法只限于 GET、POST 和 HEAD,并且只能使用有限的几个简单标头,不能添加跨域的复杂标头,相当于提交表单、` + +``` + +### URL.revokeObjectURL() + +`URL.revokeObjectURL()`方法用来释放`URL.createObjectURL()`生成的临时网址。它的参数就是`URL.createObjectURL()`方法返回的 URL 字符串。 + +下面为上一小节的示例加上`URL.revokeObjectURL()`。 + +```javascript +var div = document.getElementById('display'); + +function handleFiles(files) { + for (var i = 0; i < files.length; i++) { + var img = document.createElement('img'); + img.src = window.URL.createObjectURL(files[i]); + div.appendChild(img); + img.onload = function() { + window.URL.revokeObjectURL(this.src); + } + } +} +``` + +上面代码中,一旦图片加载成功以后,为本地文件生成的临时网址就没用了,于是可以在`img.onload`回调函数里面,通过`URL.revokeObjectURL()`方法释放资源。 + +## 实例方法 + +### toString() + +URL 实例对象的`toString()`返回`URL.href`属性,即整个网址。 + From 47f33a8ea3e5c680594f6a83a6a52b47d635bbc5 Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2023年9月12日 19:38:41 +0800 Subject: [PATCH 12/31] docs: add chapter URLSearchParams --- chapters.yml | 1 + docs/urlsearchparams.md | 298 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 docs/urlsearchparams.md diff --git a/chapters.yml b/chapters.yml index 4696bca..c2e4a9f 100644 --- a/chapters.yml +++ b/chapters.yml @@ -13,5 +13,6 @@ - server-sent-events.md: Server-Sent Events - svg.md: SVG 图像 - url.md: URL 对象 +- urlsearchparams.md: URLSearchParams 对象 - websocket.md: WebSocket - web-share-api.md: Web Share API diff --git a/docs/urlsearchparams.md b/docs/urlsearchparams.md new file mode 100644 index 0000000..f874547 --- /dev/null +++ b/docs/urlsearchparams.md @@ -0,0 +1,298 @@ +# URLSearchParams 对象 + +## 简介 + +URLSearchParams 对象表示 URL 的查询字符串(比如`?foo=bar`)。它提供一系列方法,用来操作这些键值对。URL 实例对象的`searchParams`属性,就是指向一个 URLSearchParams 实例对象。 + +URLSearchParams 实例对象可以用`for...of`进行遍历。 + +```javascript +for (const [key, value] of mySearchParams) { +} +``` + +## 构造方法 + +URLSearchParams 可以作为构造函数使用,生成一个实例对象。 + +```javascript +const params = new URLSearchParams(); +``` + +它可以接受一个查询字符串作为参数,将其转成对应的实例对象。 + +```javascript +const params = new URLSearchParams('?a=1&b=2'); +``` + +注意,它最多只能去除查询字符串的开头问号`?`,并不能解析完整的网址字符串。 + +```javascript +const paramsString = "http://example.com/search?query=%40"; +const params = new URLSearchParams(paramsString); +``` + +上面示例中,URLSearchParams 会认为键名是`http://example.com/search?query`,而不是`query`。 + +它也可以接受表示键值对的对象或数组作为参数。 + +```javascript +// 参数为数组 +const params3 = new URLSearchParams([ + ["foo", "1"], + ["bar", "2"], +]); + +// 参数为对象 +const params1 = new URLSearchParams({ foo: "1", bar: "2" }); +``` + +浏览器向服务器发送表单数据时,可以直接使用 URLSearchParams 实例作为表单数据。 + +```javascript +const params = new URLSearchParams({foo: 1, bar: 2}); +fetch('https://example.com/api', { + method: 'POST', + body: params +}).then(...) +``` + +上面示例中,fetch 向服务器发送命令时,可以直接使用 URLSearchParams 实例对象作为数据体。 + +它还可以接受另一个 URLSearchParams 实例对象作为参数,等于复制了该对象。 + +```javascript +const params1 = new URLSearchParams('?a=1&b=2'); +const params2 = new URLSearchParams(params); +``` + +上面示例中,`params1`和`params2`是两个一模一样的实例对象,但是修改其中一个,不会影响到另一个。 + +URLSearchParams会对查询字符串自动编码。 + +```javascript +const params = new URLSearchParams({'foo': '你好'}); +params.toString() // "foo=%E4%BD%A0%E5%A5%BD" +``` + +上面示例中,`foo`的值是汉字,URLSearchParams 对其自动进行 URL 编码。 + +键名可以没有键值,这时 URLSearchParams 会认为键值等于空字符串。 + +```javascript +const params1 = new URLSearchParams("foo&bar=baz"); +const params2 = new URLSearchParams("foo=&bar=baz"); +``` + +上面示例中,`foo`是一个空键名,不管它后面有没有等号,URLSearchParams 都会认为它的值是一个空字符串。 + +## 实例方法 + +### append() + +`append()`用来添加一个查询键值对。如果同名的键值对已经存在,它依然会将新的键值对添加到查询字符串的末尾。 + +它的第一个参数是键名,第二个参数是键值,下面是用法示例。 + +```javascript +const params = new URLSearchParams('?a=1&b=2'); + +params.append('a', 3); +params.toString() // 'a=1&b=2&a=3' +``` + +上面示例中,键名`a`已经存在,但是`append()`依然会将`a=3`添加在查询字符串的末尾。 + +### delete() + +`delete()`删除给定名字的键值对。 + +### get() + +`get()`返回指定键名所对应的键值。如果存在多个同名键值对,它只返回第一个键值。 + +```javascript +const params = new URLSearchParams('?a=1&b=2'); +params.get('a') // 1 +``` + +对于不存在的键名,它会返回`null`。 + +注意,`get()`会将键值里面的加号转为空格。 + +```javascript +const params = new URLSearchParams(`c=a+b`); +params.get('c') // 'a b' +``` + +上面示例中,`get()`将`a+b`转为`a b`。如果希望避免这种行为,可以先用`encodeURIComponent()`对键值进行转义。 + +### getAll() + +`getAll()`返回一个数组,里面是指定键名所对应的所有键值。 + +```javascript +const params = new URLSearchParams('?a=1&b=2&a=3'); +params.getAll('a') // [ '1', '3' ] +``` + +### has() + +`has()`返回一个布尔值,表示指定键名是否存在。 + +```javascript +const params = new URLSearchParams('?a=1&b=2'); +params.has('a') // true +params.has('c') // false +``` + +### set() + +`set()`用来设置一个键值对。如果相同键名已经存在,则会替换当前值,这是它与`append()`的不同之处。该方法适合用来修改查询字符串。 + +```javascript +const params = new URLSearchParams('?a=1&b=2'); +params.set('a', 3); +params.toString() // 'a=3&b=2' +``` + +上面示例中,`set()`修改了键`a`。 + +如果有多个的同名键,`set()`会移除现存所有的键,再添加新的键值对。 + +```javascript +const params = new URLSearchParams('?foo=1&foo=2'); +params.set('foo', 3); +params.toString() // "foo=3" +``` + +上面示例中,有两个`foo`键,`set()`会将它们都删掉,再添加一个新的`foo`键。 + +### sort() + +`sort()`按照键名(以 Unicode 码点为序)对键值对排序。如果有同名键值对,它们的顺序不变。 + +```javascript +const params = new URLSearchParams('?a=1&b=2&a=3'); +params.sort(); +params.toString() // 'a=1&a=3&b=2' +``` + +### entries() + +`entries()`方法返回一个 iterator 对象,用来遍历键名和键值。 + +```javascript +const params = new URLSearchParams("key1=value1&key2=value2"); + +for (const [key, value] of params.entries()) { + console.log(`${key}, ${value}`); +} +// key1, value1 +// key2, value2 +``` + +如果直接对 URLSearchParams 实例进行`for...of`遍历,其实内部调用的就是`entries`接口。 + +```javascript +for (var p of params) {} +// 等同于 +for (var p of params.entries()) {} +``` + +### forEach() + +`forEach()`用来依次对每个键值对执行一个回调函数。 + +它接受两个参数,第一个参数`callback`是回调函数,第二个参数`thisArg`是可选的,用来设置`callback`里面的`this`对象。 + +```javascript +forEach(callback) +forEach(callback, thisArg) +``` + +`callback`函数可以接收到以下三个参数。 + +- value:当前键值。 +- key:当前键名。 +- searchParams:当前的 URLSearchParams 实例对象。 + +下面是用法示例。 + +```javascript +const params = new URLSearchParams("key1=value1&key2=value2"); + +params.forEach((value, key) => { + console.log(value, key); +}); +// value1 key1 +// value2 key2 +``` + +### keys() + +`keys()`返回一个 iterator 对象,用来遍历所有键名。 + +```javascript +const params = new URLSearchParams("key1=value1&key2=value2"); + +for (const key of params.keys()) { + console.log(key); +} +// key1 +// key2 +``` + +### values() + +`values()`返回一个 iterator 对象,用来遍历所有键值。 + +```javascript +const params = new URLSearchParams("key1=value1&key2=value2"); + +for (const value of params.values()) { + console.log(value); +} +// value1 +// value2 +``` + +这个方法也可以用来将所有键值,转成一个数组。 + +```javascritp +Array.from(params.values()) // ['value1', 'value2'] +``` + +### toString() + +`toString()`用来将 URLSearchParams 实例对象转成一个字符串。它返回的字符串不带问号,这一点与`window.location.search`不同。 + +## 实例属性 + +### size + +`size`是一个只读属性,返回键值对的总数。 + +```javascript +const params = new URLSearchParams("c=4&a=2&b=3&a=1"); +params.size; // 4 +``` + +上面示例中,键名`a`在查询字符串里面有两个,`size`不会将它们合并。 + +如果想统计不重复的键名,可以将使用 Set 结构。 + +```javascript +[...new Set(params.keys())].length // 3 +``` + +`size`属性可以用来判别,某个网址是否有查询字符串。 + +```javascript +const url = new URL("https://example.com?foo=1&bar=2"); + +if (url.searchParams.size) { + console.log("该 URL 有查询字符串"); +} +``` + From 10ea6b227e4c2b07e22910b64987e27fb6558013 Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2023年9月25日 23:10:14 +0800 Subject: [PATCH 13/31] docs(response): add example --- docs/response.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/response.md b/docs/response.md index 4d6becc..aa975ea 100644 --- a/docs/response.md +++ b/docs/response.md @@ -20,7 +20,7 @@ new Response(body, options) 它带有两个参数,都是可选的。 -第一个参数`body`代表服务器返回的数据体,必须是下面类型之一:ArrayBuffer、ArrayBufferView、Blob、FormData、ReadableStream、String、URLSearchParams。也就是说,它可以是对象或字符串。 +第一个参数`body`代表服务器返回的数据体,必须是下面类型之一:ArrayBuffer、ArrayBufferView、Blob、FormData、ReadableStream、String、URLSearchParams。 第二个参数`init`是一个对象,代表服务器返回的数据头,类型描述如下。 @@ -40,6 +40,24 @@ const myOptions = { status: 200, statusText: "OK" }; const myResponse = new Response(myBlob, myOptions); ``` +注意,如果返回 JSON 数据,必须将其转成字符串返回。 + +```javascript +const data = { + hello: "world", +}; + +const json = JSON.stringify(data, null, 2); + +const result = new Response(json, { + headers: { + "content-type": "application/json;charset=UTF-8", + }, +}); +``` + +上面示例中,构造一个返回 JSON 数据的 Response 对象,就必须用`JSON.stringify()`方法,将第一个参数转为字符串。 + ## 实例属性 ### body,bodyUsed From 0bd4cefbd4e21b2df1ff5b7965b3d8248d4dff2d Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2023年9月30日 00:03:39 +0800 Subject: [PATCH 14/31] docs(FormData): add FormData --- docs/formdata.md | 136 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 docs/formdata.md diff --git a/docs/formdata.md b/docs/formdata.md new file mode 100644 index 0000000..bdfc8bd --- /dev/null +++ b/docs/formdata.md @@ -0,0 +1,136 @@ +# FormData 对象 + +## 简介 + +FormData 代表表单数据,是浏览器的原生对象。 + +它可以当作构造函数使用,构造一个表单实例。 + +```javascript +const formData = new FormData(); +``` + +上面示例中,`FormData()`当作构造函数使用,返回一个空的表单实例对象。 + +它也可以接受一个表单的 DOM 节点当作参数,将表单的所有元素及其值,转换成一个个键值对,包含在返回的实例对象里面。 + +```javascript +const formData = new FormData(form); +``` + +上面示例中,`FormData()`的参数`form`就是一个表单的 DOM 节点对象。 + +下面是用法示例。 + +```javascript +const form = document.querySelector('#subscription'); + +try { + let response = await fetch('subscribe.php', { + method: 'POST', + body: new FormData(form), + }); + + const result = await response.json(); + + console.log(result); +} catch (error) { + console.log(error); +} +``` + +浏览器向服务器发送 FormData 对象时,不管是用户点击 Submit 按钮发送,还是使用脚本发送,都会自动将其编码,并以`Content-Type: multipart/form-data`的形式发送。 + +## 实例方法 + +### append() + +`append()`用于添加一个键值对,即添加一个表单元素。它有两种使用形式。 + +```javascript +FormData.append(name, value) +FormData.append(name, value, file) +``` + +它的第一个参数是键名,第二个参数是键值。 + +如果键名已经存在,它会为其添加新的键值。 + +### delete() + +`delete()`用于删除指定的键值对,它的参数为键名。 + +```javascript +FormData.delete(name); +``` + +### entries() + +`entries()`返回一个迭代器,用于遍历所有键值对。 + +```javascript +FormData.entries() +``` + +下面是一个用法示例。 + +```javascript +const form = document.querySelector('#subscription'); +const formData = new FormData(form); +const values = [...formData.entries()]; +console.log(values); +``` + +### get() + +`get()`用于获取指定键名的键值,它的参数为键名。 + +```javascript +FormData.get(name) +``` + +如果该键名有多个键值,只返回第一个键值。 + +### getAll() + +`getAll()`用于获取指定键名的所有键值,它的参数为键名,返回值为一个数组。 + +```javascript +FormData.getAll(name) +``` + +### has() + +`has()`返回一个布尔值,表示是否存在指定键名,它的参数为键名。 + +```javascript +FormData.has(name) +``` + +### keys() + +`key()`返回一个键名的迭代器,用于遍历所有键名。 + +```javascript +FormData.keys() +``` + +### set() + +`set()`用于为指定键名设置新的键值。它有两种使用形式。 + +```javascript +FormData.set(name, value); +FormData.set(name, value, filename); +``` + +它的第一个参数为键名,第二个参数为键值。如果指定键名不存在,它会添加该键名。 + +### value() + +`value()`返回一个键值的迭代器,用于遍历所有键值。 + +```javascript +FormData.values() +``` + From 9668565c2b0576b8ae90287c0793743ebf6197df Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2023年9月30日 16:45:12 +0800 Subject: [PATCH 15/31] docs(FormData): finish FormData --- chapters.yml | 1 + docs/formdata.md | 155 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 136 insertions(+), 20 deletions(-) diff --git a/chapters.yml b/chapters.yml index c2e4a9f..863e2d0 100644 --- a/chapters.yml +++ b/chapters.yml @@ -2,6 +2,7 @@ - clipboard.md: Clipboard API - fetch.md: Fetch API - fontface.md: FontFace API +- formdata.md: FormData 对象 - geolocation.md: Geolocation API - headers.md: Headers 对象 - intersectionObserver.md: IntersectionObserver diff --git a/docs/formdata.md b/docs/formdata.md index bdfc8bd..c72d6ed 100644 --- a/docs/formdata.md +++ b/docs/formdata.md @@ -20,26 +20,53 @@ const formData = new FormData(form); 上面示例中,`FormData()`的参数`form`就是一个表单的 DOM 节点对象。 -下面是用法示例。 +下面是用法示例,通过脚本发送表单数据。 + +```html + + + Picture: + + + + +``` + +浏览器向服务器发送表单数据时,不管是用户点击 Submit 按钮发送,还是使用脚本发送,都会自动将其编码,并以`Content-Type: multipart/form-data`的形式发送。 + +`FormData()`还有第三种用法,如果想把"提交"(Submit)按钮也加入表单的键值对,可以把按钮的 DOM 节点当作`FormData()`的第二个参数。 ```javascript -const form = document.querySelector('#subscription'); +new FormData(form, submitter) +``` -try { - let response = await fetch('subscribe.php', { - method: 'POST', - body: new FormData(form), - }); +上面代码中,`submitter`就是提交按钮的 DOM 节点。这种用法适合表单有多个提交按钮,服务端需要通过按钮的值来判断,用户到底选用了哪个按钮。 - const result = await response.json(); +```javascript +// 表单有两个提交按钮 +// +// - console.log(result); -} catch (error) { - console.log(error); -} +const form = document.getElementById("form"); +const submitter = document.querySelector("button[value=save]"); + +const formData = new FormData(form, submitter); ``` -浏览器向服务器发送 FormData 对象时,不管是用户点击 Submit 按钮发送,还是使用脚本发送,都会自动将其编码,并以`Content-Type: multipart/form-data`的形式发送。 +上面示例中,`FormData()`加入了第二个参数,实例对象`formData`就会增加一个键值对,键名为`intent`,键值为`save`。 ## 实例方法 @@ -49,12 +76,57 @@ try { ```javascript FormData.append(name, value) -FormData.append(name, value, file) +FormData.append(name, blob, fileName) ``` -它的第一个参数是键名,第二个参数是键值。 +它的第一个参数是键名,第二个参数是键值。上面的第二种形式`FormData.append(name, blob, fileName)`,相当于添加一个文件选择器``,第二个参数`blob`是文件的二进制内容,第三个参数`fileName`是文件名。 + +如果键名已经存在,它会为其添加新的键值,即同一个键名有多个键值。 -如果键名已经存在,它会为其添加新的键值。 +下面是一个用法示例。 + +```javascript +let formData = new FormData(); +formData.append('key1', 'value1'); +formData.append('key2', 'value2'); + +for(let [name, value] of formData) { + console.log(`${name} = ${value}`); +} +// key1 = value1 +// key2 = value2 +``` + +下面是添加二进制文件的例子。 + +```javascript +// HTML 代码如下 +// + +let imageBlob = await new Promise( + resolve => canvasElem.toBlob(resolve, 'image/png') +); + +let formData = new FormData(); +formData.append('image', imageBlob, 'image.png'); + +let response = await fetch('/article/formdata/post/image-form', { + method: 'POST', + body: formData +}); + +let result = await response.json(); +console.log(result); +``` + +下面是添加 XML 文件的例子。 + +```javascript +const content = 'hey!'; +const blob = new Blob([content], { type: "text/xml" }); + +formData.append('userfile', blob); +``` ### delete() @@ -81,6 +153,19 @@ const values = [...formData.entries()]; console.log(values); ``` +下面是使用`entries()`遍历键值对的例子。 + +```javascript +formData.append("key1", "value1"); +formData.append("key2", "value2"); + +for (const pair of formData.entries()) { + console.log(`${pair[0]}, ${pair[1]}`); +} +// key1, value1 +// key2, value2 +``` + ### get() `get()`用于获取指定键名的键值,它的参数为键名。 @@ -89,11 +174,11 @@ console.log(values); FormData.get(name) ``` -如果该键名有多个键值,只返回第一个键值。 +如果该键名有多个键值,只返回第一个键值。如果找不到指定键名,则返回`null`。 ### getAll() -`getAll()`用于获取指定键名的所有键值,它的参数为键名,返回值为一个数组。 +`getAll()`用于获取指定键名的所有键值,它的参数为键名,返回值为一个数组。如果找不到指定键名,则返回一个空数组。 ```javascript FormData.getAll(name) @@ -115,16 +200,32 @@ FormData.has(name) FormData.keys() ``` +下面是用法示例。 + +```javascript +const formData = new FormData(); +formData.append("key1", "value1"); +formData.append("key2", "value2"); + +for (const key of formData.keys()) { + console.log(key); +} +// key1 +// key2 +``` + ### set() `set()`用于为指定键名设置新的键值。它有两种使用形式。 ```javascript FormData.set(name, value); -FormData.set(name, value, filename); +FormData.set(name, blob, fileName); ``` -它的第一个参数为键名,第二个参数为键值。如果指定键名不存在,它会添加该键名。 +它的第一个参数为键名,第二个参数为键值。上面第二种形式为上传文件,第二个参数`blob`为文件的二进制内容,第三个参数`fileName`为文件名。该方法没有返回值。 + +如果指定键名不存在,它会添加该键名,否则它会丢弃所有现有的键值,确保一个键名只有一个键值。这是它跟`append()`的主要区别。 ### value() @@ -134,3 +235,17 @@ FormData.set(name, value, filename); FormData.values() ``` +下面是用法示例。 + +```javascript +const formData = new FormData(); +formData.append("key1", "value1"); +formData.append("key2", "value2"); + +for (const value of formData.values()) { + console.log(value); +} +// value1 +// value2 +``` + From f0b4a1e514e0d7e7a4a64bd7dcf5ead3f48044bf Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 5 Oct 2023 18:57:12 +0800 Subject: [PATCH 16/31] docs: add Intl.Segmenter API --- chapters.yml | 1 + docs/intl-segmenter.md | 225 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 docs/intl-segmenter.md diff --git a/chapters.yml b/chapters.yml index 863e2d0..1333738 100644 --- a/chapters.yml +++ b/chapters.yml @@ -7,6 +7,7 @@ - headers.md: Headers 对象 - intersectionObserver.md: IntersectionObserver - intl-relativetimeformat.md: Intl.RelativeTimeFormat +- intl-segmenter.md: Intl.Segmenter API - page-lifecycle.md: Page Lifecycle API - page-visibility.md: Page Visibility API - request.md: Request API diff --git a/docs/intl-segmenter.md b/docs/intl-segmenter.md new file mode 100644 index 0000000..9676c08 --- /dev/null +++ b/docs/intl-segmenter.md @@ -0,0 +1,225 @@ +# Intl segmenter API + +## 简介 + +Intl.Segmenter 是浏览器内置的用于文本分词的 API。 + +使用时,先用`Intl.Segmenter()`新建一个分词器对象。 + +```javascript +const segmenter = new Intl.Segmenter( + 'en', + { granularity: 'word' } +); +``` + +`Intl.Segmenter()`接受两个参数,第一个是所要分词的语言简称(上例是`en`),第二个参数是一个配置对象,有以下两个属性。 + +- `localeMatcher`:指定分词算法,有两个可能的值,一个是`lookup`,表示采用特定的算法(BCP 47),另一个是`best fit`(默认值),表示采用操作系统或浏览器现有的尽可能适用的算法。 +- `granularity`:表示分词的颗粒度,有三个可能的值:grapheme(字符,这是默认值),word(词语),sentence(句子)。 + +拿到分词器对象以后,就可以进行分词了。 + +```javascript +const segmenter = new Intl.Segmenter( + 'en', + { granularity: 'word' } +); + +const segments = segmenter.segment('This has four words!'); + +Array.from(segments).map((segment) => segment.segment); +// ['This', ' ', 'has', ' ', 'four', ' ', 'words', '!'] +``` + +上面示例中,变量`segmenter`是分词器对象,可以对英语进行分词,颗粒度是词语。所以,"This has four words!"被分成了8个部分,包括4个词语、3个空格和1个标点符号。 + +分词器对象的`segment()`方法是实际的分词方法,它的参数是需要分词的文本,返回值是一个具有迭代器接口的分词结果对象。`Array.from()`将这个分词结果对象转成数组,也可以采用`[...segments]`的写法。 + +下面的例子是过滤掉非词语字符。 + +```javascript +const segments = segmenter.segment('This has four words!'); + +Array.from(segments) + .filter((segment) => segment.isWordLike) + .map((segment) => segment.segment); +// ['This', 'has', 'four', 'words'] +``` + +上面示例中,`Array.from()`将分词结果对象转成一个数组,变量`segment`是数组的每个成员,它也是一个对象。该对象的`isWordLike`属性是一个布尔值,表示当前值是否为一个真正的词,而该对象的`segment`属性(上例的`segment.segment`)则是真正的分词结果。 + +Intl Segmenter 支持各种语言,下面是日语分词的例子。 + +```javascript +const segmenter = new Intl.Segmenter('ja', { granularity: 'word' }); +const segments = segmenter.segment('これは日本語のテキストです'); + +Array.from(segments).map((segment) => segment.segment); +// ['これ', 'は', '日本語', 'の', 'テキスト', 'です'] +``` + +下面是法语的例子。 + +```javascript +const segmenterFr = new Intl.Segmenter('fr', { granularity: 'word' }); +const string1 = 'Que ma joie demeure'; + +const iterator1 = segmenterFr.segment(string1)[Symbol.iterator](); + +iterator1.next().value.segment // 'Que' +iterator1.next().value.segment // ' ' +``` + +## 静态方法 + +### Intl.Segmenter.supportedLocalesOf() + +`Intl.Segmenter.supportedLocalesOf()`返回一个数组,用来检测当前环境是否支持指定语言的分词。 + +```javascript +const locales1 = ['ban', 'id-u-co-pinyin', 'de-ID']; +const options1 = { localeMatcher: 'lookup', granularity: 'string' }; + +Intl.Segmenter.supportedLocalesOf(locales1, options1) +// ["id-u-co-pinyin", "de-ID"] +``` + +它接受两个参数,第一个参数是一个数组,数组成员是需要检测的语言简称;第二个参数是配置对象,跟构造方法的第二个参数是一致的,可以省略。 + +上面示例中,需要检测的三种语言分别是巴厘岛语(ban)、印度尼西亚语(id-u-co-pinyin)、德语(de-ID)。结果显示只支持前两者,不支持巴厘岛语。 + +## 实例方法 + +### resolvedOptions() + +实例对象的`resolvedOptions()`方法,用于获取构造该实例时的参数。 + +```javascript +const segmenter1 = new Intl.Segmenter('fr-FR'); +const options1 = segmenter1.resolvedOptions(); + +options1.locale // "fr-FR" +options1.granularity // "grapheme" +``` + +上面示例中,`resolveOptions()`方法返回了一个对象,该对象的`locale`属性对应构造方法的第一个参数,`granularity`属性对应构造方法第二个参数对象的颗粒度属性。 + +### segment() + +实例对象的`segment()`方法进行实际的分词。 + +```javascript +const segmenterFr = new Intl.Segmenter('fr', { granularity: 'word' }); +const string1 = 'Que ma joie demeure'; + +const segments = segmenterFr.segment(string1); + +segments.containing(5) +// {segment: 'ma', index: 4, input: 'Que ma joie demeure', isWordLike: true} +``` + +`segment()`方法的返回结果是一个具有迭代器接口的分词结果对象,有三种方法进行处理。 + +(1)使用`Array.from()`或扩展运算符(`...`)将分词结果对象转成数组。 + +```javascript +const segmenterFr = new Intl.Segmenter('fr', { granularity: 'word' }); +const string1 = 'Que ma joie demeure'; + +const iterator1 = segmenterFr.segment(string1); + +Array.from(iterator1).map(segment => { + if (segment.segment.length> 4) { + console.log(segment.segment); + } +}) +// demeure +``` + +上面示例中,`segmenterFr.segment()`返回一个针对`string1`的分词结果对象,该对象具有迭代器接口。`Array.from()`将其转为数组,数组的每个成员是一个分词颗粒对象,该对象的`segment`属性就是分词结果。分词颗粒对象的介绍,详见后文。 + +(2)使用`for...of`循环,遍历分词结果对象。 + +```javascript +const segmenterFr = new Intl.Segmenter('fr', { granularity: 'word' }); +const string1 = 'Que ma joie demeure'; + +const iterator1 = segmenterFr.segment(string1); + +for (const segment of iterator1) { + if (segment.segment.length> 4) { + console.log(segment.segment); + } +} +// demeure +``` + +上面示例中,`for...of`默认调用分词结果对象的迭代器接口,获取每一轮的分词颗粒对象。 + +由于迭代器接口是在`Symbol.iterator`属性上面,所以实际执行的代码如下。 + +```javascript +const segmenterFr = new Intl.Segmenter('fr', { granularity: 'word' }); +const string1 = 'Que ma joie demeure'; + +const iterator1 = segmenterFr.segment(string1)[Symbol.iterator](); + +for (const segment of iterator1) { + if (segment.segment.length> 4) { + console.log(segment.segment); + } +} +// "demeure" +``` + +`for...of`循环每一轮得到的是一个分词颗粒对象,该对象的`segment`属性就是当前的分词结果,详见下文。 + +(3)使用`containing()`方法获取某个位置的分词颗粒对象。 + +```javascript +const segmenterFr = new Intl.Segmenter('fr', { granularity: 'word' }); +const string1 = 'Que ma joie demeure'; + +const segments = segmenterFr.segment(string1); + +segments.containing(5) +// {segment: 'ma', index: 4, input: 'Que ma joie demeure', isWordLike: true} +``` + +`containing()`方法的参数是一个整数,表示原始字符串的指定位置(从0开始计算)。如果省略该参数,则默认为0。 + +`containing()`的返回值是该位置的分词颗粒对象,如果参数位置超出原始字符串,则返回`undefined`。分词颗粒对象有以下属性。 + +- segment:指定位置对应的分词结果。 +- index:本次分词在原始字符串的开始位置(从0开始)。 +- input:进行分词的原始字符串。 +- isWordLike:如果分词颗粒度为`word`,该属性返回一个布尔值,表示当前值是否一个真正的词。如果分词颗粒度不为`word`,则返回`undefined`。 + +```javascript +const input = "Allons-y!"; + +const segmenter = new Intl.Segmenter("fr", { granularity: "word" }); +const segments = segmenter.segment(input); + +let current = segments.containing(); +// { index: 0, segment: "Allons", isWordLike: true } + +current = segments.containing(4); +// { index: 0, segment: "Allons", isWordLike: true } + +current = segments.containing(6); +// { index: 6, segment: "-", isWordLike: false } + +current = segments.containing(current.index + current.segment.length); +// { index: 7, segment: "y", isWordLike: true } + +current = segments.containing(current.index + current.segment.length); +// { index: 8, segment: "!", isWordLike: false } + +current = segments.containing(current.index + current.segment.length); +// undefined +``` + +上面示例中,分词结果中除了空格和标点符号,其他情况下,`isWordLike`都返回`false`。 + From 124037f09734826fe8bfe18f1a4820c4504bc14c Mon Sep 17 00:00:00 2001 From: mashiguang Date: 2023年10月11日 18:21:34 +0800 Subject: [PATCH 17/31] docs(FormData): value() -> values() --- docs/formdata.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/formdata.md b/docs/formdata.md index c72d6ed..9ede3f0 100644 --- a/docs/formdata.md +++ b/docs/formdata.md @@ -227,9 +227,9 @@ FormData.set(name, blob, fileName); 如果指定键名不存在,它会添加该键名,否则它会丢弃所有现有的键值,确保一个键名只有一个键值。这是它跟`append()`的主要区别。 -### value() +### values() -`value()`返回一个键值的迭代器,用于遍历所有键值。 +`values()`返回一个键值的迭代器,用于遍历所有键值。 ```javascript FormData.values() From ff5c861de326216b181904824b5ed47bb16fcbf1 Mon Sep 17 00:00:00 2001 From: Chavin Date: 2023年10月25日 23:41:19 +0800 Subject: [PATCH 18/31] fixed a bad svg demo --- docs/svg.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/svg.md b/docs/svg.md index 62d7b79..a9fc228 100644 --- a/docs/svg.md +++ b/docs/svg.md @@ -452,20 +452,22 @@ Date |Amount ```xml - - - - + + + + + + - - 10ドル - 80ドル + + 10ドル + 80ドル - - January 2014 - April + + Jan. + Apr. From cbf0de0aab7d49d8609d3dc71746516efb83fa57 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 3 Dec 2023 13:20:51 +0800 Subject: [PATCH 19/31] docs(url): add URL.canParse() --- docs/url.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/url.md b/docs/url.md index d2f58f5..ea04cb8 100644 --- a/docs/url.md +++ b/docs/url.md @@ -171,6 +171,52 @@ function handleFiles(files) { 上面代码中,一旦图片加载成功以后,为本地文件生成的临时网址就没用了,于是可以在`img.onload`回调函数里面,通过`URL.revokeObjectURL()`方法释放资源。 +### URL.canParse() + +`URL.canParse()`用来检测一个字符串是否为有效 URL,它返回一个布尔值。 + +```javascipt +URL.canParse(url) +URL.canParse(url, base) +``` + +`URL.canParse()`可以接受两个参数。 + +- `url`:字符串或者对象(比如``元素的 DOM 对象),表示 URL。 +- `base`:字符串或者 URL 实例对象,表示 URL 的基准位置。它是可选参数,当第一个参数`url`为相对 URL 时,会使用这个参数,计算出完整的 URL,再进行判断。 + +```javascript +URL.canParse("https://developer.mozilla.org/") // true +URL.canParse("/en-US/docs") // false +URL.canParse("/en-US/docs", "https://developer.mozilla.org/") // true +``` + +上面示例中,如果第一个参数是相对 URL,这时必须要有第二个参数,否则返回`false`。 + +下面的示例是第二个参数为 URL 实例对象。 + +```javascript +let baseUrl = new URL("https://developer.mozilla.org/"); +let url = "/en-US/docs"; + +URL.canParse(url, baseUrl) // true +``` + +该方法内部使用`URL()`构造方法相同的解析算法,因此可以用`URL()`构造方法代替。 + +```javascript +function isUrlValid(string) { + try { + new URL(string); + return true; + } catch (err) { + return false; + } +} +``` + +上面示例中,给出了`URL.canParse()`的替代实现`isUrlValid()`。 + ## 实例方法 ### toString() From 9195d6523e7d024faa0f0dfc0e169e3c3c03997a Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2024年4月10日 16:46:46 +0800 Subject: [PATCH 20/31] docs(url): fixed typo --- docs/url.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/url.md b/docs/url.md index ea04cb8..10244bd 100644 --- a/docs/url.md +++ b/docs/url.md @@ -18,10 +18,10 @@ let url = new URL('https://example.com'); ```javascript const url1 = new URL('page2.html', 'http://example.com/page1.html'); -url2.href // "http://example.com/page2.html" +url1.href // "http://example.com/page2.html" const url2 = new URL('..', 'http://example.com/a/b.html') -url3.href // "http://example.com/" +url2.href // "http://example.com/" ``` 这种写法很方便基于现有网址,构造新的 URL。 From c07d5cb2d97dd2bec773fa7ea3fc14b1d79175b7 Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2024年4月11日 14:28:33 +0800 Subject: [PATCH 21/31] docs(URLSearchParams): fixed typo --- docs/urlsearchparams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/urlsearchparams.md b/docs/urlsearchparams.md index f874547..44c8000 100644 --- a/docs/urlsearchparams.md +++ b/docs/urlsearchparams.md @@ -63,7 +63,7 @@ fetch('https://example.com/api', { ```javascript const params1 = new URLSearchParams('?a=1&b=2'); -const params2 = new URLSearchParams(params); +const params2 = new URLSearchParams(params1); ``` 上面示例中,`params1`和`params2`是两个一模一样的实例对象,但是修改其中一个,不会影响到另一个。 From dfdd05261b7bd40f190e04650e4cd9e35d30195e Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2024年5月20日 11:40:22 +0800 Subject: [PATCH 22/31] docs(url): add URL.parse() --- docs/url.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/url.md b/docs/url.md index 10244bd..1cba9dc 100644 --- a/docs/url.md +++ b/docs/url.md @@ -173,7 +173,7 @@ function handleFiles(files) { ### URL.canParse() -`URL.canParse()`用来检测一个字符串是否为有效 URL,它返回一个布尔值。 +`URL()`构造函数解析非法网址时,会抛出错误,必须用`try...catch`代码块处理,这样终究不是非常方便。因此,URL 对象又引入了`URL.canParse()`方法,它返回一个布尔值,表示当前字符串是否为有效网址。 ```javascipt URL.canParse(url) @@ -217,6 +217,20 @@ function isUrlValid(string) { 上面示例中,给出了`URL.canParse()`的替代实现`isUrlValid()`。 +### URL.parse() + +`URL.parse()`是一个新添加的方法,Chromium 126 和 Firefox 126 开始支持。 + +它的主要目的就是,改变`URL()`构造函数解析非法网址抛错的问题。这个新方法不会抛错,如果参数是有效网址,则返回 URL 实例对象,否则返回`null`。 + +```javascript +const urlstring = "this is not a URL"; + +const not_a_url = URL.parse(urlstring); // null +``` + +上面示例中,`URL.parse()`的参数不是有效网址,所以返回`null`。 + ## 实例方法 ### toString() From a0502cce821d25316ad8ebcc95214234aede422a Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2024年6月19日 10:05:48 +0800 Subject: [PATCH 23/31] refactor: update dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 30c6cd9..01cf692 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "author": "Ruan Yifeng", "license": "Creative Commons Attribution-ShareAlike License", "dependencies": { - "gh-pages": "^6.0.0", + "gh-pages": "6.x", "loppo": "^0.6.25", - "loppo-theme-wangdoc": "^0.6.6" + "loppo-theme-wangdoc": "^0.7.1" } } From 9a028ab1f38f842e57dbb730754e97c7ddf0a3f0 Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2024年6月19日 10:09:23 +0800 Subject: [PATCH 24/31] refactor: update github action script --- .github/workflows/wangdoc.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wangdoc.yml b/.github/workflows/wangdoc.yml index d05b96f..5f718e6 100644 --- a/.github/workflows/wangdoc.yml +++ b/.github/workflows/wangdoc.yml @@ -10,11 +10,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@main + uses: actions/setup-node@v4 with: node-version: 'latest' - name: Install dependencies From d27b94c66687a689e5270eec56aa91c0a9cea6e0 Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2024年6月19日 17:28:28 +0800 Subject: [PATCH 25/31] docs: add URL Pattern API --- chapters.yml | 1 + docs/urlpattern.md | 547 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 548 insertions(+) create mode 100644 docs/urlpattern.md diff --git a/chapters.yml b/chapters.yml index 1333738..c0b59cb 100644 --- a/chapters.yml +++ b/chapters.yml @@ -15,6 +15,7 @@ - server-sent-events.md: Server-Sent Events - svg.md: SVG 图像 - url.md: URL 对象 +- urlpattern.md: URL Pattern API - urlsearchparams.md: URLSearchParams 对象 - websocket.md: WebSocket - web-share-api.md: Web Share API diff --git a/docs/urlpattern.md b/docs/urlpattern.md new file mode 100644 index 0000000..ba69063 --- /dev/null +++ b/docs/urlpattern.md @@ -0,0 +1,547 @@ +# URL Pattern API + +## 简介 + +URL Pattern API 基于正则表达式和通配符,对 URL 进行匹配和解析。 + +它提供一个构造函数`URLPattern()`,用于新建一个 URL 模式实例。 + +```javascript +const pattern = new URLPattern(input); +``` + +有了模式实例,就可以知道某个 URL 是否符合该模式。 + +```javascript +const pattern = new URLPattern({ pathname: "/books" }); +console.log(pattern.test("https://example.com/books")); // true +``` + +上面示例中,模式实例是 包含`/books`路径的 URL,实例方法`test()`用来检测指定网址是否符合该模式,结果为`true`。 + +URL Pattern 支持多种协议,不仅是 HTTP 协议。 + +```javascript +const pattern = new URLPattern("data\\:foo*"); +``` + +上面示例中,URL Pattern 新建了一个 Data 协议的模式。 + +## 构造函数 URLPattern() + +### 基本用法 + +构造函数`URLPattern()`用于新建一个 URL 模式实例。 + +```javascript +const pattern = new URLPattern(input); +``` + +该构造函数的参数`input`是一个模式字符串或者模式对象。 + +```javascript +new URLPattern("https://example.com/books/:id") +// { +// hasRegExpGroups: false, +// hash: "*", +// hostname: "example.com", +// password: "*", +// pathname: "/books/:id", +// port: "", +// protocol: "https", +// search: "*", +// username: "*", +// ... +// } +``` + +上面示例中,参数`https://example.com/books/:id`就是一个模式字符串,执行后返回一个 URLPattern 实例对象,包含模式的各个组成部分。 + +参数`input`也可以写成一个对象,用属性指定模式 URL 的每个部分。也就是说,模式对象可以有以下属性。 + +- protocol +- username +- password +- hostname +- port +- pathname +- search +- hash +- baseURL + +上面的示例,如果参数改成模式对象,就是下面这样。 + +```javascript +new URLPattern({ + protocol: 'https', + hostname: 'example.com', + pathname: '/books/:id', +}) +``` + +模式字符串或者模式对象之中,没有定义的部分,默认为`*`,表示所有可能的字符,包括零字符的情况。 + +`URLPattern()`正常情况下将返回一个 URLPattern 实例对象,但是遇到参数无效或语法不正确,则会报错。 + +```javascript +new URLPattern(123) // 报错 +``` + +上面示例中,参数`123`不是一个有效的 URL 模式,就报错了。 + +需要注意的是,如果模式字符串为相对路径,那么`URLPattern()`还需要第二个参数,用来指定基准 URL。 + +```javascript +new URLPattern(input, baseURL) +``` + +上面代码中,第二个参数`baseURL`就是基准 URL。 + +```javascript +new URLPattern('/books/:id') // 报错 +new URLPattern('/books/:id', 'https://example.com') // 正确 +``` + +上面示例中,第一个参数`/books/:id`是一个相对路径,这时就需要第二个参数`https://example.com`,用来指定基准 URL,否则报错。 + +但是,如果参数为模式对象,则可以只指定 URL 模式的某个部分。 + +```javascript +new URLPattern({ + pathname: '/books/:id' +}) // 正确 +``` + +上面示例中,参数是一个模式对象,那么参数允许只指定 URL 的部分模式。 + +模式对象里面,也可以指定基准 URL。 + +```javascript +let pattern4 = new URLPattern({ + pathname: "/books/:id", + baseURL: "https://example.com", +}); +``` + +基准 URL 必须是合法的 URL,不能包含模式。 + +注意,如果用了模式对象,就不能使用基准 URL 作为第二个参数,这样会报错。 + +```javascript +new URLPattern({ pathname: "/foo/bar" }, "https://example.com") // 报错 +new URLPattern({ pathname: "/foo/bar" }, "https://example.com/baz") // 报错 +``` + +上面示例中,同时使用了模式对象和第二个参数,结果就报错了。 + +`URLpattern()`还可以加入配置对象参数,用于定制匹配行为。 + +```javascript +new URLPattern(input, options) +new URLPattern(input, baseURL, options) +``` + +上面代码中,参数`options`就是一个配置对象。 + +目前,这个配置对象`options`只有`ignoreCase`一个属性,如果设为`true`,将不区分大小写,默认值为`false`,表示区分大小写。 + +```javascript +new URLPattern(input, { + ignoreCase: false // 默认值,区分大小写 +}) +``` + +请看下面的例子。 + +```javascript +const pattern = new URLPattern("https://example.com/2022/feb/*"); + +pattern.test("https://example.com/2022/feb/xc44rsz") // true +pattern.test("https://example.com/2022/Feb/xc44rsz") // false +``` + +上面示例,默认匹配时,会区分`feb`和`Feb`。 + +我们可以用`ignoreCase`将其关闭。 + +```javascript +const pattern = new URLPattern( + "https://example.com/2022/feb/*", + { ignoreCase: true, } +); + +pattern.test("https://example.com/2022/feb/xc44rsz") // true +pattern.test("https://example.com/2022/Feb/xc44rsz") // true +``` + +### 模式写法 + +模式字符串基本上采用正则表达式的写法,但是不是所有的正则语法都支持,比如先行断言和后行断言就不支持。 + +(1)普通字符 + +如果都是普通字符,就表示原样匹配。 + +```javascript +const p = new URLPattern('https://example.com/abc'); +``` + +上面代码就表示确切匹配路径`https://example.com/abc`。 + +```javascript +p.test('https://example.com') // false +p.test('https://example.com/a') //false +p.test('https://example.com/abc') // true +p.test('https://example.com/abcd') //false +p.test('https://example.com/abc/') //false +p.test('https://example.com/abc?123') //true +``` + +上面示例中,URL 必须严格匹配路径`https://example.com/abc`,即使尾部多一个斜杠都不行,但是加上查询字符串是可以的。 + +(2)`?` + +量词字符`?`表示前面的字符串,可以出现0次或1次,即该部分可选。 + +```javascript +let pattern = new URLPattern({ + protocol: "http{s}?", +}); +``` + +上面示例中,`{s}?`表示字符组`s`可以出现0次或1次。 + +`?`不包括路径的分隔符`/`。 + +```javascript +const pattern = new URLPattern("/books/:id?", "https://example.com"); + +pattern.test("https://example.com/books/123") // true +pattern.test("https://example.com/books") // true +pattern.test("https://example.com/books/") // false +pattern.test("https://example.com/books/123/456") // false +pattern.test("https://example.com/books/123/456/789") // false +pattern.test("https://example.com/books/123/456/") // false +``` + +上面示例中,`?`不能匹配网址结尾的斜杠。 + +如果一定要匹配,可以把结尾的斜杠放在`{}`里面。 + +```javascript +const pattern = new URLPattern({ pathname: "/product{/}?" }); + +pattern.test({ pathname: "/product" }) // true +pattern.test({ pathname: "/product/" }) // true +``` + +上面示例中,不管网址有没有结尾的斜杠,`{/}?`都会成功匹配。 + +(3)`+` + +量词字符`+`表示前面的字符串出现1次或多次。 + +```javascript +const pattern = new URLPattern({ + pathname: "/books/(\\d+)", +}) +``` + +上面示例中,`\\d+`表示1个或多个数字,其中的`\d`是一个内置的字符类,表示0-9的数字,因为放在双引号里面,所以反斜杠前面还要再加一个反斜杠进行转义。 + +`+`可以包括`/`分隔的路径的多个部分,但不包括路径结尾的斜杠。 + +```javascript +const pattern = new URLPattern("/books/:id+", "https://example.com"); + +pattern.test("https://example.com/books/123") // true +pattern.test("https://example.com/books") // false +pattern.test("https://example.com/books/") // false +pattern.test("https://example.com/books/123/456") // true +pattern.test("https://example.com/books/123/456/789") // true +pattern.test("https://example.com/books/123/456/") // false +``` + +(4)`*` + +量词字符`*`表示出现零次或多次。 + +```javascript +const pattern = new URLPattern('https://example.com/{abc}*'); + +pattern.test('https://example.com') // true +pattern.test('https://example.com/') // true +pattern.test('https://example.com/abc') // true +pattern.test('https://example.com/abc/') // false +pattern.test('https://example.com/ab') // false +pattern.test('https://example.com/abcabc') // true +pattern.test('https://example.com/abc/abc/abc') // false +``` + +上面示例中,`{abc}*`表示`abc`出现零次或多次,也不包括路径分隔符`/`。 + +如果`*`前面没有任何字符,就表示所有字符,包括零字符的情况,也包括分隔符`/`。 + +```javascript +let pattern = new URLPattern({ + search: "*", + hash: "*", +}); +``` + +上面示例中,`*`表示匹配所有字符,包括零字符。 + +下面是另一个例子。 + +```javascript +const pattern = new URLPattern("/*.png", "https://example.com"); + +pattern.test("https://example.com/image.png") // true +pattern.test("https://example.com/image.png/123") // false +pattern.test("https://example.com/folder/image.png") // true +pattern.test("https://example.com/.png") // true +``` + +`*`匹配的部分可以从对应部分的数字属性上获取。 + +```javascript +const pattern = new URLPattern({ + hostname: "example.com", + pathname: "/foo/*" +}); + +const result = pattern.exec("/foo/bar", "https://example.com/baz"); + +result.pathname.input // '/foo/bar' +result.pathname.groups[0] // 'bar' +``` + +上面示例中,`*`的匹配结果可以从`pathname.groups[0]`获取。 + +```javascript +const pattern = new URLPattern({ hostname: "*.example.com" }); +const result = pattern.exec({ hostname: "cdn.example.com" }); + +result.hostname.groups[0] // 'cdn' +result.hostname.input // 'cdn.example.com' +``` + +上面示例中,`*`的匹配结果可以从`hostname.groups[0]`获取。 + +(5)`{}` + +特殊字符`{}`用来定义量词`?`、`+`、`+`的生效范围。 + +如果`{}`后面没有量词,那就跟没有使用的效果一样。 + +```javascript +const pattern = new URLPattern('https://example.com/{abc}'); + +pattern.test('https://example.com/') // false +pattern.test('https://example.com/abc') // true +``` + +(6)`()` + +特殊字符`()`用来定义一个组匹配,匹配结果可以按照出现顺序的编号,从`pathname.groups`对象上获取。 + +```javascript +const pattern = new URLPattern("/books/(\\d+)", "https://example.com"); +pattern.exec("https://example.com/books/123").pathname.groups +// { '0': '123' } +``` + +上面示例中,`(\\d+)`是一个组匹配,因为它是第一个组匹配,所以匹配结果放在`pathname.groups`的属性`0`。 + +(7)`|` + +特殊字符`|`表示左右两侧的字符,都可以出现,即表示逻辑`OR`。 + +```javascript +let pattern = new URLPattern({ + port: "(80|443)", +}); +``` + +上面示例中,`(80|443)`表示80或者443都可以。 + +(8)`:` + +特殊字符`:`用来定义一个具名组匹配,后面跟着变量名。 + +```javascript +let pattern = new URLPattern({ + pathname: "/:path", +}); +``` + +上面示例中,`/:path`表示斜杠后面的部分,都被捕捉放入变量`path`,可以从匹配结果的`pathname.groups`上的对应属性获取。 + +```javascript +const pattern = new URLPattern({ pathname: "/books/:id" }); + +pattern.exec("https://example.com/books/123").pathname.groups +// { id: '123' } +``` + +上面示例中,`pathname.groups`返回一个对象,该对象的属性就是所有捕捉成功的组变量,上例是`id`。 + + +下面是另一个例子。 + +```javascript +const pattern = new URLPattern({ pathname: "/:product/:user/:action" }); +const result = pattern.exec({ pathname: "/store/wanderview/view" }); + +result.pathname.groups.product // 'store' +result.pathname.groups.user // 'wanderview' +result.pathname.groups.action // 'view' +result.pathname.input // '/store/wanderview/view' +``` + +上面示例中,`:product`、`:user`、`:action`的匹配结果,都可以从`pathname.groups`的对应属性上获取。 + +组匹配可以放在模式的前面。 + +```javascript +const pattern = new URLPattern( + "/books/:id(\\d+)", + "https://example.com" +); +``` + +上面示例中,组匹配`:id`后面跟着模型定义`\\d+`,模式需要放在括号里面。 + +**(9)特殊字符转义** + +如果要将特殊字符当作普通字符使用,必须在其前面加入双重反斜杠进行转义。 + +```javascript +let pattern1 = new URLPattern({ + pathname: "/a:b", +}); + +let pattern2 = new URLPattern({ + pathname: "/a\\:b", +}); +``` + +上面示例中,`a:b`表示路径以字符`a`开头,后面的部分都放入变量`b`。而`a\\:b`表示路径本身就是`a:b`就是。 + +## 实例属性 + +URLPattern 实例的属性对应`URLPattern()`模式对象参数的各个部分。 + +```javascript +const pattern = new URLPattern({ + hostname: "{*.}?example.com", +}); + +pattern.hostname // '{*.}?example.com' +pattern.protocol // '*' +pattern.username // '*' +pattern.password // '*' +pattern.port // "" +pattern.pathname // '*' +pattern.search // '*' +pattern.hash // '*' +``` + +上面示例中,`pattern`是一个实例对象,它的属性与`URLPattern()`的参数对象的属性一致。 + +注意,`search`不包括开头的`?`,`hash`不包括开头的`#`,但是`pathname`包括开头的`/`。 + +下面是另一个例子。 + +```javascript +const pattern = new URLPattern("https://cdn-*.example.com/*.jpg"); + +pattern.protocol // 'https' +pattern.hostname // 'cdn-*.example.com' +pattern.pathname // '/*.jpg' +pattern.username // '' +pattern.password // '' +pattern.search // '' +pattern.hash // '' +``` + +## 实例方法 + +### exec() + +实例的`exec()`方法,把模式用于解析参数网址,返回匹配结果。 + +`exec()`方法的参数与`new URLPattern()`是一致的。它可以是一个 URL 字符串。 + +```javascript +pattern.exec("https://store.example.com/books/123"); +``` + +如果第一个参数是相对 URL,那么需要基准 URL,作为第二个参数。 + +```javascript +pattern.exec("/foo/bar", "https://example.com/baz"); +``` + +`exec()`方法的参数,也可以是一个对象。 + +```javascript +pattern.exec({ + protocol: "https", + hostname: "store.example.com", + pathname: "/books/123", +}); +``` + +如果匹配成功,它返回一个包括匹配结果的对象。如果匹配失败,返回`null`。 + +```javascript +const pattern = new URLPattern("http{s}?://*.example.com/books/:id"); +pattern.exec("https://example.com/books/123") // null +``` + +上面示例中,匹配失败返回`null`。 + +匹配成功返回的对象,有一个`inputs`属性,包含传入`pattern.exec()`的参数数组。其他属性的值也是一个对象,该对象的`input`属性对应传入值,`groups`属性包含各个组匹配。 + +```javascript +const pattern = new URLPattern("http{s}?://*.example.com/books/:id"); +let match = pattern.exec("https://store.example.com/books/123"); + +match.inputs // ['https://store.example.com/books/123'] +match.protocol // { input: "https", groups: {} } +match.username // { input: "", groups: {} } +match.password // { input: "", groups: {} } +match.hostname // { input: "store.example.com", groups: { "0": "store" } } +match.port // { input: "", groups: {} } +match.pathname // { input: "/books/123", groups: { "id": "123" } } +match.search // { input: "", groups: {} } +match.hash // { input: "", groups: {} } +``` + +### test() + +实例的`test()`方法,用来检测参数网址是否符合当前模式。 + +它的参数跟`URLPattern()`是一样的,可以是模式字符串,也可以是模式对象。 + +```javascript +const pattern = new URLPattern({ + hostname: "example.com", + pathname: "/foo/*" + }); + +pattern.test({ + pathname: "/foo/bar", + baseURL: "https://example.com/baz", +}) // true + +pattern.test("/foo/bar", "https://example.com/baz") // true +``` + +正常情况下,它返回一个布尔值。但是,如果语法不合法,它也会抛错。 + +```javascript +pattern.test({ pathname: "/foo/bar" }, "https://example.com/baz") // 报错 +``` + From c9eee236955e8b1791a075654249378304d3029e Mon Sep 17 00:00:00 2001 From: ruanyf Date: 2024年6月26日 17:36:05 +0800 Subject: [PATCH 26/31] docs: add window.postMessage() --- chapters.yml | 1 + docs/postmessage.md | 116 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 docs/postmessage.md diff --git a/chapters.yml b/chapters.yml index c0b59cb..39dabaa 100644 --- a/chapters.yml +++ b/chapters.yml @@ -19,3 +19,4 @@ - urlsearchparams.md: URLSearchParams 对象 - websocket.md: WebSocket - web-share-api.md: Web Share API +- postmessage.md: window.postMessage() 方法 diff --git a/docs/postmessage.md b/docs/postmessage.md new file mode 100644 index 0000000..40bf764 --- /dev/null +++ b/docs/postmessage.md @@ -0,0 +1,116 @@ +# window.postMessage() 方法 + +## 简介 + +`window.postMessage()`用于浏览器不同窗口之间的通信,主要包括 iframe 嵌入窗口和新开窗口两种情况。它不要求两个窗口同源,所以有着广泛的应用。 + +`window.postMessage()`里面的`window`对象,是发送消息的目标窗口。比如,父窗口通过`window.open()`打开子窗口,那么子窗口可以通过`targetWindow = window.opener`获取父窗口。再比如,父窗口通过`iframe`嵌入了子窗口,那么子窗口可以通过`window.parent`获取父窗口。 + +## 参数和返回值 + +`window.postMessage()`方法有几种使用形式。 + +最简单的一种就是直接发送消息。 + +```javascript +window.postMessage(message) +``` + +上面写法中的`message`就是发送的消息,可以是字符串,也可以是对象。如果是对象,浏览器会自动将该对象序列化,以字符串形式发送。 + +由于`window.postMessage()`可以用于任意两个源(协议+域名+端口)之间的通信,为了减少安全隐患,可以使用第二个参数`targetOrigin`,指定目标窗口的源。 + +```javascript +window.postMessage(message, targetOrigin) +``` + +上面写法中的`targetOrigin`是一个字符串,表示目标窗口里面的网页的源(origin),比如`https://example.com`。如果对目标窗口不加限制,可以省略这个参数,或者写成`*`。一旦指定了该参数,只有目标窗口符合指定的源(协议+域名+端口),目标窗口才会接收到消息发送事件。 + +`window.postMessage()`还可以指定第三个参数,用于发送一些可传送物体(transferable object),比如 ArrayBuffer 对象。 + +```javascript +window.postMessage(message, targetOrigin, transfer) +``` + +上面写法中的`transfer`就是可传送物体。该物体一旦发送以后,所有权就转移到了目标窗口,当前窗口将无法再使用该物体。这样的设计是为了发送大量数据时,可以提高效率。 + +`targetOrigin`和`transfer`这两个参数,也可以写在一个对象里面,作为第二个参数。 + +```javascript +window.postMessage(message, { targetOrigin, transfer }) +``` + +下面是一个跟弹出窗口发消息的例子。 + +```javascript +const popup = window.open('http://example.com'); +popup.postMessage("hello there!", "http://example.com"); +``` + +`window.postMessage()`方法没有返回值。 + +## message 事件 + +当前窗口收到其他窗口发送的消息时,会发生 message 事件。通过监听该事件,可以接收对方发送的消息。 + +```javascript +window.addEventListener( + "message", + (event) => { + if (event.origin !== "http://example.com") return; + // ... + }, + false, +); +``` + +事件的监听函数,可以接收到一个 event 参数对象。该对象有如下属性。 + +- data:其他窗口发送的消息。 +- origin:发送该消息的窗口的源(协议+域名+端口)。 +- source:发送该消息的窗口对象的引用,使用该属性可以建立双向通信,下面是一个示例。 + +```javascript +window.addEventListener("message", (event) => { + if (event.origin !== "http://example.com:8080") return; + event.source.postMessage( + "hi there!", + event.origin, + ); +}); +``` + +## 实例 + +父页面是`origin1.com`,它打开了子页面`origin2.com`,并向其发送消息。 + +```javascript +function sendMessage() { + const otherWindow = window.open('https://origin2.com/origin2.html'); + const message = 'Hello from Origin 1!'; + const targetOrigin = 'https://origin2.com'; + otherWindow.postMessage(message, targetOrigin); +} +``` + +子页面`origin2.com`监听父页面发来的消息。 + +```javascript +window.addEventListener('message', receiveMessage, false); + +function receiveMessage(event) { + if (event.origin === 'https://origin1.com') { + console.log('Received message: ' + event.data); + } +} +``` + +下面是 iframe 嵌入窗口向父窗口`origin1.com`发送消息的例子。 + +```javascript +function sendMessage() { + const message = 'Hello from Child Window!'; + window.parent.postMessage(message, 'https://origin1.com'); +} +``` + From e97b52a502bdbc1cc0269b093a44c6e408ff72cb Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 7 Dec 2024 20:33:34 +0800 Subject: [PATCH 27/31] docs(headers): fixed typo --- docs/headers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/headers.md b/docs/headers.md index dd121fa..7140c6d 100644 --- a/docs/headers.md +++ b/docs/headers.md @@ -6,7 +6,7 @@ Headers 代表 HTTP 消息的数据头。 它通过`Headers()`构造方法,生成实例对象。`Request.headers`属性和`Response.headers`属性,指向的都是 Headers 实例对象。 -Headers 实例对象内部,以键值对的形式保存 HTTP 消息头,可以用`for...of`循环进行便利,比如`for (const p of myHeaders)`。新建的 Headers 实例对象,内部是空的,需要用`append()`方法添加键值对。 +Headers 实例对象内部,以键值对的形式保存 HTTP 消息头,可以用`for...of`循环进行遍历,比如`for (const p of myHeaders)`。新建的 Headers 实例对象,内部是空的,需要用`append()`方法添加键值对。 ## 构造函数 From 2354874a26a1efdad22ff473874360259a3e909f Mon Sep 17 00:00:00 2001 From: TaoZiNiGePiChi <73526234+taozinigepichi@users.noreply.github.com> Date: 2025年7月31日 16:16:16 +0800 Subject: [PATCH 28/31] docs: fix typo in fontface.md --- docs/fontface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fontface.md b/docs/fontface.md index fdb7708..a69d769 100644 --- a/docs/fontface.md +++ b/docs/fontface.md @@ -31,7 +31,7 @@ fontFace.family // "Roboto" - `FontFace.display`:字符串,指定字体加载期间如何展示,等同于 CSS 的`font-display`属性。它有五个可能的值:`auto`(由浏览器决定)、`block`(字体加载期间,前3秒会显示不出内容,然后只要还没完成加载,就一直显示后备字体)、`fallback`(前100毫秒显示不出内容,后3秒显示后备字体,然后只要字体还没完成加载,就一直显示后备字体)、`optional`(前100毫秒显示不出内容,然后只要字体还没有完成加载,就一直显示后备字体),`swap`(只要字体没有完成加载,就一直显示后备字体)。 - `FontFace.style`:字符串,等同于 CSS 的`font-style`属性。 - `FontFace.weight`:字符串,等同于 CSS 的`font-weight`属性。 -- `FontFace.stretch`:字符串,等同于 CSS 的`font-strentch`属性。 +- `FontFace.stretch`:字符串,等同于 CSS 的`font-stretch`属性。 - `FontFace.unicodeRange`:字符串,等同于`descriptors`对象的同名属性。 - `FontFace.variant`:字符串,等同于`descriptors`对象的同名属性。 - `FontFace.featureSettings`:字符串,等同于`descriptors`对象的同名属性。 From 0d4855b60b06a81faa1733ce96ccb0f9d60dd3a5 Mon Sep 17 00:00:00 2001 From: TaoZiNiGePiChi <73526234+taozinigepichi@users.noreply.github.com> Date: 2025年8月21日 17:38:51 +0800 Subject: [PATCH 29/31] docs: fix typo in formdata.md --- docs/formdata.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/formdata.md b/docs/formdata.md index 9ede3f0..41aa211 100644 --- a/docs/formdata.md +++ b/docs/formdata.md @@ -194,7 +194,7 @@ FormData.has(name) ### keys() -`key()`返回一个键名的迭代器,用于遍历所有键名。 +`keys()`返回一个键名的迭代器,用于遍历所有键名。 ```javascript FormData.keys() From ad6ccbccd540d0cb6766653326737af873deceb3 Mon Sep 17 00:00:00 2001 From: uphg <49513387+uphg@users.noreply.github.com> Date: 2025年10月13日 13:41:04 +0800 Subject: [PATCH 30/31] docs: fix code block name in urlsearchparams.md --- docs/urlsearchparams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/urlsearchparams.md b/docs/urlsearchparams.md index 44c8000..59163c1 100644 --- a/docs/urlsearchparams.md +++ b/docs/urlsearchparams.md @@ -259,7 +259,7 @@ for (const value of params.values()) { 这个方法也可以用来将所有键值,转成一个数组。 -```javascritp +```javascript Array.from(params.values()) // ['value1', 'value2'] ``` From 79180175f3db83bfd51708b9f1669fb089883771 Mon Sep 17 00:00:00 2001 From: uphg <49513387+uphg@users.noreply.github.com> Date: 2025年10月13日 14:01:43 +0800 Subject: [PATCH 31/31] docs: fix code block name in url.md --- docs/url.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/url.md b/docs/url.md index 1cba9dc..cde7abf 100644 --- a/docs/url.md +++ b/docs/url.md @@ -175,7 +175,7 @@ function handleFiles(files) { `URL()`构造函数解析非法网址时,会抛出错误,必须用`try...catch`代码块处理,这样终究不是非常方便。因此,URL 对象又引入了`URL.canParse()`方法,它返回一个布尔值,表示当前字符串是否为有效网址。 -```javascipt +```javascript URL.canParse(url) URL.canParse(url, base) ```

AltStyle によって変換されたページ (->オリジナル) /