diff --git a/.github/workflows/wangdoc.yml b/.github/workflows/wangdoc.yml index 8ba69af..5f718e6 100644 --- a/.github/workflows/wangdoc.yml +++ b/.github/workflows/wangdoc.yml @@ -10,27 +10,27 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + 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: '14' + node-version: 'latest' - name: Install dependencies run: npm install - name: Build pages run: npm run build - name: Deploy to website - uses: JamesIves/github-pages-deploy-action@3.7.1 + uses: JamesIves/github-pages-deploy-action@v4 with: - GIT_CONFIG_NAME: wangdoc-bot - GIT_CONFIG_EMAIL: yifeng.ruan@gmail.com - REPOSITORY_NAME: wangdoc/website - ACCESS_TOKEN: ${{ secrets.WANGDOC_BOT_TOKEN }} - BASE_BRANCH: master - BRANCH: master # The branch the action should deploy to. - FOLDER: dist # The folder the action should deploy. - TARGET_FOLDER: dist/webapi - CLEAN: true # Automatically remove deleted files from the deploy branch - COMMIT_MESSAGE: update from Web API tutorial + git-config-name: wangdoc-bot + git-config-email: yifeng.ruan@gmail.com + repository-name: wangdoc/website + token: ${{ secrets.WANGDOC_BOT_TOKEN }} + branch: master # The branch the action should deploy to. + folder: dist # The folder the action should deploy. + target-folder: dist/webapi + clean: true # Automatically remove deleted files from the deploy branch + commit-message: update from WebAPI tutorial + diff --git a/README.md b/README.md index b08393e..af6b8b9 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -Web API 教程,提供各种浏览器 API 文档,正在建设中。 +Web API 文档,记录浏览器原生的各种 API 对象,正在建设中。 diff --git a/chapters.yml b/chapters.yml index fd36abe..39dabaa 100644 --- a/chapters.yml +++ b/chapters.yml @@ -2,12 +2,21 @@ - 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 - 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 +- response.md: Response API - 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 +- postmessage.md: window.postMessage() 方法 diff --git a/docs/canvas.md b/docs/canvas.md index 3594216..9c211eb 100644 --- a/docs/canvas.md +++ b/docs/canvas.md @@ -315,9 +315,10 @@ var canvas = document.getElementById('myCanvas'); var ctx = canvas.getContext('2d'); var text1 = ctx.measureText('Hello world'); -text.width // 49.46 +text1.width // 49.46 ctx.font = 'Bold 20px Arial'; +var text2 = ctx.measureText('Hello world'); text2.width // 107.78 ``` @@ -790,7 +791,7 @@ function blobToImg(blob) { var newImg = document.createElement('img'); var url = URL.createObjectURL(blob); - newImg.onload = functio () { + newImg.onload = function () { // 使用完毕,释放 URL 对象 URL.revokeObjectURL(url); }; diff --git a/docs/fetch.md b/docs/fetch.md index 740c2fc..d1a9be0 100644 --- a/docs/fetch.md +++ b/docs/fetch.md @@ -6,15 +6,15 @@ ## 基本用法 -`fetch()`的功能与 XMLHttpRequest 基本相同,但有三个主要的差异。 +`fetch()`的功能与 XMLHttpRequest 基本相同,都是向服务器发出 HTTP 请求,但有三个主要的差异。 (1)`fetch()`使用 Promise,不使用回调函数,因此大大简化了写法,写起来更简洁。 (2)`fetch()`采用模块化设计,API 分散在多个对象上(Response 对象、Request 对象、Headers 对象),更合理一些;相比之下,XMLHttpRequest 的 API 设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。 -(3)`fetch()`通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来。 +(3)`fetch()`通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHttpRequest 对象不支持数据流,所有的数据全部放在缓存里,不支持分块读取,必须等待全部获取后,再一次性读取。 -在用法上,`fetch()`接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象。它的基本用法如下。 +用法上,`fetch()`接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象。它的基本用法如下。 ```javascript fetch(url) @@ -31,7 +31,7 @@ fetch('https://api.github.com/users/ruanyf') .catch(err => console.log('Request Failed', err)); ``` -上面示例中,`fetch()`接收到的`response`是一个 [Stream 对象](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API),`response.json()`是一个异步操作,取出所有内容,并将其转为 JSON 对象。 +上面示例中,`fetch()`接收到的`response`是一个 [Stream 对象](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API),里面的数据本例是 JSON 数据,所以使用`response.json()`方法,将其转为 JSON 对象。它是一个异步操作,返回一个 Promise 对象。 Promise 可以使用 await 语法改写,使得语义更清晰。 @@ -49,7 +49,7 @@ async function getJSON() { 上面示例中,`await`语句必须放在`try...catch`里面,这样才能捕捉异步操作中可能发生的错误。 -后文都采用`await`的写法,不使用`.then()`的写法。 +后文都采用`await`的写法,不再使用`.then()`的写法。 ## Response 对象:处理 HTTP 回应 @@ -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,并且只能使用有限的几个简单标头,不能添加跨域的复杂标头,相当于提交表单、` +``` + +浏览器向服务器发送表单数据时,不管是用户点击 Submit 按钮发送,还是使用脚本发送,都会自动将其编码,并以`Content-Type: multipart/form-data`的形式发送。 + +`FormData()`还有第三种用法,如果想把"提交"(Submit)按钮也加入表单的键值对,可以把按钮的 DOM 节点当作`FormData()`的第二个参数。 + +```javascript +new FormData(form, submitter) +``` + +上面代码中,`submitter`就是提交按钮的 DOM 节点。这种用法适合表单有多个提交按钮,服务端需要通过按钮的值来判断,用户到底选用了哪个按钮。 + +```javascript +// 表单有两个提交按钮 +// +// + +const form = document.getElementById("form"); +const submitter = document.querySelector("button[value=save]"); + +const formData = new FormData(form, submitter); +``` + +上面示例中,`FormData()`加入了第二个参数,实例对象`formData`就会增加一个键值对,键名为`intent`,键值为`save`。 + +## 实例方法 + +### append() + +`append()`用于添加一个键值对,即添加一个表单元素。它有两种使用形式。 + +```javascript +FormData.append(name, value) +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() + +`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); +``` + +下面是使用`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()`用于获取指定键名的键值,它的参数为键名。 + +```javascript +FormData.get(name) +``` + +如果该键名有多个键值,只返回第一个键值。如果找不到指定键名,则返回`null`。 + +### getAll() + +`getAll()`用于获取指定键名的所有键值,它的参数为键名,返回值为一个数组。如果找不到指定键名,则返回一个空数组。 + +```javascript +FormData.getAll(name) +``` + +### has() + +`has()`返回一个布尔值,表示是否存在指定键名,它的参数为键名。 + +```javascript +FormData.has(name) +``` + +### keys() + +`keys()`返回一个键名的迭代器,用于遍历所有键名。 + +```javascript +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, blob, fileName); +``` + +它的第一个参数为键名,第二个参数为键值。上面第二种形式为上传文件,第二个参数`blob`为文件的二进制内容,第三个参数`fileName`为文件名。该方法没有返回值。 + +如果指定键名不存在,它会添加该键名,否则它会丢弃所有现有的键值,确保一个键名只有一个键值。这是它跟`append()`的主要区别。 + +### values() + +`values()`返回一个键值的迭代器,用于遍历所有键值。 + +```javascript +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 +``` + diff --git a/docs/headers.md b/docs/headers.md new file mode 100644 index 0000000..7140c6d --- /dev/null +++ b/docs/headers.md @@ -0,0 +1,228 @@ +# Headers 对象 + +## 简介 + +Headers 代表 HTTP 消息的数据头。 + +它通过`Headers()`构造方法,生成实例对象。`Request.headers`属性和`Response.headers`属性,指向的都是 Headers 实例对象。 + +Headers 实例对象内部,以键值对的形式保存 HTTP 消息头,可以用`for...of`循环进行遍历,比如`for (const p of myHeaders)`。新建的 Headers 实例对象,内部是空的,需要用`append()`方法添加键值对。 + +## 构造函数 + +`Headers()`构造函数用来新建 Headers 实例对象。 + +```javascript +const myHeaders = new Headers(); +``` + +它可以接受一个表示 HTTP 数据头的对象,或者另一个 Headers 实例对象,作为参数。 + +```javascript +const httpHeaders = { + "Content-Type": "image/jpeg", + "X-My-Custom-Header": "Zeke are cool", +}; +const myHeaders = new Headers(httpHeaders); +``` + +最后,它还可以接受一个键值对数组,作为参数。 + +```javascript +const headers = [ + ["Set-Cookie", "greeting=hello"], + ["Set-Cookie", "name=world"], +]; +const myHeaders = new Headers(headers); +``` + +## 实例方法 + +### append() + +`append()`方法用来添加字段。如果字段已经存在,它会将新的值添加到原有值的末端。 + +它接受两个参数,第一个是字段名,第二个是字段值。它没有返回值。 + +```javascript +append(name, value) +``` + +下面是用法示例。 + +```javascript +const myHeaders = new Headers(); +myHeaders.append("Content-Type", "image/jpeg"); +``` + +下面是同名字段已经存在的情况。 + +```javascript +myHeaders.append("Accept-Encoding", "deflate"); +myHeaders.append("Accept-Encoding", "gzip"); +myHeaders.get("Accept-Encoding"); // 'deflate, gzip' +``` + +上面示例中,`Accept-Encoding`字段已经存在,所以`append()`会将新的值添加到原有值的末尾。 + +### delete() + +`delete()`用来删除一个键值对,参数`name`指定删除的字段名。 + +```javascript +delete(name) +``` + +如果参数`name`不是合法的字段名,或者是不可删除的字段,上面的命令会抛错。 + +下面是用法示例。 + +```javascript +const myHeaders = new Headers(); +myHeaders.append("Content-Type", "image/jpeg"); +myHeaders.delete("Content-Type"); +``` + +### entries() + +`entries()`方法用来遍历所有键值对,返回一个 iterator 指针,供`for...of`循环使用。 + +```javascript +const myHeaders = new Headers(); +myHeaders.append("Content-Type", "text/xml"); +myHeaders.append("Vary", "Accept-Language"); + +for (const pair of myHeaders.entries()) { + console.log(`${pair[0]}: ${pair[1]}`); +} +``` + +### forEach() + +`forEach()`方法用来遍历所有键值对,对每个指定键值对执行一个指定函数。 + +它的第一个参数是回调函数`callbackFn`,第二个参数`thisArg`是`callbackFn`所用的 this 对象。 + +```javascript +forEach(callbackFn) +forEach(callbackFn, thisArg) +``` + +回调函数`callback`会接受到以下参数。 + +- value:当前的字段值。 +- key:当前的字段名。 +- object:当前正在执行的 Headers 对象。 + +下面是用法示例。 + +```javascript +const myHeaders = new Headers(); +myHeaders.append("Content-Type", "application/json"); +myHeaders.append("Cookie", "This is a demo cookie"); +myHeaders.append("compression", "gzip"); + +myHeaders.forEach((value, key) => { + console.log(`${key} ==> ${value}`); +}); +``` + +### get() + +`get()`方法用于取出指定字段的字段值,它的参数就是字段名。如果字段名不合法(比如包含中文字符),它会抛错;如果字段在当前 Headers 对象不存在,它返回`null`。 + +```javascript +get(name) +``` + +下面是用法示例。 + +```javascript +myHeaders.append("Content-Type", "image/jpeg"); +myHeaders.get("Content-Type"); // "image/jpeg" +``` + +如果当前字段有多个值,`get()`会返回所有值。 + +### getSetCookie() + +`getSetCookie()`返回一个数组,包含所有`Set-Cookie`设定的 Cookie 值。 + +```javascript +const headers = new Headers({ + "Set-Cookie": "name1=value1", +}); + +headers.append("Set-Cookie", "name2=value2"); + +headers.getSetCookie(); +// ["name1=value1", "name2=value2"] +``` + +### has() + +`has()`返回一个布尔值,表示 Headers 对象是否包含指定字段。 + +```javascript +has(name) +``` + +如果参数`name`不是有效的 HTTP 数据头的字段名,该方法会报错。 + +下面是用法示例。 + +```javascript +myHeaders.append("Content-Type", "image/jpeg"); +myHeaders.has("Content-Type"); // true +myHeaders.has("Accept-Encoding"); // false +``` + +### keys() + +`keys()`方法用来遍历 Headers 数据头的所有字段名。它返回的是一个 iterator 对象,供`for...of`使用。 + +```javascript +const myHeaders = new Headers(); +myHeaders.append("Content-Type", "text/xml"); +myHeaders.append("Vary", "Accept-Language"); + +for (const key of myHeaders.keys()) { + console.log(key); +} +``` + +### set() + +`set()`方法用来为指定字段添加字段值。如果字段不存在,就添加该字段;如果字段已存在,就用新的值替换老的值,这是它与`append()`方法的主要区别。 + +它的第一个参数`name`是字段名,第二个参数`value`是字段值。 + +```javascript +set(name, value) +``` + +下面是用法示例。 + +```javascript +const myHeaders = new Headers(); +myHeaders.set("Accept-Encoding", "deflate"); +myHeaders.set("Accept-Encoding", "gzip"); +myHeaders.get("Accept-Encoding"); // 'gzip' +``` + +上面示例中,连续两次使用`set()`对`Accept-Encoding`赋值,第二个值会覆盖第一个值。 + +### values() + +`values()`方法用来遍历 Headers 对象的字段值。它返回一个 iterator 对象,供`for...of`使用。 + +```javascript +const myHeaders = new Headers(); +myHeaders.append("Content-Type", "text/xml"); +myHeaders.append("Vary", "Accept-Language"); + +for (const value of myHeaders.values()) { + console.log(value); +} +``` + 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`。 + 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'); +} +``` + diff --git a/docs/request.md b/docs/request.md new file mode 100644 index 0000000..afa5e27 --- /dev/null +++ b/docs/request.md @@ -0,0 +1,211 @@ +# Request API + +浏览器原生提供 Request() 构造函数,用来构造发给服务器的 HTTP 请求。它生成的 Response 实例,可以作为`fetch()`的参数。 + +注意,构造一个 Request 对象,只是构造出一个数据结构,本身并不会发出 HTTP 请求,只有将它传入`fetch()`方法才会真的发出请求。 + +## 构造方法 + +Request 作为构造函数的语法如下,返回一个 Request 实例对象。 + +```javascript +new Request(url: String, [init: Object]): Request +``` + +它的第一个参数是请求的 URL 字符串,第二个参数是一个可选的配置对象,用来构造 HTTP 请求,该对象的类型描述如下。 + +```javascript +{ + body: Object + cache: String + credentials: String + headers: Object + integrity: String + keepalive: Boolean + method: String + mode: String + redirect: String + referrer: String + referrerPolicy: String + requestMode: String + requestCredentials: String + signal: AbortSignal +} +``` + +第二个参数配置对象的各个属性的含义如下。 + +- `body`:HTTP 请求的数据体,必须是 Blob、BufferSource、FormData、String、URLSearchParams 类型之一。 +- `cache`:请求的缓存模式。 +- `credentials`:请求所用的凭证,可以设为 omit、same-origini、include。Chrome 47 之前,默认值为 same-origin;Chrome 47 之后,默认值为 include。 +- `headers`:一个代表 HTTP 请求数据头的对象,类型为 Headers 对象实例。 +- `integrity`:请求的资源的资源完整度验证值,比如`sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=`。 +- `method`:HTTP 方法,一般为`GET`、`POST`、`DELETE`,默认是`GET`。 +- `mode`:请求模式,比如 cors、no-cors、navigate,默认为 cors。 +- `redirect`:请求所用的模式,可以设为 error、follow、manual,默认为 follow。 +- `referrer`:请求的来源,默认为 about:client。 + +下面是两个示例。 + +```javascript +// 示例一 +const request = new Request('flowers.jpg'); + +// 示例二 +const myInit = { + method: "GET", + headers: { + "Content-Type": "image/jpeg", + }, + mode: "cors", + cache: "default", +}; + +const request = new Request('flowers.jpg', myInit); +``` + +`Request()`还有另一种语法,第一个参数是另一个 Request 对象,第二个参数还是一个配置对象。它返回一个新的 Request 对象,相当于对第一个参数 Request 对象进行修改。 + +```javascript +new Request(request: Request, [init: Object]): Request +``` + +## 实例属性 + +Request 实例对象的属性,大部分就是它的构造函数第二个参数配置对象的属性。 + +(1)`body` + +`body`属性返回 HTTP 请求的数据体,它的值是一个 ReadableStream 对象或 null(`GET`或`HEAD`请求时没有数据体)。 + +```javascript +const request = new Request('/myEndpoint', { + method: "POST", + body: "Hello world", +}); + +request.body; // ReadableStream 对象 +``` + +注意,Firefox 不支持该属性。 + +(2)`bodyused` + +`bodyUsed`属性是一个布尔值,表示`body`是否已经被读取了。 + +(3)`cache` + +`cache`属性是一个只读字符串,表示请求的缓存模式,可能的值有 default、force-cache、no-cache、no-store、only-if-cached、reload。 + +(4)`credentials` + +`credentials`属性是一个只读字符串,表示跨域请求时是否携带其他域的 cookie。可能的值有 omit(不携带)、 include(携带)、same-origin(只携带同源 cookie)。 + +(5)`destination` + +`destination`属性是一个字符串,表示请求内容的类型,可能的值有 ''、'audio'、'audioworklet'、'document'、'embed'、'font'、'frame'、'iframe'、'image'、'manifest'、'object'、'paintworklet'、 'report'、'script'、'sharedworker'、'style'、'track'、'video'、'worker'、'xslt' 等。 + +(6)`headers` + +`headers`属性是一个只读的 Headers 实例对象,表示请求的数据头。 + +(7)`integrity` + +`integrity`属性表示所请求资源的完整度的验证值。 + +(8)`method` + +`method`属性是一个只读字符串,表示请求的方法(GET、POST 等)。 + +(9)`mode` + +`mode`属性是一个只读字符串,用来验证是否可以有效地发出跨域请求,可能的值有 same-origin、no-cors、cors。 + +(10)`redirect` + +`redirect`属性是一个只读字符串,表示重定向时的处理模式,可能的值有 follow、error、manual。 + +(11)`referrer` + +`referrer`属性是一个只读字符串,表示请求的引荐 URL。 + +(12)`referrerPolicy` + +`referrerPolicy`属性是一个只读字符串,决定了`referrer`属性是否要包含在请求里面的处理政策。 + +(13)`signal` + +`signal`是一个只读属性,包含与当前请求相对应的中断信号 AbortSignal 对象。 + +(14)`url` + +`url`是一个只读字符串,包含了当前请求的字符串。 + +```javascript +const myRequest = new Request('flowers.jpg'); +const myURL = myRequest.url; +``` + +## 实例方法 + +### 取出数据体的方法 + +- arrayBuffer():返回一个 Promise 对象,将 Request 的数据体作为 ArrayBuffer 对象返回。 +- blob():返回一个 Promise 对象,将 Request 的数据体作为 Blob 对象返回。 +- json():返回一个 Promise 对象,将 Request 的数据体作为 JSON 对象返回。 +- text():返回一个 Promise 对象,将 Request 的数据体作为字符串返回。 +- formData():返回一个 Promise 对象,将 Request 的数据体作为表单数据 FormData 对象返回。 + +下面是`json()`方法的一个示例。 + +```javascript +const obj = { hello: "world" }; + +const request = new Request("/myEndpoint", { + method: "POST", + body: JSON.stringify(obj), +}); + +request.json().then((data) => { + // 处理 JSON 数据 +}); +``` + +`.formData()`方法返回一个 Promise 对象,最终得到的是一个 FormData 表单对象,里面是用键值对表示的各种表单元素。该方法很少使用,因为需要拦截发给服务器的请求的场景不多,一般用在 Service Worker 拦截和处理网络请求,以修改表单数据,然后再发送到服务器。 + +```javascript +self.addEventListener('fetch', event => { + // 拦截表单提交请求 + if ( + event.request.method === 'POST' && + event.request.headers.get('Content-Type') === 'application/x-www-form-urlencoded' + ) { + event.respondWith(handleFormSubmission(event.request)); + } +}); + +async function handleFormSubmission(request) { + const formData = await request.formData(); + formData.append('extra-field', 'extra-value'); + + const newRequest = new Request(request.url, { + method: request.method, + headers: request.headers, + body: new URLSearchParams(formData) + }); + + return fetch(newRequest); +} +``` + +上面示例中,Service Worker 拦截表单请求以后,添加了一个表单成员,再调用`fetch()`向服务器发出修改后的请求。 + +### clone() + +`clone()`用来复制 HTTP 请求对象。 + +```javascript +const myRequest = new Request('flowers.jpg'); +const newRequest = myRequest.clone(); +``` + diff --git a/docs/response.md b/docs/response.md new file mode 100644 index 0000000..aa975ea --- /dev/null +++ b/docs/response.md @@ -0,0 +1,263 @@ +# Response API + +浏览器原生提供`Response()`构造函数,用来构造服务器响应。 + +`fetch()`方法返回的就是一个 Response 对象。 + +## 构造方法 + +`Response()`作为构造方法调用时,返回 Response 实例。 + +```javascript +// 定义 +new Response([body:Object, [init : Object]]): Response + +// 用法 +new Response() +new Response(body) +new Response(body, options) +``` + +它带有两个参数,都是可选的。 + +第一个参数`body`代表服务器返回的数据体,必须是下面类型之一:ArrayBuffer、ArrayBufferView、Blob、FormData、ReadableStream、String、URLSearchParams。 + +第二个参数`init`是一个对象,代表服务器返回的数据头,类型描述如下。 + +```javascript +{ + status: Number + statusText: String + headers: Object +} +``` + +下面是一个例子。 + +```javascript +const myBlob = new Blob(); +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 + +`body`属性代表数据体,是一个只读的 ReadableStream 对象。 + +```javascript +const res = await fetch('/fireworks.ogv'); +const reader = res.body.getReader(); + +let result; +while (!(result = await reader.read()).done) { + console.log('chunk size:', result.value.byteLength); +} +``` + +上面示例中,先建立一个 body 的读取器,然后每次读取一段数据,输出这段数据的字段长度。 + +注意,`body`是一个 Stream 对象,只能读取一次。取出所有数据以后,第二次就读不到了。 + +`bodyUsed`属性是一个只读的布尔值,表示`body`属性是否已经读取。 + +### headers + +`headers`属性代表服务器返回的数据头,是一个只读的 Headers 对象。 + +```javascript +const res = await fetch('/flowers.jpg'); +console.log(...res.headers); +``` + +上面示例中,发出请求后,展开打印`res.headers`属性,即服务器回应的所有消息头。 + +### ok + +`ok`属性是一个布尔值,表示服务器返回的状态码是否成功(200到299),该属性只读。 + +```javascript +const res1 = await fetch('https://httpbin.org/status/200'); +console.log(res1.ok); // true + +const res2 = await fetch('https://httpbin.org/status/404'); +console.log(res2.ok); // false +``` + +### redirected + +`redirected`是一个布尔值,表示服务器返回的状态码是否跳转类型(301,302等),该属性只读。 + +```javascript +const res1 = await fetch('https://httpbin.org/status/200'); +console.log(res1.redirected); // false + +const res2 = await fetch('https://httpbin.org/status/301'); +console.log(res2.redirected); // true +``` + +### status,statusText + +`status`属性是一个数值,代表服务器返回的状态码,该属性只读。 + +```javascript +const res1 = await fetch('https://httpbin.org/status/200'); +console.log(res1.status); // 200 + +const res2 = await fetch('https://httpbin.org/status/404'); +console.log(res2.status); // 404 +``` + +`statusText`属性是一个字符串,代表服务器返回的状态码的文字描述。比如,状态码200的`statusText`一般是`OK`,也可能为空。 + +### type + +`type`属性是一个只读字符串,表示服务器回应的类型,它的值有下面几种:basic、cors、default、error、opaque、opaqueredirect。 + +### url + +`url`属性是一个字符串,代表服务器路径,该属性只读。如果请求是重定向的,该属性就是重定向后的 URL。 + +## 实例方法 + +### 数据读取 + +以下方法可以获取服务器响应的消息体,根据返回数据的不同类型,调用相应方法。 + +- .json():返回一个 Promise 对象,最终得到一个解析后的 JSON 对象。 +- .text():返回一个 Promise 对象,最终得到一个字符串。 +- .blob():返回一个 Promise 对象,最终得到一个二进制 Blob 对象,代表某个文件整体的原始数据。 +- .arrayBuffer():返回一个 Promise 对象,最终得到一个 ArrayBuffer 对象,代表一段固定长度的二进制数据。 +- .formData():返回一个 Promise 对象,最终得到一个 FormData 对象,里面是键值对形式的表单提交数据。 + +下面是从服务器获取 JSON 数据的一个例子,使用`.json()`方法,其他几个方法的用法都大同小异。 + +```javascript +async function getRedditPosts() { + try { + const response = await fetch('https://www.reddit.com/r/all/top.json?limit=10'); + const data = await response.json(); + const posts = data.data.children.map(child => child.data); + console.log(posts.map(post => post.title)); + } catch (error) { + console.error(error); + } +} +``` + +下面是从服务器获取二进制文件的例子,使用`.blob()`方法。 + +```javascript +async function displayImageAsync() { + try { + const response = await fetch('https://www.example.com/image.jpg'); + const blob = await response.blob(); + const url = URL.createObjectURL(blob); + const img = document.createElement('img'); + img.src = url; + document.body.appendChild(img); + } catch (error) { + console.error(error); + } +} +``` + +下面是从服务器获取音频文件,直接解压播放的例子,使用`.arrayBuffer()`方法。 + +```javascript +async function playAudioAsync() { + try { + const response = await fetch('https://www.example.com/audio.mp3'); + const arrayBuffer = await response.arrayBuffer(); + const audioBuffer = await new AudioContext().decodeAudioData(arrayBuffer); + const source = new AudioBufferSourceNode(new AudioContext(), { buffer: audioBuffer }); + source.connect(new AudioContext().destination); + source.start(0); + } catch (error) { + console.error(error); + } +} +``` + +### clone() + +`clone()`方法用来复制 Response 对象。 + +```javascript +const res1 = await fetch('/flowers.jpg'); +const res2 = res1.clone(); +``` + +复制以后,读取一个对象的数据,不会影响到另一个对象。 + +## 静态方法 + +### Response.json() + +`Response.json()`返回一个 Response 实例,该实例对象的数据体就是作为参数的 JSON 数据,数据头的`Content-Type`字段自动设为`application/json`。 + +```javascript +Response.json(data) +Response.json(data, options) +``` + +`Response.json()`基本上就是`Response()`构造函数的变体。 + +下面是示例。 + +```javascript +const jsonResponse1 = Response.json({ my: "data" }); + +const jsonResponse2 = Response.json( + { some: "data", more: "information" }, + { status: 307, statusText: "Temporary Redirect" }, +); +``` + +### Response.error() + +`Response.error()`用来构造一个表示报错的服务器回应,主要用在 Service worker,表示拒绝发送。 + +```javascript +self.addEventListener('fetch', (event) => { + const url = new URL(event.request.url); + if (url.pathname === '/flowers.jpg') { + event.respondWith(Response.error()); + } +}); +``` + +### Response.redirect() + +`Response.redirect()`用来构造一个表示跳转的服务器回应,主要用在 Service worker,表示跳转到其他网址。 + +```javascript +Response.redirect(url) +Response.redirect(url, status) +``` + +这个方法的第一个参数`url`是所要跳转的目标网址,第二个参数是状态码,一般是301或302(默认值)。 + +```javascript +Response.redirect("https://www.example.com", 302); +``` + 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 只对根目录`/`生效,如果要改变生效范围,可以运行下面的代码。 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. diff --git a/docs/url.md b/docs/url.md new file mode 100644 index 0000000..cde7abf --- /dev/null +++ b/docs/url.md @@ -0,0 +1,239 @@ +# URL 对象 + +浏览器内置的 URL 对象,代表一个网址。通过这个对象,就能生成和操作网址。 + +## 构造函数 + +URL 可以当作构造函数使用,生成一个实例对象。 + +它接受一个网址字符串作为参数。 + +```javascript +let url = new URL('https://example.com'); +``` + +如果网址字符串无法解析,它会报错,所以它要放在`try...catch`代码块里面。 + +如果这个参数只是一个网站路径,比如`/foo/index.html`,那么需要提供基准网址,作为第二个参数。 + +```javascript +const url1 = new URL('page2.html', 'http://example.com/page1.html'); +url1.href // "http://example.com/page2.html" + +const url2 = new URL('..', 'http://example.com/a/b.html') +url2.href // "http://example.com/" +``` + +这种写法很方便基于现有网址,构造新的 URL。 + +`URL()`的参数也可以是另一个 URL 实例。这时,`URL()`会自动读取该实例的href属性,作为实际参数。 + +## 实例属性 + +一旦得到了 URL 实例对象,就可以从它的各种属性,方便地获取 URL 的各个组成部分。 + +- href:完整的网址 +- protocol:访问协议,带结尾冒号`:`。 +- search:查询字符串,以问号`?`开头。 +- hash:哈希字符串,以`#`开头。 +- username:需要认证的网址的用户名。 +- password:需要认证的网址的密码。 +- host:主机名,不带协议,但带有端口。 +- hostname:主机名,不带协议和端口。 +- port:端口。 +- origin:包括协议、域名和端口。 +- pathname:服务器路径,以根路径`/`开头,不带有查询字符串。 +- searchParams:指向一个 URLSearchParams 实例,方便用来构造和操作查询字符串。 + +下面是用法示例。 + +```javascript +const url = new URL('http://user:pass@example.com:8080/resource/path?q=1#hash'); + +url.href // http://user:pass@example.com:8080/resource/path?q=1#hash +url.protocol // http: +url.username // user +url.password // pass +url.host // example.com:8080 +url.hostname // example.com +url.port // 8080 +url.pathname // /resource/path +url.search // ?q=1 +url.hash // #hash +url.origin // http://example.com:8080 +``` + +这些属性里面,只有`origin`属性是只读的,其他属性都可写,并且会立即生效。 + +```javascript +const url = new URL('http://example.com/index.html#part1'); + +url.pathname = 'index2.html'; +url.href // "http://example.com/index2.html#part1" + +url.hash = '#part2'; +url.href // "http://example.com/index2.html#part2" +``` + +上面示例中,改变 URL 实例的`pathname`属性和`hash`属性,都会实时反映在 URL 实例当中。 + +下面是`searchParams`属性的用法示例,它的具体属性和方法介绍参见 《URLSearchParams》一章。 + +```javascript +const url = new URL('http://example.com/path?a=1&b=2'); + +url.searchParams.get('a') // 1 +url.searchParams.get('b') // 2 + +for (const [k, v] of url.searchParams) { + console.log(k, v); +} +// a 1 +// b 2 +``` + +## 静态方法 + +### URL.createObjectURL() + +`URL.createObjectURL()`方法用来为文件数据生成一个临时网址(URL 字符串),供那些需要网址作为参数的方法使用。该方法的参数必须是 Blob 类型(即代表文件的二进制数据)。 + +```javascript +// HTML 代码如下 +//
+// +const div = document.getElementById('display'); + +function handleFiles(files) { + for (let i = 0; i < files.length; i++) { + let img = document.createElement('img'); + img.src = window.URL.createObjectURL(files[i]); + div.appendChild(img); + } +} +``` + +上面示例中,`URL.createObjectURL()`方法用来为上传的文件生成一个临时网址,作为``元素的图片来源。 + +该方法生成的 URL 就像下面的样子。 + +```javascript +blob:http://localhost/c745ef73-ece9-46da-8f66-ebes574789b1 +``` + +注意,每次使用`URL.createObjectURL()`方法,都会在内存里面生成一个 URL 实例。如果不再需要该方法生成的临时网址,为了节省内存,可以使用`URL.revokeObjectURL()`方法释放这个实例。 + +下面是生成 Worker 进程的一个示例。 + +```html + + +``` + +### 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()`方法释放资源。 + +### URL.canParse() + +`URL()`构造函数解析非法网址时,会抛出错误,必须用`try...catch`代码块处理,这样终究不是非常方便。因此,URL 对象又引入了`URL.canParse()`方法,它返回一个布尔值,表示当前字符串是否为有效网址。 + +```javascript +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()`。 + +### 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() + +URL 实例对象的`toString()`返回`URL.href`属性,即整个网址。 + 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") // 报错 +``` + diff --git a/docs/urlsearchparams.md b/docs/urlsearchparams.md new file mode 100644 index 0000000..59163c1 --- /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(params1); +``` + +上面示例中,`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 +``` + +这个方法也可以用来将所有键值,转成一个数组。 + +```javascript +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 有查询字符串"); +} +``` + diff --git a/package.json b/package.json index 64e1bf1..01cf692 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,9 @@ "build-and-commit": "npm run build && npm run commit", "commit": "gh-pages --dist dist --dest dist/webapi --branch master --add --repo git@github.com:wangdoc/website.git", "chapter": "loppo chapter", + "server": "loppo server", "test": "echo \"Error: no test specified\" && exit 1" }, - "husky": { - "hooks": { - "pre-push": "npm update" - } - }, "repository": { "type": "git", "url": "https://github.com/wangdoc/webapi-tutorial.git" @@ -29,11 +25,9 @@ ], "author": "Ruan Yifeng", "license": "Creative Commons Attribution-ShareAlike License", - "devDependencies": {}, "dependencies": { - "gh-pages": "latest", - "husky": "^4.3.8", - "loppo": "latest", - "loppo-theme-wangdoc": "latest" + "gh-pages": "6.x", + "loppo": "^0.6.25", + "loppo-theme-wangdoc": "^0.7.1" } }

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