--- url: /vite-press/frontend/javascript/ES6/ES2023-ignore.md --- ## [ES2023(ES14)](https://tc39.es/ecma262/2023/) ### 数组扩展 * Array.prototype.findLast > 接收一个方法返回从尾部查找中符合条件的第一个元素。如果没有找到符合条件的返回 undefined。 ```ts // arr.findLast(fn) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.findLast((x) => x === 'pear')) // pear ``` * Array.prototype.findLastIndex > 接收一个方法返回从尾部查找中符合条件的第一个元素的索引。如果没有找到符合条件的返回 -1。 ```ts // arr.findLastIndex(fn) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.findLastIndex((x) => x === 'pear')) // 2 ``` * Array.prototype.toReversed > 将数组中的元素反转(相对应会改变数组的方法:reverse())。 ```ts // arr.toReversed() const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.toReversed()) // ['pear', 'banana', 'apple'] ``` * Array.prototype.toSorted > 将数组中的元素排序(相对应会改变数组的方法:sort())。 ```ts // arr.toSorted(fn) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.toSorted()) // ['apple', 'banana', 'pear'] ``` * Array.prototype.toSpliced > 从数组中指定位置开始删除指定数量的元素,并可选择在删除后新增新元素(相对应会改变数组的方法:splice())。 ```ts // arr.toSpliced(start, deleteCount, ...items) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.toSpliced(1, 1)) // ['apple', 'pear'] ``` * Array.prototype.with > 将数组索引处的值,替换为新值。 ```ts // arr.with(index, value) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.with(2, '')) // ['apple', 'pear', ''] ``` ### 集合扩展 * 支持 WeakMap 使用 Symbol 作为键名 ```ts const map = new WeakMap() const symbol = Symbol('symbol') map.set(symbol, 'DEMO') ``` ### 语法支持 * Hashbang 语法 > Hashbang 注释是一种特殊的注释语法,其行为与单行注释(//)完全一样,只是它以 #! 开头,并且只在脚本或模块的最开始处有效。 > 注意,#! 标志之前不能有任何空白字符。注释由 #! 之后的所有字符组成直到第一行的末尾;只允许有一条这样的注释。 ```js #!/usr/bin/env node console.log("Hello world"); ``` --- --- url: /vite-press/frontend/javascript/ajax.md description: Ajax的了解和使用 --- # Ajax `Ajax`(`Asynchronous JavaScript And XML`)异步 `JavaScript` 和 `XML`,`Ajax` 本身不是一种技术,而是一种将一些现有技术结合起来使用的方法,包括:`HTML` 或 `XHTML`、`CSS`、`JavaScript`、`DOM`、`XML`、`XSLT`、以及最重要的 `XMLHttpRequest` 对象。当使用结合了这些技术的 `Ajax` 模型以后,网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面。 :::tip 尽管 `Ajax` 中的 `X` 代表 `XML`,但是 `JSON` 才是首选,因为它更加轻量,而且是用 `JavaScript` 编写的。在 `Ajax` 模型中,`JSON` 和 `XML` 都被用来包装信息。 ::: **优点** * 浏览器默认支持(一般浏览器都是支持 `JavaScript` 的) * 提高用户体验(不需要刷新整个页面,而只需要局部刷新) * 提高页面的性能(只需要请求部分数据,所以数据量就明显下降了) **缺点** * 破坏了浏览器的前进和后退功能(`Ajax` 不会改变网页 `URL`,因此不会在浏览器记录前后页面) * 对搜索引擎的支持较弱(搜索引擎无法监测到 `JS` 引起的数据变化) ## Ajax的使用 `Ajax` 的基本流程:创建 `XHR` 对象 => 发送数据 => 接收数据 ### 基本使用 1. 创建一个 `xhr` 的实例对象 ```js const xhr = new XMLHttpRequest() ``` 2. 创建一个请求 调用 `xhr` 对象上的 `open()` 方法创建请求。`open()` 方法接收三个参数: * 第一个参数:请求的类型(例如 `get`、`post`) * 第二个参数:请求的 `URL` * 第三个参数:是否异步发送请求(默认为 `true`) ```js xhr.open('get', 'example.php', 'true') ``` 3. 发送请求 调用 `xhr` 对象上的 `send()` 方法创建请求。`send()` 方法接收一个参数: * 参数:作为请求主体发送的数据(例如 `post` 请求携带的数据) ```js xhr.send() ``` 4. 获取数据 请求发送出去后,客户端需要接收服务器响应回来的数据,`xhr` 对象中有一些属性,它们存储着服务端返回来的一些数据信息,如下表所示: | 属性名 | 含义 | |--------------|-----------------| | responseText | 服务端返回的文本信息 | | responseXML | 服务端返回的XML DOM文档 | | status | HTTP状态码 | | statusText | HTTP状态码说明 | | readyState | xhr对象的请求响应阶段 | 既然我们要获取服务端返回的数据,我们就要知道服务端是何时返回数据的,可以通过 `readyState` 属性来判断。 `readyState` 属性一共有 `5` 个值,分别表示不同的请求响应阶段: * `0`: 还未创建请求,即未调用 `open()` 方法 * `1`: 已调用 `open()` 方法,但未发送 `send()` 方法 * `2`: 已调用 `send()` 方法,但未接收到响应 * `3`: 已接收到部分响应 * `4`: 已接收到全部的响应 同时,`xhr` 对象可以绑定一个 `readystatechange` 事件,每当 `readyState` 属性发生改变,都会触发该事件,因此,该事件在一次请求中会被多次触发 ```js xhr.onreadystatechange = function () { console.log('readyState属性发生改变了') } ``` 所以,我们可以在 `readystatechange` 事件中判断一下 `readyState` 属性是否为 `4`,即是否已经接收所有的响应,然后还可以再继续判断一下 `status` 属性,看看状态码是否为 `200`,当上述都成立了,我们再去 `responseText` 属性 或 `responseXML` 属性中获取响应数据 ```js xhr.onreadystatechange = function () { // 判断是否已接收所有响应 if (xhr.readyState === 4) { // 判断状态码是否为200 if (xhr.status === 200) { console.log(xhr.responseText) } } } ``` ### get请求 `get` 请求所携带的数据是明文的,大小只有 `4k` 左右,而且它是写在 `URL` 的 `?` 后面的,例如这样 `example.php?query=4&em=0`,所以若是我们要在发送 `get` 请求时携带数据,只需要在调用 `open()` 方法时,将数据写在第二个参数的 `URL` 的 `?` 后面即可 ```js const xhr = new XMLHttpRequest() xhr.open('get', 'example.php?query=4&em=0') xhr.send() xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { console.log(xhr.responseText); } } } ``` ### post请求 发送 `post` 请求的过程几乎和 `get` 请求一样,唯一不一样的是数据的传递。大家都知道 `post` 请求的数据是放在请求体中的,因此我们需要调用 `xhr` 对象上的 `setRequestHeader()` 方法来模仿表单提交时的内容类型,`post` 请求要发送的数据就要作为 `send()` 的参数 ```js const xhr = new XMLHttpRequest() xhr.open('post', 'example.php') xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') xhr.send('query=4&em=0') xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { console.log(xhr.responseText); } } } ``` ## Ajax的封装 ### $.get ```js let $ = { // 动态生成XHR对象的方法 createXHR: function() { if(window.XMLHttpRequest) { return new XMLHttpRequest() } else { return new ActiveXObject() } }, get: function(url, data, callback, dataType) { // 避免dataType大小写的问题 let dataType = dataType.toLowerCase() // 如果有传入data,则在url后面跟上参数 if(data) { url += '?' Object.keys(data).forEach(key => url += `${key}=${data[key]}&`) url = url.slice(0, -1) } // 调用我们封装的方法生成XHR对象 let xhr = this.createXHR() // 创建get请求 xhr.open('get', url) // 发送请求 xhr.send() xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { // 若dataType为json,则将返回的数据通过JSON.parse格式化 let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText // 调用回调函数,并把参数传进去 callback(res, xhr.status, xhr) } } } }, } ``` ### $.post ```js let $ = { // 动态生成XHR对象的方法 createXHR: function() { if(window.XMLHttpRequest) { return new XMLHttpRequest() } else { return new ActiveXObject() } }, post: function(url, data, callback, dataType) { // 避免dataType大小写的问题 dataType = dataType.toLowerCase() // 调用我们封装的方法动态生成XHR对象 let xhr = this.createXHR() let str = '' // 若传入参数,则将参数序列化 if(data) { Object.keys(data).forEach(key => str += `${key}=${data[key]}&`) str = str.slice(0, -1) } // 设置头部信息 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') // 发送请求,并携带参数 xhr.send(str) xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { // 若dataType为json,则将返回的数据通过JSON.parse格式化 let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText // 调用回调函数,把对应参数传进去 callback(res, xhr.status, xhr) } } } }, } ``` ### $.ajax ```js let $ = { createXHR: function() { if(window.XMLHttpRequest) { return new XMLHttpRequest() } else { return new ActiveXObject() } }, ajax: function(params) { // 初始化参数 let type = params.type ? params.type.toLowerCase() : 'get' let isAsync = params.isAsync ? params.isAsync : 'true' let url = params.url let data = params.data ? params.data : {} let dataType = params.dataType.toLowerCase() // 用我们封装的方法动态生成XHR对象 let xhr = this.createXHR() let str = '' // 拼接字符串 Object.keys(data).forEach(key => str += `${key}=${data[key]}&`) str = str.slice(0, -1) // 如果是get请求就把携带参数拼接到url后面 if(type === 'get') url += `?${str}`; // 返回promise对象,便于外部then和catch函数调用 return new Promise((resolve, reject) => { // 创建请求 xhr.open(type, url, isAsync) if(type === 'post') { xhr.setRequestHeader('Content-Type', 'application/x-www-form-rulencoded') xhr.send(str) } else { xhr.send() } xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText resolve(res) // 请求成功,返回数据 } else { reject(xhr.status) // 请求失败,返回状态码 } } } }) }, } ``` --- --- url: /vite-press/frontend/css/bfc.md description: 块级格式化上下文 --- # {{ $frontmatter.title }} `BFC`(Block Formatting Context,块级格式化上下文)是 `CSS` 渲染过程中的一个独立布局环境。它规定了内部块级元素的排列规则,并**隔离外部布局干扰** ,形成一个封闭的容器。其核心定义与特性如下: 1. 独立渲染区域:BFC 内部的元素布局不受外部影响,反之亦然(如浮动、外边距重叠等问题被限制在 BFC 内部)。 2. 布局规则: * 内部块级元素(Block-level box)在垂直方向依次排列。 * 相邻元素的垂直外边距(margin)会发生重叠(属于同一 BFC 时)。 * 每个元素的左外边距与包含块的左边框接触(从左到右布局时)。 3. 隔离性:BFC 的子元素不会影响外部布局,例如浮动元素不会溢出到 BFC 外部。 > **示例场景**:若父元素未触发 BFC,其内部的浮动子元素会导致高度塌陷(父元素高度为0);触发 BFC 后,父元素会自动包裹浮动子元素。 ## 触发条件 满足以下任一 CSS 属性即可触发 BFC: | 触发条件 | 属性值示例 | 说明 | |---------|----------------------------------------------------------------------|-------------------------------------------| | 浮动元素 | `float: left/right` | 元素脱离文档流,但触发 BFC 后其内部布局独立。 | | 溢出属性 | `overflow: hidden/scroll/auto` | `overflow: visible` 无效;常用 `hidden` 且副作用小。 | | 定位属性 | `position: absolute/fixed` | `position: static/relative` 无效。 | | 特殊显示模式 | `display: inline-block/table-cell/table-caption/flex/grid/flow-root` | `block/inline` 无效。 | | 根元素 | `` | 整个页面默认是 BFC。 | | CSS 新属性 | `display: flow-root` | 专为触发 BFC 设计,无副作用(旧浏览器可能不兼容)。 | > **关键说明**: > > * 同一元素可能因多个条件触发 BFC,但需注意不同属性的**副作用**(如 `float` 会改变布局模式)。 > * `display: flow-root` 是推荐方式,避免 `overflow: hidden` 裁剪内容或 `float` 破坏流式布局。 ## 核心应用场景 1. 清除浮动与解决高度塌陷 * 问题:父元素包含浮动子元素时,高度塌陷(子元素脱离文档流)。 * 解决:父元素触发 BFC(如`overflow: hidden`),自动计算浮动子元素高度。 ```css .parent { overflow: hidden; } /* 触发 BFC */ ``` 2. 避免外边距重叠(Margin Collapse) * 问题:同一 BFC 内相邻块级元素的垂直外边距会合并。 * 解决:为其中一个元素包裹父容器并触发 BFC,隔离外边距。 ```html
``` 3. 阻止元素被浮动覆盖 * 问题:非浮动元素与浮动元素并列时,可能被浮动元素覆盖。 * 解决:为非浮动元素触发 BFC,形成独立布局区域(如`overflow: hidden`)。 ```css .non-float { overflow: hidden; } /* 自适应避开浮动元素 */ ``` 4. 实现自适应两栏布局 * 方法:左侧浮动,右侧触发 BFC(如`overflow: hidden`),右侧宽度自适应。 ```css .left { float: left; width: 200px; } .right { overflow: hidden; } /* 触发 BFC 避免环绕 */ ``` ## 深度解析 1. 内部元素垂直排列:BFC 内所有块级元素从顶部开始连续排列,如同常规流。 2. 外边距计算隔离: * 子元素的外边距不会传递给父元素(避免父元素意外偏移)。 * 不同 BFC 的元素外边距不重叠。 3. 包含浮动元素:BFC 会强制计算浮动元素高度,解决高度塌陷。 4. 边框盒模型约束:元素的外边距区域(margin box)与包含块的边框区域(border box)对齐。 ## 实践建议与注意事项 1. 选择低副作用的触发方式: * 优先使用 `display: flow-root`(现代浏览器支持)或`overflow: hidden`。 * 避免滥用 `float` 或 `position: absolute`,以免破坏响应式布局。 2. BFC 的嵌套与层叠: * 一个元素可同时属于多个 BFC(如根 BFC 和局部 BFC)。 * 不同 BFC 之间互不影响,但内部规则独立生效。 3. 浏览器兼容性: * `display: flow-root` 在 IE11 及以下不支持,可回退到 `overflow: hidden`。 **总结**:BFC 是 CSS 布局的核心机制,通过触发独立渲染区域解决浮动、外边距重叠和元素覆盖问题。理解其触发条件与应用场景,能显著提升布局代码的健壮性。推荐结合`display: flow-root`或`overflow: hidden`实现高效且兼容性强的方案。 --- --- url: /vite-press/frontend/npm/changesets.md description: monorepo 发包工具 --- # {{ $frontmatter.title }} `changesets` 主要关心 `monorepo` 项目下子项目版本的更新、`changelog` 文件生成、包的发布。 ```shell pnpm add @changesets/cli -D ``` **特点** * 在开发时,需要开发者提供本次变更涉及的包名称、升级版本类型(`patch`、`minor`、`major`)及变更信息,即 `changeset`。 * 在发布版本时,会根据 `changeset` 自动升级对应包的版本号,并在对应的包中生成 `Changelog` 信息。 * 在 `Monorepo` 场景中,`changeset` 会自动生成仓库依赖图,升级时只会升级变更包及相关依赖包的版本号。 ## 初始化 执行 `changeset init`,在项目根目录下生成一个 `.changeset` 目录,里面会生成一个 `changeset` 的 `config` 文件 ```json { "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], "linked": [], "access": "restricted", "baseBranch": "master", "updateInternalDependencies": "patch", "ignore": [] } ``` * `commit`:为 `true` 时,在执行 `change` 和 `version` 命令时,将自动执行提交代码操作 * `fixed`:用于 `monorepo` 中对包进行分组,相同分组中的包版本号将进行绑定,每次执行 `version` 命令时,同一分组中的包只要有一个升级版本号,其他会一起升级。支持使用正则匹配包名称。 * `linked`:和 `fixed` 类似,也是对 `monorepo` 中对包进行分组,但是每次执行 `version` 命令时,只有和 `changeset` 声明的变更相关的包才会升级版本号,同一分组的变更包的版本号将保持一致。支持使用正则匹配包名称。 * `access`:如果配置为 `restricted`,将作为私有包发布,如果为 `public`,则发布公共范围包。 :::info 对于仓库中存在部分包需要配置 access,可以在 package.json 中配置 publishConfig,例如: ```json { "publishConfig": { "registry": "https://registry.npmjs.org/", "access": "public" } } ``` ::: * `baseBranch`:仓库主分支。该配置用于计算当前分支的变更包并进行分类。 * `updateInternalDependencies`:用于声明更新内部依赖的版本号规则。当执行 `version` 命令升级版本号时,默认会自动更新仓库中使用该包的依赖声明。配置该字段为 `minor` 后,如果升级版本号为 `patch` 类型,将不会自动更新引用依赖声明。 :::info e.g.,当配置 updateInternalDependencies 为 minor 时,升级 pkg-a 至 1.0.1 时,pkg-b 中的 pkg-a 的依赖版本将不会更新,只有 pkg-a 升级版本号为 1.1.0 或者 2.0.0 时,才会更新 pkg-b 中的 pkg-a 的依赖。 ::: * `changelog`:配置为 `false` 时,执行 `version` 命令时,在 `CHANGELOG.md` 文件中只声明版本号,不声明其他 `Changelog` 信息。 * `ignore`:用于声明执行 version 命令时忽略的包,和 `version` 命令的 `--ignore` 参数用法一致,注意两者不能同时使用。 ## 第一次发布 在项目根目录下直接运行下方指令为子项目发布第一个版本。发布完成后,提交代码~ ```shell pnpm run build pnpm changeset publish ``` :::tip * `@xxx/xx` 形式的包会被当作私有包,默认需要以组织的方式登录,可以通过参数发布为公共包 ```shell pnpm publish --access public ``` * `changeset publish` 时指定参数不生效,此时可以在 `package.json` 中指定配置 ```json { "publishConfig": { "access": "public" } } ``` * 不需要发布的包可以在 `package.json` 中设置 ```json { "private": true } ``` ::: ## 生成changeset文件 一个 `changeset` 是个包含了在某个分支或者 `commit` 上改动信息的 `md` 文件,包含的内容包括: * 本次变更涉及哪些包的变更。 * 本次变更需要升级的版本号类型,类型符合 [semver](SemVer.md) 规范。 * 本次变更的 `Changelog` 信息。 这个文件的本质是对包的版本和 `Changelog` 做一个预存储,我们也可以在这些文件中修改信息。 **模块工程方案** 在根目录执行 `pnpm changeset add`,选择本次变更需要升级的版本号类型,填入 `Changelog` 信息,并点击两次回车,执行完成后,将在项目的 `.changeset` 目录创建对应的 `changeset` 文件 **Monorepo 工程方案** 在根目录执行 `pnpm changeset add`,选择本次需要升级的包列表,分别选择不同版本类型对应的包,`changeset` 会询问版本号类型,如果存在包未选择这两种类型,将会默认使用 `patch` 类型,填入 `Changelog` 信息,并点击两次回车,执行完成后,将在项目的 `.changeset` 目录创建对应的 `changeset` 文件 :::info * 假设现在要进行迭代,我们从主分支上切一个分支出来,完成代码修改,如果需要变更记录,执行 `pnpm changeset add` 命令生成 `changeset` 文件,最后提交代码。 * `changeset` 文件需要一并提交到远程仓库中。在后续的新版本发布后,这些 `changeset` 文件是会被自动消耗掉的。 > `changesets` 会根据当前代码变更(`git diff Head...baseBranch`),将 `Monorepo` 中的 `package` 分为两类,`changed packages` 和 `unchanged packages`,方便用户进行选择。 > > > 如果当前就是主分支,代码 `commit` 之后不能区分哪些是改变的 > > ::: **参数:** * `--empty` 添加一个空的 `changeset`。 ```shell pnpm changeset add --empty ``` * `--open` 使用该参数时,在填写 `Changelog` 步骤会打开系统默认编辑器进行填写。 **注意事项** * 不是所有的变更都需要 `changeset` * 如果当前变更是修改仓库的一些基础设施,比如 `CI`、测试等,就不需要添加 `changeset` 或者可以添加一个空的 `changeset`。 * 一个 `pull request` 可以提交多个 `changeset`。当一个 `pull request` 存在多个功能开发或者问题修复时,可以多次执行 `pnpm changeset add` 添加多个 `changeset` 文件,每个文件选择对应功能的包和添加变更信息即可。 * 创建 `changeset` 时,需要选择该功能相关的所有包 ## 发布测试版本 1. 进入 `pre` 模式 执行下方命令中的一条 ```shell pnpm changeset pre enter alpha # 发布 alpha 版本 pnpm changeset pre enter beta # 发布 beta 版本 pnpm changeset pre enter rc # 发布 rc 版本 ``` 2. 修改包的版本 ```shell pnpm changeset version` ``` 执行后包的版本发生变化,其他项目以来的此包的版本也相应发生变化 3. 发布测试版本 ```shell pnpm run build pnpm changeset publish ``` 5. 退出 `pre` 模式 ```shell pnpm changeset pre exit ``` ## 发布正式版本 1. 修改版本号 ```shell pnpm changeset version ``` 2. 发布 ```shell pnpm changeset publish ``` `changeset` 会检查当前工作区中所有包的版本是否已经被发布过,如果没有则自动发布。 ## 用法假想 初次开发完毕后,直接打包、发布,提交代码到主分支 `master`;从主分支切新分支更新迭代(e.g.,例如新增 `dev` 分支,日常开发从 `dev` 切新分支),新分支开发完提交合并到 `dev`, 代码都合并到 `dev` 分支之后生成 `changeset` 文件,提交合并到主分支,在主分支修改包的版本(消耗掉 `changeset`),发布新版本(自动 `tag`) ```json { "scripts": { "build": "turbo run build", "dev": "turbo run dev --parallel", "changeset:add": "changeset add", "changeset:version": "changeset version", "changeset:publish": "changeset publish", "release": "pnpm run build && pnpm run changeset:add && pnpm run changeset:version && pnpm run changeset:publish" } } ``` --- --- url: /vite-press/cli/git/cli.md description: Git常用命令 --- # Git 常用命令 ## 仓库 ```shell # 在当前目录新建一个Git代码库 git init # 新建一个目录,将其初始化为Git代码库 git init [project-name] # 下载一个项目和它的整个代码历史 git clone [url] # 使用参数下载一个项目 git clone [options] [] ``` ## 配置 ```shell # 显示当前的Git配置 git config --list # 编辑Git配置文件 git config -e [--global] # 查看某个配置选项 git config user.name # 设置提交代码时的用户信息 git config [--global] user.name "[name]" git config [--global] user.email "[email address]" ``` ## 增加/删除文件 ```shell # 添加指定文件到暂存区 git add [file1] [file2] ... # 添加指定目录到暂存区,包括子目录 git add [dir] # 添加当前目录的所有文件到暂存区 git add . # 添加每个变化前,都会要求确认 # 对于同一个文件的多处变化,可以实现分次提交 git add -p # 删除工作区文件,并且将这次删除放入暂存区 git rm [file1] [file2] ... # 停止追踪指定文件,但该文件会保留在工作区 git rm --cached [file] # 改名文件,并且将这个改名放入暂存区 git mv [file-original] [file-renamed] ``` ## 代码提交 ```shell # 提交暂存区到仓库区 git commit -m [message] # 提交暂存区的指定文件到仓库区 git commit [file1] [file2] ... -m [message] # 提交工作区自上次commit之后的变化,直接到仓库区 git commit -a # 提交时显示所有diff信息 git commit -v # 使用一次新的commit,替代上一次提交 # 如果代码没有任何新变化,则用来改写上一次commit的提交信息 git commit --amend -m [message] # 重做上一次commit,并包括指定文件的新变化 git commit --amend [file1] [file2] ... ``` ## 分支 ```shell # 列出所有本地分支 git branch # 列出所有远程分支 git branch -r # 列出所有本地分支和远程分支 git branch -a # 新建一个分支,但依然停留在当前分支 git branch [branch-name] # 新建一个分支,并切换到该分支 git checkout -b [branch] # 从远程分支新建一个分支,并切换到该分支 git checkout -b [branch] origin/[remote-branch] // [!code hl] # 新建一个分支,指向指定commit git branch [branch] [commit] # 新建一个分支,与指定的远程分支建立追踪关系 git branch --track [branch] [remote-branch] # 切换到指定分支,并更新工作区 git checkout [branch-name] # 切换到上一个分支 git checkout - # 建立追踪关系,在现有分支与指定的远程分支之间 git branch --set-upstream [branch] [remote-branch] # 合并指定分支到当前分支 git merge [branch] # 选择一个commit,合并进当前分支 git cherry-pick [commit] # 删除分支 git branch -d [branch-name] # 删除远程分支 git push origin --delete [branch-name] git branch -dr [remote/branch] ``` ## 标签 ```shell # 列出所有tag git tag # 新建一个tag在当前commit git tag [tag] # 新建一个tag在指定commit git tag [tag] [commit] # 删除本地tag git tag -d [tag] # 删除远程tag git push origin :refs/tags/[tagName] # 查看tag信息 git show [tag] # 提交指定tag git push [remote] [tag] # 提交所有tag git push [remote] --tags # 新建一个分支,指向某个tag git checkout -b [branch] [tag] ``` ## 查看信息 ```shell # 显示有变更的文件 git status # 显示当前分支的版本历史 git log # 显示commit历史,以及每次commit发生变更的文件 git log --stat # 搜索提交历史,根据关键词 git log -S [keyword] # 显示某个commit之后的所有变动,每个commit占据一行 git log [tag] HEAD --pretty=format:%s # 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件 git log [tag] HEAD --grep feature # 显示某个文件的版本历史,包括文件改名 git log --follow [file] git whatchanged [file] # 显示指定文件相关的每一次diff git log -p [file] # 显示过去5次提交 git log -5 --pretty --oneline # 显示所有提交过的用户,按提交次数排序 git shortlog -sn # 显示指定文件是什么人在什么时间修改过 git blame [file] # 显示暂存区和工作区的差异 git diff # 显示暂存区和上一个commit的差异 git diff --cached [file] # 显示工作区与当前分支最新commit之间的差异 git diff HEAD # 显示两次提交之间的差异 git diff [first-branch]...[second-branch] # 显示今天你写了多少行代码 git diff --shortstat "@{0 day ago}" # 显示某次提交的元数据和内容变化 git show [commit] # 显示某次提交发生变化的文件 git show --name-only [commit] # 显示某次提交时,某个文件的内容 git show [commit]:[filename] # 显示当前分支的最近几次提交 git reflog ``` ## 远程同步 ```shell # 下载远程仓库的所有变动 git fetch [remote] # 显示所有远程仓库 git remote -v # 显示某个远程仓库的信息 git remote show [remote] # 增加一个新的远程仓库,并命名 git remote add [shortname] [url] # 取回远程仓库的变化,并与本地分支合并 git pull [remote] [branch] # 上传本地指定分支到远程仓库 git push [remote] [branch] # 强行推送当前分支到远程仓库,即使有冲突 git push [remote] --force # 推送所有分支到远程仓库 git push [remote] --all ``` ## 撤销 ```shell # 恢复暂存区的指定文件到工作区 git checkout [file] # 恢复某个commit的指定文件到暂存区和工作区 git checkout [commit] [file] # 恢复暂存区的所有文件到工作区 git checkout . # 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变 git reset [file] # 重置暂存区与工作区,与上一次commit保持一致 git reset --hard # 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变 git reset [commit] # 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致 git reset --hard [commit] # 重置当前HEAD为指定commit,但保持暂存区和工作区不变 git reset --keep [commit] # 新建一个commit,用来撤销指定commit # 后者的所有变化都将被前者抵消,并且应用到当前分支 git revert [commit] #暂时将未提交的变化移除,稍后再移入 git stash git stash pop ``` ## 其他 ```shell # 生成一个可供发布的压缩包 git archive ``` --- --- url: /vite-press/cli.md --- # CLI --- --- url: /vite-press/cli/linux/cli.md description: Linux常用命令 --- # Linux CLI 这些命令在前端开发中常用于文件和目录的操作、版本控制、进程管理、网络测试、包管理和构建工具等方面。使用这些命令可以进行代码管理、构建优化、调试和部署等任务。 > 可以通过 `man` 命令查看每个命令的完整参数说明和用法。例如,使用 `man ls` 查看 `ls` 命令的详细说明。 ## 文件和目录操作 * `cd` - 切换当前目录。 * `ls` - 列出当前目录中的文件和文件夹。 * `mkdir` - 创建新的文件夹。 * `cp` - 复制文件或文件夹。 * `rm` - 删除文件或文件夹。 * `mv` - 移动文件或文件夹,或重命名。 * `cat` - 显示文本文件的内容。 * `grep` - 在文件中查找匹配的文本。 ## 进程管理 * `ps` - 显示当前运行的进程列表。 * `kill` - 终止运行的进程。 ## 网络相关 * `ping` - 测试与另一台计算机的网络连接。 * `ifconfig` - 显示网络接口的配置信息。 * `curl` - 发送 HTTP 请求并显示响应。 ## 其他 * `chmod` - 修改文件或目录的权限。 * `chown` - 修改文件或目录的所有者。 * `tar` - 创建和解压归档文件。 * `ssh` - 远程登录到另一台计算机。 --- --- url: /vite-press/cli/windows/cli.md description: Windows常用命令 --- # [Windows CLI](https://learn.microsoft.com/zh-cn/windows-server/administration/windows-commands/windows-commands) 这些命令在前端开发中常用于文件和目录的操作、进程管理、网络测试以及命令行窗口的控制等。使用这些命令可以提高工作效率并进行必要的调试和测试。 > 在使用这些命令时,可以通过在命令后添加 `/?` 参数来获取更详细的帮助信息,例如 `dir /?`。 ## 文件和目录操作 * `cd` - 切换当前目录。 * `dir` - 列出当前目录中的文件和文件夹。 * `mkdir` - 创建新的文件夹。 * `rmdir` - 删除文件夹。 * `copy` - 复制文件或文件夹。 * `xcopy` - 复制文件和文件夹,并支持递归复制。 * `del` - 删除文件。 * `ren` - 重命名文件或文件夹。 * `type` - 显示文本文件的内容。 * `tree` - 显示目录结构。 ## 进程管理 * `tasklist` - 显示当前运行的进程列表。 * `taskkill` - 终止运行的进程。 ## 网络相关 * `ping` - 测试与另一台计算机的网络连接。 * `ipconfig` - 显示计算机的网络配置信息。 * `netstat` - 显示网络连接和端口信息。 ## 其他 * `cls` - 清除命令行窗口内容。 * `exit` - 退出命令行窗口。 * `start` - 启动应用程序或打开文件。 ## 使用 ### 端口占用 * 查看所有端口的占用情况 ```shell netstat -ano ``` * 查看指定端口的占用情况 ```shell netstat -ano|findstr "端口" ``` * 查看 `PID` 对应的进程名 ```shell tasklist|findstr "PID" ``` * 结束进程 ```shell taskkill /f /t /im PID ``` ### 删除文件(夹) ```shell #删除文件夹 rmdir [/s] [/q] [drive:]path #or rd [/s] [/q] [drive:]path #只删除文件 del [/s] [/q] [drive:]path ``` * `/s` 除目录本身外,还将删除指定目录下的所有子目录和文件。用于删除目录树。 * `/q` 安静模式,带 `/s` 删除目录树时不要求确认 --- --- url: /vite-press/frontend/npm/cli.md description: npm 命令行 --- # NPM CLI --- --- url: /vite-press/cli/git/commitlint.md description: git 提交约束 --- # {{ $frontmatter.title }} * `husky`:触发 `Git Hooks`,执行脚本 * `lint-staged`:检测文件,只对暂存区中有改动的文件进行检测,可以在提交前进行 `Lint` 操作 * `commitizen`:使用规范化的 `message` 提交 * `commitlint`:检查 `message` 是否符合规范 * `cz-conventional-changelog`:适配器。提供 `conventional-changelog` 标准(约定式提交标准)。基于不同需要也可以使用不同适配器(比如:`cz-customizable`) ## commit 定制化 通过 `commitzen` 插件,可以帮助我们来实现 `git commit` 得统一性,只不过它是一个比较基础的插件,社区还有更多功能增强的插件。例如: * `cz-customizable`:可以自定义 `git commit`,通过配置文件引导用户进行 `commit` 操作。 * `commitizen-emoji`:带有 `emoji` 表情的 `git Commit`,但是不能自定义配置,只能使用固定的格式。 为了定制化,我们使用 `cz-customizable` 来配置属于我们自己项目的 `commit` 规范。 ```shell pnpm add -D cz-customizable ``` 新建 `.cz-config.js`,使用 `module` 模式则创建 `.cz-config.cjs` ```js .cz-config.js module.exports = { // 引导用户输入的提示信息 types: [ { value: ":rocket: initial", name: "🎉 initial: 初始化项目" }, { value: ":construction: wip", name: "🚧 wip: 工作进行中" }, { value: ":sparkles: feat", name: "✨ feat: 新增一个功能" }, { value: ":bug: fix", name: "🐛 fix: 修复一个Bug" }, { value: ":hammer: refactor", name: "🔨 refactor: 重构(既不修复bug也不添加特性的代码更改)", }, { value: ":pencil: docs", name: "📝 docs: 文档变更" }, { value: ":white_check_mark: test", name: "✅ test: 添加缺失的测试或更正现有的测试", }, { value: ":thought_balloon: chore", name: "💭 chore: 构建过程或辅助工具的变动", }, { value: ":rewind: revert", name: "⏪ revert: 代码回退" }, { value: ":zap: perf", name: "⚡️ perf: 提升性能" }, { value: ":lipstick: ui", name: "💄 ui: 更新UI和样式" }, { value: ":art: style", name: "🎨 style: 改进代码结构/代码格式" }, { value: ":truck: mv", name: "🚚 mv: 移动重命名文件" }, { value: ":fire: delete", name: "🔥 delete: 删除文件" }, { value: ":arrow_up: up", name: "⬆️ up: 升级依赖" }, { value: ":arrow_down: down", name: "⬇️ down: 降级依赖" }, { value: ":whale: docker", name: "🐳 docker: docker相关" }, { value: ":bookmark: tag", name: "🔖 tag: 发行/版本标签" }, { value: ":ambulance: patch", name: "🚑 patch: 重要补丁" }, ], // 选择scope的提示信息 messages: { type: "请选择提交类型:", scope: "请输入修改范围(可选):", // allowCustomScopes为true时使用 customScope: "请输入文件修改范围(可选):", subject: "请简要描述提交(必选):", body: "请输入详细描述,使用'|'换行(可选):", breaking: "列出任何突破性的变化(可选):", footer: "请输入要关闭的issue(可选)。例:#31,#34:", confirmCommit: "确定要继续执行上面的提交吗?", }, scopes: [ "user", "login", "home", "order", "product", "cart", "address", "pay", "coupon", "search", "category", "detail", "other", ], // 跳过某些问题 skipQuestions: [], allowCustomScopes: true, allowBreakingChanges: [ "feat", "fix" ], subjectLimit: 100, }; ``` 配置 `package.json` ```json lines { "script": { "commit": "cz-customizable" }, "config": { "commitizen": { "path": "cz-customizable" } } } ``` :::warning * 使用 `git cz` 需要安装 `commitizen` * 如果使用 `cjs` 需要手动指定配置文件,否则识别不到 ```json lines { "config": { "commitizen": { "path": "cz-customizable" }, // [!code ++:4] "cz-customizable": { "config": ".cz-config.cjs" } } } ``` ::: 使用方法 ```shell git add . pnpm commit ``` ## 拦截 git commit 我们已经约束好啦 `Commit` 规范,但是开发者还是开始可以通过 `git commit` 命令提交约束之外的消息。这也使得我们的规范失去了意义。 因此需要在开发者 `commit` 的时候对其提交消息做一次拦截验证,只有符合规范的才能被正确的提交到`Git` 仓库,从而 `push` 到远端。 而 `husky` 则是专门做这种操作的,它支持所有的 `Git` 钩子,在触发钩子之前做一些额外的事情,例如 `commit` 校验、运行测试等等。 因为我们定制的 `commit` 是以 `emoji` 开头的,而 `commitlint` 中并没有提供匹配 `emoji` 开头的校验包,因此需要接入 `commitlint-config-gitmoji` 来完成 `commit` 检验。 * `commitlint`:检查你的提交消息是否符合常规的提交格式。 * `husky`:当你提交或推送时,使用它来整理你的提交信息、运行测试、`lint` 代码等。 * `commitlint-config-gitmoji`:当你的提交信息是以 `emoji` 开头的,通过它来校验。默认 `@commitlint/config-conventional` ```shell pnpm add -D @commitlint/cli commitlint-config-gitmoji pnpm dlx husky-init pnpm install ``` 执行成功后,会在项目根目录下生成 `.husky` 的一个文件夹,里面包含 `pre-commit` 脚本,表示在 `commit` 之前,需要做的一些事情。(例如:运行 `pnpm run test`,如果失败,终止提交。) *** 添加 `commilint` 的 `husky` 钩子。 ```shell pnpm dlx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"' ``` 配置 `commitlint.config.js` ```js module.exports = { extends: [ "gitmoji" ], rules: { "type-enum": [ 2, "always", [ "initial", "build", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test", "chore", "wip", "mv", "delete", "ui", "up", "down", "docker", ], ], }, }; ``` ## lint-staged ```shell pnpm add lint-staged -D ``` 配置方式: * `package.json` 中的 `lint-staged` 配置项 * `JSON` 风格或 `YML` 风格的 `.lintstagedrc` ```json { "lint-staged": { "*.{js,jsx,less,md,json}": [ "prettier --write" ], "*.ts?(x)": [ "prettier --parser=typescript --write", "eslint --quiet" ] } } ``` 虽然配置好了 `lint-staged`,但目前还不能自动地使用它,因为通常它是放在 `Git` 的 `pre-commit` 阶段去执行,因此要在 `.husky/pre-commit` 文件中增加执行 `lint-staged` 的命令 ```shell # 如果使用的是 npm/yarn 且 npm 版本为 v8 及以下,就加下面的命令 npx lint-staged # 如果使用的是 npm/yarn 且 npm 版本为 v9 及以上,就加下面的命令 npm exec lint-staged # 如果使用的是 pnpm,就加下面的命令 pnpm exec lint-staged # ! 注意,上面的这三个命令必须根据实际情况来选择,且只能存在一个 ``` ## 集成 `husky` 和 `lint-staged` 的官方都推荐了使用 `mrm` 这个库来同时集成 `husky` 和 `lint-staged`。 在项目根目录下,直接执行如下命令: ```shell pnpx mrm lint-staged ``` 执行命令之后,就会看到 `package.json` 里多了一个 `lint-staged` 配置项,且根目录下多了一个 `.husky` 目录,里面就包含了 `pre-commit` 文件,里面包含了一个最基础的命令:`npx lint-staged`。 在后续的开发工作中,任何需要在 `pre-commit` 阶段执行的命令,都可以添加到 `.husky/pre-commit` 文件中 --- --- url: /vite-press/frontend/css.md --- --- --- url: /vite-press/frontend/javascript/ES6+.md description: ES 新版本 API 总结 --- # ES6+ `ES6` 是 [ECMA](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/) 为 `JavaScript` 制定的第 6 个标准版本。 `ES6` 既是一个历史名词也是一个泛指,含义是 `5.1` 版本以后的 JavaScript 下一代标准。 ## [ES6(2015)](https://262.ecma-international.org/6.0/) `ES6` 新增内容主要有:变量声明、函数扩展、字符串扩展、数组扩展、集合操作、异步编程、迭代等 ### 声明和变量声明 包括使用 `let`、`const` 声明变量和对**对象**、**数组**解构绑定 #### 变量声明 `let` 和 `const`,用于解决使用 `var` 声明常量时存在的问题:不能定义常量,可以重复声明变量、存在声明提升、不支持块级作用域的问题 > 使用 `const` 可以定义常量,不能给常量重新赋值,但是如果是引用类型的话可以修改; 剖析暂时性死区的原理,其实 `let/const` 同样也有提升的作用,但是和 `var` 的区别在于 * `var` 在创建时就被初始化,并且赋值为 `undefined` * `let/const` 在进入块级作用域后,会因为提升的原因先创建,但不会被初始化,直到声明语句执行的时候才被初始化,初始化的时候如果使用 `let` 声明的变量没有赋值,则会默认赋值为 `undefined`,而 `const` 必须在初始化的时候赋值。而创建到初始化之间的代码片段就形成了暂时性死区 > > 由 `let/const` 声明的变量,当它们包含的词法环境(Lexical Environment)被实例化时会被创建,但只有在变量的词法绑定( > > LexicalBinding)已经被求值运算后,才能够被访问 ```ts let a = 'let' // 变量 const b = 'const' // 常量 ``` #### 解构绑定 解构赋值语法是 `JavaScript` 的一种表达式,可以方便的从数组或者对象中快速提取值赋给定义的变量。 > ObjectBindingPattern 和 ArrayBindingPattern * 数组解构 > 按照索引解构 ```ts const arr = [ 1, 2, 3 ] const [ one, two, three ] = arr // 忽略某些值 const [ , , threeA ] = arr // 默认值 const [ a = 2, b = 4, c = 6 ] = [] ``` * 对象解构 > 按 `key` 解构 ```ts const info = { name: 'Yanlu', age: 24 } const { name, age } = info // 支持别名和默认值 const { name: myName, age: myAge = 18 } = info ``` ### 函数扩展 新增了箭头函数、新的方法定义方式、支持默认参数,新增 `Class` #### 箭头函数 箭头函数是使用 `=>` 语法的函数简写。与一般函数不同的是 * 函数体内的 `this` 对象,就是定义时所在的对象,而不是使用时所在的对象。 * `this` 对象的指向是可变的,但是在箭头函数中,它是固定的。 * 不可以当作构造函数,也就是说,不可以使用 `new` 命令,否则会抛出一个错误。 * 不可以使用 `arguments` 对象,该对象在函数体内不存在。如果要用,可以用 `rest` 参数代替。 * 不可以使用 `yield` 命令,因此箭头函数不能用作 `Generator` 函数。 ```ts const date = () => Date.now() ``` #### 方法定义 > 省略 `function` 关键字 ```ts // object 中 const info = { say() { return 'Hello' } } // class 中 class Person { say() { return 'Hello' } } ``` #### 类(Class) > 构造函数语法糖,增强 `ES5` 构造函数 > > > `extends`,`constructor`,`super`,`static` ```ts class Persion { constructor(id, name) { this.name = name } info() { return this.name } } class Student extends Persion { constructor(id, name) { super(id, name) } } ``` #### 默认参数 定义函数的时候为其设置默认值 ```ts const fn = (a = 1) => a * 2 ``` ### 对象扩展 增强了字面量定义对象,更加便捷;扩展了对象的**静态方法** #### 字面量增强 * 属性简写 > 允许直接写入变量,作为对象的属性 ```ts const name = 'Yanlu' const info = { name } // 等价于: const infoCopy = { name: name } ``` * 方法 > 允许直接写入方法,作为对象的属性 ```ts const infoCopy = { say() { return 'hello' } } // 等价于: const info = { say: function () { return 'hello' } } ``` * 动态属性 > 通过中括号 `[]` 的方式来给对象动态增加属性 ```ts const dynamic = 'address' const getAddress = () => 'HangZhou' const infoCopy = { [dynamic]: 'YuHang', [getAddress() + 'Desc']: 'HangZhou YuHang', } // 等价于 const info = {} info[dynamic] = 'YuHang' info[getAddress() + 'Desc'] = 'HangZhou YuHang' ``` #### 新增方法 * Object.is > 方法判断两个值是否为同一个值 ```ts // Object.is(value1, value2); Object.is(25, 25); // true Object.is('foo', 'foo'); // true Object.is('foo', 'bar'); // false ``` * Object.setPrototypeOf > 设置一个指定的对象的原型(即,内部 \[\[Prototype]] 属性)到另一个对象或 `null`。 ```ts // Object.setPrototypeOf(obj, prototype) function Human(name, level) { this.name = name this.level = level } function SuperHero(name, level) { Human.call(this, name, level) } Object.setPrototypeOf(SuperHero.prototype, Human.prototype) ``` * Object.assign > 将所有可枚举(`Object.propertyIsEnumerable()` 返回 `true`)的自有(`Object.hasOwnProperty()` 返回 > `true`)属性从一个或多个源对象复制到目标对象,返回修改后的对象。 > > > 浅拷贝 ```ts // Object.assign(target, ...sources) let obj1 = { a: 0, b: { c: 0 } }; let obj2 = Object.assign({}, obj1); console.log(JSON.stringify(obj2)); // { "a": 0, "b": { "c": 0}} ``` * Object.getOwnPropertySymbols > 返回一个给定对象自身的所有 `Symbol` 属性的数组。 ```ts // Object.getOwnPropertySymbols(obj) let obj = {}; let a = Symbol("a"); let b = Symbol.for("b"); obj[a] = "localSymbol"; obj[b] = "globalSymbol"; let objectSymbols = Object.getOwnPropertySymbols(obj); console.log(objectSymbols.length); // 2 console.log(objectSymbols) // [Symbol(a), Symbol(b)] console.log(objectSymbols[0]) // Symbol(a) ``` ### 数组扩展 扩展了数组的 **静态方法** 和 **实例方法** #### 静态方法 * Array.from > 从数组类对象或可迭代对象创建一个新的 `Array` 实例 ```ts // Array.from(arrayLike, (element, index) => { /* … */ }, thisArg) Array.from("foo"); // [ "f", "o", "o" ] ``` * Array.of > 通过可变数量的参数创建一个新的 `Array` 实例,而不考虑参数的数量或类型 ```ts // Array.of(element0, element1, /* … ,*/ elementN) Array.of(1); // [1] Array.of(1, 2, 3); // [1, 2, 3] Array.of(undefined); // [undefined] ``` #### 实例方法 * Array.prototype.copyWithin > 浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度 ```ts // arr.copyWithin(target[, start[, end]]) [ 1, 2, 3, 4, 5 ].copyWithin(-2) // [1, 2, 3, 1, 2] [1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5] [1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5] ``` * Array.prototype.entries > 返回一个新的数组迭代器对象,该对象包含数组中每个索引的键/值对 ```ts // arr.entries() const array = [ "a", "b", "c" ]; const arrayEntries = array.entries(); for (const element of arrayEntries) { console.log(element); } // [0, 'a'] // [1, 'b'] // [2, 'c'] ``` * Array.prototype.fill > 用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引 ```ts // arr.fill(value[, start[, end]]) [ 1, 2, 3 ].fill(4); // [4, 4, 4] [ 1, 2, 3 ].fill(4, 1); // [1, 4, 4] [ 1, 2, 3 ].fill(4, 1, 2); // [1, 4, 3] ``` * Array.prototype.find > 返回数组中满足提供的测试函数的第一个元素的值。否则返回 `undefined`。 ```ts // arr.find((element, index, array) => { /* … */ }, thisArg) const inventory = [ { name: 'apples', quantity: 2 }, { name: 'bananas', quantity: 0 }, { name: 'cherries', quantity: 5 } ]; const result = inventory.find(({ name }) => name === 'cherries'); console.log(result) // { name: 'cherries', quantity: 5 } ``` * Array.prototype.findIndex > 返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回 `-1` ```ts // arr.findIndex((element, index, array) => { /* … */ }, thisArg) console.log([ 4, 6, 7, 12 ].findIndex(num => num === 1)); // -1 console.log([ 4, 6, 7, 12 ].findIndex(num => num === 7)); // 2 ``` * Array.prototype.keys > 返回一个包含数组中每个索引键的 `Array Iterator` 对象 ```ts // arr.keys() const arr = [ "a", , "c" ]; const sparseKeys = Object.keys(arr); const denseKeys = [ ...arr.keys() ]; console.log(sparseKeys); // ['0', '2'] console.log(denseKeys); // [0, 1, 2] ``` * Array.prototype.values > 返回一个新的 `Array Iterator` 对象,该对象包含数组每个索引的值 ```ts // arr.values() const arr = [ 'a', 'b', 'c', 'd', 'e' ] const iterator = arr.values() for (const letter of iterator) { console.log(letter) } //"a" "b" "c" "d" "e" ``` ### 字符串扩展 新增了模板字符串;扩展了字符串的实例方法 #### 模板字符串 增强的字符串,通过 `反引号` 定义,可以通过 `${}` 使用变量,支持换行 ```ts const name = 'Yanlu' const getName = ` my name is ${ name } my age is 18 ` ``` #### 新增方法 * String.prototype.startsWith > 判断当前字符串是否以另外一个给定的子字符串开头 ```ts // str.startsWith(searchString[, position]) const str = "To be, or not to be, that is the question."; alert(str.startsWith("To be")); // true alert(str.startsWith("not to be")); // false alert(str.startsWith("not to be", 10)); // true ``` * String.prototype.endsWith > 判断当前字符串是否是以另外一个给定的子字符串结尾 ```ts // str.endsWith(searchString[, length]) const str = "To be, or not to be, that is the question."; alert(str.endsWith("question.")); // true alert(str.endsWith("to be")); // false alert(str.endsWith("to be", 19)); // true ``` * String.prototype.includes > 判断当前字符串是否以包含一个给定的子字符串,区分大小写 ```ts // str.includes(searchString[, position]) const str = "To be, or not to be, that is the question."; console.log(str.includes("To be")); // true console.log(str.includes("question")); // true console.log(str.includes("nonexistent")); // false console.log(str.includes("To be", 1)); // false ``` * String.prototype.repeat > 构造并返回一个新字符串,该字符串包含被连接在一起的指定数量的字符串的副本 ```ts // str.repeat(count) "abc".repeat(-1) // RangeError: repeat count must be positive and less than inifinity "abc".repeat(0) // "" "abc".repeat(1) // "abc" "abc".repeat(2) // "abcabc" ``` ### 运算符扩展 `...` 运算符由上下文环境决定是展开运算符 `Spread operator` 还是剩余运算符 `Rest operator` #### 展开运算符 展开运算符允许一个表达式在某处展开。展开运算符在多个参数(用于函数调用)或多个元素(用于数组字面量)或者多个变量(用于解构赋值)的地方可以使用 * 函数调用-展开参数 ```ts const sum = (a, b, c) => a + b + c const args = [ 1, 2, , 3 ] sum(...args) ``` * 数组构建 ```ts const arr = [ 1, 2, 3 ] const args = [ ...arr, ...'456', 7 ] ``` #### 剩余运算符 > 剩余运算符和扩展运算符的区别就是,剩余运算符会收集这些集合,放到右边的数组中,扩展运算符是将右边的数组拆分成元素的集合,它们是相反的 * 函数 rest 参数 ```ts // rest 形参 const fn = (...rest) => console.log(rest) ``` * 解构赋值 > 剩余运算符可以和数组的解构赋值一起使用,但是必须放在最后一个,因为剩余运算符的原理其实是利用了数组的迭代器,它会消耗3个点后面的数组的所有迭代器 ```ts const arr = [ 1, 2, 3, 4, 5 ] const [ one, two, ...other ] = arr ``` ### for...of `for...of` 允许遍历一个含有 `iterator` 接口的数据结构并且返回各项的值 和 `ES3` 中的 `for...in` 的区别如下 * `for...of` 只能用在可迭代对象上,获取的是迭代器返回的 `value值`,`for...in` 可以获取所有对象的 `键名` * `for...in` 会遍历对象的整个原型链,性能非常差不推荐使用,而 `for...of` 只遍历当前对象不会遍历它的原型链 * 对于数组的遍历,`for...in` 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),`for...of` 只返回数组的下标对应的属性值 ```ts let arr = [ { a: 1 }, { a: 2 }, { a: 3 } ] for ({ a: aa } of arr) { console.log(aa) } ``` ### 控制抽象对象 [有 Iteration、GeneratorFunction、Generator、Promise 四大内容](https://262.ecma-international.org/6.0/#sec-control-abstraction-objects) #### Promise [`Promise`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 是异步编程的一种解决方案,其实是一个构造函数,有 `all`、`race`、`reject`、`resolve` 静态方法,原型上有 `then`、`catch` 方法 `Promise` 对象有两个特点: * 对象的状态不受外界影响:`promise` 对象代表一个异步操作看,有 `pending`(进行中),`fulfilled`(已成功)和 `rejected`(已失败),对象的状态只能由异步操作结果决定,其他的操作不会产生影响,这也是 `promise`(承诺)名字的由来,表示其他手段不能改变 * 一旦状态改变,就不会再发生改变:`promise` 对象状态的改变只有两种可能,由 `pending` 到 `fulfilled` 或者由 `pending` 到 `rejected`,只要这两种情况发生了,状态就凝固了,不会再改变了,会一直保持这个结果,这时处于 `resolved`(已定型),状态发生来改变,给 `promise` 对象添加回调函数也会立即得到这个值,这与事件(`event`)的错过了就再也得不到了有着很大的区别。 ##### 静态方法 * Promise.all > 等待所有都完成(或第一个失败) ```ts const p1 = Promise.resolve(3); const p2 = 1337; const p3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([ p1, p2, p3 ]).then(values => { console.log(values); // [3, 1337, "foo"] }); ``` * Promise.race > 只要给定的迭代中的第一个 `Promise` 解决或拒绝 ```ts const p1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, 'one') }) const p2 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'two') }) Promise.race([ p1, p2 ]).then((value) => { console.log(value) // "two" // 两个都完成,但 p2 更快 }) ``` * Promise.reject > 返回一个被拒绝的 `Promise` 对象 ```ts Promise.reject(new Error('fail')) // 等同于: new Promise((resolve, reject) => { reject(new Error('fail')) }) Promise.reject(new Error('fail')) .then( (res) => { // not called }, (error) => { console.error(error) // Stacktrace }, ) ``` * Promise.resolve > 返回一个以给定值解析后的 `Promise` 对象 ```ts Promise.resolve('Success') // 等同于: new Promise((resolve, reject) => { resolve('Success') }) Promise.resolve('Success') .then( (value) => { console.log(value) // "Success" }, (value) => { // 不会被调用 }, ) ``` ##### 实例方法 * Promise.prototype.then > promise 被兑现和被拒绝状态的回调函数 * Promise.prototype.catch > `Promise` 被拒绝状态的回调函数 > > > `catch` 的另一个作用是如果 `resolve` 的执行过程中出现异常,将会进入 `catch` 方法,不会报错卡死 `js` #### Iteration 迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。具体分为: * 可迭代协议允许 `JavaScript` 对象定义或定制它们的迭代行为,例如,在一个 `for..of` 结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 `Array` 或者 `Map`,而其他内置类型则不是(比如 `Object`) * 迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式,当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。 #### Generator [`Generator`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Generator) 对象由生成器函数返回并且它符合 [可迭代协议和迭代器协议](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols)。 `Generator` 的声明与一般函数类似,只是多了一个 `*`,内部有 `yield` 关键字,迭代器有一个 `next` 方法,每次执行会返回一个对象,对象有两个属性,一个是 `value` 表示返回的值,另一个是布尔值 `done`,表示迭代是否完成 > `Generator` 构造函数并不是全局可用。`Generator` 的实例必须从生成器函数返回: * 生成器函数:`function* () {}` ```ts function* generator() { yield 1; yield 2; yield 3; } const gen = generator(); // "Generator { }" console.log(gen.next().value); // 1 console.log(gen.next().value); // 2 console.log(gen.next().value); // 3 ``` #### GeneratorFunction 生成器函数实际上都是 `GeneratorFunction` 对象 ```ts const GeneratorFunction = function* () {}.constructor; const g = new GeneratorFunction("a", "yield a * 2"); const iterator = g(10); console.log(iterator.next().value); // 20 ``` ### Module 一个独立文件就是一个模块,该文件内部的所有变量,外部无法获取。 * `ES6` 模块自动开启严格模式,不管你有没有在模块头部加上 `use strict;` * 模块中可以导入(`import`)和导出(`export`、`export default`)各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。 * 每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。 * 每一个模块只加载一次(是单例的),若再去加载同目录下同文件,直接从内存中读取。 * `import` 导入的变量都是只读的,`import` 命令具有提升效果,会提升到整个模块的头部,首先执行 > 一个模块中,`export` 支持导出多个但是导入时使用导出时的名字而且要使用 `{}`,`export default` 只能有一个但是导入时不一定使用导入时的名字 > > > `as` 关键字可实现对模块内变量名的改写,但不能用于 `export default` 导出的变量 ```ts // a.ts export const name = 'Yanlu' export const age = 18 export default function hello() { return 'hello' } ``` ```ts // b.ts import hello, { name, age as myAge } from 'a.js' import * as aModule from 'a.js' ``` ### Proxy、Reflect `Proxy`、`Reflect` 通常一起使用 #### Proxy [`Proxy`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy) 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 ```ts const handler = { get: function (obj, prop) { return prop in obj ? obj[prop] : 37 }, } const p = new Proxy({}, handler) p.a = 1 p.b = undefined console.log(p.a, p.b) // 1, undefined console.log('c' in p, p.c) // false, 37 ``` #### Reflect [`Reflect`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect) 是一个内置的对象,它提供拦截 `JavaScript` 操作的方法。这些方法与 `proxy handler` 的方法相同。`Reflect` 不是一个函数对象,因此它是不可构造的。 ```ts const duck = { name: 'Maurice', color: 'white', greeting: function () { console.log(`Quaaaack! My name is ${ this.name }`); } } Reflect.has(duck, 'color'); // true Reflect.has(duck, 'haircut'); // false ``` ### 集合操作 #### Map [`Map`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map) 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。 ```ts const myMap = new Map(); const keyString = 'a string'; const keyObj = {}; const keyFunc = function () {}; // 添加键 myMap.set(keyString, "和键'a string'关联的值"); myMap.set(keyObj, "和键 keyObj 关联的值"); myMap.set(keyFunc, "和键 keyFunc 关联的值"); console.log(myMap.size); // 3 // 读取值 console.log(myMap.get(keyString)); // "和键'a string'关联的值" console.log(myMap.get(keyObj)); // "和键 keyObj 关联的值" console.log(myMap.get(keyFunc)); // "和键 keyFunc 关联的值" console.log(myMap.get('a string')); // "和键'a string'关联的值",因为 keyString === 'a string' console.log(myMap.get({})); // undefined,因为 keyObj !== {} console.log(myMap.get(function () {})); // undefined,因为 keyFunc !== function () {} ``` #### Set [`Set`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set) 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。 > `Set` 对象是值的集合,你可以按照插入的顺序迭代它的元素。`Set` 中的元素只会***出现一次***,即 `Set` 中的元素是唯一的 > > > `NaN` 和 `undefined` 都可以被存储在 `Set` 中,`NaN` 之间被视为相同的值(`NaN` 被认为是相同的,尽管 `NaN !== NaN`) ```ts let mySet = new Set(); mySet.add(1); // Set [ 1 ] mySet.add(5); // Set [ 1, 5 ] mySet.add(5); // Set [ 1, 5 ] mySet.add("some text"); // Set [ 1, 5, "some text" ] ``` #### WeakMap [`WeakMap`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。 > 不支持迭代 #### WeakSet [`WeakSet`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) 对象允许你将弱保持对象存储在一个集合中,只能是对象的集合 > 不支持迭代 ### Symbol [`symbol`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol) 是一种基本数据类型,`Symbol()` 返回的 `symbol` 值都是唯一的。一个 `symbol` 值能作为对象属性的标识符;这是该数据类型仅有的目的。 ## [ES2016(ES7)](https://tc39.es/ecma262/2016/) ### 数组扩展 * Array.prototype.includes > 用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 `true`,否则返回 `false` ```ts // arr.includes(searchElement[, fromIndex]) [ 1, 2, 3 ].includes(2); // true [ 1, 2, 3 ].includes(4); // false [ 1, 2, 3 ].includes(3, 3); // false [ 1, 2, 3 ].includes(3, -1); // true [ 1, 2, NaN ].includes(NaN); // true ``` ### 运算符扩展 * `**` 幂运算符 ```ts console.log(Math.pow(2, 10)); // 1024 console.log(2 ** 10); // 1024 ``` ## [ES2017(ES8)](https://tc39.es/ecma262/2017/) ### 对象扩展 * Object.values > 返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用 `for...in` 循环的顺序相同(区别在于 `for-in` 循环枚举原型链中的属性)。 ```ts // Object.values(obj) const objA = { foo: 'bar', baz: 42 }; console.log(Object.values(objA)); // ['bar', 42] // array like object const obj = { 0: 'a', 1: 'b', 2: 'c' }; console.log(Object.values(obj)); // ['a', 'b', 'c'] // array like object with random key ordering // when we use numeric keys, the value returned in a numerical order according to the keys const an_obj = { 100: 'a', 2: 'b', 7: 'c' }; console.log(Object.values(an_obj)); // ['b', 'c', 'a'] // getFoo is property which isn't enumerable const my_obj = Object.create({}, { getFoo: { value: function () { return this.foo; } } }); my_obj.foo = 'bar'; console.log(Object.values(my_obj)); // ['bar'] // non-object argument will be coerced to an object console.log(Object.values('foo')); // ['f', 'o', 'o'] ``` * Object.entries > 返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 `for...in` 循环遍历该对象时返回的顺序一致(区别在于 `for-in` 循环还会枚举原型链中的属性) ```ts // Object.entries(obj) const objA = { foo: 'bar', baz: 42 }; console.log(Object.entries(objA)); // [ ['foo', 'bar'], ['baz', 42] ] // array like object const objB = { 0: 'a', 1: 'b', 2: 'c' }; console.log(Object.entries(objB)); // [ ['0', 'a'], ['1', 'b'], ['2', 'c'] ] // array like object with random key ordering const anObj = { 100: 'a', 2: 'b', 7: 'c' }; console.log(Object.entries(anObj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ] // getFoo is property which isn't enumerable const myObj = Object.create({}, { getFoo: { value() { return this.foo; } } }); myObj.foo = 'bar'; console.log(Object.entries(myObj)); // [ ['foo', 'bar'] ] // non-object argument will be coerced to an object console.log(Object.entries('foo')); // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ] // iterate through key-value gracefully const obj = { a: 5, b: 7, c: 9 }; for (const [ key, value ] of Object.entries(obj)) { console.log(`${ key } ${ value }`); // "a 5", "b 7", "c 9" } // Or, using array extras Object.entries(obj).forEach(([ key, value ]) => { console.log(`${ key } ${ value }`); // "a 5", "b 7", "c 9" }); ``` * Object.getOwnPropertyDescriptors > 获取一个对象的所有自身属性的描述符。 ```ts // Object.getOwnPropertyDescriptors(obj) // 浅拷贝一个对象 Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj) ); ``` ### 字符串扩展 * String.prototype.padStart > 用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充。 ```ts // str.padStart(targetLength[, padString]) 'abc'.padStart(10); // " abc" 'abc'.padStart(10, "foo"); // "foofoofabc" 'abc'.padStart(6, "123465"); // "123abc" 'abc'.padStart(8, "0"); // "00000abc" 'abc'.padStart(1); // "abc" ``` * String.prototype.padEnd > 用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。 ```ts // str.padEnd(targetLength[, padString]) 'abc'.padEnd(10); // "abc " 'abc'.padEnd(10, "foo"); // "abcfoofoof" 'abc'.padEnd(6, "123456"); // "abc123" 'abc'.padEnd(1); // "abc" ``` ### 函数扩展 函数参数列表结尾允许逗号 ### Promise 增强 * async/await > `async` 和 `await` 关键字让我们可以用一种更简洁的方式写出基于 `Promise` 的异步行为,而无需刻意地链式调用 `promise` ```ts const getInfo = async () => { await fetch() } ``` ## [ES2018(ES9)](https://tc39.es/ecma262/2018/) ### 运算符扩展 `...` 运算符增加了对 `Object` 的支持 ```ts const obj = { name: 'Yanlu' } const info = { ...obj, ...'name' } const { name, ...other } = info ``` ### 字符串扩展 放松对标签模板里字符串转义的限制, 遇到不合法的字符串转义会返回 `undefined`,并且从 `raw` 上可获取原字符串 ::: warning 在模板字符串中,如果输入无效的 `unicode` 字符,还是会报错 ::: ### for await...of `for await...of` 语句创建一个循环,该循环遍历异步可迭代对象以及同步可迭代对象,包括:内置的 `String`, `Array`,类似数组对象 (例如 `arguments` 或 `NodeList`),`TypedArray`, `Map`, `Set` 和 `用户定义的异步/同步迭代器`。它使用对象的每个不同属性的值调用要执行的语句来调用自定义迭代钩子。 > 类似于 `await` 运算符一样,该语句只能在一个 `async function` 内部使用。 :::warning `for await...of` 不适用于不是异步可迭代的异步迭代器。 ::: ```ts for await (variable of iterable) { // statement } ``` ### Promise 扩展 * Promise.prototype.finally > 无论结果是 `fulfilled` 或者是 `rejected`,都会执行指定的回调函数。避免了同样的语句需要在 `then()` 和 `catch() `中各写一次的情况 ```ts let isLoading = true; fetch(myRequest) .then(function (response) { var contentType = response.headers.get("content-type"); if (contentType && contentType.includes("application/json")) { return response.json(); } throw new TypeError("Oops, we haven't got JSON!"); }) .then(function (json) { /* process your JSON further */ }) .catch(function (error) { console.log(error); }) .finally(function () { isLoading = false; }); ``` ## [ES2019(ES10)](https://tc39.es/ecma262/2019/) ### 对象扩展 * Object.fromEntries > 把键值对列表转换为一个对象。 ```ts // Object.fromEntries(iterable); const map = new Map([ [ 'foo', 'bar' ], [ 'baz', 42 ] ]); const obj = Object.fromEntries(map); console.log(obj); // { foo: "bar", baz: 42 } const arr = [ [ '0', 'a' ], [ '1', 'b' ], [ '2', 'c' ] ]; const objA = Object.fromEntries(arr); console.log(objA); // { 0: "a", 1: "b", 2: "c" } ``` ### 数组扩展 * Array.prototype.flat > 按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。 ```ts // arr.flat([depth]) const arr1 = [ 1, 2, [ 3, 4 ] ]; arr1.flat(); // [1, 2, 3, 4] const arr2 = [ 1, 2, [ 3, 4, [ 5, 6 ] ] ]; arr2.flat(); // [1, 2, 3, 4, [5, 6]] const arr3 = [ 1, 2, [ 3, 4, [ 5, 6 ] ] ]; arr3.flat(2); // [1, 2, 3, 4, 5, 6] //使用 Infinity,可展开任意深度的嵌套数组 const arr4 = [ 1, 2, [ 3, 4, [ 5, 6, [ 7, 8, [ 9, 10 ] ] ] ] ]; arr4.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ``` * Array.prototype.flatMap > 首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 `map` 连着深度值为 `1` 的 `flat` 几乎相同,但 `flatMap` 通常在合并成一种方法的效率稍微高一些 ```ts // arr.flatMap(callbackFn[, thisArg]) var arr1 = [ 1, 2, 3, 4 ]; arr1.map(x => [ x * 2 ]); // [[2], [4], [6], [8]] arr1.flatMap(x => [ x * 2 ]); // [2, 4, 6, 8] // only one level is flattened arr1.flatMap(x => [ [ x * 2 ] ]); // [[2], [4], [6], [8]] let arr2 = [ "it's Sunny in", "", "California" ]; arr2.map(x => x.split(" ")); // [["it's","Sunny","in"],[""],["California"]] arr2.flatMap(x => x.split(" ")); // ["it's","Sunny","in", "", "California"] ``` ### 字符串扩展 * String.prototype.trimStart > 删除字符串开头的空白字符。`trimLeft()` 是此方法的别名 ```ts // str.trimStart() let str = " foo "; console.log(str.length); // 8 str = str.trimStart(); console.log(str.length); // 5 console.log(str); // 'foo ' ``` * String.prototype.trimEnd > 删除字符串末尾的空白字符。`trimRight()` 是这个方法的别名 ```ts // str.trimEnd() let str = " foo "; console.log(str.length); // 8 str = str.trimEnd(); console.log(str.length); // 6 console.log(str); // ' foo' ``` ### try...catch 扩展 可选的 `Catch Binding` ```ts // 之前 try { // tryCode } catch ( err ) { // catchCode } // 现在 try { console.log('Foobar') } catch { console.error('Bar') } ``` ### Symbol 扩展 * Symbol.prototype.description > 只读属性,它会返回 `Symbol` 对象的可选描述的字符串。 ```ts console.log(Symbol('desc').description); // Expected output: "desc" console.log(Symbol.iterator.description); // Expected output: "Symbol.iterator" console.log(Symbol.for('foo').description); // Expected output: "foo" ``` ### JSON增强 * JSON.stringify > 修复了对于一些超出范围的 `Unicode` 展示错误的问题,用转义字符的方式来处理这部分字符(`0xD800–0xDFFF`)而非编码的方式 ### 函数增强 * Function.prototype.toString > 以前函数的 `toString` 方法来自 `Object.prototype.toString()`,现在的 `Function.prototype.toString()` 方法返回一个表示当前函数源代码的字符串。以前只会返回这个函数,不包含注释、空格等 ## [ES2020(ES11)](https://tc39.es/ecma262/2020/) ### 运算符扩展 * `??` 空值合并操作符 > `??` 是一个逻辑操作符,当左侧的操作数为 `null` 或者 `undefined` 时,返回其右侧操作数,否则返回左侧操作数 ```ts const foo = undefined ?? "foo" const bar = null ?? "bar" console.log(foo) // foo console.log(bar) // bar ``` * `?.` 可选链(Optional chaining) > 可选链操作符 `?.` 允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效 ```ts const obj = { name: 'Ynalu', say() { return 'hello' } } console.log(obj?.name) console.log(obj?.say?.()) ``` ### globalThis `globalThis` 提供了一个标准的方式来获取不同环境下的全局 `this` 对象(也就是全局对象自身) ### 数字扩展 * BigInt > `BigInt` 是一种内置对象,它提供了一种方法来表示大于 `2的53次方 \- 1` 的整数,这原本是 `Javascript` 中可以用 `Number` 表示的最大数字。`BigInt` 可以表示任意大的整数。 ```ts const bigIntNum = BigInt(9007199254740993) // 9007199254740992n const bigIntNumA = BigInt(9007199254740993n) // 9007199254740993n const bigInt = 9007199254740993n console.log(bigInt) // 9007199254740993n console.log(typeof bigInt) // bigint // `BigInt` 和 [`Number`]不是严格相等的,但是宽松相等的。 console.log(1n == 1) // true console.log(1n === 1) // false // `Number` 和 `BigInt` 可以进行比较。 1n < 2 // ↪ true 2n > 1 // ↪ true ``` ### 字符串扩展 * String.prototype.matchAll > 返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。 :::warning RegExp 必须是设置了全局模式 `g` 的形式,否则会抛出异常 `TypeError` ::: ```ts // str.matchAll(regexp) const regexp = RegExp('foo[a-z]*', 'g'); const str = 'table football, foosball'; const matches = str.matchAll(regexp); for (const match of matches) { console.log(`Found ${ match[0] } start=${ match.index } end=${ match.index + match[0].length }.`); } // expected output: "Found football start=6 end=14." // expected output: "Found foosball start=16 end=24." // matches iterator is exhausted after the for..of iteration // Call matchAll again to create a new iterator Array.from(str.matchAll(regexp), m => m[0]); // Array [ "football", "foosball" ] ``` ### Promise 扩展 * Promise.allSettled > Promise.allSettled() 方法以 `promise` 组成的可迭代对象作为输入,并且返回一个 `Promise` 实例。当输入的所有 `promise` 都已敲定时(包括传递空的可迭代类型),返回的 `promise` 将兑现,并带有描述每个 > `promise` 结果的对象数组。 ```ts // Promise.allSettled(iterable) Promise.allSettled([ Promise.resolve(33), new Promise((resolve) => setTimeout(() => resolve(66), 0)), 99, Promise.reject(new Error("an error")), ]).then((values) => console.log(values)); // [ // { status: 'fulfilled', value: 33 }, // { status: 'fulfilled', value: 66 }, // { status: 'fulfilled', value: 99 }, // { status: 'rejected', reason: Error: an error } // ] ``` ### Module 扩展 * `import()` 动态导入 > 在希望按照一定的条件或者按需加载模块的时候,动态 `import()` 是非常有用的 ```ts let module = await import('/modules/my-module.js'); ``` ## [ES2021(ES12)](https://tc39.es/ecma262/2021/) ### 运算符扩展 * `&&=` 逻辑与赋值 > 逻辑与赋值 `x ||= y` 运算仅在 `x` 为 `true` 时赋值 ```ts x &&= y // x && (x = y) let a = 1; let b = 0; a &&= 2; console.log(a); // 2 b &&= 2; console.log(b); // 0 ``` * `||=` 逻辑或赋值 > 逻辑或赋值 `x ||= y` 运算仅在 `x` 为 `false` 时赋值 ```ts x ||= y // x || (x = y) const a = { duration: 50, title: '' }; a.duration ||= 10; console.log(a.duration); // 50 a.title ||= 'title is empty.'; console.log(a.title); // "title is empty" ``` * `??=` 逻辑空赋值 > 逻辑空赋值运算符 `x ??= y` 仅在 `x` 是 `nullish(null 或 undefined)` 时对其赋值 ```ts x ??= y // x ?? (x = y) const a = { duration: 50 }; a.duration ??= 10; console.log(a.duration); // 50 a.speed ??= 25; console.log(a.speed); // 25 ``` ### 字符串扩展 * String.prototype.replaceAll > 返回一个新字符串,新字符串所有满足 `pattern` 的部分都已被 `replacement` 替换。 ::: warning 使用正则表达式搜索值时,它必须是全局的 ::: ```ts // const newStr = str.replaceAll(regexp|substr, newSubstr|function) 'aabbcc'.replaceAll('b', '.'); // 'aa..cc' 'aabbcc'.replaceAll(/b/g, '.'); "aa..cc" ``` ### 数字扩展 * 数字分隔符 1. 不能放在数值的最前面(`leading`)或最后面(`trailing`)。 2. 不能两个或两个以上的分隔符连在一起。 3. 小数点的前后不能有分隔符。 4. 科学计数法里面,表示指数的 `e` 或 `E` 前后不能有分隔符。 ```ts let budget = 1_000_000_000_000; budget === 10 ** 12 // true ``` ### Promise 扩展 * Promise.any > `Promise.any()` 接收一个由 `promise` 所组成的可迭代对象,该方法会返回一个新的 `promise`,一旦可迭代对象内的任意一个 `promise` 变成了兑现状态,那么由该方法所返回的 `promise` > 就会变成兑现状态,并且它的兑现值就是可迭代对象内的首先兑现的 `promise` 的兑现值。如果可迭代对象内的 `promise` 最终都没有兑现(即所有 `promise` 都被拒绝了),那么该方法所返回的 `promise` > 就会变成拒绝状态,并且它的拒因会是一个 `AggregateError` 实例,这是 `Error` 的子类,用于把单一的错误集合在一起。 1. `Promise.all()` 会返回一组兑现值 2. `Promise.race()` 总是返回第一个敲定值(兑现或拒绝) 3. `Promise.any()` 第一个兑现的值 ```ts // Promise.any(iterable); const pErr = new Promise((resolve, reject) => { reject("总是失败"); }); const pSlow = new Promise((resolve, reject) => { setTimeout(resolve, 500, "最终完成"); }); const pFast = new Promise((resolve, reject) => { setTimeout(resolve, 100, "很快完成"); }); Promise.any([ pErr, pSlow, pFast ]).then((value) => { console.log(value); // pFast fulfils first }) // 期望输出:"很快完成" ``` ### WeakRef `WeakRef` 对象允许您保留对另一个对象的弱引用,而不会阻止被弱引用对象被 `GC` 回收 ```ts const ref = new WeakRef(element); const el = ref.deref(); ``` ## [ES2022(ES13)](https://tc39.es/ecma262/2022/) ### Class 扩展 * 类字段定义 > 类字段可以在类的顶层被定义和初始化。在这之前,类的字段定义和初始化是在类的构造函数中完成的 :::tip 类公有字段通过 `Object.defineProperty` 定义 ::: ```ts class Person { name; age: 18; } ``` * 类私有域 > 类属性在默认情况下是公有的,但可以使用增加哈希前缀 `#` 的方法来定义私有类属性 1. 私有字段:包括私有**实例字段**和私有**静态字段** 2. 私有方法:包括私有**实例方法**和私有**静态方法** :::tip `in` 操作符,如果指定的属性/字段在指定的对象/类中,则返回真,并且也能判断私有字段 :::: ```ts class Person { #privateField; static #PRIVATE_STATIC_FIELD; #privateMethod() { return 'hello world'; } static #privateStaticMethod() { return 42; } static hasAttr() { return this.#PRIVATE_STATIC_FIELD in this } } ``` * 类静态域 > 在类字段和私有字段基础上,增加了静态公共字段、静态私有方法和 静态私有字段 ```ts class Person { static name; static #age; static #getAge() { return this.#age } } ``` * 静态块 > 可以访问修改私有静态字段和方法 ```ts class Counter { static #baseNum = 100; static getDoubleBaseNum() { return this.#baseNum * 2; } static { this.#baseNum = 200; } } console.log(Counter.getDoubleBaseNum()); // 400 ``` ### 数组扩展 * Array.prototype.at > 接收一个整数值并返回该索引对应的元素,允许正数和负数。负整数从数组中的最后一个元素开始倒数。 ```ts // arr.at(index) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.at(0)) // apple console.log(cart.at(-1)) // pear ``` ### 字符串扩展 * String.prototype.at > 接受一个整数值,并返回一个新的 `String`,该字符串由位于指定偏移量处的单个 `UTF-16` 码元组成。该方法允许正整数和负整数。负整数从字符串中的最后一个字符开始倒数。 ```ts // str.at(index) const myString = 'Every green bus drives fast.'; console.log(myString.at(0)) // E console.log(myString.at(-1)) // . ``` ### 正则扩展 * `/d` 修饰符 > 利用 `/d` 标识符来表示想要匹配字符串的开始和结束索引 > > > 通过 `/d` 标识符,匹配结果会多出一个属性 `.indices` ```ts const re1 = /a+(z)?/d; const s1 = "xaaaz"; const m1 = re1.exec(s1); console.log(m1.indices[0]); // [1, 5] console.log(s1.slice(...m1.indices[0])); // 'aaaz' console.log(m1.indices[1]); // [4, 5] console.log(s1.slice(...m1.indices[1])); // 'z' ``` ### 对象扩展 * Object.hasOwn > 如果指定的对象自身有指定的属性,则静态方法` Object.hasOwn()` 返回 `true`。如果属性是继承的或者不存在,该方法返回 `false` > > > `Object.hasOwn()` 旨在取代 `Object.prototype.hasOwnProperty()` ```ts // Object.hasOwn(instance, prop) const example = {}; Object.hasOwn(example, 'prop'); // false - 'prop' has not been defined example.prop = 'exists'; Object.hasOwn(example, 'prop'); // true - 'prop' has been defined example.prop = null; Object.hasOwn(example, 'prop'); // true - own property exists with value of null example.prop = undefined; Object.hasOwn(example, 'prop'); // true - own property exists with value of undefined ``` ### Error 扩展 * Error cause > `Error` 实例中的 `cause` 数据属性指示导致该错误的具体原始原因。 在捕获错误时,我们可能会使用更具体或更加实用的信息对错误进行包装,再将其重新抛出。`cause` 属性就用于这一场景,以便仍然可以访问原始的错误。 :::tip 🎉 `Error cause` 是第一个由中国公司(阿里巴巴 `TC39` 代表 `legendecas`)代表主导推动的 `JavaScript` 达到 `stage 4` 的新特性提案! ::: ```ts try { connectToDatabase(); } catch ( err ) { throw new Error('Connecting to database failed.', { cause: err }); } ``` ### Promise 扩展 * 顶层 await > 在模块的顶层,可以单独使用关键字 `await`(异步函数的外面)。也就是说一个模块如果包含用了 `await` 的子模块,该模块就会等待该子模块,这一过程并不会阻塞其他子模块 :::warning 在 `Class` 代码块或非 `async` 函数仍不支持 ::: 任何文件只要导入这个模块,后面的代码就会等待,直到 `fetch` 完成。 ```ts // fetch request const colors = fetch("../data/colors.json").then((response) => response.json()); export default await colors; ``` ## [ES2023(ES14)](https://tc39.es/ecma262/2023/) ### 数组扩展 * Array.prototype.findLast > 接收一个方法返回从尾部查找中符合条件的第一个元素。如果没有找到符合条件的返回 undefined。 ```ts // arr.findLast(fn) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.findLast((x) => x === 'pear')) // pear ``` * Array.prototype.findLastIndex > 接收一个方法返回从尾部查找中符合条件的第一个元素的索引。如果没有找到符合条件的返回 -1。 ```ts // arr.findLastIndex(fn) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.findLastIndex((x) => x === 'pear')) // 2 ``` * Array.prototype.toReversed > 将数组中的元素反转(相对应会改变数组的方法:reverse())。 ```ts // arr.toReversed() const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.toReversed()) // ['pear', 'banana', 'apple'] ``` * Array.prototype.toSorted > 将数组中的元素排序(相对应会改变数组的方法:sort())。 ```ts // arr.toSorted(fn) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.toSorted()) // ['apple', 'banana', 'pear'] ``` * Array.prototype.toSpliced > 从数组中指定位置开始删除指定数量的元素,并可选择在删除后新增新元素(相对应会改变数组的方法:splice())。 ```ts // arr.toSpliced(start, deleteCount, ...items) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.toSpliced(1, 1)) // ['apple', 'pear'] ``` * Array.prototype.with > 将数组索引处的值,替换为新值。 ```ts // arr.with(index, value) const cart = [ 'apple', 'banana', 'pear' ]; console.log(cart.with(2, '')) // ['apple', 'pear', ''] ``` ### 集合扩展 * 支持 WeakMap 使用 Symbol 作为键名 ```ts const map = new WeakMap() const symbol = Symbol('symbol') map.set(symbol, 'DEMO') ``` ### 语法支持 * Hashbang 语法 > Hashbang 注释是一种特殊的注释语法,其行为与单行注释(//)完全一样,只是它以 #! 开头,并且只在脚本或模块的最开始处有效。 > 注意,#! 标志之前不能有任何空白字符。注释由 #! 之后的所有字符组成直到第一行的末尾;只允许有一条这样的注释。 ```js #!/usr/bin/env node console.log("Hello world"); ``` ## [ES2024(ES15)](https://tc39.es/ecma262/2024/) ### ArrayBuffers 扩展 * Atomics.waitAsync > 异步等待共享内存的特定位置并返回一个 Promise。 > \[!WARNING] > 此操作仅适用于基于 SharedArrayBuffer 的 Int32Array 或 BigInt64Array 视图。 ```ts Atomics.waitAsync(typedArray, index, value) Atomics.waitAsync(typedArray, index, value, timeout) ``` ### 正则扩展 * `/v` 标志 > "Verbose mode"(可读性模式)让正则更易读、更易维护 ```ts const reg = new RegExp('d+', 'v') ``` ### Promise 扩展 * Promise.withResolvers > 静态方法返回一个对象,其包含一个新的 Promise 对象和两个函数,用于解决或拒绝它,对应于传入给 Promise() 构造函数执行器的两个参数。 ```ts const { promise, resolve, reject } = Promise.withResolvers(); ``` ### 对象扩展 * Object.groupBy > 根据提供的回调函数返回的**字符串值**对给定可迭代对象中的元素进行分组。 > \[!WARNING] > 分组名称必须是字符串 ```ts // Object.groupBy(obj, fn) const inventory = [ { name: "asparagus", type: "vegetables", quantity: 9 }, { name: "bananas", type: "fruit", quantity: 5 }, { name: "goat", type: "meat", quantity: 23 }, { name: "cherries", type: "fruit", quantity: 12 }, { name: "fish", type: "meat", quantity: 22 }, ]; const restock = { restock: true }; const sufficient = { restock: false }; const result = Object.groupBy(inventory, ({ quantity }) => quantity < 6 ? "restock" : "sufficient", ); console.log(result.restock); // [{ name: "bananas", type: "fruit", quantity: 5 }] ``` ### 集合扩展 * Map.groupBy > 提供的回调函数返回的值对给定可迭代对象中的元素进行分组。 ```ts // Map.groupBy(obj, fn) const inventory = [ { name: "asparagus", type: "vegetables", quantity: 9 }, { name: "bananas", type: "fruit", quantity: 5 }, { name: "goat", type: "meat", quantity: 23 }, { name: "cherries", type: "fruit", quantity: 12 }, { name: "fish", type: "meat", quantity: 22 }, ]; const restock = { restock: true }; const sufficient = { restock: false }; const result = Map.groupBy(inventory, ({ quantity }) => quantity < 6 ? restock : sufficient, ); console.log(result.get(restock)); // [{ name: "bananas", type: "fruit", quantity: 5 }] ``` ### 字符串扩展 * String.prototype.isWellFormed > 返回一个表示该字符串是否包含单独代理项的布尔值。 ```ts // 合法 UTF-16:一个完整的 emoji 表情 const str1 = 'A\uD83D\uDE00B'; // 😀 是代理对 \uD83D\uDE00 console.log(str1.isWellFormed()); // true // 非法 UTF-16:只有一个孤立高位代理项 const str2 = 'A\uD800B'; console.log(str2.isWellFormed()); // false // 非法 UTF-16:只有一个孤立低位代理项 const str3 = 'A\uDC00B'; console.log(str3.isWellFormed()); // false ``` * String.prototype.toWellFormed > 此方法将其中所有的单独代理项被替换为 `Unicode` 替换字符 `U+FFFD`。 ```ts // str.toWellFormed() const str = 'A\uD800B'; // A + lone surrogate + B console.log(str); // 输出时可能看起来正常,但含非法字符 const wellFormed = str.toWellFormed(); console.log(wellFormed); // "A�B" ``` --- --- url: /vite-press/frontend/code/EventLoop.md description: EventLoop --- # {{ $frontmatter.title }} ## Node ::: code-group ```TypeScript import { readFile } from 'node:fs' // 同步 console.log('🟢 [sync] start') // ✅ process.nextTick 微任务 process.nextTick(() => { console.log('🔁 [nextTick]') }) // ✅ Promise 微任务 Promise.resolve().then(() => { console.log('🧩 [Promise.then]') }) // ✅ setTimeout (timers) setTimeout(() => { console.log('⏰ [setTimeout]') // 👀 setImmediate inside setTimeout setImmediate(() => { console.log('⚡ [setImmediate in setTimeout]') }) process.nextTick(() => { console.log('🔁 [nextTick in setTimeout]') }) Promise.resolve().then(() => { console.log('🧩 [Promise in setTimeout]') }) }, 0) // ✅ setImmediate (check) setImmediate(() => { console.log('⚡ [setImmediate]') }) // ✅ fs.readFile (poll) readFile('.', () => { console.log('📄 [fs.readFile callback]') setImmediate(() => { console.log('⚡ [setImmediate in fs.readFile]') }) setTimeout(() => { console.log('⏰ [setTimeout in fs.readFile]') // 👀 setImmediate inside setTimeout setImmediate(() => { console.log('⚡ [setImmediate in fs.readFile - setTimeout]') }) process.nextTick(() => { console.log('🔁 [nextTick in fs.readFile - setTimeout]') }) Promise.resolve().then(() => { console.log('🧩 [Promise in fs.readFile - setTimeout]') }) }, 0) process.nextTick(() => { console.log('🔁 [nextTick in fs.readFile]') }) Promise.resolve().then(() => { console.log('🧩 [Promise in fs.readFile]') }) }) // 同步 console.log('🟢 [sync] end') ``` ```text 🟢 [sync] start 🟢 [sync] end 🧩 [Promise.then] 🔁 [nextTick] ⚡ [setImmediate] ⏰ [setTimeout] 🔁 [nextTick in setTimeout] 🧩 [Promise in setTimeout] ⚡ [setImmediate in setTimeout] 📄 [fs.readFile callback] 🔁 [nextTick in fs.readFile] 🧩 [Promise in fs.readFile] ⚡ [setImmediate in fs.readFile] ⏰ [setTimeout in fs.readFile] 🔁 [nextTick in fs.readFile - setTimeout] 🧩 [Promise in fs.readFile - setTimeout] ⚡ [setImmediate in fs.readFile - setTimeout] ``` ```text 🟢 Sync ├─ console.log('start') └─ console.log('end') 🧩 Promise microtasks └─ [Promise.then] 🔁 nextTick queue └─ [nextTick] ⏰ timers phase └─ [setTimeout] ├─ nextTick → [nextTick in setTimeout] ├─ Promise → [Promise in setTimeout] └─ schedule setImmediate → [setImmediate in setTimeout] ⚡ check phase ├─ [setImmediate] └─ [setImmediate in setTimeout] 📄 poll phase (fs.readFile callback) ├─ nextTick → [nextTick in fs.readFile] ├─ Promise → [Promise in fs.readFile] ├─ setImmediate → [setImmediate in fs.readFile] └─ setTimeout → [setTimeout in fs.readFile] ├─ nextTick → [nextTick in fs.readFile - setTimeout] ├─ Promise → [Promise in fs.readFile - setTimeout] └─ setImmediate → [setImmediate in fs.readFile - setTimeout] ⚡ check phase (fs.readFile 内 setTimeout 排的 setImmediate) └─ [setImmediate in fs.readFile - setTimeout] ``` ::: ## Browser ::: code-group ```TypeScript // 同步 console.log('🟢 [sync] start') // ✅ Promise 微任务 Promise.resolve().then(() => { console.log('🧩 [Promise.then]') }) // ✅ setTimeout (timers) setTimeout(() => { console.log('⏰ [setTimeout]') Promise.resolve().then(() => { console.log('🧩 [Promise in setTimeout]') }) }, 0) // 同步 console.log('🟢 [sync] end') ``` ```text 🟢 [sync] start 🟢 [sync] end 🧩 [Promise.then] ⏰ [setTimeout] 🧩 [Promise in setTimeout] ``` ::: --- --- url: /vite-press/frontend/javascript/fetch.md description: 了解支持promise的HTTP请求方法 --- # Fetch API > `fetch` 是基于 `promise` 的异步获取资源接口,支持 `promise` > 的所有方法,[MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API) ## 参数 > 第一个参数:获取资源的 `URL`。 > 第二个参数:`options` 对象,包括: * `method`: 请求使用的方法,如 `GET、POST`。 * `headers`: 请求的头信息,形式为 `Headers` 的对象或包含 `ByteString` 值的对象字面量。 * `body`: 请求的 `body` 信息:可能是一个 `Blob`、 `BufferSource`、`FormData`、 `URLSearchParams` 或者 `USVString` 对象。**注意 `GET` 或 `HEAD` 方法的请求不能包含 `body` 信息。** * `mode`: 请求的模式,如 `cors`、 `no-cors` 或者 `same-origin`。 * `credentials`: 请求的 `credentials`,如 `omit`、 `same-origin` 或者 `include`。为了在当前域名内自动发送 `cookie` , 必须提供这个选项, 从 `Chrome 50` 开始, 这个属性也可以接受 `FederatedCredential` 实例或是一个 `PasswordCredential` 实例。 * `cache`: 请求的 `cache` 模式: `default`、 `no-store` 、 `reload` 、 `no-cache` 、 `force-cache` 或者 `only-if-cached`。 * `redirect`: 可用的 `redirect` 模式: `follow` (自动重定向), `error` (如果产生重定向将自动终止并且抛出一个错误), 或者 `manual` (手动处理重定向). 在 `Chrome` 中,`Chrome 47` 之前的默认值是 `follow`,从 `Chrome` 47开始是 `manual`。 * `referrer`: 一个 `USVString` ,可以是 `no-referrer` 、 `client` 或一个 `URL`。默认是 `client`。 * `referrerPolicy`: 指定引用 `HTTP` 标头的值。可能是以下之一 `no-referrer`、 `no-referrer-when-downgrade`、 `origin`、 `origin-when-cross-origin`、 `unsafe-url`. * `integrity`: 包括请求的 [subresource integrity](https://developer.mozilla.org/zh-CN/docs/Web/Security/Subresource_Integrity) 值(例如: `sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=`)。 > > 上面诸多 **option** 中,其实常用的就是 **method、headers、body** 以及 **mode** 等 ### headers 可以为 `Headers` 对象,也可以是一个对象字面量,通常情况下对象字面量就足够 ```ts const url = 'https://......'; const headers = { Accept: 'image/jpeg', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }; fetch(url, { headers }).then(function () { // ... }) ``` ### mode * `same-origin` 表示必须同源,绝对禁止跨域,这个是老版本浏览器默认的安全策略。 * `cors` 表示允许跨域,顾名思义它是以 `CORS` 的形式跨域;当然该模式也可以同域请求。只需要服务器的响应头中带有 `Access-Control-Allow-Origin: *` 就行。 * `no-cors` 这个就很特殊了,字面意思是禁止以 `CORS` 的形式跨域,其实它的效果是,对外域的请求可以发送,外域服务器无论设不设 `Access-Control-Allow-Origin: *` 都会接收请求并处理请求,但是浏览器不接收响应,即使外域返回了内容,浏览器也当做没接到。 这个 `no-cors` 的用途是保证数据安全。 ## Response 传给 `then` 的回调函数可以接收一个参数,这个参数就是 `Response`,它的属性基本上是只读属性。 * `Response.clone()` 创建一个 `Response` 对象的克隆 * `Response.error()` 返回一个绑定了网络错误的新的 `Response` 对象 * `Response.redirect()` 用另一个URL创建一个新的 `Response`. * `Response.arrayBuffer()` 读取 `Response` 对象并且将它设置为已读(因为 `Responses` 对象被设置为了 `stream` 的方式,所以它们只能被读取一次) ,并返回一个被解析为 `ArrayBuffer` 格式的 `promise` 对象 * `Response.blob()` 读取 `Response` 对象并且将它设置为已读(因为 `Responses` 对象被设置为了 `stream` 的方式,所以它们只能被读取一次) ,并返回一个被解析为 `Blob` 格式的 `promise` 对象 * `Response.formData()` 读取 `Response` 对象并且将它设置为已读(因为 `Responses` 对象被设置为了 `stream` 的方式,所以它们只能被读取一次) ,并返回一个被解析为 `FormData` 格式的 `promise` 对象 * `Response.json()` 读取 `Response` 对象并且将它设置为已读(因为 `Responses` 对象被设置为了 `stream` 的方式,所以它们只能被读取一次) ,并返回一个被解析为 `JSON` 格式的 `promise` 对象 * `Response.text()` 读取 `Response` 对象并且将它设置为已读(因为 `Responses` 对象被设置为了 `stream` 的方式,所以它们只能被读取一次) ,并返回一个被解析为 `USVString` 格式的 `promise` 对象 --- --- url: /vite-press/frontend/html.md --- --- --- url: /vite-press/frontend/html/misc.md --- # {{ $frontmatter.title }} ## 浏览器地址栏 ### 运行 JavaScript > 浏览器地址栏可以直接运行 `JavaScript` 代码,做法是以 `javascript:` 开头后跟要执行的语句。 ```js javascript:alert('hello from address bar :)'); ``` ### 运行 HTML > 非IE内核的浏览器地址栏可以直接运行HTML代码!做法是以 `data:text/html,` 开头后跟要显示的标签。 ```html data:text/html,

Hello, world!

``` ```html data:text/html, ``` ## 实时 style ```html Document title ``` ## Web开发技巧 [Web开发技巧](https://www.runoob.com/w3cnote/web-app-tips.html) --- --- url: /vite-press/interview/03-HTML && CSS.md --- # {{ $frontmatter.title }} ## 🟢 初级(基础掌握) ### ✅ HTML5 基础 #### 1. HTML5 有哪些新特性? * 语义化标签:`
`, `