From b7b2d152556112d97d4d131d5a1b5e2efa3672e1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 14:43:33 +0000 Subject: [PATCH] Deploy to GitHub pages --- 404.html | 33 + assets/01.html-3a34044f.js | 1 + assets/01.html-4acb4a17.js | 1 + assets/01.html-5c5f6cc2.js | 1 + assets/01.html-805a6dfc.js | 22 + assets/02.html-47ac2807.js | 1 + assets/02.html-9d224a5a.js | 1 + assets/02.html-b7bb51d8.js | 15 + assets/02.html-d142d1c1.js | 1 + assets/03.html-6e4a25f7.js | 1 + assets/03.html-ae256909.js | 118 ++ assets/04.html-c3176eab.js | 28 + assets/04.html-cf0be750.js | 1 + assets/05.html-577620a2.js | 76 + assets/05.html-ddabefdf.js | 1 + assets/06.html-907b0bed.js | 1 + assets/06.html-a31227f4.js | 314 ++++ assets/07.html-04baef94.js | 1 + assets/07.html-3af3ff8d.js | 246 +++ assets/08.html-80458ad2.js | 317 ++++ assets/08.html-ce11059a.js | 1 + assets/09.html-4c52d85e.js | 81 + assets/09.html-e58cbdae.js | 1 + assets/10.html-20b3ccad.js | 1 + assets/10.html-92f88bd8.js | 1 + assets/404.html-aae12503.js | 1 + assets/404.html-f9875e7b.js | 1 + assets/ali-iconfont.html-60c01382.js | 78 + assets/ali-iconfont.html-7f7a09e2.js | 1 + assets/app-142ed948.js | 10 + assets/assembly-line-544d32be.png | Bin 0 -> 184474 bytes assets/back-to-top-8efcbe56.svg | 1 + assets/default-serve-mux-01211d08.png | Bin 0 -> 1147214 bytes assets/file-server-demo-b798ce94.png | Bin 0 -> 11095 bytes assets/git.html-3b96bfc9.js | 1 + assets/git.html-9353183f.js | 24 + assets/handler-dbd08f93.png | Bin 0 -> 186199 bytes assets/index.html-05ab2485.js | 1 + assets/index.html-0a28c9d4.js | 1 + assets/index.html-0c2aaaa6.js | 1 + assets/index.html-0cc578c7.js | 1 + assets/index.html-0f265d49.js | 105 ++ assets/index.html-126cc323.js | 1 + assets/index.html-29da378b.js | 1 + assets/index.html-3757d620.js | 1 + assets/index.html-380c9dcf.js | 1 + assets/index.html-394fa650.js | 1 + assets/index.html-3a7a3c33.js | 1 + assets/index.html-55586ed8.js | 1 + assets/index.html-6ae52046.js | 1 + assets/index.html-71b8f972.js | 1 + assets/index.html-7999330b.js | 526 ++++++ assets/index.html-80dba622.js | 1 + assets/index.html-81ef2a0a.js | 1496 ++++++++++++++++ assets/index.html-9d2db2ec.js | 13 + assets/index.html-ae208ab7.js | 58 + assets/index.html-b7b4e1e2.js | 1 + assets/index.html-beb4b1c4.js | 1 + assets/index.html-c32cb8cd.js | 1 + assets/index.html-dc3dd49a.js | 1 + assets/index.html-dd18e07a.js | 1 + assets/index.html-e05eac13.js | 1 + assets/index.html-e3f8779d.js | 1 + assets/json-server.html-1754c8fe.js | 30 + assets/json-server.html-c81eb9f6.js | 1 + assets/linked-list-890353c2.png | Bin 0 -> 92282 bytes assets/more-handler-e3085276.png | Bin 0 -> 1694396 bytes assets/style-24736ef4.css | 1 + backend/go/base/index.html | 1528 +++++++++++++++++ backend/go/index.html | 33 + backend/go/web/index.html | 558 ++++++ favicon.ico | Bin 0 -> 15406 bytes frontend/engineering/umi/index.html | 137 ++ .../framework/react/redux-mobx/index.html | 90 + frontend/framework/we-app/01/index.html | 33 + frontend/framework/we-app/index.html | 33 + frontend/js/red-book/01.html | 33 + frontend/js/red-book/02.html | 47 + frontend/js/red-book/03.html | 150 ++ frontend/js/red-book/04.html | 60 + frontend/js/red-book/05.html | 108 ++ frontend/js/red-book/06.html | 346 ++++ frontend/js/red-book/07.html | 278 +++ frontend/js/red-book/08.html | 349 ++++ frontend/js/red-book/09.html | 113 ++ frontend/js/red-book/10.html | 33 + frontend/js/red-book/index.html | 33 + frontend/other/errors/index.html | 45 + frontend/other/tools/ali-iconfont.html | 110 ++ frontend/other/tools/git.html | 56 + frontend/other/tools/index.html | 33 + frontend/other/tools/json-server.html | 62 + general/algorithm/01.html | 54 + general/algorithm/02.html | 33 + general/algorithm/index.html | 33 + general/network/index.html | 33 + imgs/logo.png | Bin 0 -> 17088 bytes index.html | 33 + 98 files changed, 8087 insertions(+) create mode 100644 404.html create mode 100644 assets/01.html-3a34044f.js create mode 100644 assets/01.html-4acb4a17.js create mode 100644 assets/01.html-5c5f6cc2.js create mode 100644 assets/01.html-805a6dfc.js create mode 100644 assets/02.html-47ac2807.js create mode 100644 assets/02.html-9d224a5a.js create mode 100644 assets/02.html-b7bb51d8.js create mode 100644 assets/02.html-d142d1c1.js create mode 100644 assets/03.html-6e4a25f7.js create mode 100644 assets/03.html-ae256909.js create mode 100644 assets/04.html-c3176eab.js create mode 100644 assets/04.html-cf0be750.js create mode 100644 assets/05.html-577620a2.js create mode 100644 assets/05.html-ddabefdf.js create mode 100644 assets/06.html-907b0bed.js create mode 100644 assets/06.html-a31227f4.js create mode 100644 assets/07.html-04baef94.js create mode 100644 assets/07.html-3af3ff8d.js create mode 100644 assets/08.html-80458ad2.js create mode 100644 assets/08.html-ce11059a.js create mode 100644 assets/09.html-4c52d85e.js create mode 100644 assets/09.html-e58cbdae.js create mode 100644 assets/10.html-20b3ccad.js create mode 100644 assets/10.html-92f88bd8.js create mode 100644 assets/404.html-aae12503.js create mode 100644 assets/404.html-f9875e7b.js create mode 100644 assets/ali-iconfont.html-60c01382.js create mode 100644 assets/ali-iconfont.html-7f7a09e2.js create mode 100644 assets/app-142ed948.js create mode 100644 assets/assembly-line-544d32be.png create mode 100644 assets/back-to-top-8efcbe56.svg create mode 100644 assets/default-serve-mux-01211d08.png create mode 100644 assets/file-server-demo-b798ce94.png create mode 100644 assets/git.html-3b96bfc9.js create mode 100644 assets/git.html-9353183f.js create mode 100644 assets/handler-dbd08f93.png create mode 100644 assets/index.html-05ab2485.js create mode 100644 assets/index.html-0a28c9d4.js create mode 100644 assets/index.html-0c2aaaa6.js create mode 100644 assets/index.html-0cc578c7.js create mode 100644 assets/index.html-0f265d49.js create mode 100644 assets/index.html-126cc323.js create mode 100644 assets/index.html-29da378b.js create mode 100644 assets/index.html-3757d620.js create mode 100644 assets/index.html-380c9dcf.js create mode 100644 assets/index.html-394fa650.js create mode 100644 assets/index.html-3a7a3c33.js create mode 100644 assets/index.html-55586ed8.js create mode 100644 assets/index.html-6ae52046.js create mode 100644 assets/index.html-71b8f972.js create mode 100644 assets/index.html-7999330b.js create mode 100644 assets/index.html-80dba622.js create mode 100644 assets/index.html-81ef2a0a.js create mode 100644 assets/index.html-9d2db2ec.js create mode 100644 assets/index.html-ae208ab7.js create mode 100644 assets/index.html-b7b4e1e2.js create mode 100644 assets/index.html-beb4b1c4.js create mode 100644 assets/index.html-c32cb8cd.js create mode 100644 assets/index.html-dc3dd49a.js create mode 100644 assets/index.html-dd18e07a.js create mode 100644 assets/index.html-e05eac13.js create mode 100644 assets/index.html-e3f8779d.js create mode 100644 assets/json-server.html-1754c8fe.js create mode 100644 assets/json-server.html-c81eb9f6.js create mode 100644 assets/linked-list-890353c2.png create mode 100644 assets/more-handler-e3085276.png create mode 100644 assets/style-24736ef4.css create mode 100644 backend/go/base/index.html create mode 100644 backend/go/index.html create mode 100644 backend/go/web/index.html create mode 100644 favicon.ico create mode 100644 frontend/engineering/umi/index.html create mode 100644 frontend/framework/react/redux-mobx/index.html create mode 100644 frontend/framework/we-app/01/index.html create mode 100644 frontend/framework/we-app/index.html create mode 100644 frontend/js/red-book/01.html create mode 100644 frontend/js/red-book/02.html create mode 100644 frontend/js/red-book/03.html create mode 100644 frontend/js/red-book/04.html create mode 100644 frontend/js/red-book/05.html create mode 100644 frontend/js/red-book/06.html create mode 100644 frontend/js/red-book/07.html create mode 100644 frontend/js/red-book/08.html create mode 100644 frontend/js/red-book/09.html create mode 100644 frontend/js/red-book/10.html create mode 100644 frontend/js/red-book/index.html create mode 100644 frontend/other/errors/index.html create mode 100644 frontend/other/tools/ali-iconfont.html create mode 100644 frontend/other/tools/git.html create mode 100644 frontend/other/tools/index.html create mode 100644 frontend/other/tools/json-server.html create mode 100644 general/algorithm/01.html create mode 100644 general/algorithm/02.html create mode 100644 general/algorithm/index.html create mode 100644 general/network/index.html create mode 100644 imgs/logo.png create mode 100644 index.html diff --git a/404.html b/404.html new file mode 100644 index 0000000..fde24c2 --- /dev/null +++ b/404.html @@ -0,0 +1,33 @@ + + +
+ + + + + +小结
1. JavaScript 由哪三部分组成?
2. JavaScript 和 ECMAScript 有什么关系?
ECMAScript 是 JavaScript 的标准化规范,JavaScript 是 ECMAScript 的一个实现
JavaScript 不限于 ECMA-262 所定义的那样,它包含以下几个部分:
ECMA-262 定义了什么?
ECMAScript 是实现 ECMA-262 这个规范描述的所有方面的一门语言,JavaScript 和 Adobe ActionScript 都实现了 ECMAScript
Web 浏览器是 ECMAScript 的一种宿主环境,宿主环境提供 ECMAScript 的基准实现和与环境自身交互必需的扩展,扩展(如 DOM)使用 ECMAScript 的核心类型和语法提供特定于环境的额外功能
文档对象模型(Document Object Model)是一个应用编程接口(API),DOM 通过创建表示文档的树,让开发者可以控制网页的结构和内容,使用 DOM API 可以轻松删除、添加、替换、修改节点
浏览器对象模型 BOM,用于支持访问和操作浏览器的窗口
',16),p=[e];function l(d,o){return a(),t("div",null,p)}const n=i(c,[["render",l],["__file","01.html.vue"]]);export{n as default}; diff --git a/assets/01.html-4acb4a17.js b/assets/01.html-4acb4a17.js new file mode 100644 index 0000000..626608e --- /dev/null +++ b/assets/01.html-4acb4a17.js @@ -0,0 +1 @@ +const e=JSON.parse('{"key":"v-415e2210","path":"/frontend/js/red-book/01.html","title":"红宝书","lang":"zh-CN","frontmatter":{"title":"红宝书","prev":{"text":"目录","link":"./README.md"},"next":{"text":"HTML 中的 JavaScript","link":"./02.md"}},"headers":[{"level":2,"title":"什么是 JavaScript","slug":"什么是-javascript","link":"#什么是-javascript","children":[{"level":3,"title":"历史","slug":"历史","link":"#历史","children":[]},{"level":3,"title":"JavaScript 实现","slug":"javascript-实现","link":"#javascript-实现","children":[{"level":4,"title":"ECMAScript","slug":"ecmascript","link":"#ecmascript","children":[]},{"level":4,"title":"DOM","slug":"dom","link":"#dom","children":[]},{"level":4,"title":"BOM","slug":"bom","link":"#bom","children":[]}]}]}],"git":{"updatedTime":1687690350000},"filePathRelative":"frontend/js/red-book/01.md"}');export{e as data}; diff --git a/assets/01.html-5c5f6cc2.js b/assets/01.html-5c5f6cc2.js new file mode 100644 index 0000000..ec37027 --- /dev/null +++ b/assets/01.html-5c5f6cc2.js @@ -0,0 +1 @@ +const l=JSON.parse('{"key":"v-6b452f98","path":"/general/algorithm/01.html","title":"数据结构与算法","lang":"zh-CN","frontmatter":{"title":"数据结构与算法","prev":{"text":"目录","link":"./README.md"}},"headers":[{"level":2,"title":"基础","slug":"基础","link":"#基础","children":[{"level":3,"title":"JS 语法","slug":"js-语法","link":"#js-语法","children":[{"level":4,"title":"数组","slug":"数组","link":"#数组","children":[]}]},{"level":3,"title":"常见数据结构","slug":"常见数据结构","link":"#常见数据结构","children":[]},{"level":3,"title":"二叉树的递归遍历","slug":"二叉树的递归遍历","link":"#二叉树的递归遍历","children":[]},{"level":3,"title":"评价算法能力","slug":"评价算法能力","link":"#评价算法能力","children":[]}]}],"git":{"updatedTime":1699368149000},"filePathRelative":"general/algorithm/01.md"}');export{l as data}; diff --git a/assets/01.html-805a6dfc.js b/assets/01.html-805a6dfc.js new file mode 100644 index 0000000..ffc5b5c --- /dev/null +++ b/assets/01.html-805a6dfc.js @@ -0,0 +1,22 @@ +import{_ as n,o as s,c as a,e as t}from"./app-142ed948.js";const p="/study-notes/assets/linked-list-890353c2.png",o={},e=t(`创建一个长度确定、同时每一个元素的值也都确定的数组
const arr = new Array(10).fill(0);
+
增加/删除元素的三种方法
注意
const preorder = (root) => {
+ if (!root) return;
+ console.log("当前遍历的节点是:", root.val);
+ preorder(root.left);
+ preorder(root.right);
+};
+
+const inorder = (root) => {
+ if (!root) return;
+ inorder(root.left);
+ console.log("当前遍历的节点是:", root.val);
+ inorder(root.right);
+};
+
+const postorder = (root) => {
+ if (!root) return;
+ postorder(root.left);
+ postorder(root.right);
+ console.log("当前遍历的节点是:", root.val);
+};
+
时间复杂度(常见的如下):反映算法对应的执行总次数的一个变化趋势
O(1)
O(logn)
O(n)
O(nlogn)
O(n^2)
O(n^3)
O(2^n)
空间复杂度(常见的如下):对一个算法在运行过程中临时占用存储空间大小的量度,反映内存增长的趋势
小结
1. script 元素有哪些属性?
async、defer、type、src、crossorigin、integrity
2. noscript
当浏览器不支持脚本或禁用脚本时,noscript 元素会显示出来
<script>
元素有以下属性:
使用 <script>
的方式有两种:
<script>
+ function sayScript() {
+ // 出现字符串 </script> 时,需要转义
+ console.log('<\\/script>')
+ }
+</script>
+
注:使用了 src 属性的 <script>
元素不应该在其 <script>
和 </script>
标签之间再包含额外的 JavaScript 代码,否则会忽略这些额外的代码
<head>
元素中<body>
元素中的页面内容后面在 <script>
元素中设置 defer 属性(只适用于外部脚本),告诉浏览器立即下载,但延迟执行(解析到 </html>
后)
HTML5 规范要求脚本按照它们出现的先后顺序执行,因此第一个延迟脚本会先于第二个延迟脚本执行,而这两个脚本会先于 DOMContentLoaded 事件执行。但是延迟脚本不一定会按照顺序执行,也不一定会在 DOMContentLoaded 事件触发前执行,因此最好只包含一个延迟脚本
在 <script>
元素中设置 async 属性(只适用于外部脚本),告诉浏览器立即下载,与 defer 的区别是,异步脚本不保证按照它们的先后顺序执行
给脚本添加 async 属性的目的是告诉浏览器,不必等待其他脚本,也不必阻塞文档呈现,立即下载并执行脚本。从这个意义上讲,标记为 async 的脚本不应该在加载期间修改 DOM。异步脚本一定会在页面的 load 事件前执行,但可能会在 DOMContentLoaded 事件触发之前或之后执行
通过向 DOM 中动态添加 <script>
元素加载指定的脚本
const script = document.createElement('script')
+script.src = 'gibberish.js'
+document.head.appendChild(script)
+
默认情况下,动态添加的脚本是异步执行的(相当于 async 为 true)
因为所有浏览器都支持 createElement() 方法,但不是所有浏览器都支持 async 属性,因此如果要统一动态脚本的加载行为,可以明确将其设置为同步加载:
const script = document.createElement('script')
+script.src = 'gibberish.js'
+script.async = false
+document.head.appendChild(script)
+
以这种方式获取的资源对浏览器预加载器是不可见的,严重影响它们在资源获取队列中的优先级,可能会影响性能
要想让预加载器知道这些动态请求文件的存在,可以在文档头部显示生声明它们:
<link rel="preload" href="gibberish.js" />
+
<noscript>
元素可以包含任何可以出现在 <body>
元素中的 HTML 元素,它的作用是提供替代内容,只有以下情况下才会显示:
小结
1. 语法特点?
//
,多行注释 /* */
{}
2. let、var 和 const
3. 数据类型
ECMAScript 标准定义了 8 种数据类型:
4. null 和 undefined
5. 转布尔值为 false 的值''
、0
、NaN
、null
、undefined
6. 转数值
以下三个函数最终得到的都是十进制数或者 NaN
Number()
:
''
转为 0NaN
valueOf()
方法,然后依照前面的规则转换返回的值。如果转换的结果是 NaN
,则调用对象的 toString()
方法,然后再次依照前面的规则转换返回的值parseInt()
区别与 Number()
:
''
转为 NaNparseFloat()
区别与 parseInt()
:
console.log(parseFloat('0x6')) // 0
7. 转字符串
ECMA-262 以一个名为 ECMAScript 的伪语言(pseudo language)的形式,定义了 JavaScript 的所有这些方面
Number 类型使用 IEEE754 格式来表示整数和浮点值
超出 Number.MAX_VALUE
的值会被转换为 Infinity
isFinite() 函数可以用来判断一个数值是否有限
const result = Number.MAX_VALUE + Number.MAX_VALUE
+console.log(isFinite(result)) // false
+
NaN 是一个特殊的数值,表示一个本来要返回数值的操作失败了
console.log(0 / 0) // NaN
+console.log(Infinity / Infinity) // NaN
+console.log(5 / 0) // Infinity
+
+console.log(NaN == NaN) // false
+
+// isNaN() 函数可以用来判断一个数值是否是 NaN
+console.log(isNaN(NaN)) // true
+
String 类型表示零或多个 16 位 Unicode 字符序列
模板字面量标签函数:
const a = 6
+const b = 9
+
+const zipTag = (strings, ...expressions) => {
+ console.log(strings)
+ console.log(expressions)
+ return expressions.reduce((prev, cur, i) => {
+ return prev + cur + strings[i + 1]
+ }, strings[0])
+}
+const taggedResult = zipTag\`\${a} + \${b} = \${a + b}\`
+
+console.log(taggedResult)
+
String.raw() 函数:获取原始字符串
console.log(String.raw\`Hi\\n\${2 + 3}!\`) // Hi\\n5!
+
Symbol 的用途是确保对象属性使用唯一标识符,没有字面量语法
使用 Symbol() 函数来初始化
在全局符号注册表中创建并重用符号,使用 Symbol.for() 函数
Symbol.keyFor() 函数
const s1 = Symbol.for('foo')
+console.log(Symbol.keyFor(s1)) // foo
+
凡是可以使用字符串或者数值作为属性的地方,都可以使用符号
const s1 = Symbol('foo')
+const s2 = Symbol('bar')
+const s3 = Symbol('baz')
+
+const o = { [s1]: 'foo val' }
+Object.defineProperty(o, s2, { value: 'bar val' })
+Object.defineProperties(o, {
+ [s3]: { value: 'baz val' },
+})
+console.log(o)
+
const s1 = Symbol('foo')
+const s2 = Symbol('bar')
+
+const o = {
+ [s1]: 'foo val',
+ [s2]: 'bar val',
+ baz: 'baz val',
+ qux: 'qux val',
+}
+
+// ['baz', 'qux']
+console.log(Object.getOwnPropertyNames(o))
+
+// [Symbol(foo), Symbol(bar)]
+console.log(Object.getOwnPropertySymbols(o))
+
+/**
+{
+ "baz": {
+ "value": "baz val",
+ "writable": true,
+ "enumerable": true,
+ "configurable": true
+ },
+ "qux": {...},
+ Symbol(bar): {...},
+ Symbol(foo): {...}
+}
+ */
+console.log(Object.getOwnPropertyDescriptors(o))
+
+// ['baz', 'qux', Symbol(foo), Symbol(bar)]
+console.log(Reflect.ownKeys(o))
+
每个 Object 实例都有如下属性和方法:
递增 ++
、递减 --
操作符
后缀版与前缀版的主要区别在于,后缀版递增和递减语在语句被求值后再发生
作用于非数值时,会先使用 Number() 函数将其转换为数值,再进行操作
一元加和减
ECMAScript 中的所有数值都以 IEEE754 64 位格式存储,但是位操作符先把数值转换为 32 位整数,再进行操作,最后再将结果转换回 64 位
`,28),r=s("strong",null,"符号位",-1),d=a(`正值以真正的二进制格式存储,负值则以二进制补码形式存储
位操作应用到非数值,首先会使用 Number() 函数将该值转换为数值,然后再应用位操作
ECMAScript 中的所有整数都表示为有符号数。特殊值 NaN 和 Infinity 在位操作中都会被当成 0
按位非(~)
按位与(&)
按位或(|)
按位异或(^)
左移(<<)
有符号的右移(>>)
无符号右移(>>>)
逻辑非(!)
逻辑与(&&)
逻辑或(||)
如果操作数不是数值,会先使用 Number() 函数将其转换为数值,再进行操作
乘法(*)
除法(/)
求模(%)
指数操作符(**)
console.log(3 ** 2) // 9
squared **= 2 // 指数赋值操作符
加法操作符(+)
减法操作符(-)
<
、>
、<=
、>=
相等和不相等(== 和 !=)
全等和不全等(=== 和 !==)
variable = boolean_expression ? true_value : false_value
简单赋值用 =
表示
每个数学操作符以及其他一些操作符都有对应的复合赋值操作符,如 +=
、-=
、*=
、/=
、%=
、**=
、<<=
、>>=
、>>>=
逗号操作符可以用来在一条语句中执行多个操作,如
let num1 = 1,
+ num2 = 2,
+ num3 = 3
+
在赋值语句中,逗号操作符会返回表达式中的最后一项,如
let num = (5, 1, 4, 8, 0) // num 的值为 0
+
if (expression) statement1 else statement2
+
do {
+ statement
+} while (expression)
+
while (expression) statement
+
for (initialization; expression; post - loop - expression) statement
+
for (property in expression) statement
+
ECMAScript 中对象的属性是无序的,因此通过 for-in 循环输出的属性名的顺序是不可预测的。换句话说,所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异
for (variable of object) statement
+
for-of 循环会按照可迭代对象的 next() 方法产生值的顺序迭代元素。如果尝试迭代的变量不支持迭代,则会抛出错误
用于给语句加标签
label: statement
+
在下面的例子中, start 是一个标签,可以在后面通过 break 或 continue 语句引用它
start: for (let i = 0; i < count; i++) {
+ console.log(i)
+}
+
break 和 continue 都可以与标签语句一起使用,返回代码中特定的位置。通常是在嵌套循环中,如:
let num = 0
+outermost: for (let i = 0; i < 10; i++) {
+ for (let j = 0; j < 10; j++) {
+ if (i === 5 && j === 5) {
+ break outermost
+ }
+ num++
+ }
+}
+console.log(num) // 55
+
严格模式下不允许使用 with 语句
with 语句影响性能且难于调试其中的代码,因此不建议使用
with 语句的作用是将代码的作用域设置到一个特定的对象中
with (expression) statement
+
使用 with 语句的主要场景是针对一个对象反复操作,如:
let qs = location.search.substring(1)
+let hostName = location.hostname
+let url = location.href
+
上面的每一行都包含 location 对象,如果使用 with 语句,可以简化为:
with (location) {
+ let qs = search.substring(1)
+ let hostName = hostname
+ let url = href
+}
+
switch (expression) {
+ case value1:
+ statement
+ break
+ case value2:
+ statement
+ break
+ case value3:
+ statement
+ break
+ default:
+ statement
+}
+
最佳实践是函数要么返回值,要么不返回值。只在某个条件下返回值的函数会带来麻烦,尤其是调试时
严格模式对函数有一些限制:
result = variable instanceof constructor
执行上下文(execution context,EC):JavaScript 代码被解析和执行时所在环境的抽象概念
全局上下文是最外层的上下文,根据 ECMAScript 实现的宿主环境,表示全局上下文的对象可能不一样,浏览器中是 window 对象,Node.js 中是 global 对象
上下文在其所有代码都执行完毕后会被销毁,包括定义在上下文中的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)
上下文中的代码在执行的时候,会创建变量对象的一个作用域链,这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序
代码正在执行的上下文变量对象始终位于作用域链最前端,全局上下文的变量对象始终位于作用域链的最末端
如果是函数上下文,其活动对象(activation object,AO)用作变量对象
函数参数被认为是当前上下文中的变量
虽然执行上下文主要有全局上下文和函数上下文两种(eval() 调用内部存在第三种上下文),但有其他方式来增强作用域链
某些语句会导致在作用域链前端临时添加一个变量对象,该变量对象会在代码执行后被移除
严格来讲, let 在 JavaScript 运行时中也会被提升,但由于暂时性死区(temporal dead zone,TDZ)的存在,直到执行到 let 语句时,变量才会被添加到执行上下文中
由于 const 声明暗示变量的值是单一类型且不可修改,JavaScript 运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找。谷歌的 V8 引擎就执行这种优化
JavaScript 是使用垃圾回收的语言,执行环境负责在代码执行时管理内存,这个过程是周期性的,垃圾回收程序每隔一定时间(或者说在代码执行过程中某个预定的收集时间)就会自动运行
JavaScript 通过自动内存管理实现内存分配和闲置资源回收
JavaScript 最常用的垃圾回收策略是标记清理。当变量进入上下文,这个变量就会被加上存在于上下文中的标记,当变量离开上下文时,就会被加上离开上下文的标记
垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记就是待删除的了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并回收它们所占用的内存空间
引用计数的思路是对每个值都记录它被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是 1。如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收程序下次再运行时,它就会释放那些引用次数为 0 的值所占用的内存
引用计数的问题在于循环引用,两个对象相互引用,导致它们的引用次数都不为 0,所以垃圾回收程序不会回收它们占用的内存,比如:
function problem() {
+ var objectA = new Object()
+ var objectB = new Object()
+ objectA.someOtherObject = objectB
+ objectB.anotherObject = objectA
+}
+
如果数据不再必要,最好通过将其值设置为 null 来释放其引用,这个做法叫解除引用。这个建议最适合全局变量和全局对象的属性,局部变量在超出作用域后会被自动解除引用,比如
function createPerson(name) {
+ var localPerson = new Object()
+ localPerson.name = name
+ return localPerson
+}
+var globalPerson = createPerson('Nicholas')
+// 手动解除引用
+globalPerson = null
+
引用值(或者对象)是某个特定引用类型的实例
Date 对象基于 Unix Time Stamp,即自 1970 年 1 月 1 日(UTC)起经过的毫秒数
new Date(); // 实例化时刻的日期和时间
+new Date(value); // value 表示 1970 年 1 月 1 日(UTC)起经过的毫秒数
+new Date(dateString); // dateString 表示日期字符串,该字符串应该能被 Date.parse() 正确方法识别
+new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]);
+
Date 类型有几个专门用于格式化日期的方法,它们都会返回字符串
`,12),r={href:"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date#%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95",target:"_blank",rel:"noopener noreferrer"},k=e(`这些方法的输出与 toLocaleString() 和 toString() 一样,会因浏览器而异,因此不能用于在用户界面上一致的显示日期
let expression = /pattern/flags;
+
每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法
引用类型与原始包装类型的主要区别在于对象的生命周期。在通过 new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间。这意味着不能在运行时给原始值添加属性和方法,比如:
let name = 'Nicholas'
+name.age = 27
+console.log(name.age) // undefined
+
可以显示地使用 Boolean、Number 和 String 创建原始值包装对象,实例上调用 typeof 会返回 object
原始值和包装对象之间的区别:
要创建一个 Boolean 对象,就使用 Boolean 构造函数并传入 true 或 false
let booleanObject = new Boolean(true)
+
要创建一个 Number 对象,就使用 Number 构造函数并传入数值
let numberObject = new Number(10)
+
继承的方法:
let num = 10
+console.log(num.toString()) // '10'
+console.log(num.toString(2)) // '1010'
+console.log(num.toString(8)) // '12'
+console.log(num.toString(10)) // '10'
+console.log(num.toString(16)) // 'a'
+
格式化数值:
isInteger() 方法与安全整数:
console.log(Number.isInteger(1)) // true
+console.log(Number.isInteger(1.0)) // true
+console.log(Number.isInteger(1.1)) // false
+
console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER)) // true
+console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1)) // false
+
要创建一个 String 对象,就使用 String 构造函数并传入字符串
let stringObject = new String('hello world')
+
继承的方法:
1. JavaScript 字符串
JavaScript 字符串由 16 位码元(code unit)组成。对于大多数字符,每个码元对应一个字符
对于 U+0000~U+FFFF 范围内的字符,上面的属性和方法返回的结果都跟预期是一样的
16 位只能唯一表示 2^16 个字符,这对于大多数语言字符集是足够了,在 Unicode 中称为基本多语言平面(BMP)
为了表示更多的字符,Unicode 采用了一个策略,即每个字符使用另外 16 位去选择一个增补平面。这种每个字符使用两个 16 位码元的策略称为代理对(surrogate pair)
在涉及增补平面的字符时,前面讨论的字符串方法和属性就会出问题
// "smiling face with smiling eyes" 表情符号的码点是 U+1F60A
+// 0x1F60A === 128522
+let message = 'ab😊de'
+console.log(message.length) // 6
+console.log(message.charAt(1)) // 'b'
+console.log(message.charAt(2)) // '�'
+console.log(message.charAt(3)) // '�'
+console.log(message.charAt(4)) // 'd'
+
+console.log(message.charCodeAt(1)) // 98
+console.log(message.charCodeAt(2)) // 55357
+console.log(message.charCodeAt(3)) // 56842
+console.log(message.charCodeAt(4)) // 100
+
+console.log(String.fromCharCode(0x1f60a)) // '�'
+console.log(String.fromCodePoint(0x1f60a)) // '😊'
+console.log(String.fromCharCode(97, 98, 55357, 56842, 100, 101)) // 'ab😊de'
+
码点是 Unicode 中一个字符的完整标识
迭代字符串可以智能地识别代理对的码点:
console.log([...'ab😊de']) // ['a', 'b', '😊', 'd', 'e']
+
2. normalize() 方法
某些 Unicode 字符可以有多种编码方式,多种形式间使用 === 的结果为 false。为解决这个问题,ES6 提供了 normalize() 方法,用于将字符的不同表示方法统一为同样的形式,使用时需要传入表示哪种形式的字符串:"NFC"、"NFD"、"NFKC"、"NFKD"
3. 字符串操作方法
4. 字符串位置方法
5. 字符串包含方法
6. trim() 方法
去除字符串两端的空格,返回新字符串
trimLeft() / trimRight(),去除字符串左边 / 右边的空格,返回新字符串
7. repeat() 方法
接收一个整数参数,表示将原字符串重复多少次,返回新字符串
8. padStart() / padEnd() 方法
两个方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件
第一个参数是长度,第二个参数是可选的填充字符,默认为空格
9. 字符串迭代与解构
字符串的原型上暴露了一个 @@iterator 方法,表示可迭代字符串中的每个字符
手动使用迭代器:
let message = 'ab😊de'
+let iterator = message[Symbol.iterator]()
+console.log(iterator.next()) // { value: 'a', done: false }
+console.log(iterator.next()) // { value: 'b', done: false }
+console.log(iterator.next()) // { value: '😊', done: false }
+console.log(iterator.next()) // { value: 'd', done: false }
+console.log(iterator.next()) // { value: 'e', done: false }
+console.log(iterator.next()) // { value: undefined, done: true }
+
在 for-of 循环中可以通过这个迭代器按序访问每个字符:
let message = 'ab😊de'
+for (let c of message) {
+ console.log(c)
+}
+// 'a'
+// 'b'
+// '😊'
+// 'd'
+// 'e'
+
字符串也可以通过解构赋值的方式进行迭代:
let message = 'ab😊de'
+let [a, b, c, d, e] = message
+console.log(a, b, c, d, e) // 'a' 'b' '😊' 'd' 'e'
+
10. 字符串大小写转换
如果不知道代码涉及什么语言,则最好使用地区特定的转换方法
11. 字符串模式匹配方法
String 类型专门为在字符串中实现模式匹配设计了几个方法
12. localeCompare() 方法
因为返回的具体值可能因具体实现而异,所以最好像这样使用:
function determineOrder(value) {
+ let result = str1.localeCompare(value)
+ if (result < 0) {
+ console.log('str1 comes before ' + value)
+ } else if (result > 0) {
+ console.log('str1 comes after ' + value)
+ } else {
+ console.log('str1 is equal to ' + value)
+ }
+}
+
localeCompare() 的独特之处在于,实现所在的地区(国家和语言)决定了这个方法如何比较字符串
ECMA-262 对内置对象的定义是“任何由 ECMAScript 实现提供、与宿主环境无关,并在 ECMAScript 程序开始执行时就存在的对象”,如前面接触的 Object、Array、String、Function、Date、RegExp、Error、Boolean、Number,包括接下来介绍的两个单例内置对象 Math、Global
ECMA-262 规定 Global 对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。在全局作用域中定义的变量和函数都会变成 Global 对象的属性。isNaN()、isFinite()、parseInt() 和 parseFloat() 都是 Global 对象的方法,除了这些 ECMAScript 还为 Global 对象定义了其他方法
1. URL 编码方法
有效的 URI 不能包含某些字符,比如空格。使用 URI 编码方法可以让浏览器理解它们,同时又以特殊的 UTF-8 编码替换所有无效字符,比如空格会被替换成 %20
encodeURI()、encodeURIComponent()、decodeURI()、decodeURIComponent(),用于对 统一资源标识符(URI)进行编码和解码
2. eval() 方法
这个方法就是一个完整的 ECMAScript 解释器,它接收一个参数,即要执行的 ECMAScript(或 JavaScript)字符串
eval('console.log("hi")') // 'hi'
+
3. Global 对象的属性
4. window 对象
虽然 ECMA-262 没有规定直接访问 Global 对象的方式,但浏览器将 window 对象实现为 Global 对象的代理
另一种获取 Global 对象的方式:
var global = (function () {
+ return this
+})()
+
ECMAScript 提供了 Math 对象作为保存数学公式、信息和计算的地方。Math 对象提供了一些辅助计算的属性和方法
1. Math 对象属性
2. min() 和 max() 方法
接收任意多个参数
3. 舍入方法
4. random 方法
返回大于等于 0 小于 1 的一个随机数
可以基于如下公式使用 Math.random() 从一组整数中随机选择一个整数:
Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)
+
5. 其他方法
`,99),b={href:"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math#%E6%96%B9%E6%B3%95",target:"_blank",rel:"noopener noreferrer"};function h(f,j){const s=o("ExternalLinkIcon");return c(),l("div",null,[u,n("p",null,[n("a",r,[a("日期/时间组件方法"),t(s)])]),k,n("p",null,[n("a",d,[a("实例属性"),t(s)])]),n("p",null,[n("a",m,[a("实例方法"),t(s)])]),n("p",null,[n("a",v,[a("静态属性"),t(s)])]),g,n("p",null,[n("a",b,[a("Math 的方法"),t(s)])])])}const E=p(i,[["render",h],["__file","05.html.vue"]]);export{E as default}; diff --git a/assets/05.html-ddabefdf.js b/assets/05.html-ddabefdf.js new file mode 100644 index 0000000..ef13e59 --- /dev/null +++ b/assets/05.html-ddabefdf.js @@ -0,0 +1 @@ +const l=JSON.parse('{"key":"v-4831848c","path":"/frontend/js/red-book/05.html","title":"红宝书","lang":"zh-CN","frontmatter":{"title":"红宝书","prev":{"text":"变量、作用域与内存","link":"./04.md"},"next":{"text":"集合引用类型","link":"./06.md"}},"headers":[{"level":2,"title":"基本引用类型","slug":"基本引用类型","link":"#基本引用类型","children":[{"level":3,"title":"Date","slug":"date","link":"#date","children":[{"level":4,"title":"继承的方法","slug":"继承的方法","link":"#继承的方法","children":[]},{"level":4,"title":"日期格式化方法","slug":"日期格式化方法","link":"#日期格式化方法","children":[]}]},{"level":3,"title":"RegExp","slug":"regexp","link":"#regexp","children":[]},{"level":3,"title":"原始值包装类型","slug":"原始值包装类型","link":"#原始值包装类型","children":[{"level":4,"title":"Boolean","slug":"boolean","link":"#boolean","children":[]},{"level":4,"title":"Number","slug":"number","link":"#number","children":[]},{"level":4,"title":"String","slug":"string","link":"#string","children":[]}]},{"level":3,"title":"单例内置对象","slug":"单例内置对象","link":"#单例内置对象","children":[{"level":4,"title":"Global","slug":"global","link":"#global","children":[]},{"level":4,"title":"Math","slug":"math","link":"#math","children":[]}]}]}],"git":{"updatedTime":1690188978000},"filePathRelative":"frontend/js/red-book/05.md"}');export{l as data}; diff --git a/assets/06.html-907b0bed.js b/assets/06.html-907b0bed.js new file mode 100644 index 0000000..344dd34 --- /dev/null +++ b/assets/06.html-907b0bed.js @@ -0,0 +1 @@ +const l=JSON.parse('{"key":"v-49e65d2b","path":"/frontend/js/red-book/06.html","title":"红宝书","lang":"zh-CN","frontmatter":{"title":"红宝书","prev":{"text":"基本引用类型","link":"./05.md"},"next":{"text":"迭代器与生成器","link":"./07.md"}},"headers":[{"level":2,"title":"集合引用类型","slug":"集合引用类型","link":"#集合引用类型","children":[{"level":3,"title":"Object","slug":"object","link":"#object","children":[]},{"level":3,"title":"Array","slug":"array","link":"#array","children":[{"level":4,"title":"创建数组","slug":"创建数组","link":"#创建数组","children":[]},{"level":4,"title":"数组空位","slug":"数组空位","link":"#数组空位","children":[]},{"level":4,"title":"数组索引","slug":"数组索引","link":"#数组索引","children":[]},{"level":4,"title":"检测数组","slug":"检测数组","link":"#检测数组","children":[]},{"level":4,"title":"迭代器方法","slug":"迭代器方法","link":"#迭代器方法","children":[]},{"level":4,"title":"复制和填充方法","slug":"复制和填充方法","link":"#复制和填充方法","children":[]},{"level":4,"title":"转换方法","slug":"转换方法","link":"#转换方法","children":[]},{"level":4,"title":"栈方法","slug":"栈方法","link":"#栈方法","children":[]},{"level":4,"title":"队列方法","slug":"队列方法","link":"#队列方法","children":[]},{"level":4,"title":"排序方法","slug":"排序方法","link":"#排序方法","children":[]},{"level":4,"title":"操作方法","slug":"操作方法","link":"#操作方法","children":[]},{"level":4,"title":"搜素和位置方法","slug":"搜素和位置方法","link":"#搜素和位置方法","children":[]},{"level":4,"title":"迭代方法","slug":"迭代方法","link":"#迭代方法","children":[]},{"level":4,"title":"归并方法","slug":"归并方法","link":"#归并方法","children":[]}]},{"level":3,"title":"定型数组","slug":"定型数组","link":"#定型数组","children":[{"level":4,"title":"ArrayBuffer","slug":"arraybuffer","link":"#arraybuffer","children":[]},{"level":4,"title":"DataView","slug":"dataview","link":"#dataview","children":[]},{"level":4,"title":"定型数组","slug":"定型数组-1","link":"#定型数组-1","children":[]}]},{"level":3,"title":"Map","slug":"map","link":"#map","children":[{"level":4,"title":"基本 API","slug":"基本-api","link":"#基本-api","children":[]},{"level":4,"title":"顺序与迭代","slug":"顺序与迭代","link":"#顺序与迭代","children":[]}]},{"level":3,"title":"WeakMap","slug":"weakmap","link":"#weakmap","children":[{"level":4,"title":"基本 API","slug":"基本-api-1","link":"#基本-api-1","children":[]},{"level":4,"title":"弱键","slug":"弱键","link":"#弱键","children":[]},{"level":4,"title":"不可迭代键","slug":"不可迭代键","link":"#不可迭代键","children":[]}]},{"level":3,"title":"Set","slug":"set","link":"#set","children":[{"level":4,"title":"基本 API","slug":"基本-api-2","link":"#基本-api-2","children":[]},{"level":4,"title":"顺序与迭代","slug":"顺序与迭代-1","link":"#顺序与迭代-1","children":[]}]},{"level":3,"title":"WeakSet","slug":"weakset","link":"#weakset","children":[{"level":4,"title":"基本 API","slug":"基本-api-3","link":"#基本-api-3","children":[]},{"level":4,"title":"弱值","slug":"弱值","link":"#弱值","children":[]},{"level":4,"title":"不可迭代值","slug":"不可迭代值","link":"#不可迭代值","children":[]}]},{"level":3,"title":"迭代与扩展操作","slug":"迭代与扩展操作","link":"#迭代与扩展操作","children":[]}]}],"git":{"updatedTime":1690280044000},"filePathRelative":"frontend/js/red-book/06.md"}');export{l as data}; diff --git a/assets/06.html-a31227f4.js b/assets/06.html-a31227f4.js new file mode 100644 index 0000000..c53cacb --- /dev/null +++ b/assets/06.html-a31227f4.js @@ -0,0 +1,314 @@ +import{_ as n,o as s,c as a,e as t}from"./app-142ed948.js";const e={},p=t(`显示的创建 Object 实例有两种方式:
let person = new Object()
+person.name = 'Nicholas'
+person.age = 29
+
let person = {
+ name: 'Nicholas',
+ age: 29,
+ 5: true, // 数值属性会自动转换成字符串
+}
+
在这个例子中,左大括号({)表示对象字面量开始,因为它出现在一个**表达式上下文(expression context)**中。在 ECMAScript 中,表达式上下文是指期待返回值的上下文。
同样是左大括号({),如果出现在**语句上下文(statement context)**中,比如 if 语句的条件后面,则表示一个语句块的开始
注意:在使用字面量表示法定义对象时,并不会实际调用 Object 构造函数
存取属性的两种方式:
点语法
中括号
从功能上讲,这两种存取属性的方式没有区别
使用中括号的主要优势:
- 可以通过变量来访问属性
- 属性名(可以包含非字母数字字符)中包含会导致语法错误的字符时,必须使用中括号语法
有两种基本的方式可以创建数组:
let colors = new Array(20)
let colors = new Array('red', 'blue', 'green')
创建数组时给构造函数传入一个值。如果是数值,则会创建一个长度为指定数值的数组;如果是其他类型,则会创建包含那个值的数组
在使用 Array 构造函数时,也可以省略 new 操作符,结果是一样的
与对象一样,在使用数组字面量表示法创建数组时,不会调用 Array 构造函数
Array 构造函数还有两个 ES6 新增的用于创建数组的静态方法:
from(),用于将类数组结构转换为数组实例
of(),用于将一组参数转换为数组实例
使用数组字面量初始化数组时,可以使用一串逗号来创建空位(hole)
const options = [, , , , ,]
+console.log(options.length) // 5
+console.log(options, options[0]) // [empty × 5] undefined
+
注:ES6 新增的方法普遍将这些空位当成存在的元素;而 ES6 之前的方法则会忽略这个空位,但具体的行为也会因方法而异。因此实践中要避免使用数组空位,可以显式地使用 undefined 值代替
let colors = ['red', 'blue', 'green']
+console.log(colors[0]) // red
+colors[2] = 'black'
+colors[3] = 'brown'
+
在中括号提供索引,表示要访问的值。如果把一个值设置给超过数组最大索引的索引,则数组长度会自动扩展到该索引值 + 1
数组中元素的数量保存在 length(>= 0) 中,它不是只读的。可以通过修改 length 属性从数组末尾删除或者添加元素
let colors = ['red', 'blue', 'green']
+colors.length = 2
+console.log(colors[2]) // undefined
+
+colors.length = 4 // 新添加的值都将以 undefined 填充
+console.log(colors[3]) // undefined
+
使用 length 属性可以方便地向数组末尾添加元素
let colors = ['red', 'blue', 'green']
+colors[colors.length] = 'black'
+colors[colors.length] = 'brown'
+
如果判断一个对象是不是数组?
在只有一个网页(因为只有一个全局作用域)的情况下,使用 instanceof 操作符足矣:
if (value instanceof Array) {
+ // do something
+}
+
如果网页里有多个框架,则可能涉及两个不同的全局上下文,因此会有两个不同版本的 Array 构造函数。如果把数组从一个框架传给另一个框架,则这个数组的构造函数将有别于在另一个框架内本地创建的数组
为解决这个问题,ECMAScript 提供了 Array.isArray() 方法
if (Array.isArray(value)) {
+ // do something
+}
+
ES6 中,Array 的原型上暴露了 3 个用于检索数组内容的方法:keys()、values() 和 entries()
const colors = ['red', 'blue', 'green']
+const keys = colors.keys()
+const values = colors.values()
+const entries = colors.entries()
+console.log(Array.isArray(keys)) // false
+
+const keysArr = Array.from(keys)
+const valuesArr = Array.from(values)
+const entriesArr = Array.from(entries)
+
+console.log(keysArr, typeof keysArr[0] === 'number') // [0, 1, 2] true
+console.log(valuesArr) // ['red', 'blue', 'green']
+console.log(entriesArr) // [[0, 'red'], [1, 'blue'], [2, 'green']]
+
ES6 新增了两个方法:批量复制方法 copyWithin() 和填充数组方法 fill()
fill(),向一个已有的数组中插入全部或部分相同的值
copyWithin() 按照指定范围浅复制数组中的部分内容,然后将它们插入到指定索引开始的位置(开始索引和结束索引同 fill())
const values = [0, 1, 5, 10, 15]
+values.sort()
+console.log(values) // [0, 1, 10, 15, 5]
+
concat(),基于当前数组中的所有项创建一个新数组
const colors = ['red', 'green', 'blue']
+const colors2 = colors.concat('yellow', ['black', 'brown'])
+console.log(colors) // ['red', 'green', 'blue']
+console.log(colors2) // ['red', 'green', 'blue', 'yellow', 'black', 'brown']
+
const colors = ['red', 'green', 'blue']
+const newColors = ['black', 'brown']
+let moreNewColors = {
+ [Symbol.isConcatSpreadable]: true,
+ length: 2,
+ 0: 'pink',
+ 1: 'cyan',
+}
+newColors[Symbol.isConcatSpreadable] = false
+
+// 强制不打平数组
+const colors2 = colors.concat('yellow', newColors)
+
+// 强制打平类数组对象
+const colors3 = colors.concat(moreNewColors)
+
+console.log(colors) // ['red', 'green', 'blue']
+console.log(colors2) // ['red', 'green', 'blue', 'yellow', Array(2)]
+console.log(colors3) // ['red', 'green', 'blue', 'pink', 'cyan']
+
slice(),用于创建一个包含原数组中部分项的新数组
splice(),主要目的是在数组中间插入元素,但有 3 种不同的方式使用这个方法
splice(0, 2)
splice(2, 0, 'red', 'green')
splice(2, 1, 'red', 'green')
ECMAScript 提供两类搜索数组的方法:按严格相等搜索和按断言函数搜索
1. 严格相等(===)
以下三个方法接收两个参数:要查找的项和(可选的)表示查找起点位置的索引
2. 断言函数
这两个方法也都接受第二个可选参数,用于指定断言函数内部 this 的值
ECMAScript 为数组定义了 5 个迭代方法,每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响 this 的值
ECMAScript 为数组提供了两个归并方法:reduce() 和 reduceRight()
定型数组(typed array)是 ECMAScript 新增的结构,目的是提升向原生库传输数据的效率,它所指的其实是一种特殊的包含数值类型的数组
Float32Array 实际上是一种“视图”,可以允许 JavaScript 运行时访问一块名为 ArrayBuffer 的预分配内存。ArrayBuffer 是所有定型数组及视图引用的基本单位
ArrayBuffer() 是一个普通的 JavaScript 构造函数,可用于在内存中分配特定数量的字节空间:
const buffer = new ArrayBuffer(16) // 在内存中分配 16 字节
+console.log(buffer.byteLength) // 16
+
ArrayBuffer 一经创建就不能再调整大小。不过,可以使用 slice() 复制其全部或者部分到一个新的 ArrayBuffer 实例中:
const buf1 = new ArrayBuffer(16)
+const buf2 = buf1.slice(4, 12)
+console.log(buf2.byteLength) // 8
+
不能仅通过对 ArrayBuffer 的引用就读取或者写入内容,而是需要通过视图来实现。视图有不同的类型,但引用的都是 ArrayBuffer 中存储的二进制数据
ArrayBuffer 在分配失败时会抛出错误
ArrayBuffer 分配的内存不能超过 Number.MAX_SAFE_INTEGER(2^53 - 1)个字节
声明 ArrayBuffer 会将所有二进制初始化为 0
通过声明 ArrayBuffer 分配的堆内存可以被当成垃圾回收,不用手动释放
DataView 是允许读写 ArrayBuffer 的一种视图,专为文件 I/O 和网络 I/O 设计,其 API 支持对缓冲数据的高度控制,但相比于其他类型的视图性能差一些。DataView 对缓冲内容没有任何预设,也不能迭代
必须在对已有的 ArrayBuffer 读取或者写入时才能创建 DataView 实例:
const buf = new ArrayBuffer(16)
+
+// DataView 默认使用整个 ArrayBuffer
+const fullDataView = new DataView(buf)
+console.log(fullDataView.byteOffset) // 0
+console.log(fullDataView.byteLength) // 16
+console.log(fullDataView.buffer === buf) // true
+
+// 构造函数接收一个可选的字节偏移量和一个可选的字节长度
+// byteOffset=0 表示视图从缓冲起点开始
+// byteLength=8 表示限制视图为前 8 个字节
+const firstHalfDataView = new DataView(buf, 0, 8)
+console.log(firstHalfDataView.byteOffset) // 0
+console.log(firstHalfDataView.byteLength) // 8
+console.log(firstHalfDataView.buffer === buf) // true
+
+// 如果不指定,则 DataView 会使用剩余的缓存
+// byteOffset=8 表示视图从缓冲的第 8 个字节开始
+// byteLength 未指定,默认为剩余缓冲
+const secondHalfDataView = new DataView(buf, 8)
+console.log(secondHalfDataView.byteOffset) // 8
+console.log(secondHalfDataView.byteLength) // 8
+console.log(secondHalfDataView.buffer === buf) // true
+
要通过 DataView 读取缓冲,还需要几个组件:
1. ElementType
DateView 对存储在缓冲内的数据类型没有预设。它暴露的 API 强制开发者在读、写时指定一个 ElementType,然后 DataView 会忠实地为读、写完成相应的转换
ECMAScript6 支持 8 种不同的 ElementType(见下表):
ElementType | 字节 | 说明 | 等价的 C 类型 | 值的范围 |
---|---|---|---|---|
Int8 | 1 | 8 位有符号整数 | signed char | -128 ~ 127 |
Uint8 | 1 | 8 位无符号整数 | unsigned char | 0 ~ 255 |
Int16 | 2 | 16 位有符号整数 | short | -32768 ~ 32767 |
Uint16 | 2 | 16 位无符号整数 | unsigned short | 0 ~ 65535 |
Int32 | 4 | 32 位有符号整数 | int | -2147483648 ~ 2147483647 |
Uint32 | 4 | 32 位无符号整数 | unsigned int | 0 ~ 4294967295 |
Float32 | 4 | 32 位浮点数 | float | 1.2e-38 ~ 3.4e38 |
Float64 | 8 | 64 位浮点数 | double | 5.0e-324 ~ 1.8e308 |
DataView 为上表中的每种类型都暴露了 get 和 set 方法,这些方法使用 byteOffset 定位要读取或者写入值的位置。类型时可以互换使用的,如下例所示:
// 在内存中分配两个字节并声明一个 DataView
+const buf = new ArrayBuffer(2)
+const view = new DataView(buf)
+
+// 说明整个缓冲确实所有二进制位都是 0
+// 检查第一个和第二个字符
+console.log(view.getInt8(0)) // 0
+console.log(view.getInt8(1)) // 0
+// 检查整个缓冲
+console.log(view.getInt16(0)) // 0
+
+// 将整个缓冲都设置为 1
+// 255 的二进制表示为 11111111 (2^8 - 1)
+view.setUint8(0, 255)
+
+// DataView 会自动将数据转换为特定的 ElementType
+// 255 的十六进制表示是 0xFF
+view.setUint8(1, 0xff)
+
+// 现在缓冲里都是 1 了
+// 如果把它当作二补数的有符号整数,则应该是 -1
+console.log(view.getInt16(0)) // -1
+
2. 字节序
“字节序”指的是计算系统维护的一种字节顺序的约定,DataView 只支持两种约定:大端字节序和小端字节序
JavaScript 运行时所在系统的原生字节序决定了如何读取或写入数据,但 DataView 并不遵守这个约定。对一段内存而言,DataView 是一个中立接口,它遵守指定的字节序。DataView 的所有 API 方法都以大端字节序作为默认值,但接收一个可选的布尔值参数,设置为 true 即可启用小端字节序
// 在内存中分配两个字节并声明一个 DataView
+const buf = new ArrayBuffer(2)
+const view = new DataView(buf)
+
+// 填充缓冲,让第一位和最后一位都是 1
+view.setUint8(0, 0x80) // 设置最左边的位等于 1(1000 0000)
+view.setUint8(1, 0x01) // 设置最右边的位等于 1 (0000 0001)
+// 则缓冲的内容为 1000 0000 0000 0001
+
+// 按大端字节序读取 Unit16
+// 0x80 是高字节,0x01 是低字节
+// 0x8001 = 2^15 + 2^0 = 32769
+console.log(view.getUint16(0)) // 32769
+
+// 按小端字节序读取 Unit16
+// 0x80 是低字节,0x01 是高字节
+// 0x0180 = 2^7 + 2^8 = 384
+console.log(view.getUint16(0, true)) // 384
+
+// 按大端字节序写入 Uint16
+view.setUint16(0, 0x0004)
+// 缓冲内容:
+// 0000 0000 0000 0100
+console.log(view.getUint8(0)) // 0
+console.log(view.getUint8(1)) // 4
+
+// 按小端字节序写入 Uint16
+view.setUint16(0, 0x0002, true)
+// 缓冲内容:
+// 0000 0010 0000 0000
+console.log(view.getUint8(0)) // 2
+console.log(view.getUint8(1)) // 0
+
3. 边界情形
DataView 完成读、写操作的前提是必须有充足的缓冲区,否则会抛出 RangeError 异常
const buf = new ArrayBuffer(6)
+const view = new DataView(buf)
+
+// 尝试读取部分超出缓冲范围的值
+view.getInt32(4) // RangeError
+
+// 尝试读取超出缓冲范围的值
+view.getInt32(8) // RangeError
+view.getInt32(-1) // RangeError
+
+// 尝试写入超出缓冲范围的值
+view.setInt32(4, 123) // RangeError
+
DataView 在写入缓冲里会尽最大努力把一个值转换为适当的类型,后备为 0。如果无法转换,则抛出 TypeError 异常
const buf = new ArrayBuffer(1)
+const view = new DataView(buf)
+
+view.setInt8(0, 1.5)
+console.log(view.getInt8(0)) // 1
+
+view.setInt8(0, [4])
+console.log(view.getInt8(0)) // 4
+
+view.setInt8(0, 'f')
+console.log(view.getInt8(0)) // 0
+
+view.setInt8(0, Symbol()) // TypeError
+
定型数组是另一种形式的 ArrayBuffer 视图。虽然概念上与 DataView 接近,但定型数组的区别在于,它特定于一种 ElementType 且遵循系统原生的字节序。相应地,定型数组提供了适用面更广的 API 和更高的性能
设计定型数组的目的是提高于 WebGL 等原生库交换二进制数据的效率
创建定型数组的方式包括:读取已有的缓冲、使用自由缓冲、填充可迭代结构,以及填充基于任意类型的定型数组。另外通过 <ElementType>.from()
和 <ElementType>.of()
也可以创建定型数组
// 创建一个 12 字节的缓冲
+const buf = new ArrayBuffer(12)
+// 创建一个引用该缓冲的 Int32Array
+const ints = new Int32Array(buf)
+// 这个定型数组知道自己每个元素需要 4 字节
+// 因此长度为 3
+console.log(ints.length) // 3
+
+// 创建一个长度为 6 的 Int32Array
+const ints2 = new Int32Array(6)
+// 每个数值使用 4 个字节,因此 ArrayBuffer 需要 24 字节
+console.log(ints2.length) // 6
+// 类似 DataView,定型数组也有一个指向关联缓冲的引用
+console.log(ints2.buffer.byteLength) // 24
+
+// 创建一个包含 [2, 4, 6, 8] 的 Int32Array
+const ints3 = new Int32Array([2, 4, 6, 8])
+console.log(ints3.length) // 4
+console.log(ints3.buffer.byteLength) // 16
+console.log(ints3[2]) // 6
+
+// 通过复制 ints3 创建一个 Int16Array
+const ints4 = new Int16Array(ints3)
+// 这个新类型数组会分配自己的缓冲
+// 对应索引的值会相应地转换为新格式
+console.log(ints4.length) // 4
+console.log(ints4.buffer.byteLength) // 8
+console.log(ints4[2]) // 6
+
+// 基于普通数组来创建一个 Int16Array
+const ints5 = Int16Array.from([3, 5, 7, 9])
+console.log(ints5.length) // 4
+console.log(ints5.buffer.byteLength) // 8
+console.log(ints5[2]) // 7
+
+// 基于传入的参数创建一个 Float32Array
+const floats = Float32Array.of(3.14, 2.718, 1.618)
+console.log(floats.length) // 3
+console.log(floats.buffer.byteLength) // 12
+console.log(floats[2]) // 1.6180000305175781
+
定型数组的构造函数和实例都有一个 BYTES_PER_ELEMENT
属性,返回该类型数组中每个元素的大小:
console.log(Int16Array.BYTES_PER_ELEMENT) // 2
+console.log(Int32Array.BYTES_PER_ELEMENT) // 4
+
+const ints = new Int32Array(1),
+ floats = new Float64Array(1)
+
+console.log(ints.BYTES_PER_ELEMENT) // 4
+console.log(floats.BYTES_PER_ELEMENT) // 8
+
如果定型数组没有用任何值初始化,则其关联的缓冲会以 0 填充:
const ints = new Int32Array(4)
+console.log(ints[0]) // 0
+console.log(ints[1]) // 0
+console.log(ints[2]) // 0
+console.log(ints[3]) // 0
+
1. 定型数组的行为
定型数组的行为与普通数组类似,但也有一些不同之处
不能在定型数组中使用的方法:
concat()
pop()
push()
shift()
splice()
unshift()
2. 上溢和下溢
// 长度为 2 有符号整数数组
+// 每个索引保存一个二补数形式的有符号整数
+// 范围是 -128 到 127
+const ints = new Int8Array(2)
+
+// 长度为 2 的无符号整数数组
+// 每个索引保存一个无符号整数
+// 范围是 0 到 255
+const unsignedInts = new Uint8Array(2)
+
+// 上溢的位不会影响相邻索引
+// 索引只取最低有效位上的 8 位
+unsignedInts[1] = 256
+console.log(unsignedInts[1]) // [0, 0]
+unsignedInts[1] = 511
+console.log(unsignedInts[1]) // [0, 255]
+
+// 下溢的位会被转换为无符号的等价值
+// 0xFF 是以二补数形式表示的 -1(截取到 8 位)
+// 但 255 是一个无符号整数
+unsignedInts[1] = -1
+console.log(unsignedInts[1]) // [0, 255]
+
+// 上溢自动变成二补数形式
+// 0x80 是无符号整数的 128,是二补数形式的 -128
+ints[1] = 128
+console.log(ints[1]) // [0, -128]
+
+// 下溢自动变为二补数形式
+// 0xFF 是无符号整数的 255,是二补数形式的 -1
+ints[1] = 255
+console.log(ints[1]) // [0, -1]
+
除了 8 种元素类型,还有一种“夹板”数组类型:Uint8ClampedArray
,不允许任何方向溢出(除非真的做跟 canvas 相关的开发,否则不要使用它)。超出最大值 255 的值会被向下舍入为 255,低于最小值 0 的值会被向上舍入为 0
const clampedInts = new Uint8ClampedArray([-1, 0, 255, 256])
+console.log(clampedInts[0]) // [0, 0, 255, 255]
+
作为 ECMAScript6 的新增特性,Map 是一种新的集合类型,为这门语言带来了真正的键/值存储机制。Map 的大多数特性都可以通过 Object 来实现,但二者之间还是存在一些细微的差异:
创建和初始化:
// 使用嵌套数组初始化映射
+const m1 = new Map([
+ ['key1', 'val1'],
+ ['key2', 'val2'],
+ ['key3', 'val3'],
+])
+console.log(m1.size) // 3
+
+// 使用自定义迭代器初始化映射
+const m2 = new Map({
+ [Symbol.iterator]: function* () {
+ yield ['key1', 'val1']
+ yield ['key2', 'val2']
+ yield ['key3', 'val3']
+ },
+})
+console.log(m2.size) // 3
+console.log(m2.has(undefined)) // false
+console.log(m2.get(undefined)) // undefined
+
映射实例可以提供一个迭代器(Iterator),能以插入顺序生成 [key, value] 形式的数组。可以通过 entries() 方法(或者 Symbol.iterator 属性,它引用 entires())获取这个迭代器
因为 entires() 是默认迭代器,因此可以直接对映射实例使用扩展操作符,把映射转换为数组
WeakMap 是 Map 的“兄弟”类型,其 API 也是 Map 的子集,但有一些重要的区别:
创建和初始化:
const key1 = { id: 1 },
+ key2 = { id: 2 },
+ key3 = { id: 3 }
+
+// 使用嵌套数组初始化弱映射
+const wm1 = new WeakMap([
+ [key1, 'val1'],
+ [key2, 'val2'],
+ [key3, 'val3'],
+])
+
WeakMap 的键是弱键,这意味着如果键不再被引用,它所对应的值也会被回收
WeakMap 的键不可迭代,因此没有 entries()、keys() 和 values() 方法,也没有 forEach() 方法,同时也没有 clear() 方法
WeakMap 实例之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值
Set 是 ECMAScript6 新增的集合类型,在很多方面都像是加强的 Map,因为它们的大多数 API 和行为都是共有的
创建和初始化:
// 使用数组初始化集合
+const s1 = new Set(['val1', 'val2', 'val3'])
+console.log(s1.size)
+
+// 使用自定义迭代器初始化集合
+const s2 = new Set({
+ [Symbol.iterator]: function* () {
+ yield 'val1'
+ yield 'val2'
+ yield 'val3'
+ },
+})
+console.log(s2.size)
+
Set 会维护插入时的顺序,因此支持顺序迭代
集合实例可以提供一个迭代器,能以插入顺序生成集合内容的数组。可以通过 values() 方法及其别名方法 keys()(或者 Symbol.iterator 属性,它引用 values())获取这个迭代器
因为 values() 是默认迭代器,随意可以直接对集合实例使用扩展操作,把集合转换为数组
集合的 entires() 方法返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合种每个值的重复出现
WeakSet 是 Set 的“兄弟”类型,其 API 也是 Set 的子集,但有一些重要的区别:
创建和初始化:
const val1 = { id: 1 },
+ val2 = { id: 2 },
+ val3 = { id: 3 }
+
+// 使用数组初始化弱集合
+const ws1 = new WeakSet([val1, val2, val3])
+
WeakSet 的值是弱值,这意味着如果值不再被引用,它就会被回收
const ws = new WeakSet()
+ws.add({}) // 因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象值就会被当作垃圾回收
+
WeakSet 的值不可迭代,因此没有 entries()、keys() 和 values() 方法,也没有 forEach() 方法,同时也没有 clear() 方法
ECMAScript6 新增的迭代器和扩展操作符对集合引用类型特别有用,有 4 种原生集合类型定义了默认迭代器:
意味着上述所有类型都支持顺序迭代,都可以传入 for-of 循环,兼容扩展操作符...
`,168),o=[p];function c(l,i){return s(),a("div",null,o)}const r=n(e,[["render",c],["__file","06.html.vue"]]);export{r as default}; diff --git a/assets/07.html-04baef94.js b/assets/07.html-04baef94.js new file mode 100644 index 0000000..6778490 --- /dev/null +++ b/assets/07.html-04baef94.js @@ -0,0 +1 @@ +const l=JSON.parse('{"key":"v-4b9b35ca","path":"/frontend/js/red-book/07.html","title":"红宝书","lang":"zh-CN","frontmatter":{"title":"红宝书","prev":{"text":"集合引用类型","link":"./06.md"},"next":{"text":"对象、类与面向对象编程","link":"./08.md"}},"headers":[{"level":2,"title":"迭代器与生成器","slug":"迭代器与生成器","link":"#迭代器与生成器","children":[{"level":3,"title":"理解迭代","slug":"理解迭代","link":"#理解迭代","children":[]},{"level":3,"title":"迭代器模式","slug":"迭代器模式","link":"#迭代器模式","children":[{"level":4,"title":"可迭代协议","slug":"可迭代协议","link":"#可迭代协议","children":[]},{"level":4,"title":"迭代器协议","slug":"迭代器协议","link":"#迭代器协议","children":[]},{"level":4,"title":"自定义迭代器","slug":"自定义迭代器","link":"#自定义迭代器","children":[]},{"level":4,"title":"提前终止迭代器","slug":"提前终止迭代器","link":"#提前终止迭代器","children":[]}]},{"level":3,"title":"生成器","slug":"生成器","link":"#生成器","children":[{"level":4,"title":"通过 yield 中断执行","slug":"通过-yield-中断执行","link":"#通过-yield-中断执行","children":[]},{"level":4,"title":"生成器作为默认迭代器","slug":"生成器作为默认迭代器","link":"#生成器作为默认迭代器","children":[]},{"level":4,"title":"提前终止生成器","slug":"提前终止生成器","link":"#提前终止生成器","children":[]}]}]}],"git":{"updatedTime":1690363481000},"filePathRelative":"frontend/js/red-book/07.md"}');export{l as data}; diff --git a/assets/07.html-3af3ff8d.js b/assets/07.html-3af3ff8d.js new file mode 100644 index 0000000..251ca0c --- /dev/null +++ b/assets/07.html-3af3ff8d.js @@ -0,0 +1,246 @@ +import{_ as n,o as s,c as a,e as t}from"./app-142ed948.js";const p={},e=t(`小结
迭代器是一个可以由任意对象实现的接口,支持连续获取对象产出的每一个值。任何实现 Iterable 接口的对象有一个 Symbol.iterator 属性,这个属性引用默认迭代器。默认迭代器就像一个迭代器工厂,也就是一个函数,调用之后会产生一个实现 Iterator 接口的对象
生成器是一种特殊的函数,调用之后会返回一个生成器对象。生成器对象实现了 Iterable 接口,因此可用在任何消费可迭代对象的地方。生成器的独特之处在于支持 yield 关键字,这个关键字能够暂停执行生成器函数。使用 yield 关键字还可以通过 next() 方法接收输入和产出输出。在加上 * 之后,yield 可以将跟在它后面的可迭代对象序列化为一连串值
在 ECMAScript 较早的版本中,执行迭代必须使用循环或其他辅助结构。随着代码里增加,代码会变得越发混乱。很多语言都通过原生语言结构解决了这个问题,开发者无须事先知道如何迭代就能实现迭代操作。这个解决方案就是迭代器模式
迭代器是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联可迭代对象的 API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值
很多内置对象都实现了 Iterable 接口(可迭代协议):
检查是否存在默认迭代器属性可以暴露这个工厂函数(调用工厂函数会生成一个迭代器):
function isIterable(object) {
+ return typeof object[Symbol.iterator] === 'function'
+}
+
实际写代码过程中,不需要显示调用这个工厂函数来生成迭代器。实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性
接收可迭代对象的语言特性包括:
迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对象。迭代器 API 使用 next() 方法在可迭代对象中遍历数据。每次调用 next() 方法都会返回一 IteratorResult 对象,这个结果对象包含两个属性:done 和 value
每个迭代器都表示对可迭代对象的一次性有序遍历,不同迭代器实例之间没有联系,只会独立地遍历可迭代对象
迭代器并不与可迭代对象某个时刻的快照绑定,而仅仅是使用游标来记录遍历可迭代对象的历程。如果可迭代对象在迭代期间被修改,迭代器会反映出这些变化:
const arr = ['foo', 'bar']
+const iter = arr[Symbol.iterator]()
+console.log(iter.next()) // { value: 'foo', done: false }
+arr.splice(1, 0, 'baz')
+console.log(iter.next()) // { value: 'baz', done: false }
+console.log(iter.next()) // { value: 'bar', done: false }
+console.log(iter.next()) // { value: undefined, done: true }
+
注意
迭代器维护一个指向可迭代对象的引用,因此迭代器会阻止垃圾回收程序回收可迭代对象
与 Iterator 接口类似,任何实现 Iterator 接口的对象都可以作为迭代器使用
class Counter {
+ constructor(limit) {
+ this.limit = limit
+ }
+
+ [Symbol.iterator]() {
+ let count = 1
+ const limit = this.limit
+ return {
+ next() {
+ if (count <= limit) {
+ return { done: false, value: count++ }
+ } else {
+ return { done: true, value: undefined }
+ }
+ },
+ }
+ }
+}
+const counter = new Counter(3)
+
+for (const i of counter) {
+ console.log(i)
+}
+
每个以这种方式创建的迭代器也实现了 Iterable 接口,并且 Symbol.iterator 属性引用的工厂函数会返回相同的迭代器
const arr = ['foo', 'bar', 'baz']
+const iter1 = arr[Symbol.iterator]()
+console.log(iter1[Symbol.iterator]) // ƒ [Symbol.iterator]() { [native code] }
+const iter2 = iter1[Symbol.iterator]()
+console.log(iter1 === iter2)
+
可选的(意味着并不是所有的迭代器都是可关闭的,可以测试这个迭代器实例的 return 属性是不是函数来判断该迭代器是否可关闭) return() 方法用于指定在迭代器提前关闭时执行的逻辑。执行迭代的结构在想让迭代器知道它不想遍历到可迭代对象耗尽时,就可以“关闭”迭代器。可能的情况包括:
class Counter {
+ constructor(limit) {
+ this.limit = limit
+ }
+
+ [Symbol.iterator]() {
+ let count = 1
+ const limit = this.limit
+ return {
+ next() {
+ if (count <= limit) {
+ return { done: false, value: count++ }
+ } else {
+ return { done: true, value: undefined }
+ }
+ },
+ return() {
+ console.log('Exiting early')
+ return { done: true }
+ },
+ }
+ }
+}
+const counter = new Counter(5)
+
+for (const i of counter) {
+ if (i > 2) {
+ break
+ }
+
+ console.log(i)
+}
+
+const [a, b] = counter
+
如果迭代器没有关闭,则还可以继续从上次离开的地方继续迭代。比如,数组的迭代器就是不能关闭的:
const a = [1, 2, 3, 4, 5]
+const iter = a[Symbol.iterator]()
+
+for (const i of iter) {
+ console.log(i)
+ if (i > 2) {
+ break
+ }
+}
+// 1
+// 2
+// 3
+
+for (const i of iter) {
+ console.log(i)
+}
+// 4
+// 5
+
注意,仅仅给一个不可关闭的迭代器增加 return() 方法并不能让它变成可关闭的,这个因为调用 return() 并不会强制迭代器进入关闭状态。即便如此,return() 方法还是会被调用:
const a = [1, 2, 3, 4, 5]
+const iter = a[Symbol.iterator]()
+
+iter.return = function () {
+ console.log('Exiting early')
+ return { done: true }
+}
+
+for (const i of iter) {
+ console.log(i)
+ if (i > 2) {
+ break
+ }
+}
+// 1
+// 2
+// 3
+// Exiting early
+
+for (const i of iter) {
+ console.log(i)
+}
+// 4
+// 5
+
生成器的形式是一个函数,函数名称前面加一个 *
表示它是一个生成器,标识生成器的星号不受两侧空格的影响。只要是可以定义函数的地方就可以定义生成器
注意
箭头函数不能用来定义生成器函数
调用生成器会产生一个生成器对象。生成器对象一开始处于暂停执行(suspended)的状态。与迭代器相似,生成器也实现了 Iterator 接口,因此具有 next() 方法,调用这个方法会让生成器开始或恢复执行
function* generatorFn() {}
+
+const g = generatorFn()
+console.log(g) // generatorFn {<suspended>}
+console.log(g.next()) // {value: undefined, done: true}
+
next() 方法的返回值类似于迭代器,由一个 done 属性和一个 value 属性
value 属性是生成器函数的返回值,默认值为 undefined,可以通过生成器函数的返回值指定:
function* generatorFn() {
+ return 'foo'
+}
+
+const generatorObject = generatorFn()
+console.log(generatorObject) // generatorFn {<suspended>}
+console.log(generatorObject.next()) // {value: 'foo', done: true}
+
生成器对象实现了 Iterator 接口,它们默认的迭代器是自引用的
yield 关键字可以让生成器停止和开始执行。生成器函数在遇到 yield 关键字之前会正常执行,遇到这个关键字之后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用 next() 方法来恢复执行
yield 关键字生成的值会出现在 next() 方法返回的对象里。通过 yield 退出的生成器函数会处在 done:false 状态;通过 return 退出的生成器函数会处于 done:true 状态
生成器函数内部的执行流程会针对每个生成器对象区分作用域
yield 只能在生成器函数内部使用,用在其他地方会抛出错误
1. 生成器对象作为可迭代对象
在生成器对象上显示调用 next() 方法的用处并不大。如果把生成器当作可迭代对象:
function* generatorFn() {
+ yield 1
+ yield 2
+ yield 3
+}
+
+for (const i of generatorFn()) {
+ console.log(i)
+}
+// 1
+// 2
+// 3
+
2. 使用 yield 实现输入和输出
除了作为函数的中间返回语句使用,yield 还可以作为函数的中间参数使用。yield 语句的值可以通过 next() 方法的参数传入生成器函数
function* generatorFn(initial) {
+ console.log(initial)
+ console.log(yield)
+ console.log(yield)
+}
+
+const generatorObject = generatorFn('foo')
+generatorObject.next('bar') // foo
+generatorObject.next('baz') // baz
+generatorObject.next('qux') // qux
+
yield 可以同时用于输入和输出
function* generatorFn() {
+ return yield 'foo'
+}
+
+const generatorObject = generatorFn()
+console.log(generatorObject.next()) // {value: 'foo', done: false}
+console.log(generatorObject.next('bar')) // {value: 'bar', done: true}
+
使用生成器填充数组
function* zeros(n) {
+ for (let i = 0; i < n; i++) {
+ yield 0
+ }
+}
+console.log(Array.from(zeros(3)))
+
3. 产生可迭代对象 可以使用星号(两侧的空格不影响)增强 yield 的行为,让它那能够迭代一个可迭代对象,从而一次产出一个值:
function* generatorFn() {
+ yield* [1, 2, 3]
+}
+
+for (const i of generatorFn()) {
+ console.log(i)
+}
+// 1
+// 2
+// 3
+
yield* 的值是关联迭代器返回 done: true 时的 value 属性
4. 使用 yield* 实现递归算法
yield* 最有用的地方时实现递归操作,此时生成器可以产生自身:
function* nTimes(n) {
+ if (n > 0) {
+ yield* nTimes(n - 1)
+ yield n - 1
+ }
+}
+
+for (const i of nTimes(3)) {
+ console.log(i)
+}
+// 0
+// 1
+// 2
+
因为生成器对象实现了 Iterable 接口,而且生成器函数和默认迭代器被调用之后都产生迭代器,所以生成器格外适合作为默认迭代器
class Foo {
+ constructor() {
+ this.values = [1, 2, 3]
+ }
+
+ *[Symbol.iterator]() {
+ yield* this.values
+ }
+}
+
1. return()
return() 方法会强制生成器进入关闭状态。提供给 return() 方法的值,就是终止迭代器对象的值
所有的生成器都有 return(),只要通过它进入关闭状态,就无法恢复了
function* generatorFn() {
+ yield* [1, 2, 3]
+}
+
+const g = generatorFn()
+console.log(g) // generatorFn {<suspended>}
+console.log(g.return(4)) // {value: 4, done: true}
+console.log(g) // generatorFn {<closed>}
+
for-of 循环等内置语言结构会忽略状态为 done: true 的 IteratorObject 内部返回的值
function* generatorFn() {
+ yield* [1, 2, 3]
+}
+
+const g = generatorFn()
+
+for (const i of g) {
+ if (i > 1) {
+ g.return(4)
+ }
+
+ console.log(i)
+}
+// 1
+// 2
+
2. throw()
throw() 方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器就会关闭:
function* generatorFn() {
+ yield* [1, 2, 3]
+}
+
+const g = generatorFn()
+
+console.log(g) // generatorFn {<suspended>}
+try {
+ g.throw(new Error('foo'))
+} catch (e) {
+ console.log(e) // Error: foo
+}
+console.log(g) // generatorFn {<closed>}
+
假如生成器内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。错误处理会跳过对应的 yield:
function* generatorFn() {
+ for (const x of [1, 2, 3]) {
+ try {
+ yield x
+ } catch (e) {
+ console.log(e)
+ }
+ }
+}
+
+const g = generatorFn()
+
+console.log(g.next()) // {value: 1, done: false}
+g.throw('foo')
+console.log(g.next()) // {value: 3, done: false}
+
ECMA-262 将对象定义为一组属性的无序集合
ECMA-262 使用一些内部特性来描述属性的特征,属性分为两种:数据属性和访问器属性
1. 数据属性
数据属性包含一个保存数据值的位置,在这个位置可以读取和写入值。数据属性有 4 个特性描述它的行为:
Object.defineProperty() 修改属性的默认特性,如果不指定 configurable / enumerable / writable,则默认为 false:
let person = {}
+Object.defineProperty(person, 'name', {
+ writable: false,
+ value: 'Nicholas',
+})
+
2. 访问器属性
访问器属性不包含数据值。相反,它们包含一个获取(getter)函数和一个设置(setter)函数,不过这两个函数不是必需的。访问器属性有 4 个特性描述它的行为:
访问器属性是不能直接定义的,必须使用 Object.definedProperty():
let book = {
+ year_: 2004, // 下划线表示该属性并不希望在对象外部被访问
+ edition: 1,
+}
+
+Object.defineProperty(book, 'year', {
+ get() {
+ return this.year_
+ },
+ set(newValue) {
+ if (newValue > 2004) {
+ this.year_ = newValue
+ this.edition += newValue - 2004
+ }
+ },
+})
+
+book.year = 2005
+console.log(book.edition) // 2
+
Object.defineProperties() 可以通过多个描述符一次定义多个属性,如果数据属性不指定 configurable / enumerable / writable,则默认为 false
Object.getOwnPropertyDescriptor() 可以取得给定属性的描述符
ECMAScript 2017 新增了 Object.getOwnPropertyDescriptors() 静态方法,可以取得给定对象所有自有属性的描述符
Object.assign() 可以把任意多个源对象可枚举的自有属性浅拷贝到目标对象,然后返回目标对象。对每个符合条件的属性,这个方法会使用源对象上的 [[Get]] 取得属性值,然后使用目标对象上的 [[Set]] 设置属性值
const dest = {
+ set a(val) {
+ console.log(\`Invoked dest setter with param \${val}\`)
+ },
+}
+
+const src = {
+ get a() {
+ console.log('Invoked src getter')
+ return 'foo'
+ },
+}
+
+Object.assign(dest, src)
+// Invoked src getter
+// Invoked dest setter with param foo
+console.log(dest)
+
Object.is() 用于比较两个值是否相等,与 === 的行为基本一致,但是它对 NaN 和 +0/-0 作了特殊处理
要检查超过两个值,递归地利用相等性传递即可:
function recursivelyCheckEqual(x, ...rest) {
+ return Object.is(x, rest[0]) && (rest.length < 2 || recursivelyCheckEqual(...rest))
+}
+
ECMAScript 6 为定义和操作对象新增了很多及其有用的语法糖特性:
对象解构就是使用与对象匹配的结构来实现对象属性赋值
如果是给事先声明的变量赋值,则赋值表达式必须包含在一对括号中,否则会被当成一个代码块:
let personName, personAge
+let person = {
+ name: 'Nicholas',
+ age: 29,
+}
+;({ name: personName, age: personAge } = person)
+
使用 Object 构造函数和对象字面量可以方便的创建对象,但是这种方式有个缺点:创建具有同样接口的多个对象需要重复编写很多代码
工厂模式用于抽象创建特定对象的过程
下面的例子展示了一种按照特定接口创建对象的方式:
function createPerson(name, age, job) {
+ let o = new Object()
+ o.name = name
+ o.age = age
+ o.job = job
+ o.sayName = function () {
+ console.log(this.name)
+ }
+ return o
+}
+
+let person1 = createPerson('Nicholas', 29, 'Software Engineer')
+let person2 = createPerson('Greg', 27, 'Doctor')
+
这种工厂模式虽然可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)
ECMAScript 中的构造函数就是用于创建特定类型对象的
构造函数不一定要写成函数声明的形式,赋值给变量的函数表达式也可以
使用 new 操作符调用构造函数会执行如下操作:
instanceof
操作符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
构造函数的问题在于其定义的方法会在每个实例上都创建一遍
每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法
function Person() {}
+
+Person.prototype.name = 'Nicholas'
+Person.prototype.age = 29
+Person.prototype.job = 'Software Engineer'
+Person.prototype.sayName = function () {
+ console.log(this.name)
+}
+
+let person1 = new Person()
+person1.sayName() // Nicholas
+
+let person2 = new Person()
+person2.sayName() // Nicholas
+
+console.log(person1.sayName === person2.sayName) // true
+
与构造函数模式不同,使用这种原型模式定义的属性和方法是由所有实例共享的
1. 理解原型
构造函数.prototype 指向原型对象,原型对象.constructor 指向构造函数,实例对象.__proto__ 指向原型对象
isPrototypeOf()
方法用于检测当前对象是否是传入对象的原型
Object.getPrototypeOf()
方法返回传入对象的原型
Object.setPrototypeOf()
方法用于设置传入对象的原型,但其可能会严重影响性能,因为其涉及所有访问了那些修改过 [[Prototype]] 的对象的代码
Object.create()
方法创建一个新对象,将参数作为新创建的对象的__proto__
2. 原型层级
hasOwnProperty()
方法用于确定某个属性实在实例上还是在原型对象上,继承自 Object
Object.getOwnPropertyDescriptor()
方法也只对实例有效
3. 原型和 in 操作符
有两张方式使用 in 操作符:
单独使用 in 结合 'hasOwnProperty()' 方法可以确定属性是否存在于原型上:
function hasPrototypeProperty(object, name) {
+ return !object.hasOwnProperty(name) && name in object
+}
+
在 for-in 循环中使用 in 操作符时,可以通过对象访问且可以被枚举的属性都会返回
Object.keys()
方法返回对象上所有可枚举的实例属性
Object.getOwnPropertyNames()
方法返回对象上所有实例属性,无论是否可枚举
Object.getOwnPropertySymbols()
方法同上,但是只针对符号
4. 属性枚举顺序
const k1 = Symbol('k1')
+const k2 = Symbol('k2')
+
+let o = {
+ 1: 1,
+ first: 'first',
+ [k2]: 'k2',
+ second: 'second',
+ 0: 0,
+}
+
+o[k1] = 'k1'
+o[3] = 3
+o.third = 'third'
+o[2] = 2
+
+console.log(Object.getOwnPropertyNames(o)) // ['0', '1', '2', '3', 'first', 'second', 'third']
+console.log(Object.getOwnPropertySymbols(o)) // [Symbol(k2), Symbol(k1)]
+
Object.values() / Object.entires
:非字符串属性会被转换为字符串输出,且执行对象的浅复制,符号属性会被忽略
1. 其他原型语法
function Person() {}
+
+Person.prototype = {
+ name: 'Nicholas',
+ age: 29,
+ job: 'Software Engineer',
+ sayName() {
+ console.log(this.name)
+ },
+}
+
+// 恢复 constructor 属性
+Object.defineProperty(Person.prototype, 'constructor', {
+ enumerable: false,
+ value: Person,
+})
+
2. 原生对象原型
不推荐在产品环境中修改原生对象原型,而是创建一个自定义类继承原生类型
3. 原型的问题
最主要的问题源自它的共享特性,针对包含引用值的属性,会导致实例间相互影响,故通常不单独使用原型模式
实现继承是 ECMAScript 唯一支持的继承方式,且主要通过原型链实现
ECMA-262 把原型链定义为 ECMAScript 的主要继承方式,其基本思想是通过原型继承多个引用类型的属性和方法
1. 默认原型
任何函数的默认原型都是一个 Object 的实例
2. 原型与继承关系
确定原型与实例的关系:
instanceof
操作符,检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上isPrototypeOf()
方法,如果传入的对象是实例的原型,则返回 true3. 原型链的问题
这些问题导致原型链基本不会单独使用
为了解决原型包含引用值导致继承问题,一种叫作“盗用构造函数(constructor stealing)”的技术在开发社区流行起来,有时也称作“对象伪装”或“经典继承”
基本思路:在子类构造函数中调用父类构造函数
function SuperType() {
+ this.colors = ['red', 'blue', 'green']
+}
+
+function SubType() {
+ // 继承了 SuperType
+ SuperType.call(this)
+}
+
+let instance1 = new SubType()
+
1. 传递参数
相比于使用原型链,盗用构造函数的一个优点就是可以在子类构造函数中向父类构造函数传参
2. 盗用构造函数的问题
主要缺点:必须在构造函数中定义方法,导致函数不能重用。此外子类也不能访问父类原型上定义的方法,导致所有类型只能使用构造函数模式
故“盗用构造函数”基本上也不能单独使用
组合继承(伪经典继承)综合了原型链和盗用构造函数,将两者的优点集中了起来
基本思路:通过原型链继承原型上的属性和方法,通过盗用构造函数继承实例属性
这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性
function SuperType(name) {
+ this.name = name
+ this.colors = ['red', 'blue', 'green']
+}
+
+SuperType.prototype.sayName = function () {
+ console.log(this.name)
+}
+
+function SubType(name, age) {
+ // 继承属性
+ SuperType.call(this, name)
+
+ this.age = age
+}
+
+// 继承方法
+SubType.prototype = new SuperType()
+
+SubType.prototype.sayAge = function () {
+ console.log(this.age)
+}
+
原型式继承的思路:即使不自定义类型也可以通过原型实现对象之间的信息共享
原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合
ECMAScript 5 通过 Object.create() 方法将原型式继承的概念规范化了
const person = {
+ name: 'Nicholas',
+ friends: ['Shelby', 'Court', 'Van'],
+}
+
+const anotherPerson = Object.create(person, {
+ name: {
+ value: 'Greg',
+ },
+})
+
寄生式继承的思路:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象
function createAnother(original) {
+ const clone = Object.create(original) // 通过调用函数创建一个新对象(任何返回新对象的函数都可以在这里使用)
+ clone.sayHi = function () {
+ // 以某种方式增强这个对象
+ console.log('hi')
+ }
+ return clone // 返回这个对象
+}
+
通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似
组合继承存在效率问题:父类构造函数始终会被调用两次(一次是创建子类原型时调用,另一次是在子类构造函数中调用),且子类的原型上会存在多余的属性(构造函数中的)
寄生式组合继承的基本思路:通过盗用构造函数来继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本
function inheritPrototype(subType, superType) {
+ const prototype = Object.create(superType.prototype) // 创建对象
+ prototype.constructor = subType // 增强对象
+ subType.prototype = prototype // 指定对象
+}
+
+function SuperType(name) {
+ this.name = name
+ this.colors = ['red', 'blue', 'green']
+}
+
+SuperType.prototype.sayName = function () {
+ console.log(this.name)
+}
+
+function SubType(name, age) {
+ SuperType.call(this, name)
+
+ this.age = age
+}
+
+inheritPrototype(SubType, SuperType)
+
+SubType.prototype.sayAge = function () {
+ console.log(this.age)
+}
+
ECMAScript 6 类表面上看起来可以支持正式第面向对象编程,但实际上背后使用的仍然是原型和构造函数的概念
类定义主要有两种方式:类声明和类表达式
// 类声明
+class Person {}
+
+// 类表达式
+const Animal = class {}
+
默认情况下,类定义中的代码都在严格模式下执行
类构造函数与构造函数的主要区别:类构造函数必须使用 new 操作符, 否则会抛出错误;而普通构造函数如果不使用 new 调用,那么就会以全局的 this 作为内部对象
从各方面看,ECMAScript 类就是一种特殊函数
typeof 类名 === ’function‘
类是 JavaScript 的一等公民,因此可以像其他对象或函数引用一样把类作为参数传递
1. 实例成员
通过 new 调用类标识符,都会执行构造函数。在这个函数的内部,可以为新创建的实例添加“自有”属性
2. 原型方法与访问器
为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法
类方法等同于对象属性,因此可以使用字符串、符号或计算的值作为键
类定义也支持获取和设置访问器,语法与行为跟普通对象一样:
class Person {
+ get name() {
+ return this.name_
+ }
+ set name(newName) {
+ this.name_ = newName
+ }
+}
+
3. 静态类方法
静态类成员在类定义中使用 static 关键字作为前缀。在静态成员中,this 引用类自身
静态类方法非常适合作为实例工厂:
class Person {
+ constructor(age) {
+ this.age_ = age
+ }
+
+ sayAge() {
+ console.log(this.age_)
+ }
+
+ static create() {
+ return new Person(Math.floor(Math.random() * 100))
+ }
+}
+
4. 迭代器与生成器方法
类定义语法支持在原型和类本身上定义生成器方法,所以可以通过一个默认的迭代器,把类实例变成可迭代对象
class Person {
+ constructor() {
+ this.nickNames = ['di', 'diqiu', 'didi']
+ }
+
+ // *[Symbol.iterator]() {
+ // yield* this.nickNames
+ // }
+
+ // 也可以只返回迭代器实例
+ [Symbol.iterator]() {
+ return this.nickNames.values()
+ }
+}
+
+const p = new Person()
+
+for (const nickName of p) {
+ console.log(nickName)
+}
+
ECMAScript 6 新增特性中最出色的一个就是原生支持了类继承机制。虽然类继承使用的是新语法,但背后依旧使用的是原型链
1. 继承基础
ES6 类支持单继承,使用 extends 关键字,就可以继承任何拥有 [[Construct]] 和原型的对象(类和普通的构造函数)
派生类都会通过原型链访问到类和原型上定义的方法
2. 构造函数、HomeObject 和 super()
派生类的方法可以通过 super 关键字引用它们的原型
super 关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部
在类构造函数中使用 super 可以调用父类构造函数:
class Vehicle {
+ constructor() {
+ this.hasEngine = true
+ }
+}
+
+class Bus extends Vehicle {
+ constructor() {
+ /* 不要在 super 之前引用 this,否则会抛出 ReferenceError */
+ super() // 相当于 super.constructor()
+ console.log(this instanceof Vehicle) // true
+ console.log(this) // Bus {hasEngine: true}
+ }
+}
+
+new Bus()
+
在静态方法中可以通过 super 调用继承的类上的静态方法:
class Vehicle {
+ static identify() {
+ console.log('vehicle')
+ }
+}
+
+class Bus extends Vehicle {
+ static identify() {
+ super.identify()
+ }
+}
+
+Bus.identify() // vehicle
+
ES6 给类构造函数和静态方法内部添加了内部属性 [[HomeObject]],这个特性是一个指针,指向定义该方法的对象。这个指针是自动赋值的,而且只能在 JavaScript 引擎内部访问。super 始终会定义为 [[HomeObject]] 的原型
在使用 super 时需要注意几个问题:
3. 抽象基类
有时候可能需要定义这样一个类,它可供其他类继承,但本身不会被实例化
虽然 ECMAScript 没有专门支持这种类的语法,但通过 new.target(保存通过 new 关键字调用的类或函数) 也很容易实现
通过在实例化时检测 new.target 是不是抽象基类,可以阻止对抽象基类实例化:
// 抽象基类
+class Vehicle {
+ constructor() {
+ console.log(new.target)
+ if (new.target === Vehicle) {
+ throw new Error('Vehicle cannot be directly instantiated')
+ }
+ }
+}
+
+// 派生类
+class Bus extends Vehicle {}
+
+new Bus() // Bus {}
+
+new Vehicle() // Uncaught Error: Vehicle cannot be directly instantiated
+
另外,通过在抽象基类构造函数中进行检查,可以要求派生类必须定义某个方法(原型方法在嗲用类构造函数之前就已经存在了):
class Vehicle {
+ constructor() {
+ if (new.target === Vehicle) {
+ throw new Error('Vehicle cannot be directly instantiated')
+ }
+ if (!this.foo) {
+ throw new Error('Inheriting class must define foo()')
+ }
+ console.log('success')
+ }
+}
+
+// 派生类
+class Bus extends Vehicle {
+ foo() {}
+}
+
+// 派生类
+class Van extends Vehicle {}
+
+// new Bus() // success
+new Van() // Uncaught Error: Inheriting class must define foo()
+
4. 继承内置类型
ES6 类为继承内置引用类型提供了顺畅的机制,开发者可以方便地扩展内置类型:
有些内置类型的方法会返回新实例。默认情况下,返回实例的类型与原始实例的类型时一致的。如果想覆盖这个默认行为,则可以覆盖 Symbol.species 访问器,这个访问器决定在创建返回的实例时使用的类:
class SuperArray extends Array {
+ static get [Symbol.species]() {
+ return Array
+ }
+}
+
+const a1 = new SuperArray(1, 2, 3, 4)
+const a2 = a1.map(x => x * x)
+
+console.log(a1 instanceof SuperArray) // true
+console.log(a2 instanceof SuperArray) // false
+
5. 类混入
把不同类的行为集中到一个类是一种常见的 JavaScript 模式。虽然 ES6 没有显示支持多类继承,但通过现有特性可以轻松地模拟这种行为
注意
Object.assign() 方法是为了混入对象行为而设计的。只有在需要混入类的行为时才有必要自己实现混入表达式。如果只是需要混入多个对象的属性,那么使用 Object.assign() 就足够了
很多 JavaScript 框架(特别是 React)已经抛弃混入模式,转向了组合模式(把方法提取到独立的类和辅助对象中,然后把它们组合起来,但不是继承)。这反映了那个众所周知的软件设计原则:“组合胜过继承”
ECMAScript 6 新增的代理和反射为开发者提供了拦截并向基本操作嵌入额外行为的能力
代理是目标对象的抽象
最简单的代理是空代理,即除了作为一个抽象的目标对象,什么也不做
代理使用 Proxy 构造函数创建,接收两个必填参数:目标对象和处理程序对象
const target = {
+ id: 'target',
+}
+
+const handler = {}
+
+const proxy = new Proxy(target, handler)
+
+console.log(target === proxy) // false
+
针对上述代码中
target
和proxy
两个对象,注意:
- hasOwnProperty() 方法都会应用到目标对象
- Proxy.prototype 是 undefined,因此不能使用 instanceof 操作符检查对象是否是代理
使用代理的主要目的是可以定义捕获器(trap)
例如,定义一个 get() 捕获器,在 ECMAScript 操作以某种形式调用 get() 时触发:
const target = {
+ foo: 'bar',
+}
+
+const handler = {
+ get() {
+ return 'handler override'
+ },
+}
+
+const proxy = new Proxy(target, handler)
+
注意:
- get() 不是 ECMAScript 对象可以调用的方法
- proxy[property]、proxy.property 或 Object.create(proxy)[property] 等操作都可以触发基本的 get() 操作以获取属性
所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为
比如,get() 捕获器接收以下参数:
通过这些参数可以重建被捕获方法的原始行为:
const target = {
+ foo: 'bar',
+}
+
+const handler = {
+ get(trapTarget, property, receiver) {
+ return trapTarget[property]
+ },
+}
+
+const proxy = new Proxy(target, handler)
+
但并非所有捕获器行为都像 get() 这么简单,因此 ECMAScript 6 为所有捕获器定义了一组默认行为,这些行为可以在 Reflect 对象上找到
处理程序对象所有可以捕获的方法都有对应的反射(Reflect)API 方法,使用反射 API 定义空代理对象:
const target = {
+ foo: 'bar',
+}
+
+const proxy = new Proxy(target, Reflect)
+
在反射 API 的基础上可以用最少的代码修改捕获的方法。比如,在某个属性被访问时,对返回的值进行一番修饰:
const target = {
+ foo: 'bar',
+}
+
+const handler = {
+ get(trapTarget, property, receiver) {
+ const decoration = property === 'foo' ? '!!!' : ''
+ return Reflect.get(...arguments) + decoration
+ },
+}
+
+const proxy = new Proxy(target, handler)
+
+console.log(proxy.foo) // bar!!!
+
捕获器处理程序的行为必须遵守“捕获器不变式(trap invariant)”
比如,如果目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出 TypeError
new Proxy() 创建的代理对象与目标对象之间的联系会在代理对象的生命周期内一直存在
Proxy.revocable() 方法创建一个可撤销的代理,返回一个对象,包含两个属性:
注意:
- 撤销代理的操作时不可逆的
- 撤销函数(revoke())是幂等的,调用多少次结果都一样
- 撤销代理之后再调用代理会抛出 TypeError
某些情况下应该优先使用反射 API:
1. 反射 API 与 对象 API
2. 状态标记
很多反射方法返回称作“状态标记”的布尔值,表示操作是否成功。
以下方法都会提供状态标记:
3. 用一等函数替代操作符
4. 安全地应用函数
在通过 apply 方法调用函数,被调用的函数可能也定义了自己的 apply 属性,此时:
Function.prototype.apply.call(myFunc, thisVal, argumentList)
+
+// 可替换为
+Reflect.apply(myFunc, thisVal, argumentList)
+
代理可以拦截反射 API 的操作,而这意味着完全可以创建一个代理,通过它去代理另外一个代理。这样就可以在一个目标对象上建立多层拦截网:
const target = {
+ foo: 'bar',
+}
+
+const firstProxy = new Proxy(target, {
+ get() {
+ console.log('first proxy')
+ return Reflect.get(...arguments)
+ },
+})
+
+const secondProxy = new Proxy(firstProxy, {
+ get() {
+ console.log('second proxy')
+ return Reflect.get(...arguments)
+ },
+})
+
+console.log(secondProxy.foo)
+// second proxy
+// first proxy
+// bar
+
1. 代理中的 this
如果目标对象依赖于对象标识,那就可能遇到意料之外的问题
2. 代理与内部插槽 有些 ECMAScript 内置类型可能会依赖代理无法控制的机制,结果导致在代理上调用某些方法会出错
比如,Date 类型方法的执行依赖 this 值上的内部槽位 [[NumberDate]],代理对象上不存在这个内部槽位,而且这个内部槽位的值也不能通过普通的 get() 和 set() 访问到,于是代理拦截后本应转发给目标对象的方法会抛出 TypeError:
const target = new Date()
+const proxy = new Proxy(target, {})
+console.log(proxy instanceof Date) // true
+proxy.getDate() // TypeError
+
代理可以捕获 13 种不同的基本操作,这些操作有不同的反射 API 方法、参数、关联 ECMAScript 操作和不变式
使用代理可以在代码中实现一些有用的编程模式
只需要引入 css 和字体文件,把字体编码为 base64 格式,那只需要引入一个 css 文件即可
import fetch from 'node-fetch'
+import fs from 'fs'
+import prompts from 'prompts'
+
+const main = async () => {
+const validateUrl = url => (/\\/(font.*)\\.css/.test(url) ? true : '地址不对哦')
+const questions = [
+ {
+ type: 'text',
+ name: 'url',
+ message: '输入下iconfont的fontClass地址?',
+ validate: validateUrl
+ }
+]
+const { url } = await prompts(questions)
+const cssUrl = \`https://\${url}\`
+// 指定保存字体文件的目录
+const fontDirectory = './fonts'
+const cssDirectory = '.s'
+// 创建字体文件保存目录
+if (!fs.existsSync(fontDirectory)) {
+ fs.mkdirSync(fontDirectory)
+}
+// 使用node-fetch获取CSS文件内容
+fetch(cssUrl)
+ .then(response => response.text())
+ .then(cssContent => {
+ // 使用正则表达式提取字体文件的URL
+ const fontUrls = cssContent.match(/url\\('\\/\\/([^']+)'\\)/g)
+
+ if (fontUrls) {
+ // 使用Promise.all()等待所有字体文件的下载完成
+ Promise.all(
+ fontUrls.map(fontUrl => {
+ // 提取URL中的字体文件链接
+ const urlMatch = fontUrl.match(/url\\('\\/\\/([^']+)'\\)/)
+ if (urlMatch && urlMatch[1]) {
+ const fontFileUrl = \`https://\${urlMatch[1]}\` // 添加协议
+ // 下载字体文件并转换为Base64编码
+ return fetchAndEncodeToBase64(fontFileUrl)
+ }
+ return Promise.resolve('')
+ })
+ ).then(encodedFonts => {
+ // 将所有字体的Base64编码插入CSS
+ encodedFonts.forEach((encodedFont, index) => {
+ cssContent = cssContent.replace(fontUrls[index], encodedFont)
+ })
+
+ // 生成包含所有字体的Base64编码的CSS文件
+ fs.writeFileSync('src/styles/iconfont.scss', cssContent)
+ console.log('包含所有字体的Base64编码的CSS文件已生成')
+ })
+ } else {
+ console.error('未找到字体文件URL')
+ }
+ })
+ .catch(error => {
+ console.error('下载CSS文件时出错:', error)
+ })
+}
+
+// 下载字体文件并转换为Base64编码
+async function fetchAndEncodeToBase64(fontFileUrl) {
+ try {
+ const response = await fetch(fontFileUrl)
+ const buffer = await response.arrayBuffer()
+ const base64Font = Buffer.from(buffer).toString('base64')
+ const ext = fontFileUrl.substring(fontFileUrl.lastIndexOf('.') + 1)
+ return \`url('data:application/font-\${ext};charset=utf-8;base64,\${base64Font}')\`
+ } catch (error) {
+ console.error(\`下载字体文件并转换为Base64编码时出错:\${fontFileUrl}\`, error)
+ return '' // 返回一个空字符串以避免破坏CSS
+ }
+}
+
+main()
+
9HB9(1^w rqjoP}lsPV2Ol
zAvHU|RfgzyIaJ8AX)q4X*BZZ~Bf;Ncf?e;5#8#@eg?ERdJ5!FWC+;08tGwLlK6doc
z(9=%2ymJ03`}n-qFW+xJ!LJ7C
zF$%VWj?}g=7oF99eE&hZ;mysB`S|IhxxKk1TQyDsIXgcy&!4}j=+=qfR8mZ~U-+?h
z*fGzKNq?31$Gp2tS%=2@P#M!QB{$hYi0uKjQ;;RQG@|L*qG+wtru4i)|I;0tGB9dxc^;n>St%|bi&vPkr`mS`Cwz(g4=$BVnl&C(Q?Hx