From 176fe9dcd650905b904029a21ae8963c3fae4719 Mon Sep 17 00:00:00 2001 From: Liu Date: Fri, 6 Sep 2019 18:11:51 +0800 Subject: [PATCH 001/280] docs(proxy): add spaces around the operator --- docs/proxy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proxy.md b/docs/proxy.md index a9b1d4ddd..0ec65c95f 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -1131,7 +1131,7 @@ service.employees().then(json => { function createWebService(baseUrl) { return new Proxy({}, { get(target, propKey, receiver) { - return () => httpGet(baseUrl+'/' + propKey); + return () => httpGet(baseUrl + '/' + propKey); } }); } From e87aba7bd95154b31ddbb45001fc209d9f0a7211 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 7 Sep 2019 11:39:10 +0800 Subject: [PATCH 002/280] feat: add banner --- js/ditto.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/js/ditto.js b/js/ditto.js index 7247ee301..3985123b0 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -207,6 +207,23 @@ function li_create_linkage(li_tag, header_level) { }); } +function create_banner(element) { + var styleStr = [ + 'margin: 1em 0', + 'padding: 1em', + 'background-color: #c4e0e1', + 'border-radius: 5px', + 'font-size: 90%' + ].join(';'); + + var text = 'Vue 实战教程 ' + + '深入学习一线大厂必备 Vue 技能。VIP 教程限时免费领取。' + + '⇐ 立即查看'; + + var banner = $('
' + text + '
') + .insertAfter(element); +} + function create_page_anchors() { // create page anchors by matching li's to headers // if there is a match, create click listeners @@ -243,6 +260,9 @@ function create_page_anchors() { .insertAfter('#content h1') .addClass('content-toc') .attr('id', 'content-toc'); + + create_banner(ul_tag); + for (var j = 0; j < headers.length; j++) { var li_tag = $('
  • ').html('' + headers[j] + ''); ul_tag.append(li_tag); From 6128c2eec31ad140b11b108b8e0ad9f75d438b73 Mon Sep 17 00:00:00 2001 From: Vincent Hy Date: Wed, 11 Sep 2019 14:02:19 +0800 Subject: [PATCH 003/280] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20'WeaKSet'=20?= =?UTF-8?q?=E5=90=8D=E8=AF=8D=E6=8B=BC=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/set-map.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/set-map.md b/docs/set-map.md index 4680c8bad..f22a87541 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -354,7 +354,7 @@ const ws = new WeakSet(b); // Uncaught TypeError: Invalid value used in weak set(…) ``` -上面代码中,数组`b`的成员不是对象,加入 WeaKSet 就会报错。 +上面代码中,数组`b`的成员不是对象,加入 WeakSet 就会报错。 WeakSet 结构有以下三个方法。 From 10338972d5ee6815dd5a9419ee2a936913d2fc0f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 14 Sep 2019 17:37:20 +0800 Subject: [PATCH 004/280] docs(class): fix #906 --- docs/class.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/class.md b/docs/class.md index ba4d6378d..a235dd689 100644 --- a/docs/class.md +++ b/docs/class.md @@ -681,7 +681,7 @@ Foo.prop // 1 上面的写法为`Foo`类定义了一个静态属性`prop`。 -目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个[提案](https://github.com/tc39/proposal-class-fields)提供了类的静态属性,写法是在实例属性法的前面,加上`static`关键字。 +目前,只有这种写法可行,因为 ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个[提案](https://github.com/tc39/proposal-class-fields)提供了类的静态属性,写法是在实例属性的前面,加上`static`关键字。 ```javascript class MyClass { From fa183748b39d004a29099f2007e07642f247d0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E8=85=BE=E9=81=93?= Date: Sat, 14 Sep 2019 22:34:42 +0800 Subject: [PATCH 005/280] Update regex.md --- docs/regex.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/regex.md b/docs/regex.md index 9fa854fa2..c95e4fe39 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -132,6 +132,15 @@ codePointLength(s) // 2 上面代码中,不加`u`修饰符,就无法识别非规范的`K`字符。 +**(6)转义** + +在没有`u`修饰符的情况下,正则中没有定义的转义(如`/\,/`)相当于没有转义(`/,/`);而在`u`模式下,这会报错。 + +```javascript +/\,/ // /\,/ +/\,/u // Uncaught SyntaxError: Invalid regular expression: /\,/: Invalid escape +``` + ## RegExp.prototype.unicode 属性 正则实例对象新增`unicode`属性,表示是否设置了`u`修饰符。 From 8372288661c62c2a58bb1e65dc8c086314d8dd48 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 18 Sep 2019 01:47:26 +0800 Subject: [PATCH 006/280] docs(regex): edit u flag --- docs/regex.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/regex.md b/docs/regex.md index c95e4fe39..370b01fa4 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -134,13 +134,15 @@ codePointLength(s) // 2 **(6)转义** -在没有`u`修饰符的情况下,正则中没有定义的转义(如`/\,/`)相当于没有转义(`/,/`);而在`u`模式下,这会报错。 +没有`u`修饰符的情况下,正则中没有定义的转义(如逗号的转义`\,`)无效,而在`u`模式会报错。 ```javascript /\,/ // /\,/ -/\,/u // Uncaught SyntaxError: Invalid regular expression: /\,/: Invalid escape +/\,/u // 报错 ``` +上面代码中,没有`u`修饰符时,逗号前面的反斜杠是无效的,加了`u`修饰符就报错。 + ## RegExp.prototype.unicode 属性 正则实例对象新增`unicode`属性,表示是否设置了`u`修饰符。 From 95352fdf1196b0ccb23991d37c1ac462e1baa980 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 18 Sep 2019 17:55:23 +0800 Subject: [PATCH 007/280] docs(proposals): edit Nullish coalescing --- docs/proposals.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/proposals.md b/docs/proposals.md index 8cf5c0b1c..7746b8f3a 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -273,12 +273,60 @@ const showSplashScreen = response.settings.showSplashScreen ?? true; 上面代码中,默认值只有在属性值为`null`或`undefined`时,才会生效。 -这个运算符可以跟链判断运算符`?.`配合使用。 +这个运算符的一个目的,就是跟链判断运算符`?.`配合使用,为`null`或`undefined`的值设置默认值。 ```javascript const animationDuration = response.settings?.animationDuration ?? 300; ``` +上面代码中,`response.settings`如果是`null`或`undefined`,就会返回默认值300。 + +这个运算符很适合判断函数参数是否赋值。 + +```javascript +function Component(props) { + const enable = props.enabled ?? true; + // … +} +``` + +上面代码判断`props`参数的`enabled`属性是否赋值,等同于下面的写法。 + +```javascript +function Component(props) { + const { + enabled: enable = true, + } = props; + // … +} +``` + +`??`有一个运算优先级问题,它与`&&`和`||`的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。 + +```javascript +// 报错 +lhs && middle ?? rhs +lhs ?? middle && rhs +lhs || middle ?? rhs +lhs ?? middle || rhs +``` + +上面四个表达式都会报错,必须加入表明优先级的括号。 + +```javascript +(lhs && middle) ?? rhs; +lhs && (middle ?? rhs); + +(lhs ?? middle) && rhs; +lhs ?? (middle && rhs); + +(lhs || middle) ?? rhs; +lhs || (middle ?? rhs); + +(lhs ?? middle) || rhs; +lhs ?? (middle || rhs); +``` + ## 函数的部分执行 ### 语法 From 22665e7efc6f5345e0e2d2d3e8d609fc44fe0baa Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 20 Sep 2019 22:12:44 +0800 Subject: [PATCH 008/280] docs(generator): fix #910 --- docs/generator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index e08a149af..c0683c6b4 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -700,7 +700,7 @@ g.next() // { value: 1, done: false } g.return() // { value: undefined, done: true } ``` -如果 Generator 函数内部有`try...finally`代码块,且正在执行`try`代码块,那么`return`方法会推迟到`finally`代码块执行完再执行。 +如果 Generator 函数内部有`try...finally`代码块,且正在执行`try`代码块,那么`return`方法会导致立刻进入`finally`代码块,执行完以后,整个函数才会结束。 ```javascript function* numbers () { @@ -722,7 +722,7 @@ g.next() // { value: 5, done: false } g.next() // { value: 7, done: true } ``` -上面代码中,调用`return`方法后,就开始执行`finally`代码块,然后等到`finally`代码块执行完,再执行`return`方法。 +上面代码中,调用`return()`方法后,就开始执行`finally`代码块,不执行`try`里面剩下的代码了,然后等到`finally`代码块执行完,再返回`return()`方法指定的返回值。 ## next()、throw()、return() 的共同点 From 561b30426ef7e49775e6849a142a4e58d71dc5e8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 20 Sep 2019 22:35:01 +0800 Subject: [PATCH 009/280] docs(string-methods): fix #913 --- docs/string-methods.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/string-methods.md b/docs/string-methods.md index 318d0f724..0d91bb305 100644 --- a/docs/string-methods.md +++ b/docs/string-methods.md @@ -49,16 +49,16 @@ String.raw`Hi\\n` === "Hi\\\\n" // true `String.raw()`方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。 -`String.raw()`方法也可以作为正常的函数使用。这时,它的第一个参数,应该是一个具有`raw`属性的对象,且`raw`属性的值应该是一个数组。 +`String.raw()`本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有`raw`属性的对象,且`raw`属性的值应该是一个数组,对应模板字符串解析后的值。 ```javascript -String.raw({ raw: 'test' }, 0, 1, 2); -// 't0e1s2t' - +// `foo${1 + 2}bar` // 等同于 -String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2); +String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar" ``` +上面代码中,`String.raw()`方法的第一个参数是一个对象,它的`raw`属性等同于原始的模板字符串解析后得到的数组。 + 作为函数,`String.raw()`的代码实现基本如下。 ```javascript From 0e7b53f00f6235929753c0fa526bf9e8e41a039f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 20 Sep 2019 22:37:47 +0800 Subject: [PATCH 010/280] docs(string-methods): fix #913 --- docs/string-methods.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/string-methods.md b/docs/string-methods.md index 0d91bb305..99233f310 100644 --- a/docs/string-methods.md +++ b/docs/string-methods.md @@ -31,11 +31,11 @@ String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y' ES6 还为原生的 String 对象,提供了一个`raw()`方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。 ```javascript -String.raw`Hi\n${2+3}!`; -// 返回 "Hi\\n5!" +String.raw`Hi\n${2+3}!` +// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!" String.raw`Hi\u000A!`; -// 返回 "Hi\\u000A!" +// 实际返回 "Hi\\u000A!",显示的是转义后的结果 "Hi\u000A!" ``` 如果原字符串的斜杠已经转义,那么`String.raw()`会进行再次转义。 From 647f1221a774c352dfe5d6937261fd031d9d5bbb Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 6 Oct 2019 09:48:19 +0800 Subject: [PATCH 011/280] =?UTF-8?q?docs(array):=20Array.prototype.sore=20?= =?UTF-8?q?=E7=9A=84=E6=8E=92=E5=BA=8F=E7=A8=B3=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/array.md | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/array.md b/docs/array.md index 08dd118b7..d1f123f46 100644 --- a/docs/array.md +++ b/docs/array.md @@ -32,7 +32,7 @@ const numbers = [4, 38]; add(...numbers) // 42 ``` -上面代码中,`array.push(...items)`和`add(...numbers)`这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。 +上面代码中,`array.push(...items)`和`add(...numbers)`这两行,都是函数的调用,它们都使用了扩展运算符。该运算符将一个数组,变为参数序列。 扩展运算符与正常的函数参数可以结合使用,非常灵活。 @@ -947,3 +947,43 @@ for (let i of arr) { ``` 由于空位的处理规则非常不统一,所以建议避免出现空位。 + +## Array.prototype.sort() 的排序稳定性 + +排序稳定性(stable sorting)是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变。 + +```javascript +const arr = [ + 'peach', + 'straw', + 'apple', + 'spork' +]; + +const stableSorting = (s1, s2) => { + if (s1[0] < s2[0]) return -1; + return 1; +}; + +arr.sort(stableSorting) +// ["apple", "peach", "straw", "spork"] +``` + +上面代码对数组`arr`按照首字母进行排序。排序结果中,`straw`在`spork`的前面,跟原始顺序一致,所以排序算法`stableSorting`是稳定排序。 + +```javascript +const unstableSorting = (s1, s2) => { + if (s1[0] <= s2[0]) return -1; + return 1; +}; + +arr.sort(unstableSorting) +// ["apple", "peach", "spork", "straw"] +``` + +上面代码中,排序结果是`spork`在`straw`前面,跟原始顺序相反,所以排序算法`unstableSorting`是不稳定的。 + +常见的排序算法之中,插入排序、合并排序、冒泡排序等都是稳定的,堆排序、快速排序等是不稳定的。不稳定排序的主要缺点是,多重排序时可能会产生问题。假设有一个姓和名的列表,按照“先姓,后名”进行排序。开发者可能会先按名字排序,再按姓氏进行排序。如果排序算法是稳定的,这样也可以达到“先姓,后名”的排序效果。如果是不稳定的,就不行。 + +早先的 ECMAScript 没有规定,`Array.prototype.sort()`的默认排序算法是否稳定,留给浏览器自己决定,这导致某些实现是不稳定的。[ES2019](https://github.com/tc39/ecma262/pull/1340) 明确规定,`Array.prototype.sort()`的默认排序算法必须稳定。这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。 + From 99b39cc2300015a8e194ac9466a9cf460878e526 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 6 Oct 2019 20:11:13 +0800 Subject: [PATCH 012/280] docs(Promise): edit Promise --- docs/promise.md | 141 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 6 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 27ea878a3..4b62999e2 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -567,13 +567,13 @@ Promise.reject(3).finally(() => {}) ## Promise.all() -`Promise.all`方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 +`Promise.all()`方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 ```javascript const p = Promise.all([p1, p2, p3]); ``` -上面代码中,`Promise.all`方法接受一个数组作为参数,`p1`、`p2`、`p3`都是 Promise 实例,如果不是,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。(`Promise.all`方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。) +上面代码中,`Promise.all()`方法接受一个数组作为参数,`p1`、`p2`、`p3`都是 Promise 实例,如果不是,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。另外,`Promise.all()`方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。 `p`的状态由`p1`、`p2`、`p3`决定,分成两种情况。 @@ -662,7 +662,7 @@ Promise.all([p1, p2]) ## Promise.race() -`Promise.race`方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 +`Promise.race()`方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 ```javascript const p = Promise.race([p1, p2, p3]); @@ -670,7 +670,7 @@ const p = Promise.race([p1, p2, p3]); 上面代码中,只要`p1`、`p2`、`p3`之中有一个实例率先改变状态,`p`的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给`p`的回调函数。 -`Promise.race`方法的参数与`Promise.all`方法一样,如果不是 Promise 实例,就会先调用下面讲到的`Promise.resolve`方法,将参数转为 Promise 实例,再进一步处理。 +`Promise.race()`方法的参数与`Promise.all()`方法一样,如果不是 Promise 实例,就会先调用下面讲到的`Promise.resolve()`方法,将参数转为 Promise 实例,再进一步处理。 下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为`reject`,否则变为`resolve`。 @@ -689,9 +689,138 @@ p 上面代码中,如果 5 秒之内`fetch`方法无法返回结果,变量`p`的状态就会变为`rejected`,从而触发`catch`方法指定的回调函数。 +## Promise.allSettled() + +`Promise.allSettled()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是`fulfilled`还是`rejected`,包装实例才会结束。该方法由[ES2020](https://github.com/tc39/proposal-promise-allSettled) 引入。 + +```javascript +const promises = [ + fetch('/api-1'), + fetch('/api-2'), + fetch('/api-3'), +]; + +await Promise.allSettled(promises); +removeLoadingIndicator(); +``` + +上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。 + +该方法返回的新的 Promise 实例,一旦结束,状态总是`fulfilled`,不会变成`rejected`。状态变成`fulfilled`后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入`Promise.allSettled()`的 Promise 实例。 + +```javascript +const resolved = Promise.resolve(42); +const rejected = Promise.reject(-1); + +const allSettledPromise = Promise.allSettled([resolved, rejected]); + +allSettledPromise.then(function (results) { + console.log(results); +}); +// [ +// { status: 'fulfilled', value: 42 }, +// { status: 'rejected', reason: -1 } +// ] +``` + +上面代码中,`Promise.allSettled()`的返回值`allSettledPromise`,状态只可能变成`fulfilled`。它的监听函数接收到的参数是数组`results`。该数组的每个成员都是一个对象,对应传入`Promise.allSettled()`的两个 Promise 实例。每个对象都有`status`属性,该属性的值只可能是字符串`fulfilled`或字符串`rejected`。`fulfilled`时,对象有`value`属性,`rejected`时有`reason`属性,对应两种状态的返回值。 + +下面是返回值用法的例子。 + +```javascript +const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ]; +const results = await Promise.allSettled(promises); + +// 过滤出成功的请求 +const successfulPromises = results.filter(p => p.status === 'fulfilled'); + +// 过滤出失败的请求,并输出原因 +const errors = results + .filter(p => p.status === 'rejected') + .map(p => p.reason); +``` + +有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,`Promise.allSettled()`方法就很有用。如果没有这个方法,想要确保所有操作都结束,就很麻烦。`Promise.all()`方法无法做到这一点。 + +```javascript +const urls = [ /* ... */ ]; +const requests = urls.map(x => fetch(x)); + +try { + await Promise.all(requests); + console.log('所有请求都成功。'); +} catch { + console.log('至少一个请求失败,其他请求可能还没结束。'); +} +``` + +上面代码中,`Promise.all()`无法确定所有请求都结束。想要达到这个目的,写起来很麻烦,有了`Promise.allSettled()`,这就很容易了。 + +## Promise.any() + +`Promise.any()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。该方法目前是一个第三阶段的[提案](https://github.com/tc39/proposal-promise-any) 。 + +`Promise.all()`跟`Promise.race()`方法很像,只有一点不同,就是不会因为某个 Promise 变成`rejected`状态而结束。 + +```javascript +const promises = [ + fetch('/endpoint-a').then(() => 'a'), + fetch('/endpoint-b').then(() => 'b'), + fetch('/endpoint-c').then(() => 'c'), +]; +try { + const first = await Promise.any(promises); + console.log(first); +} catch (error) { + console.log(error); +} +``` + +上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled`,`Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么就会`await`命令就会抛出错误。 + +`Promise.any()`抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。 + +```javascript +new AggregateError() extends Array -> AggregateError + +const err = new AggregateError(); +err.push(new Error("first error")); +err.push(new Error("second error")); +throw err; +``` + +捕捉错误时,如果不用`try...catch`结构和 await 命令,可以像下面这样写。 + +```javascript +Promise.any(promises).then( + (first) => { + // Any of the promises was fulfilled. + }, + (error) => { + // All of the promises were rejected. + } +); +``` + +下面是一个例子。 + +```javascript +var resolved = Promise.resolve(42); +var rejected = Promise.reject(-1); +var alsoRejected = Promise.reject(Infinity); + +Promise.any([resolved, rejected, alsoRejected]).then(function (result) { + console.log(result); // 42 +}); + +Promise.any([rejected, alsoRejected]).catch(function (results) { + console.log(results); // [-1, Infinity] +}); +``` + ## Promise.resolve() -有时需要将现有对象转为 Promise 对象,`Promise.resolve`方法就起到这个作用。 +有时需要将现有对象转为 Promise 对象,`Promise.resolve()`方法就起到这个作用。 ```javascript const jsPromise = Promise.resolve($.ajax('/whatever.json')); @@ -699,7 +828,7 @@ const jsPromise = Promise.resolve($.ajax('/whatever.json')); 上面代码将 jQuery 生成的`deferred`对象,转为一个新的 Promise 对象。 -`Promise.resolve`等价于下面的写法。 +`Promise.resolve()`等价于下面的写法。 ```javascript Promise.resolve('foo') From eebbf77f99d2553c8a144cf766f34cc8e3ab2d5e Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 6 Oct 2019 22:58:18 +0800 Subject: [PATCH 013/280] docs(proposals): edit BigInt --- docs/promise.md | 2 +- docs/proposals.md | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 4b62999e2..5dd098504 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -691,7 +691,7 @@ p ## Promise.allSettled() -`Promise.allSettled()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是`fulfilled`还是`rejected`,包装实例才会结束。该方法由[ES2020](https://github.com/tc39/proposal-promise-allSettled) 引入。 +`Promise.allSettled()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是`fulfilled`还是`rejected`,包装实例才会结束。该方法由 [ES2020](https://github.com/tc39/proposal-promise-allSettled) 引入。 ```javascript const promises = [ diff --git a/docs/proposals.md b/docs/proposals.md index 7746b8f3a..f10c1caaa 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -694,12 +694,15 @@ BigInt(1.5) // RangeError BigInt('1.5') // SyntaxError ``` -BigInt 对象继承了 Object 提供的实例方法。 +BigInt 对象继承了 Object 对象的两个实例方法。 -- `BigInt.prototype.toLocaleString()` - `BigInt.prototype.toString()` - `BigInt.prototype.valueOf()` +它还继承了 Number 对象的一个实例方法。 + +- `BigInt.prototype.toLocaleString()` + 此外,还提供了三个静态方法。 - `BigInt.asUintN(width, BigInt)`: 给定的 BigInt 转为 0 到 2width - 1 之间对应的值。 From 7a028069c94ef37a715b83be81c26eef08b4f82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E9=B1=BC=E4=BD=A0=E4=B8=AA=E9=94=85=E9=94=85?= <31118840+igwhaler@users.noreply.github.com> Date: Thu, 10 Oct 2019 11:45:05 +0800 Subject: [PATCH 014/280] Update promise.md --- docs/promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/promise.md b/docs/promise.md index 5dd098504..bd2b631e4 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -760,7 +760,7 @@ try { `Promise.any()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。该方法目前是一个第三阶段的[提案](https://github.com/tc39/proposal-promise-any) 。 -`Promise.all()`跟`Promise.race()`方法很像,只有一点不同,就是不会因为某个 Promise 变成`rejected`状态而结束。 +`Promise.any()`跟`Promise.race()`方法很像,只有一点不同,就是不会因为某个 Promise 变成`rejected`状态而结束。 ```javascript const promises = [ From 00f624e13707a02556d7a23a6948cead9c051c20 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 13 Oct 2019 22:23:44 +0800 Subject: [PATCH 015/280] feat: add banner --- js/ditto.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 3985123b0..edccf29bd 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -95,6 +95,8 @@ function init_sidebar_section() { } location.hash = menu[i + 1]; }); + create_banner($(ditto.sidebar_id).find('p:nth-child(3)').first()); + }, "text").fail(function() { alert("Opps! can't find the sidebar file to display!"); }); @@ -208,19 +210,24 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { + // 2019年11月20日 + var deadline = new Date(2019, 10, 20); + if (deadline - (new Date()) < 0) return; + var styleStr = [ 'margin: 1em 0', 'padding: 1em', 'background-color: #c4e0e1', 'border-radius: 5px', - 'font-size: 90%' + 'font-size: 75%', + 'color: #333333' ].join(';'); - var text = 'Vue 实战教程 ' + - '深入学习一线大厂必备 Vue 技能。VIP 教程限时免费领取。' + - '⇐ 立即查看'; + var text = 'ES6 实战教程 ' + + '深入学习一线大厂必备 ES6 技能。VIP 教程限时免费领取。' + + '⇐ 立即查看'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } @@ -261,7 +268,7 @@ function create_page_anchors() { .addClass('content-toc') .attr('id', 'content-toc'); - create_banner(ul_tag); + // create_banner(ul_tag); for (var j = 0; j < headers.length; j++) { var li_tag = $('
  • ').html('' + headers[j] + ''); From 4ebdc52dfbf8c8f44d6b72b0183e87e2c76124e1 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 13 Oct 2019 22:30:14 +0800 Subject: [PATCH 016/280] refact: add banner --- js/ditto.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/ditto.js b/js/ditto.js index edccf29bd..e11931f16 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -220,6 +220,7 @@ function create_banner(element) { 'background-color: #c4e0e1', 'border-radius: 5px', 'font-size: 75%', + 'width: 210px', 'color: #333333' ].join(';'); From 247a499d6ed5e4131e0094694f7d31daf86bc730 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 15 Oct 2019 18:45:13 +0800 Subject: [PATCH 017/280] refact: edit banner --- js/ditto.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index e11931f16..c7ede6cfd 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -95,7 +95,7 @@ function init_sidebar_section() { } location.hash = menu[i + 1]; }); - create_banner($(ditto.sidebar_id).find('p:nth-child(3)').first()); + // create_banner($(ditto.sidebar_id).find('p:nth-child(3)').first()); }, "text").fail(function() { alert("Opps! can't find the sidebar file to display!"); @@ -219,14 +219,15 @@ function create_banner(element) { 'padding: 1em', 'background-color: #c4e0e1', 'border-radius: 5px', - 'font-size: 75%', - 'width: 210px', + 'font-size: 90%', + // 'font-size: 75%', + // 'width: 210px', 'color: #333333' ].join(';'); - var text = 'ES6 实战教程 ' + + var text = '《ES6 实战教程》 ' + '深入学习一线大厂必备 ES6 技能。VIP 教程限时免费领取。' + - '⇐ 立即查看'; + ' ⇐ 立即查看'; var banner = $('
    ' + text + '
    ') .insertAfter(element); @@ -269,7 +270,7 @@ function create_page_anchors() { .addClass('content-toc') .attr('id', 'content-toc'); - // create_banner(ul_tag); + create_banner(ul_tag); for (var j = 0; j < headers.length; j++) { var li_tag = $('
  • ').html('' + headers[j] + ''); From dff5c20de71b7811466a80920f650bbe0e72741b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=87=92=E8=B1=86?= Date: Tue, 29 Oct 2019 14:35:24 +0800 Subject: [PATCH 018/280] fix:Add description fix:Add description --- docs/symbol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/symbol.md b/docs/symbol.md index 3233e8000..f2ecc1711 100644 --- a/docs/symbol.md +++ b/docs/symbol.md @@ -370,7 +370,7 @@ Object.getOwnPropertySymbols(x) // [Symbol(size)] ## Symbol.for(),Symbol.keyFor() -有时,我们希望重新使用同一个 Symbol 值,`Symbol.for`方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。 +有时,我们希望重新使用同一个 Symbol 值,`Symbol.for`方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称注册到全局的 Symbol 值然后返回。 ```javascript let s1 = Symbol.for('foo'); From 3484625f67fee6874c12c9212eecb6940cb916ce Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 31 Oct 2019 01:28:52 +0800 Subject: [PATCH 019/280] docs(symbol): edit Symbol.for() --- docs/symbol.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/symbol.md b/docs/symbol.md index f2ecc1711..39b304b99 100644 --- a/docs/symbol.md +++ b/docs/symbol.md @@ -370,7 +370,7 @@ Object.getOwnPropertySymbols(x) // [Symbol(size)] ## Symbol.for(),Symbol.keyFor() -有时,我们希望重新使用同一个 Symbol 值,`Symbol.for`方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称注册到全局的 Symbol 值然后返回。 +有时,我们希望重新使用同一个 Symbol 值,`Symbol.for()`方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。 ```javascript let s1 = Symbol.for('foo'); @@ -393,7 +393,7 @@ Symbol("bar") === Symbol("bar") 上面代码中,由于`Symbol()`写法没有登记机制,所以每次调用都会返回一个不同的值。 -`Symbol.keyFor`方法返回一个已登记的 Symbol 类型值的`key`。 +`Symbol.keyFor()`方法返回一个已登记的 Symbol 类型值的`key`。 ```javascript let s1 = Symbol.for("foo"); @@ -405,7 +405,21 @@ Symbol.keyFor(s2) // undefined 上面代码中,变量`s2`属于未登记的 Symbol 值,所以返回`undefined`。 -需要注意的是,`Symbol.for`为 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。 +注意,`Symbol.for()`为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行。 + +```javascript +function foo() { + return Symbol.for('bar'); +} + +const x = foo(); +const y = Symbol.for('bar'); +console.log(x === y); // true +``` + +上面代码中,`Symbol.for('bar')`是函数内部运行的,但是生成的 Symbol 值是登记在全局环境的。所以,第二次运行`Symbol.for('bar')`可以取到这个 Symbol 值。 + +`Symbol.for()`的这个全局登记特性,可以用在不同的 iframe 或 service worker 中取到同一个值。 ```javascript iframe = document.createElement('iframe'); From 6deab4b66c79893c65c321cdecc36bc97f065d16 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 8 Nov 2019 10:40:11 +0800 Subject: [PATCH 020/280] refactor: modify banner date --- js/ditto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index c7ede6cfd..6d48b4421 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -210,8 +210,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2019年11月20日 - var deadline = new Date(2019, 10, 20); + // 2020年3月22日 + var deadline = new Date(2020, 2, 22); if (deadline - (new Date()) < 0) return; var styleStr = [ From 7145158b2a1322603d93e99a2dd2d1dc9ab02e79 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 8 Nov 2019 10:53:57 +0800 Subject: [PATCH 021/280] =?UTF-8?q?docs(Symbol):=20edit=20=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E5=90=8D=E7=9A=84=E9=81=8D=E5=8E=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/symbol.md | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/docs/symbol.md b/docs/symbol.md index 39b304b99..e8d97cb66 100644 --- a/docs/symbol.md +++ b/docs/symbol.md @@ -280,9 +280,9 @@ const shapeType = { ## 属性名的遍历 -Symbol 作为属性名,该属性不会出现在`for...in`、`for...of`循环中,也不会被`Object.keys()`、`Object.getOwnPropertyNames()`、`JSON.stringify()`返回。但是,它也不是私有属性,有一个`Object.getOwnPropertySymbols`方法,可以获取指定对象的所有 Symbol 属性名。 +Symbol 作为属性名,遍历对象的时候,该属性不会出现在`for...in`、`for...of`循环中,也不会被`Object.keys()`、`Object.getOwnPropertyNames()`、`JSON.stringify()`返回。 -`Object.getOwnPropertySymbols`方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。 +但是,它也不是私有属性,有一个`Object.getOwnPropertySymbols()`方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。 ```javascript const obj = {}; @@ -298,31 +298,27 @@ objectSymbols // [Symbol(a), Symbol(b)] ``` -下面是另一个例子,`Object.getOwnPropertySymbols`方法与`for...in`循环、`Object.getOwnPropertyNames`方法进行对比的例子。 +上面代码是`Object.getOwnPropertySymbols()`方法的示例,可以获取所有 Symbol 属性名。 + +下面是另一个例子,`Object.getOwnPropertySymbols()`方法与`for...in`循环、`Object.getOwnPropertyNames`方法进行对比的例子。 ```javascript const obj = {}; +const foo = Symbol('foo'); -let foo = Symbol("foo"); - -Object.defineProperty(obj, foo, { - value: "foobar", -}); +obj[foo] = 'bar'; for (let i in obj) { console.log(i); // 无输出 } -Object.getOwnPropertyNames(obj) -// [] - -Object.getOwnPropertySymbols(obj) -// [Symbol(foo)] +Object.getOwnPropertyNames(obj) // [] +Object.getOwnPropertySymbols(obj) // [Symbol(foo)] ``` -上面代码中,使用`Object.getOwnPropertyNames`方法得不到`Symbol`属性名,需要使用`Object.getOwnPropertySymbols`方法。 +上面代码中,使用`for...in`循环和`Object.getOwnPropertyNames()`方法都得不到 Symbol 键名,需要使用`Object.getOwnPropertySymbols()`方法。 -另一个新的 API,`Reflect.ownKeys`方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。 +另一个新的 API,`Reflect.ownKeys()`方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。 ```javascript let obj = { @@ -335,7 +331,7 @@ Reflect.ownKeys(obj) // ["enum", "nonEnum", Symbol(my_key)] ``` -由于以 Symbol 值作为名称的属性,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。 +由于以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。 ```javascript let size = Symbol('size'); From 37c199d560bcb93878843b79053efc6e3e35a927 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 14 Nov 2019 14:30:22 +0800 Subject: [PATCH 022/280] docs(proposal): edit BigInt --- docs/proposals.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/proposals.md b/docs/proposals.md index f10c1caaa..e119a63e9 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -664,6 +664,26 @@ BigInt 可以使用负号(`-`),但是不能使用正号(`+`),因为 +42n // 报错 ``` +JavaScript 以前不能计算70的阶乘(即`70!`),因为超出了可以表示的精度。 + +```javascript +let p = 1; +for (let i = 1; i <= 70; i++) { + p *= i; +} +console.log(p); // 1.197857166996989e+100 +``` + +现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。 + +```javascript +let p = 1n; +for (let i = 1n; i <= 70n; i++) { + p *= i; +} +console.log(p); // 11978571...00000000n +``` + ### BigInt 对象 JavaScript 原生提供`BigInt`对象,可以用作构造函数生成 BigInt 类型的数值。转换规则基本与`Number()`一致,将其他类型的值转为 BigInt。 From f0aa112f97e16e3c39f27699ff178cec81481ce9 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 20 Nov 2019 17:37:59 +0800 Subject: [PATCH 023/280] docs(module-loader): add jQuery example --- docs/module-loader.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/module-loader.md b/docs/module-loader.md index 4a3fa0e50..88899b060 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -72,6 +72,15 @@ ES6 模块也允许内嵌在网页中,语法行为与加载外部脚本完全 ``` +举例来说,jQuery 就支持模块加载。 + +```html + +``` + 对于外部的模块脚本(上例是`foo.js`),有几点需要注意。 - 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。 From 6918f7abaa84431e8ad46e17db93804b2e961f2c Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 7 Dec 2019 01:01:06 +0800 Subject: [PATCH 024/280] =?UTF-8?q?docs(object):=20Null=20=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E8=BF=90=E7=AE=97=E7=AC=A6=E7=A7=BB=E5=85=A5=20Object?= =?UTF-8?q?=20=E7=AB=A0=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/object.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++ docs/proposals.md | 76 ----------------------------------------------- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/docs/object.md b/docs/object.md index 4ff75b69d..74748c003 100644 --- a/docs/object.md +++ b/docs/object.md @@ -698,3 +698,79 @@ let runtimeError = { }; ``` +## Null 判断运算符 + +读取对象属性的时候,如果某个属性的值是`null`或`undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。 + +```javascript +const headerText = response.settings.headerText || 'Hello, world!'; +const animationDuration = response.settings.animationDuration || 300; +const showSplashScreen = response.settings.showSplashScreen || true; +``` + +上面的三行代码都通过`||`运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为`null`或`undefined`,默认值就会生效,但是属性的值如果为空字符串或`false`或`0`,默认值也会生效。 + +为了避免这种情况,[ECMAScript2020](https://github.com/tc39/proposal-nullish-coalescing) 引入了一个新的 Null 判断运算符`??`。它的行为类似`||`,但是只有运算符左侧的值为`null`或`undefined`时,才会返回右侧的值。 + +```javascript +const headerText = response.settings.headerText ?? 'Hello, world!'; +const animationDuration = response.settings.animationDuration ?? 300; +const showSplashScreen = response.settings.showSplashScreen ?? true; +``` + +上面代码中,默认值只有在属性值为`null`或`undefined`时,才会生效。 + +这个运算符的一个目的,就是跟链判断运算符`?.`配合使用,为`null`或`undefined`的值设置默认值。 + +```javascript +const animationDuration = response.settings?.animationDuration ?? 300; +``` + +上面代码中,`response.settings`如果是`null`或`undefined`,就会返回默认值300。 + +这个运算符很适合判断函数参数是否赋值。 + +```javascript +function Component(props) { + const enable = props.enabled ?? true; + // … +} +``` + +上面代码判断`props`参数的`enabled`属性是否赋值,等同于下面的写法。 + +```javascript +function Component(props) { + const { + enabled: enable = true, + } = props; + // … +} +``` + +`??`有一个运算优先级问题,它与`&&`和`||`的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。 + +```javascript +// 报错 +lhs && middle ?? rhs +lhs ?? middle && rhs +lhs || middle ?? rhs +lhs ?? middle || rhs +``` + +上面四个表达式都会报错,必须加入表明优先级的括号。 + +```javascript +(lhs && middle) ?? rhs; +lhs && (middle ?? rhs); + +(lhs ?? middle) && rhs; +lhs ?? (middle && rhs); + +(lhs || middle) ?? rhs; +lhs || (middle ?? rhs); + +(lhs ?? middle) || rhs; +lhs ?? (middle || rhs); +``` + diff --git a/docs/proposals.md b/docs/proposals.md index e119a63e9..a8d40af3b 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -251,82 +251,6 @@ a?.b = c 为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。 -## Null 判断运算符 - -读取对象属性的时候,如果某个属性的值是`null`或`undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。 - -```javascript -const headerText = response.settings.headerText || 'Hello, world!'; -const animationDuration = response.settings.animationDuration || 300; -const showSplashScreen = response.settings.showSplashScreen || true; -``` - -上面的三行代码都通过`||`运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为`null`或`undefined`,默认值就会生效,但是属性的值如果为空字符串或`false`或`0`,默认值也会生效。 - -为了避免这种情况,现在有一个[提案](https://github.com/tc39/proposal-nullish-coalescing),引入了一个新的 Null 判断运算符`??`。它的行为类似`||`,但是只有运算符左侧的值为`null`或`undefined`时,才会返回右侧的值。 - -```javascript -const headerText = response.settings.headerText ?? 'Hello, world!'; -const animationDuration = response.settings.animationDuration ?? 300; -const showSplashScreen = response.settings.showSplashScreen ?? true; -``` - -上面代码中,默认值只有在属性值为`null`或`undefined`时,才会生效。 - -这个运算符的一个目的,就是跟链判断运算符`?.`配合使用,为`null`或`undefined`的值设置默认值。 - -```javascript -const animationDuration = response.settings?.animationDuration ?? 300; -``` - -上面代码中,`response.settings`如果是`null`或`undefined`,就会返回默认值300。 - -这个运算符很适合判断函数参数是否赋值。 - -```javascript -function Component(props) { - const enable = props.enabled ?? true; - // … -} -``` - -上面代码判断`props`参数的`enabled`属性是否赋值,等同于下面的写法。 - -```javascript -function Component(props) { - const { - enabled: enable = true, - } = props; - // … -} -``` - -`??`有一个运算优先级问题,它与`&&`和`||`的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。 - -```javascript -// 报错 -lhs && middle ?? rhs -lhs ?? middle && rhs -lhs || middle ?? rhs -lhs ?? middle || rhs -``` - -上面四个表达式都会报错,必须加入表明优先级的括号。 - -```javascript -(lhs && middle) ?? rhs; -lhs && (middle ?? rhs); - -(lhs ?? middle) && rhs; -lhs ?? (middle && rhs); - -(lhs || middle) ?? rhs; -lhs || (middle ?? rhs); - -(lhs ?? middle) || rhs; -lhs ?? (middle || rhs); -``` - ## 函数的部分执行 ### 语法 From f4ce061d564bc34f5b9fc013fbe5e26ef5f62ea9 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 7 Dec 2019 17:42:05 +0800 Subject: [PATCH 025/280] =?UTF-8?q?docs(object):=20=E9=93=BE=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E8=BF=90=E7=AE=97=E7=AC=A6=E7=A7=BB=E5=85=A5=20Object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/object.md | 133 ++++++++++++++++++++++++++++++++++++++++++++++ docs/proposals.md | 133 ---------------------------------------------- 2 files changed, 133 insertions(+), 133 deletions(-) diff --git a/docs/object.md b/docs/object.md index 74748c003..f3f4c6c96 100644 --- a/docs/object.md +++ b/docs/object.md @@ -698,6 +698,139 @@ let runtimeError = { }; ``` +## 链判断运算符 + +编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取`message.body.user.firstName`,安全的写法是写成下面这样。 + +```javascript +const firstName = (message + && message.body + && message.body.user + && message.body.user.firstName) || 'default'; +``` + +或者使用三元运算符`?:`,判断一个对象是否存在。 + +```javascript +const fooInput = myForm.querySelector('input[name=foo]') +const fooValue = fooInput ? fooInput.value : undefined +``` + +这样的层层判断非常麻烦,因此 [ECMAScript2020](https://github.com/tc39/proposal-optional-chaining) 引入了“链判断运算符”(optional chaining operator)`?.`,简化上面的写法。 + +```javascript +const firstName = message?.body?.user?.firstName || 'default'; +const fooValue = myForm.querySelector('input[name=foo]')?.value +``` + +上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null`或`undefined`。如果是的,就不再往下运算,而是返回`undefined`。 + +链判断运算符有三种用法。 + +- `obj?.prop` // 对象属性 +- `obj?.[expr]` // 同上 +- `func?.(...args)` // 函数或对象方法的调用 + +下面是判断对象方法是否存在,如果存在就立即执行的例子。 + +```javascript +iterator.return?.() +``` + +上面代码中,`iterator.return`如果有定义,就会调用该方法,否则直接返回`undefined`。 + +对于那些可能没有实现的方法,这个运算符尤其有用。 + +```javascript +if (myForm.checkValidity?.() === false) { + // 表单校验失败 + return; +} +``` + +上面代码中,老式浏览器的表单可能没有`checkValidity`这个方法,这时`?.`运算符就会返回`undefined`,判断语句就变成了`undefined === false`,所以就会跳过下面的代码。 + +下面是这个运算符常见的使用形式,以及不使用该运算符时的等价形式。 + +```javascript +a?.b +// 等同于 +a == null ? undefined : a.b + +a?.[x] +// 等同于 +a == null ? undefined : a[x] + +a?.b() +// 等同于 +a == null ? undefined : a.b() + +a?.() +// 等同于 +a == null ? undefined : a() +``` + +上面代码中,特别注意后两种形式,如果`a?.b()`里面的`a.b`不是函数,不可调用,那么`a?.b()`是会报错的。`a?.()`也是如此,如果`a`不是`null`或`undefined`,但也不是函数,那么`a?.()`会报错。 + +使用这个运算符,有几个注意点。 + +(1)短路机制 + +```javascript +a?.[++x] +// 等同于 +a == null ? undefined : a[++x] +``` + +上面代码中,如果`a`是`undefined`或`null`,那么`x`不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。 + +(2)delete 运算符 + +```javascript +delete a?.b +// 等同于 +a == null ? undefined : delete a.b +``` + +上面代码中,如果`a`是`undefined`或`null`,会直接返回`undefined`,而不会进行`delete`运算。 + +(3)括号不改变运算顺序 + +```javascript +(a?.b).c +// 等价于 +(a == null ? undefined : a.b).c +``` + +上面代码中,`?.`对圆括号没有影响,不管`a`对象是否存在,圆括号后面的`.c`总是会执行。 + +一般来说,使用`?.`运算符的场合,不应该使用圆括号。 + +(4)报错场合 + +以下写法是禁止的,会报错。 + +```javascript +// 构造函数 +new a?.() +new a?.b() + +// 链判断运算符的右侧有模板字符串 +a?.`{b}` +a?.b`{c}` + +// 链判断运算符的左侧是 super +super?.() +super?.foo + +// 链运算符用于赋值运算符左侧 +a?.b = c +``` + +(5)右侧不得为十进制数值 + +为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。 + ## Null 判断运算符 读取对象属性的时候,如果某个属性的值是`null`或`undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。 diff --git a/docs/proposals.md b/docs/proposals.md index a8d40af3b..50097f39e 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -118,139 +118,6 @@ class Product { 语法上,`throw`表达式里面的`throw`不再是一个命令,而是一个运算符。为了避免与`throw`命令混淆,规定`throw`出现在行首,一律解释为`throw`语句,而不是`throw`表达式。 -## 链判断运算符 - -编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取`message.body.user.firstName`,安全的写法是写成下面这样。 - -```javascript -const firstName = (message - && message.body - && message.body.user - && message.body.user.firstName) || 'default'; -``` - -或者使用三元运算符`?:`,判断一个对象是否存在。 - -```javascript -const fooInput = myForm.querySelector('input[name=foo]') -const fooValue = fooInput ? fooInput.value : undefined -``` - -这样的层层判断非常麻烦,因此现在有一个[提案](https://github.com/tc39/proposal-optional-chaining),引入了“链判断运算符”(optional chaining operator)`?.`,简化上面的写法。 - -```javascript -const firstName = message?.body?.user?.firstName || 'default'; -const fooValue = myForm.querySelector('input[name=foo]')?.value -``` - -上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null`或`undefined`。如果是的,就不再往下运算,而是返回`undefined`。 - -链判断运算符有三种用法。 - -- `obj?.prop` // 对象属性 -- `obj?.[expr]` // 同上 -- `func?.(...args)` // 函数或对象方法的调用 - -下面是判断对象方法是否存在,如果存在就立即执行的例子。 - -```javascript -iterator.return?.() -``` - -上面代码中,`iterator.return`如果有定义,就会调用该方法,否则直接返回`undefined`。 - -对于那些可能没有实现的方法,这个运算符尤其有用。 - -```javascript -if (myForm.checkValidity?.() === false) { - // 表单校验失败 - return; -} -``` - -上面代码中,老式浏览器的表单可能没有`checkValidity`这个方法,这时`?.`运算符就会返回`undefined`,判断语句就变成了`undefined === false`,所以就会跳过下面的代码。 - -下面是这个运算符常见的使用形式,以及不使用该运算符时的等价形式。 - -```javascript -a?.b -// 等同于 -a == null ? undefined : a.b - -a?.[x] -// 等同于 -a == null ? undefined : a[x] - -a?.b() -// 等同于 -a == null ? undefined : a.b() - -a?.() -// 等同于 -a == null ? undefined : a() -``` - -上面代码中,特别注意后两种形式,如果`a?.b()`里面的`a.b`不是函数,不可调用,那么`a?.b()`是会报错的。`a?.()`也是如此,如果`a`不是`null`或`undefined`,但也不是函数,那么`a?.()`会报错。 - -使用这个运算符,有几个注意点。 - -(1)短路机制 - -```javascript -a?.[++x] -// 等同于 -a == null ? undefined : a[++x] -``` - -上面代码中,如果`a`是`undefined`或`null`,那么`x`不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。 - -(2)delete 运算符 - -```javascript -delete a?.b -// 等同于 -a == null ? undefined : delete a.b -``` - -上面代码中,如果`a`是`undefined`或`null`,会直接返回`undefined`,而不会进行`delete`运算。 - -(3)括号不改变运算顺序 - -```javascript -(a?.b).c -// 等价于 -(a == null ? undefined : a.b).c -``` - -上面代码中,`?.`对圆括号没有影响,不管`a`对象是否存在,圆括号后面的`.c`总是会执行。 - -一般来说,使用`?.`运算符的场合,不应该使用圆括号。 - -(4)报错场合 - -以下写法是禁止的,会报错。 - -```javascript -// 构造函数 -new a?.() -new a?.b() - -// 链判断运算符的右侧有模板字符串 -a?.`{b}` -a?.b`{c}` - -// 链判断运算符的左侧是 super -super?.() -super?.foo - -// 链运算符用于赋值运算符左侧 -a?.b = c -``` - -(5)右侧不得为十进制数值 - -为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。 - ## 函数的部分执行 ### 语法 From 726e37ecde8e892ffe87bb665fc7c6a00085a9b1 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 7 Dec 2019 18:16:49 +0800 Subject: [PATCH 026/280] docs(object): edit object --- docs/object.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/object.md b/docs/object.md index f3f4c6c96..38323ad28 100644 --- a/docs/object.md +++ b/docs/object.md @@ -716,7 +716,7 @@ const fooInput = myForm.querySelector('input[name=foo]') const fooValue = fooInput ? fooInput.value : undefined ``` -这样的层层判断非常麻烦,因此 [ECMAScript2020](https://github.com/tc39/proposal-optional-chaining) 引入了“链判断运算符”(optional chaining operator)`?.`,简化上面的写法。 +这样的层层判断非常麻烦,因此 [ES2020](https://github.com/tc39/proposal-optional-chaining) 引入了“链判断运算符”(optional chaining operator)`?.`,简化上面的写法。 ```javascript const firstName = message?.body?.user?.firstName || 'default'; @@ -843,7 +843,7 @@ const showSplashScreen = response.settings.showSplashScreen || true; 上面的三行代码都通过`||`运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为`null`或`undefined`,默认值就会生效,但是属性的值如果为空字符串或`false`或`0`,默认值也会生效。 -为了避免这种情况,[ECMAScript2020](https://github.com/tc39/proposal-nullish-coalescing) 引入了一个新的 Null 判断运算符`??`。它的行为类似`||`,但是只有运算符左侧的值为`null`或`undefined`时,才会返回右侧的值。 +为了避免这种情况,[ES2020](https://github.com/tc39/proposal-nullish-coalescing) 引入了一个新的 Null 判断运算符`??`。它的行为类似`||`,但是只有运算符左侧的值为`null`或`undefined`时,才会返回右侧的值。 ```javascript const headerText = response.settings.headerText ?? 'Hello, world!'; From add1bb8f822bed70912f64aeda2f3c0345e421ad Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 7 Dec 2019 18:50:42 +0800 Subject: [PATCH 027/280] docs(array): edit sort() --- docs/array.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/array.md b/docs/array.md index d1f123f46..59450e175 100644 --- a/docs/array.md +++ b/docs/array.md @@ -983,7 +983,7 @@ arr.sort(unstableSorting) 上面代码中,排序结果是`spork`在`straw`前面,跟原始顺序相反,所以排序算法`unstableSorting`是不稳定的。 -常见的排序算法之中,插入排序、合并排序、冒泡排序等都是稳定的,堆排序、快速排序等是不稳定的。不稳定排序的主要缺点是,多重排序时可能会产生问题。假设有一个姓和名的列表,按照“先姓,后名”进行排序。开发者可能会先按名字排序,再按姓氏进行排序。如果排序算法是稳定的,这样也可以达到“先姓,后名”的排序效果。如果是不稳定的,就不行。 +常见的排序算法之中,插入排序、合并排序、冒泡排序等都是稳定的,堆排序、快速排序等是不稳定的。不稳定排序的主要缺点是,多重排序时可能会产生问题。假设有一个姓和名的列表,要求按照“姓氏为主要关键字,名字为次要关键字”进行排序。开发者可能会先按名字排序,再按姓氏进行排序。如果排序算法是稳定的,这样就可以达到“先姓氏,后名字”的排序效果。如果是不稳定的,就不行。 早先的 ECMAScript 没有规定,`Array.prototype.sort()`的默认排序算法是否稳定,留给浏览器自己决定,这导致某些实现是不稳定的。[ES2019](https://github.com/tc39/ecma262/pull/1340) 明确规定,`Array.prototype.sort()`的默认排序算法必须稳定。这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。 From c4f4570f676a24a56255df237805b11516abd69e Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 7 Dec 2019 22:08:20 +0800 Subject: [PATCH 028/280] docs(object): fix #931 --- docs/object.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/object.md b/docs/object.md index 38323ad28..4f57408ba 100644 --- a/docs/object.md +++ b/docs/object.md @@ -794,7 +794,9 @@ a == null ? undefined : delete a.b 上面代码中,如果`a`是`undefined`或`null`,会直接返回`undefined`,而不会进行`delete`运算。 -(3)括号不改变运算顺序 +(3)括号的影响 + +如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。 ```javascript (a?.b).c @@ -802,7 +804,7 @@ a == null ? undefined : delete a.b (a == null ? undefined : a.b).c ``` -上面代码中,`?.`对圆括号没有影响,不管`a`对象是否存在,圆括号后面的`.c`总是会执行。 +上面代码中,`?.`对圆括号外部没有影响,不管`a`对象是否存在,圆括号后面的`.c`总是会执行。 一般来说,使用`?.`运算符的场合,不应该使用圆括号。 From ad12c153b33e133fd4a21f03dacd2ac9424cb398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BE=B7=E5=B8=A5?= Date: Mon, 16 Dec 2019 15:48:15 +0800 Subject: [PATCH 029/280] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit property 改为 property key 防止产生歧义 --- docs/proxy.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index 0ec65c95f..09dad1d0b 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -8,13 +8,13 @@ Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界 ```javascript var obj = new Proxy({}, { - get: function (target, key, receiver) { - console.log(`getting ${key}!`); - return Reflect.get(target, key, receiver); + get: function (target, propKey, receiver) { + console.log(`getting ${propKey}!`); + return Reflect.get(target, propKey, receiver); }, - set: function (target, key, value, receiver) { - console.log(`setting ${key}!`); - return Reflect.set(target, key, value, receiver); + set: function (target, propKey, value, receiver) { + console.log(`setting ${propKey}!`); + return Reflect.set(target, propKey, value, receiver); } }); ``` @@ -44,7 +44,7 @@ Proxy 对象的所有用法,都是上面这种形式,不同的只是`handler ```javascript var proxy = new Proxy({}, { - get: function(target, property) { + get: function(target, propKey) { return 35; } }); @@ -80,7 +80,7 @@ Proxy 实例也可以作为其他对象的原型对象。 ```javascript var proxy = new Proxy({}, { - get: function(target, property) { + get: function(target, propKey) { return 35; } }); @@ -155,11 +155,11 @@ var person = { }; var proxy = new Proxy(person, { - get: function(target, property) { - if (property in target) { - return target[property]; + get: function(target, propKey) { + if (propKey in target) { + return target[propKey]; } else { - throw new ReferenceError("Property \"" + property + "\" does not exist."); + throw new ReferenceError("Prop name \"" + propKey + "\" does not exist."); } } }); @@ -281,7 +281,7 @@ document.body.appendChild(el); ```javascript const proxy = new Proxy({}, { - get: function(target, property, receiver) { + get: function(target, key, receiver) { return receiver; } }); @@ -292,7 +292,7 @@ proxy.getReceiver === proxy // true ```javascript const proxy = new Proxy({}, { - get: function(target, property, receiver) { + get: function(target, key, receiver) { return receiver; } }); From a49a10ca485ed2065b9f297df31ca135ac0fb1b9 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 16 Dec 2019 16:11:16 +0800 Subject: [PATCH 030/280] docs(module-loader): update Node.js' esm support --- docs/module-loader.md | 354 ++++++++++++++++++++++++++---------------- 1 file changed, 219 insertions(+), 135 deletions(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index 88899b060..c4fa9dd5c 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -1,6 +1,6 @@ # Module 的加载实现 -上一章介绍了模块的语法,本章介绍如何在浏览器和 Node 之中加载 ES6 模块,以及实际开发中经常遇到的一些问题(比如循环加载)。 +上一章介绍了模块的语法,本章介绍如何在浏览器和 Node.js 之中加载 ES6 模块,以及实际开发中经常遇到的一些问题(比如循环加载)。 ## 浏览器加载 @@ -108,7 +108,7 @@ const isNotModuleScript = this !== undefined; ## ES6 模块与 CommonJS 模块的差异 -讨论 Node 加载 ES6 模块之前,必须了解 ES6 模块与 CommonJS 模块完全不同。 +讨论 Node.js 加载 ES6 模块之前,必须了解 ES6 模块与 CommonJS 模块完全不同。 它们有两个重大差异。 @@ -271,226 +271,310 @@ $ babel-node main.js 这就证明了`x.js`和`y.js`加载的都是`C`的同一个实例。 -## Node 加载 +## Node.js 加载 ### 概述 -Node 对 ES6 模块的处理比较麻烦,因为它有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。 +Node.js 对 ES6 模块的处理比较麻烦,因为它有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。从 v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。 -Node 要求 ES6 模块采用`.mjs`后缀文件名。也就是说,只要脚本文件里面使用`import`或者`export`命令,那么就必须采用`.mjs`后缀名。`require`命令不能加载`.mjs`文件,会报错,只有`import`命令才可以加载`.mjs`文件。反过来,`.mjs`文件里面也不能使用`require`命令,必须使用`import`。 +Node.js 要求 ES6 模块采用`.mjs`后缀文件名。也就是说,只要脚本文件里面使用`import`或者`export`命令,那么就必须采用`.mjs`后缀名。Node.js 遇到`.mjs`文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定`"use strict"`。 -目前,这项功能还在试验阶段。安装 Node v8.5.0 或以上版本,要用`--experimental-modules`参数才能打开该功能。 +如果不希望将后缀名改成`.mjs`,可以在项目的`package.json`文件中,指定`type`字段为`module`。 -```bash -$ node --experimental-modules my-app.mjs +```javascript +{ + "type": "module" +} ``` -为了与浏览器的`import`加载规则相同,Node 的`.mjs`文件支持 URL 路径。 +一旦设置了以后,该目录里面的 JS 脚本,就被解释用 ES6 模块。 -```javascript -import './foo?query=1'; // 加载 ./foo 传入参数 ?query=1 +```bash +# 解释成 ES6 模块 +$ node my-app.js ``` -上面代码中,脚本路径带有参数`?query=1`,Node 会按 URL 规则解读。同一个脚本只要参数不同,就会被加载多次,并且保存成不同的缓存。由于这个原因,只要文件名中含有`:`、`%`、`#`、`?`等特殊字符,最好对这些字符进行转义。 +如果这时还要使用 CommonJS 模块,那么需要将 CommonJS 脚本的后缀名都改成`.cjs`。如果没有`type`字段,或者`type`字段为`commonjs`,则`.js`脚本会被解释成 CommonJS 模块。 + +总结为一句话:`.mjs`文件总是以 ES6 模块加载,`.cjs`文件总是以 CommonJS 模块加载,`.js`文件的加载取决于`package.json`里面`type`字段的设置。 -目前,Node 的`import`命令只支持加载本地模块(`file:`协议),不支持加载远程模块。 +注意,ES6 模块与 CommonJS 模块尽量不要混用。`require`命令不能加载`.mjs`文件,会报错,只有`import`命令才可以加载`.mjs`文件。反过来,`.mjs`文件里面也不能使用`require`命令,必须使用`import`。 -如果模块名不含路径,那么`import`命令会去`node_modules`目录寻找这个模块。 +### main 字段 + +`package.json`文件有两个字段可以指定模块的入口文件:`main`和`exports`。比较简单的模块,可以只使用`main`字段,指定模块加载的入口文件。 ```javascript -import 'baz'; -import 'abc/123'; +// ./node_modules/es-module-package/package.json +{ + "type": "module", + "main": "./src/index.js" +} ``` -如果模块名包含路径,那么`import`命令会按照路径去寻找这个名字的脚本文件。 +上面代码指定项目的入口脚本为`./src/index.js`,它的格式为 ES6 模块。如果没有`type`字段,`index.js`就会被解释为 CommonJS 模块。 + +然后,`import`命令就可以加载这个模块。 ```javascript -import 'file:///etc/config/app.json'; -import './foo'; -import './foo?search'; -import '../bar'; -import '/baz'; +// ./my-app.mjs + +import { something } from 'es-module-package'; +// 实际加载的是 ./node_modules/es-module-package/src/index.js ``` -如果脚本文件省略了后缀名,比如`import './foo'`,Node 会依次尝试四个后缀名:`./foo.mjs`、`./foo.js`、`./foo.json`、`./foo.node`。如果这些脚本文件都不存在,Node 就会去加载`./foo/package.json`的`main`字段指定的脚本。如果`./foo/package.json`不存在或者没有`main`字段,那么就会依次加载`./foo/index.mjs`、`./foo/index.js`、`./foo/index.json`、`./foo/index.node`。如果以上四个文件还是都不存在,就会抛出错误。 +上面代码中,运行该脚本以后,Node.js 就会到`./node_modules`目录下面,寻找`es-module-package`模块,然后根据该模块`package.json`的`main`字段去执行入口文件。 -最后,Node 的`import`命令是异步加载,这一点与浏览器的处理方法相同。 +这时,如果用 CommonJS 模块的`require()`命令去加载`es-module-package`模块会报错,因为 CommonJS 模块不能处理`export`命令。 -### 内部变量 +### exports 字段 -ES6 模块应该是通用的,同一个模块不用修改,就可以用在浏览器环境和服务器环境。为了达到这个目标,Node 规定 ES6 模块之中不能使用 CommonJS 模块的特有的一些内部变量。 +`exports`字段的优先级高于`main`字段。它有多种用法。 -首先,就是`this`关键字。ES6 模块之中,顶层的`this`指向`undefined`;CommonJS 模块的顶层`this`指向当前模块,这是两者的一个重大差异。 +(1)子目录别名 -其次,以下这些顶层变量在 ES6 模块之中都是不存在的。 +`package.json`文件的`exports`字段可以指定脚本或子目录的别名。 -- `arguments` -- `require` -- `module` -- `exports` -- `__filename` -- `__dirname` +```javascript +// ./node_modules/es-module-package/package.json +{ + "exports": { + "./submodule": "./src/submodule.js" + } +} +``` -如果你一定要使用这些变量,有一个变通方法,就是写一个 CommonJS 模块输出这些变量,然后再用 ES6 模块加载这个 CommonJS 模块。但是这样一来,该 ES6 模块就不能直接用于浏览器环境了,所以不推荐这样做。 +上面的代码指定`src/submodule.js`别名为`submodule`,然后就可以从别名加载这个文件。 ```javascript -// expose.js -module.exports = {__dirname}; - -// use.mjs -import expose from './expose.js'; -const {__dirname} = expose; +import submodule from 'es-module-package/submodule'; +// 加载 ./node_modules/es-module-package/src/submodule.js ``` -上面代码中,`expose.js`是一个 CommonJS 模块,输出变量`__dirname`,该变量在 ES6 模块之中不存在。ES6 模块加载`expose.js`,就可以得到`__dirname`。 +下面是子目录别名的例子。 -### ES6 模块加载 CommonJS 模块 +```javascript +// ./node_modules/es-module-package/package.json +{ + "exports": { + "./features/": "./src/features/" + } +} -CommonJS 模块的输出都定义在`module.exports`这个属性上面。Node 的`import`命令加载 CommonJS 模块,Node 会自动将`module.exports`属性,当作模块的默认输出,即等同于`export default xxx`。 +import feature from 'es-module-package/features/x.js'; +// 加载 ./node_modules/es-module-package/src/features/x.js +``` -下面是一个 CommonJS 模块。 +如果没有指定别名,就不能用“模块+脚本名”这种形式加载脚本。 ```javascript -// a.js -module.exports = { - foo: 'hello', - bar: 'world' -}; +// 报错 +import submodule from 'es-module-package/private-module.js'; -// 等同于 -export default { - foo: 'hello', - bar: 'world' -}; +// 不报错 +import submodule from './node_modules/es-module-package/private-module.js'; ``` -`import`命令加载上面的模块,`module.exports`会被视为默认输出,即`import`命令实际上输入的是这样一个对象`{ default: module.exports }`。 +(2)main 的别名 -所以,一共有三种写法,可以拿到 CommonJS 模块的`module.exports`。 +`exports`字段的别名如果是`.`,就代表模块的主入口,优先级高于`main`字段,并且可以直接简写成`exports`字段的值。 ```javascript -// 写法一 -import baz from './a'; -// baz = {foo: 'hello', bar: 'world'}; +{ + "exports": { + ".": "./main.js" + } +} -// 写法二 -import {default as baz} from './a'; -// baz = {foo: 'hello', bar: 'world'}; +// 等同于 +{ + "exports": "./main.js" +} +``` + +由于`exports`字段只有支持 ES6 的 Node.js 才认识,所以可以用来兼容旧版本的 Node.js。 -// 写法三 -import * as baz from './a'; -// baz = { -// get default() {return module.exports;}, -// get foo() {return this.default.foo}.bind(baz), -// get bar() {return this.default.bar}.bind(baz) -// } +```javascript +{ + "main": "./main-legacy.cjs", + "exports": { + ".": "./main-modern.cjs" + } +} ``` -上面代码的第三种写法,可以通过`baz.default`拿到`module.exports`。`foo`属性和`bar`属性就是可以通过这种方法拿到了`module.exports`。 +上面代码中,老版本的 Node.js (不支持 ES6 模块)的入口文件是`main-legacy.cjs`,新版本的 Node.js 的入口文件是`main-modern.cjs`。 + +**(3)条件加载** -下面是一些例子。 +利用`.`这个别名,可以为 ES6 模块和 CommonJS 指定不同的入口。目前,这个功能需要在 Node.js 运行的时候,打开`--experimental-conditional-exports`标志。 ```javascript -// b.js -module.exports = null; +{ + "type": "module", + "exports": { + ".": { + "require": "./main.cjs", + "default": "./main.js" + } + } +} +``` -// es.js -import foo from './b'; -// foo = null; +上面代码中,别名`.`的`require`条件指定`require()`命令的入口文件(即 CommonJS 的入口),`default`条件指定其他情况的入口(即 ES6 的入口)。 -import * as bar from './b'; -// bar = { default:null }; +上面的写法可以简写如下。 + +```javascript +{ + "exports": { + "require": "./main.cjs", + "default": "./main.js" + } +} ``` -上面代码中,`es.js`采用第二种写法时,要通过`bar.default`这样的写法,才能拿到`module.exports`。 +注意,如果同时还有其他别名,就不能采用简写,否则或报错。 ```javascript -// c.js -module.exports = function two() { - return 2; -}; +{ + // 报错 + "exports": { + "./feature": "./lib/feature.js", + "require": "./main.cjs", + "default": "./main.js" + } +} +``` -// es.js -import foo from './c'; -foo(); // 2 +### ES6 模块加载 CommonJS 模块 -import * as bar from './c'; -bar.default(); // 2 -bar(); // throws, bar is not a function -``` +目前,一个模块同时支持 ES6 和 CommonJS 两种格式的常见方法是,`package.json`文件的`main`字段指定 CommonJS 入口,给 Node.js 使用;`module`字段指定 ES6 模块入口,给打包工具使用,因为 Node.js 不认识`module`字段。 -上面代码中,`bar`本身是一个对象,不能当作函数调用,只能通过`bar.default`调用。 +有了上一节的条件加载以后,Node.js 本身就可以同时处理两种模块。 -CommonJS 模块的输出缓存机制,在 ES6 加载方式下依然有效。 +```javascript +// ./node_modules/pkg/package.json +{ + "type": "module", + "main": "./index.cjs", + "exports": { + "require": "./index.cjs", + "default": "./wrapper.mjs" + } +} +``` + +上面代码指定了 CommonJS 入口文件`index.cjs`,下面是这个文件的代码。 ```javascript -// foo.js -module.exports = 123; -setTimeout(_ => module.exports = null); +// ./node_modules/pkg/index.cjs +exports.name = 'value'; ``` -上面代码中,对于加载`foo.js`的脚本,`module.exports`将一直是`123`,而不会变成`null`。 +然后,ES6 模块可以加载这个文件。 -由于 ES6 模块是编译时确定输出接口,CommonJS 模块是运行时确定输出接口,所以采用`import`命令加载 CommonJS 模块时,不允许采用下面的写法。 +```javascript +// ./node_modules/pkg/wrapper.mjs +import cjsModule from './index.cjs'; +export const name = cjsModule.name; +``` + +注意,`import`命令加载 CommonJS 模块,只能整体加载,不能只加载单一的输出项。 ```javascript -// 不正确 -import { readFile } from 'fs'; +// 正确 +import packageMain from 'commonjs-package'; + +// 报错 +import { method } from 'commonjs-package'; ``` -上面的写法不正确,因为`fs`是 CommonJS 格式,只有在运行时才能确定`readFile`接口,而`import`命令要求编译时就确定这个接口。解决方法就是改为整体输入。 +还有一种变通的加载方法,就是使用 Node.js 内置的`module.createRequire()`方法。 ```javascript -// 正确的写法一 -import * as express from 'express'; -const app = express.default(); +// cjs.cjs +module.exports = 'cjs'; + +// esm.mjs +import { createRequire } from 'module'; -// 正确的写法二 -import express from 'express'; -const app = express(); +const require = createRequire(import.meta.url); + +const cjs = require('./cjs.cjs'); +cjs === 'cjs'; // true ``` +上面代码中,ES6 模块通过`module.createRequire()`方法可以加载 CommonJS 模块 + ### CommonJS 模块加载 ES6 模块 -CommonJS 模块加载 ES6 模块,不能使用`require`命令,而要使用`import()`函数。ES6 模块的所有输出接口,会成为输入对象的属性。 +CommonJS 的`require`命令不能加载 ES6 模块,会报错,只能使用`import()`这个方法加载。 + +```javascript +(async () => { + await import('./my-app.mjs'); +})(); +``` + +上面代码可以在 CommonJS 模块中运行。 + +### Node.js 的内置模块 + +Node.js 的内置模块可以整体加载,也可以加载指定的输出项。 ```javascript -// es.mjs -let foo = { bar: 'my-default' }; -export default foo; +// 整体加载 +import EventEmitter from 'events'; +const e = new EventEmitter(); -// cjs.js -const es_namespace = await import('./es.mjs'); -// es_namespace = { -// get default() { -// ... -// } -// } -console.log(es_namespace.default); -// { bar:'my-default' } +// 加载指定的输出项 +import { readFile } from 'fs'; +readFile('./foo.txt', (err, source) => { + if (err) { + console.error(err); + } else { + console.log(source); + } +}); ``` -上面代码中,`default`接口变成了`es_namespace.default`属性。 +### 加载路径 -下面是另一个例子。 +ES6 模块的加载路径必须给出脚本的完整路径,不能省略脚本的后缀名。`import`命令和`package.json`文件的`main`字段如果省略脚本的后缀名,会报错。 ```javascript -// es.js -export let foo = { bar:'my-default' }; -export { foo as bar }; -export function f() {}; -export class c {}; +// ES6 模块中将报错 +import { something } from './index'; +``` + +为了与浏览器的`import`加载规则相同,Node.js 的`.mjs`文件支持 URL 路径。 -// cjs.js -const es_namespace = await import('./es'); -// es_namespace = { -// get foo() {return foo;} -// get bar() {return foo;} -// get f() {return f;} -// get c() {return c;} -// } +```javascript +import './foo.mjs?query=1'; // 加载 ./foo 传入参数 ?query=1 ``` +上面代码中,脚本路径带有参数`?query=1`,Node 会按 URL 规则解读。同一个脚本只要参数不同,就会被加载多次,并且保存成不同的缓存。由于这个原因,只要文件名中含有`:`、`%`、`#`、`?`等特殊字符,最好对这些字符进行转义。 + +目前,Node.js 的`import`命令只支持加载本地模块(`file:`协议)和`data:`协议,不支持加载远程模块。另外,脚本路径只支持相对路径,不支持绝对路径(即以`/`或`//`开头的路径)。 + +最后,Node 的`import`命令是异步加载,这一点与浏览器的处理方法相同。 + +### 内部变量 + +ES6 模块应该是通用的,同一个模块不用修改,就可以用在浏览器环境和服务器环境。为了达到这个目标,Node 规定 ES6 模块之中不能使用 CommonJS 模块的特有的一些内部变量。 + +首先,就是`this`关键字。ES6 模块之中,顶层的`this`指向`undefined`;CommonJS 模块的顶层`this`指向当前模块,这是两者的一个重大差异。 + +其次,以下这些顶层变量在 ES6 模块之中都是不存在的。 + +- `arguments` +- `require` +- `module` +- `exports` +- `__filename` +- `__dirname` + ## 循环加载 “循环加载”(circular dependency)指的是,`a`脚本的执行依赖`b`脚本,而`b`脚本的执行又依赖`a`脚本。 From ce7bfc95af9fdebcf335a1b150581b9835627afd Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 16 Dec 2019 16:21:17 +0800 Subject: [PATCH 031/280] docs(module-loader): edit --- docs/module-loader.md | 71 ------------------------------------------- 1 file changed, 71 deletions(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index c4fa9dd5c..0bbfe3e74 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -831,74 +831,3 @@ $ node TypeError: even is not a function ``` -## ES6 模块的转码 - -浏览器目前还不支持 ES6 模块,为了现在就能使用,可以将其转为 ES5 的写法。除了 Babel 可以用来转码之外,还有以下两个方法,也可以用来转码。 - -### ES6 module transpiler - -[ES6 module transpiler](https://github.com/esnext/es6-module-transpiler)是 square 公司开源的一个转码器,可以将 ES6 模块转为 CommonJS 模块或 AMD 模块的写法,从而在浏览器中使用。 - -首先,安装这个转码器。 - -```bash -$ npm install -g es6-module-transpiler -``` - -然后,使用`compile-modules convert`命令,将 ES6 模块文件转码。 - -```bash -$ compile-modules convert file1.js file2.js -``` - -`-o`参数可以指定转码后的文件名。 - -```bash -$ compile-modules convert -o out.js file1.js -``` - -### SystemJS - -另一种解决方法是使用 [SystemJS](https://github.com/systemjs/systemjs)。它是一个垫片库(polyfill),可以在浏览器内加载 ES6 模块、AMD 模块和 CommonJS 模块,将其转为 ES5 格式。它在后台调用的是 Google 的 Traceur 转码器。 - -使用时,先在网页内载入`system.js`文件。 - -```html - -``` - -然后,使用`System.import`方法加载模块文件。 - -```html - -``` - -上面代码中的`./app`,指的是当前目录下的 app.js 文件。它可以是 ES6 模块文件,`System.import`会自动将其转码。 - -需要注意的是,`System.import`使用异步加载,返回一个 Promise 对象,可以针对这个对象编程。下面是一个模块文件。 - -```javascript -// app/es6-file.js: - -export class q { - constructor() { - this.es6 = 'hello'; - } -} -``` - -然后,在网页内加载这个模块文件。 - -```html - -``` - -上面代码中,`System.import`方法返回的是一个 Promise 对象,所以可以用`then`方法指定回调函数。 From 5a465dda710bb1d16d97fe0783b6fcf0e7439d46 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 18 Dec 2019 17:08:06 +0800 Subject: [PATCH 032/280] docs(array): fix #934 --- docs/array.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/array.md b/docs/array.md index 59450e175..3dc145483 100644 --- a/docs/array.md +++ b/docs/array.md @@ -204,7 +204,7 @@ a3[0] === a1[0] // true a4[0] === a1[0] // true ``` -上面代码中,`a3`和`a4`是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了原数组的成员,会同步反映到新数组。 +上面代码中,`a3`和`a4`是用两种不同方法合并而成的新数组,但是它们的成员都是对原数组成员的引用,这就是浅拷贝。如果修改了引用指向的值,会同步反映到新数组。 **(3)与解构赋值结合** From 69cae47007874d7fd80371a5bdecf202e55fe4a4 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 25 Dec 2019 15:24:51 +0800 Subject: [PATCH 033/280] docs: update ES2020 --- docs/let.md | 2 +- docs/module.md | 32 +++++- docs/number.md | 261 ++++++++++++++++++++++++++++++++++++++++++++++ docs/proposals.md | 260 --------------------------------------------- docs/regex.md | 8 +- 5 files changed, 297 insertions(+), 266 deletions(-) diff --git a/docs/let.md b/docs/let.md index 4ec238f9d..b92887beb 100644 --- a/docs/let.md +++ b/docs/let.md @@ -626,7 +626,7 @@ var getGlobal = function () { }; ``` -现在有一个[提案](https://github.com/tc39/proposal-global),在语言标准的层面,引入`globalThis`作为顶层对象。也就是说,任何环境下,`globalThis`都是存在的,都可以从它拿到顶层对象,指向全局环境下的`this`。 +[ES2020](https://github.com/tc39/proposal-global) 在语言标准的层面,引入`globalThis`作为顶层对象。也就是说,任何环境下,`globalThis`都是存在的,都可以从它拿到顶层对象,指向全局环境下的`this`。 垫片库[`global-this`](https://github.com/ungap/global-this)模拟了这个提案,可以在所有环境拿到`globalThis`。 diff --git a/docs/module.md b/docs/module.md index 4c3141d24..2760b8074 100644 --- a/docs/module.md +++ b/docs/module.md @@ -670,7 +670,7 @@ const myModual = require(path); 上面的语句就是动态加载,`require`到底加载哪一个模块,只有运行时才知道。`import`命令做不到这一点。 -因此,有一个[提案](https://github.com/tc39/proposal-dynamic-import),建议引入`import()`函数,完成动态加载。 +[ES2020提案](https://github.com/tc39/proposal-dynamic-import) 引入`import()`函数,支持动态加载模块。 ```javascript import(specifier) @@ -800,3 +800,33 @@ async function main() { } main(); ``` + +## import.meta + +开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。现在有一个[提案](https://github.com/tc39/proposal-import-meta),为 import 命令添加了一个元属性`import.beta`,返回当前模块的元信息。 + +`import.meta`只能在模块内部使用,如果在模块外部使用会报错。 + +**(1)import.meta.url** + +`import.meta.url`返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是`https://foo.com/main.js`,`import.meta.url`就返回这个路径。如果模块里面还有一个数据文件`data.txt`,那么就可以用下面的代码,获取这个数据文件的路径。 + +```javascript +new URL('data.txt', import.meta.url) +``` + +注意,Node.js 环境中,`import.meta.url`返回的总是本地路径,即是`file:URL`协议的字符串,比如`file:///home/user/foo.js`。 + +**(2)import.meta.scriptElement** + +`import.meta.scriptElement`是浏览器特有的元属性,返回加载模块的那个` + +// my-module.js 内部执行下面的代码 +import.meta.scriptElement.dataset.foo +// "abc" +``` + diff --git a/docs/number.md b/docs/number.md index 677114de0..25611e439 100644 --- a/docs/number.md +++ b/docs/number.md @@ -697,3 +697,264 @@ Math.pow(99, 99) ``` 上面代码中,两个运算结果的最后一位有效数字是有差异的。 + +## BigInt 数据类型 + +### 简介 + +JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回`Infinity`。 + +```javascript +// 超过 53 个二进制位的数值,无法保持精度 +Math.pow(2, 53) === Math.pow(2, 53) + 1 // true + +// 超过 2 的 1024 次方的数值,无法表示 +Math.pow(2, 1024) // Infinity +``` + +[ES2020](https://github.com/tc39/proposal-bigint) 引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。 + +```javascript +const a = 2172141653n; +const b = 15346349309n; + +// BigInt 可以保持精度 +a * b // 33334444555566667777n + +// 普通整数无法保持精度 +Number(a) * Number(b) // 33334444555566670000 +``` + +为了与 Number 类型区别,BigInt 类型的数据必须添加后缀`n`。 + +```javascript +1234 // 普通整数 +1234n // BigInt + +// BigInt 的运算 +1n + 2n // 3n +``` + +BigInt 同样可以使用各种进制表示,都要加上后缀`n`。 + +```javascript +0b1101n // 二进制 +0o777n // 八进制 +0xFFn // 十六进制 +``` + +BigInt 与普通整数是两种值,它们之间并不相等。 + +```javascript +42n === 42 // false +``` + +`typeof`运算符对于 BigInt 类型的数据返回`bigint`。 + +```javascript +typeof 123n // 'bigint' +``` + +BigInt 可以使用负号(`-`),但是不能使用正号(`+`),因为会与 asm.js 冲突。 + +```javascript +-42n // 正确 ++42n // 报错 +``` + +JavaScript 以前不能计算70的阶乘(即`70!`),因为超出了可以表示的精度。 + +```javascript +let p = 1; +for (let i = 1; i <= 70; i++) { + p *= i; +} +console.log(p); // 1.197857166996989e+100 +``` + +现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。 + +```javascript +let p = 1n; +for (let i = 1n; i <= 70n; i++) { + p *= i; +} +console.log(p); // 11978571...00000000n +``` + +### BigInt 对象 + +JavaScript 原生提供`BigInt`对象,可以用作构造函数生成 BigInt 类型的数值。转换规则基本与`Number()`一致,将其他类型的值转为 BigInt。 + +```javascript +BigInt(123) // 123n +BigInt('123') // 123n +BigInt(false) // 0n +BigInt(true) // 1n +``` + +`BigInt()`构造函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。 + +```javascript +new BigInt() // TypeError +BigInt(undefined) //TypeError +BigInt(null) // TypeError +BigInt('123n') // SyntaxError +BigInt('abc') // SyntaxError +``` + +上面代码中,尤其值得注意字符串`123n`无法解析成 Number 类型,所以会报错。 + +参数如果是小数,也会报错。 + +```javascript +BigInt(1.5) // RangeError +BigInt('1.5') // SyntaxError +``` + +BigInt 对象继承了 Object 对象的两个实例方法。 + +- `BigInt.prototype.toString()` +- `BigInt.prototype.valueOf()` + +它还继承了 Number 对象的一个实例方法。 + +- `BigInt.prototype.toLocaleString()` + +此外,还提供了三个静态方法。 + +- `BigInt.asUintN(width, BigInt)`: 给定的 BigInt 转为 0 到 2width - 1 之间对应的值。 +- `BigInt.asIntN(width, BigInt)`:给定的 BigInt 转为 -2width - 1 到 2width - 1 - 1 之间对应的值。 +- `BigInt.parseInt(string[, radix])`:近似于`Number.parseInt()`,将一个字符串转换成指定进制的 BigInt。 + +```javascript +const max = 2n ** (64n - 1n) - 1n; + +BigInt.asIntN(64, max) +// 9223372036854775807n +BigInt.asIntN(64, max + 1n) +// -9223372036854775808n +BigInt.asUintN(64, max + 1n) +// 9223372036854775808n +``` + +上面代码中,`max`是64位带符号的 BigInt 所能表示的最大值。如果对这个值加`1n`,`BigInt.asIntN()`将会返回一个负值,因为这时新增的一位将被解释为符号位。而`BigInt.asUintN()`方法由于不存在符号位,所以可以正确返回结果。 + +如果`BigInt.asIntN()`和`BigInt.asUintN()`指定的位数,小于数值本身的位数,那么头部的位将被舍弃。 + +```javascript +const max = 2n ** (64n - 1n) - 1n; + +BigInt.asIntN(32, max) // -1n +BigInt.asUintN(32, max) // 4294967295n +``` + +上面代码中,`max`是一个64位的 BigInt,如果转为32位,前面的32位都会被舍弃。 + +下面是`BigInt.parseInt()`的例子。 + +```javascript +// Number.parseInt() 与 BigInt.parseInt() 的对比 +Number.parseInt('9007199254740993', 10) +// 9007199254740992 +BigInt.parseInt('9007199254740993', 10) +// 9007199254740993n +``` + +上面代码中,由于有效数字超出了最大限度,`Number.parseInt`方法返回的结果是不精确的,而`BigInt.parseInt`方法正确返回了对应的 BigInt。 + +对于二进制数组,BigInt 新增了两个类型`BigUint64Array`和`BigInt64Array`,这两种数据类型返回的都是64位 BigInt。`DataView`对象的实例方法`DataView.prototype.getBigInt64()`和`DataView.prototype.getBigUint64()`,返回的也是 BigInt。 + +### 转换规则 + +可以使用`Boolean()`、`Number()`和`String()`这三个方法,将 BigInt 可以转为布尔值、数值和字符串类型。 + +```javascript +Boolean(0n) // false +Boolean(1n) // true +Number(1n) // 1 +String(1n) // "1" +``` + +上面代码中,注意最后一个例子,转为字符串时后缀`n`会消失。 + +另外,取反运算符(`!`)也可以将 BigInt 转为布尔值。 + +```javascript +!0n // true +!1n // false +``` + +### 数学运算 + +数学运算方面,BigInt 类型的`+`、`-`、`*`和`**`这四个二元运算符,与 Number 类型的行为一致。除法运算`/`会舍去小数部分,返回一个整数。 + +```javascript +9n / 5n +// 1n +``` + +几乎所有的数值运算符都可以用在 BigInt,但是有两个例外。 + +- 不带符号的右移位运算符`>>>` +- 一元的求正运算符`+` + +上面两个运算符用在 BigInt 会报错。前者是因为`>>>`运算符是不带符号的,但是 BigInt 总是带有符号的,导致该运算无意义,完全等同于右移运算符`>>`。后者是因为一元运算符`+`在 asm.js 里面总是返回 Number 类型,为了不破坏 asm.js 就规定`+1n`会报错。 + +BigInt 不能与普通数值进行混合运算。 + +```javascript +1n + 1.3 // 报错 +``` + +上面代码报错是因为无论返回的是 BigInt 或 Number,都会导致丢失精度信息。比如`(2n**53n + 1n) + 0.5`这个表达式,如果返回 BigInt 类型,`0.5`这个小数部分会丢失;如果返回 Number 类型,有效精度只能保持 53 位,导致精度下降。 + +同样的原因,如果一个标准库函数的参数预期是 Number 类型,但是得到的是一个 BigInt,就会报错。 + +```javascript +// 错误的写法 +Math.sqrt(4n) // 报错 + +// 正确的写法 +Math.sqrt(Number(4n)) // 2 +``` + +上面代码中,`Math.sqrt`的参数预期是 Number 类型,如果是 BigInt 就会报错,必须先用`Number`方法转一下类型,才能进行计算。 + +asm.js 里面,`|0`跟在一个数值的后面会返回一个32位整数。根据不能与 Number 类型混合运算的规则,BigInt 如果与`|0`进行运算会报错。 + +```javascript +1n | 0 // 报错 +``` + +### 其他运算 + +BigInt 对应的布尔值,与 Number 类型一致,即`0n`会转为`false`,其他值转为`true`。 + +```javascript +if (0n) { + console.log('if'); +} else { + console.log('else'); +} +// else +``` + +上面代码中,`0n`对应`false`,所以会进入`else`子句。 + +比较运算符(比如`>`)和相等运算符(`==`)允许 BigInt 与其他类型的值混合计算,因为这样做不会损失精度。 + +```javascript +0n < 1 // true +0n < true // true +0n == 0 // true +0n == false // true +0n === 0 // false +``` + +BigInt 与字符串混合运算时,会先转为字符串,再进行运算。 + +```javascript +'' + 123n // "123" +``` + diff --git a/docs/proposals.md b/docs/proposals.md index 50097f39e..6d9c35e21 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -391,266 +391,6 @@ Number('123_456') // NaN parseInt('123_456') // 123 ``` -## BigInt 数据类型 - -### 简介 - -JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回`Infinity`。 - -```javascript -// 超过 53 个二进制位的数值,无法保持精度 -Math.pow(2, 53) === Math.pow(2, 53) + 1 // true - -// 超过 2 的 1024 次方的数值,无法表示 -Math.pow(2, 1024) // Infinity -``` - -现在有一个[提案](https://github.com/tc39/proposal-bigint),引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。 - -```javascript -const a = 2172141653n; -const b = 15346349309n; - -// BigInt 可以保持精度 -a * b // 33334444555566667777n - -// 普通整数无法保持精度 -Number(a) * Number(b) // 33334444555566670000 -``` - -为了与 Number 类型区别,BigInt 类型的数据必须添加后缀`n`。 - -```javascript -1234 // 普通整数 -1234n // BigInt - -// BigInt 的运算 -1n + 2n // 3n -``` - -BigInt 同样可以使用各种进制表示,都要加上后缀`n`。 - -```javascript -0b1101n // 二进制 -0o777n // 八进制 -0xFFn // 十六进制 -``` - -BigInt 与普通整数是两种值,它们之间并不相等。 - -```javascript -42n === 42 // false -``` - -`typeof`运算符对于 BigInt 类型的数据返回`bigint`。 - -```javascript -typeof 123n // 'bigint' -``` - -BigInt 可以使用负号(`-`),但是不能使用正号(`+`),因为会与 asm.js 冲突。 - -```javascript --42n // 正确 -+42n // 报错 -``` - -JavaScript 以前不能计算70的阶乘(即`70!`),因为超出了可以表示的精度。 - -```javascript -let p = 1; -for (let i = 1; i <= 70; i++) { - p *= i; -} -console.log(p); // 1.197857166996989e+100 -``` - -现在支持大整数了,就可以算了,浏览器的开发者工具运行下面代码,就OK。 - -```javascript -let p = 1n; -for (let i = 1n; i <= 70n; i++) { - p *= i; -} -console.log(p); // 11978571...00000000n -``` - -### BigInt 对象 - -JavaScript 原生提供`BigInt`对象,可以用作构造函数生成 BigInt 类型的数值。转换规则基本与`Number()`一致,将其他类型的值转为 BigInt。 - -```javascript -BigInt(123) // 123n -BigInt('123') // 123n -BigInt(false) // 0n -BigInt(true) // 1n -``` - -`BigInt()`构造函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。 - -```javascript -new BigInt() // TypeError -BigInt(undefined) //TypeError -BigInt(null) // TypeError -BigInt('123n') // SyntaxError -BigInt('abc') // SyntaxError -``` - -上面代码中,尤其值得注意字符串`123n`无法解析成 Number 类型,所以会报错。 - -参数如果是小数,也会报错。 - -```javascript -BigInt(1.5) // RangeError -BigInt('1.5') // SyntaxError -``` - -BigInt 对象继承了 Object 对象的两个实例方法。 - -- `BigInt.prototype.toString()` -- `BigInt.prototype.valueOf()` - -它还继承了 Number 对象的一个实例方法。 - -- `BigInt.prototype.toLocaleString()` - -此外,还提供了三个静态方法。 - -- `BigInt.asUintN(width, BigInt)`: 给定的 BigInt 转为 0 到 2width - 1 之间对应的值。 -- `BigInt.asIntN(width, BigInt)`:给定的 BigInt 转为 -2width - 1 到 2width - 1 - 1 之间对应的值。 -- `BigInt.parseInt(string[, radix])`:近似于`Number.parseInt()`,将一个字符串转换成指定进制的 BigInt。 - -```javascript -const max = 2n ** (64n - 1n) - 1n; - -BigInt.asIntN(64, max) -// 9223372036854775807n -BigInt.asIntN(64, max + 1n) -// -9223372036854775808n -BigInt.asUintN(64, max + 1n) -// 9223372036854775808n -``` - -上面代码中,`max`是64位带符号的 BigInt 所能表示的最大值。如果对这个值加`1n`,`BigInt.asIntN()`将会返回一个负值,因为这时新增的一位将被解释为符号位。而`BigInt.asUintN()`方法由于不存在符号位,所以可以正确返回结果。 - -如果`BigInt.asIntN()`和`BigInt.asUintN()`指定的位数,小于数值本身的位数,那么头部的位将被舍弃。 - -```javascript -const max = 2n ** (64n - 1n) - 1n; - -BigInt.asIntN(32, max) // -1n -BigInt.asUintN(32, max) // 4294967295n -``` - -上面代码中,`max`是一个64位的 BigInt,如果转为32位,前面的32位都会被舍弃。 - -下面是`BigInt.parseInt()`的例子。 - -```javascript -// Number.parseInt() 与 BigInt.parseInt() 的对比 -Number.parseInt('9007199254740993', 10) -// 9007199254740992 -BigInt.parseInt('9007199254740993', 10) -// 9007199254740993n -``` - -上面代码中,由于有效数字超出了最大限度,`Number.parseInt`方法返回的结果是不精确的,而`BigInt.parseInt`方法正确返回了对应的 BigInt。 - -对于二进制数组,BigInt 新增了两个类型`BigUint64Array`和`BigInt64Array`,这两种数据类型返回的都是64位 BigInt。`DataView`对象的实例方法`DataView.prototype.getBigInt64()`和`DataView.prototype.getBigUint64()`,返回的也是 BigInt。 - -### 转换规则 - -可以使用`Boolean()`、`Number()`和`String()`这三个方法,将 BigInt 可以转为布尔值、数值和字符串类型。 - -```javascript -Boolean(0n) // false -Boolean(1n) // true -Number(1n) // 1 -String(1n) // "1" -``` - -上面代码中,注意最后一个例子,转为字符串时后缀`n`会消失。 - -另外,取反运算符(`!`)也可以将 BigInt 转为布尔值。 - -```javascript -!0n // true -!1n // false -``` - -### 数学运算 - -数学运算方面,BigInt 类型的`+`、`-`、`*`和`**`这四个二元运算符,与 Number 类型的行为一致。除法运算`/`会舍去小数部分,返回一个整数。 - -```javascript -9n / 5n -// 1n -``` - -几乎所有的数值运算符都可以用在 BigInt,但是有两个例外。 - -- 不带符号的右移位运算符`>>>` -- 一元的求正运算符`+` - -上面两个运算符用在 BigInt 会报错。前者是因为`>>>`运算符是不带符号的,但是 BigInt 总是带有符号的,导致该运算无意义,完全等同于右移运算符`>>`。后者是因为一元运算符`+`在 asm.js 里面总是返回 Number 类型,为了不破坏 asm.js 就规定`+1n`会报错。 - -BigInt 不能与普通数值进行混合运算。 - -```javascript -1n + 1.3 // 报错 -``` - -上面代码报错是因为无论返回的是 BigInt 或 Number,都会导致丢失精度信息。比如`(2n**53n + 1n) + 0.5`这个表达式,如果返回 BigInt 类型,`0.5`这个小数部分会丢失;如果返回 Number 类型,有效精度只能保持 53 位,导致精度下降。 - -同样的原因,如果一个标准库函数的参数预期是 Number 类型,但是得到的是一个 BigInt,就会报错。 - -```javascript -// 错误的写法 -Math.sqrt(4n) // 报错 - -// 正确的写法 -Math.sqrt(Number(4n)) // 2 -``` - -上面代码中,`Math.sqrt`的参数预期是 Number 类型,如果是 BigInt 就会报错,必须先用`Number`方法转一下类型,才能进行计算。 - -asm.js 里面,`|0`跟在一个数值的后面会返回一个32位整数。根据不能与 Number 类型混合运算的规则,BigInt 如果与`|0`进行运算会报错。 - -```javascript -1n | 0 // 报错 -``` - -### 其他运算 - -BigInt 对应的布尔值,与 Number 类型一致,即`0n`会转为`false`,其他值转为`true`。 - -```javascript -if (0n) { - console.log('if'); -} else { - console.log('else'); -} -// else -``` - -上面代码中,`0n`对应`false`,所以会进入`else`子句。 - -比较运算符(比如`>`)和相等运算符(`==`)允许 BigInt 与其他类型的值混合计算,因为这样做不会损失精度。 - -```javascript -0n < 1 // true -0n < true // true -0n == 0 // true -0n == false // true -0n === 0 // false -``` - -BigInt 与字符串混合运算时,会先转为字符串,再进行运算。 - -```javascript -'' + 123n // "123" -``` - ## Math.signbit() `Math.sign()`用来判断一个值的正负,但是如果参数是`-0`,它会返回`-0`。 diff --git a/docs/regex.md b/docs/regex.md index 370b01fa4..a64ad1e65 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -594,7 +594,7 @@ RE_TWICE.test('abc!abc!abc') // true RE_TWICE.test('abc!abc!ab') // false ``` -## String.prototype.matchAll +## String.prototype.matchAll() 如果一个正则表达式在字符串里面有多个匹配,现在一般使用`g`修饰符或`y`修饰符,在循环里面逐一取出。 @@ -618,7 +618,7 @@ matches 上面代码中,`while`循环取出每一轮的正则匹配,一共三轮。 -目前有一个[提案](https://github.com/tc39/proposal-string-matchall),增加了`String.prototype.matchAll`方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。 +[ES2020](https://github.com/tc39/proposal-string-matchall) 增加了`String.prototype.matchAll()`方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器(Iterator),而不是数组。 ```javascript const string = 'test1test2test3'; @@ -636,12 +636,12 @@ for (const match of string.matchAll(regex)) { 上面代码中,由于`string.matchAll(regex)`返回的是遍历器,所以可以用`for...of`循环取出。相对于返回数组,返回遍历器的好处在于,如果匹配结果是一个很大的数组,那么遍历器比较节省资源。 -遍历器转为数组是非常简单的,使用`...`运算符和`Array.from`方法就可以了。 +遍历器转为数组是非常简单的,使用`...`运算符和`Array.from()`方法就可以了。 ```javascript // 转为数组方法一 [...string.matchAll(regex)] // 转为数组方法二 -Array.from(string.matchAll(regex)); +Array.from(string.matchAll(regex)) ``` From e28d4087c511300928f5e8d5aad41713e3608a4c Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 25 Dec 2019 15:31:47 +0800 Subject: [PATCH 034/280] docs: update es2020 --- docs/module.md | 29 -------------------------- docs/proposals.md | 53 +++++++++++++---------------------------------- 2 files changed, 14 insertions(+), 68 deletions(-) diff --git a/docs/module.md b/docs/module.md index 2760b8074..62cffbe84 100644 --- a/docs/module.md +++ b/docs/module.md @@ -801,32 +801,3 @@ async function main() { main(); ``` -## import.meta - -开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。现在有一个[提案](https://github.com/tc39/proposal-import-meta),为 import 命令添加了一个元属性`import.beta`,返回当前模块的元信息。 - -`import.meta`只能在模块内部使用,如果在模块外部使用会报错。 - -**(1)import.meta.url** - -`import.meta.url`返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是`https://foo.com/main.js`,`import.meta.url`就返回这个路径。如果模块里面还有一个数据文件`data.txt`,那么就可以用下面的代码,获取这个数据文件的路径。 - -```javascript -new URL('data.txt', import.meta.url) -``` - -注意,Node.js 环境中,`import.meta.url`返回的总是本地路径,即是`file:URL`协议的字符串,比如`file:///home/user/foo.js`。 - -**(2)import.meta.scriptElement** - -`import.meta.scriptElement`是浏览器特有的元属性,返回加载模块的那个` - -// my-module.js 内部执行下面的代码 -import.meta.scriptElement.dataset.foo -// "abc" -``` - diff --git a/docs/proposals.md b/docs/proposals.md index 6d9c35e21..c4c5733d4 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -603,57 +603,32 @@ $ hello.js ## import.meta -加载 JavaScript 脚本的时候,有时候需要知道脚本的元信息。Node.js 提供了两个特殊变量`__filename`和`__dirname`,用来获取脚本的文件名和所在路径。 +开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。现在有一个[提案](https://github.com/tc39/proposal-import-meta),为 import 命令添加了一个元属性`import.beta`,返回当前模块的元信息。 -```javascript -const fs = require('fs'); -const path = require('path'); -const bytes = fs.readFileSync(path.resolve(__dirname, 'data.bin')); -``` - -上面代码中,`__dirname`用于加载与脚本同一个目录的数据文件`data.bin`。 +`import.meta`只能在模块内部使用,如果在模块外部使用会报错。 -但是,浏览器没有这两个特殊变量。如果需要知道脚本的元信息,就只有手动提供。 +这个属性返回一个对象,该对象的各种属性就是当前运行的脚本的元信息。具体包含哪些属性,标准没有规定,由各个运行环境自行决定。一般来说,`import.meta`至少会有下面两个属性。 -```html - -``` +**(1)import.meta.url** -上面这一行 HTML 代码加载 JavaScript 脚本,使用`data-`属性放入元信息。如果脚本内部要获知元信息,可以像下面这样写。 +`import.meta.url`返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是`https://foo.com/main.js`,`import.meta.url`就返回这个路径。如果模块里面还有一个数据文件`data.txt`,那么就可以用下面的代码,获取这个数据文件的路径。 ```javascript -const theOption = document.currentScript.dataset.option; +new URL('data.txt', import.meta.url) ``` -上面代码中,`document.currentScript`属性可以拿到当前脚本的 DOM 节点。 - -由于 Node.js 和浏览器做法的不统一,现在有一个[提案](https://github.com/tc39/proposal-import-meta),提出统一使用`import.meta`属性在脚本内部获取元信息。这个属性返回一个对象,该对象的各种属性就是当前运行的脚本的元信息。具体包含哪些属性,标准没有规定,由各个运行环境自行决定。 +注意,Node.js 环境中,`import.meta.url`返回的总是本地路径,即是`file:URL`协议的字符串,比如`file:///home/user/foo.js`。 -一般来说,浏览器的`import.meta`至少会有两个属性。 +**(2)import.meta.scriptElement** -- `import.meta.url`:脚本的 URL。 -- `import.meta.scriptElement`:加载脚本的那个` -``` - -上面这行代码加载的脚本内部,就可以使用`import.meta`获取元信息。 +`import.meta.scriptElement`是浏览器特有的元属性,返回加载模块的那个` - const image = new Image(); - image.src = URL.createObjectURL(blob); - image.width = image.height = size; - - document.body.appendChild(image); -})(); +// my-module.js 内部执行下面的代码 +import.meta.scriptElement.dataset.foo +// "abc" ``` -上面代码中,`import.meta`用来获取所加载的图片的尺寸。 - From 653f87a5e76f64e1050e95003ca0e61865270d2d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 27 Dec 2019 18:18:38 +0800 Subject: [PATCH 035/280] docs: fix #937 --- docs/object.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/object.md b/docs/object.md index 4f57408ba..f353fef9c 100644 --- a/docs/object.md +++ b/docs/object.md @@ -144,6 +144,20 @@ console.log({user, foo}) 上面代码中,`console.log`直接输出`user`和`foo`两个对象时,就是两组键值对,可能会混淆。把它们放在大括号里面输出,就变成了对象的简洁表示法,每组键值对前面会打印对象名,这样就比较清晰了。 +注意,简写的对象方法不能用作构造函数,会报错。 + +```javascript +const obj = { + f() { + this.foo = 'bar'; + } +}; + +new obj.f() // 报错 +``` + +上面代码中,`f`是一个简写的对象方法,所以`obj.f`不能当作构造函数使用。 + ## 属性名表达式 JavaScript 定义对象的属性,有两种方法。 From 4db83bb093acbd913dbb90ed1a514d7745fa1546 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 3 Jan 2020 14:32:52 +0800 Subject: [PATCH 036/280] docs: edit index.html --- README.md | 4 ++-- index.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a4cc5baec..686b01bdd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ECMAScript 6 入门 +# ECMAScript 6 (ES6) 入门教程 -《ECMAScript 6 入门》是一本开源的 JavaScript 语言教程,全面介绍 ECMAScript 6 新引入的语法特性。 +《ECMAScript 6 入门教程》是一本开源的 JavaScript 语言教程,全面介绍 ECMAScript 6 新引入的语法特性。 [![cover](images/cover_thumbnail_3rd.jpg)](images/cover-3rd.jpg) diff --git a/index.html b/index.html index 98936328a..b9a5c087e 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - ECMAScript 6入门 + ECMAScript 6(ES6)入门教程 @@ -29,7 +29,7 @@ From bf59c210cae89aec13e87fad751771d3a003bebd Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 3 Jan 2020 14:36:01 +0800 Subject: [PATCH 037/280] docs: edit index.html --- README.md | 2 +- index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 686b01bdd..0e80abe03 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ECMAScript 6 (ES6) 入门教程 +# ES6 入门教程 《ECMAScript 6 入门教程》是一本开源的 JavaScript 语言教程,全面介绍 ECMAScript 6 新引入的语法特性。 diff --git a/index.html b/index.html index b9a5c087e..76253f553 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - ECMAScript 6(ES6)入门教程 + ES6 入门教程 From 33408fd3a366a51a4676c4fe83c8f813281befb8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 3 Jan 2020 15:06:05 +0800 Subject: [PATCH 038/280] feat: add statistics code --- js/ditto.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/js/ditto.js b/js/ditto.js index 6d48b4421..eb7f13ca2 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -316,6 +316,14 @@ function show_loading() { return loading; } +function statistics() { + var _hmt = _hmt || []; + var hm = document.createElement("script"); + hm.src = "https://hm.baidu.com/hm.js?519d72adb78a0bf66de7bae18e994322"; + var s = document.getElementsByTagName("script")[0]; + s.parentNode.insertBefore(hm, s); +} + function router() { var path = location.hash.replace(/#([^#]*)(#.*)?/, './$1'); @@ -415,6 +423,8 @@ function router() { $('#pagedown').css('display', 'inline-block'); } + statistics(); + (function() { var $w = $(window); var $prog2 = $('.progress-indicator-2'); From a6abc6775ee9f880a69b5755e93390b7dc3e30b8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 3 Jan 2020 15:10:15 +0800 Subject: [PATCH 039/280] feat: edit statistics code --- js/ditto.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index eb7f13ca2..03135258e 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -355,6 +355,9 @@ function router() { // otherwise get the markdown and render it var loading = show_loading(); + + statistics(); + $.get(path, function(data) { $(ditto.error_id).hide(); $(ditto.content_id).html(marked(data) + disqusCode); @@ -423,8 +426,6 @@ function router() { $('#pagedown').css('display', 'inline-block'); } - statistics(); - (function() { var $w = $(window); var $prog2 = $('.progress-indicator-2'); From 942bcc3ee26ff86ab9d9c7f8a5b6ebf9bf328fa0 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 17 Jan 2020 00:45:29 +0800 Subject: [PATCH 040/280] docs(proxy): fix #943 --- docs/proxy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proxy.md b/docs/proxy.md index 09dad1d0b..6c57552c0 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -613,7 +613,7 @@ var handler = { }; ``` -`construct`方法可以接受两个参数。 +`construct`方法可以接受三个参数。 - `target`:目标对象 - `args`:构造函数的参数对象 From 0d958f5c62969537b5c141403afb076f511168b9 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 17 Jan 2020 00:50:08 +0800 Subject: [PATCH 041/280] docs(string): fix #942 --- docs/string.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/string.md b/docs/string.md index 0806a433a..3c451ea53 100644 --- a/docs/string.md +++ b/docs/string.md @@ -421,7 +421,7 @@ div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] }); ```javascript alert`123` // 等同于 -alert(123) +alert([123]) ``` 标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。 From f9e16e462df103b8df30f7c7ce742a3b0898c9b4 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 17 Jan 2020 00:52:58 +0800 Subject: [PATCH 042/280] docs(string): fix #942 --- docs/string.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/string.md b/docs/string.md index 3c451ea53..3a3d78560 100644 --- a/docs/string.md +++ b/docs/string.md @@ -419,9 +419,9 @@ div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] }); 模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。 ```javascript -alert`123` +alert`hello` // 等同于 -alert([123]) +alert(['hello']) ``` 标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。 From 0cc6602aca37b0764ee5c3d753f28c5fd27ca107 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 29 Jan 2020 19:50:24 +0800 Subject: [PATCH 043/280] docs: fix #947 --- docs/set-map.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/set-map.md b/docs/set-map.md index f22a87541..25eac94ee 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -1083,18 +1083,20 @@ undefined 前文说过,WeakMap 应用的典型场合就是 DOM 节点作为键名。下面是一个例子。 ```javascript -let myElement = document.getElementById('logo'); let myWeakmap = new WeakMap(); -myWeakmap.set(myElement, {timesClicked: 0}); +myWeakmap.set( + document.getElementById('logo'), + {timesClicked: 0}) +; -myElement.addEventListener('click', function() { - let logoData = myWeakmap.get(myElement); +document.getElementById('logo').addEventListener('click', function() { + let logoData = myWeakmap.get(document.getElementById('logo')); logoData.timesClicked++; }, false); ``` -上面代码中,`myElement`是一个 DOM 节点,每当发生`click`事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是`myElement`。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。 +上面代码中,`document.getElementById('logo')`是一个 DOM 节点,每当发生`click`事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。 WeakMap 的另一个用处是部署私有属性。 From e1fcd8b61d042fed0945c8e45bca4ee4f29bdd5c Mon Sep 17 00:00:00 2001 From: wzx <308929264@qq.com> Date: Sun, 2 Feb 2020 15:20:25 +0800 Subject: [PATCH 044/280] =?UTF-8?q?=E5=AD=97=E6=AF=8D=E6=9C=89=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/proposals.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposals.md b/docs/proposals.md index c4c5733d4..aa077ac3a 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -603,7 +603,7 @@ $ hello.js ## import.meta -开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。现在有一个[提案](https://github.com/tc39/proposal-import-meta),为 import 命令添加了一个元属性`import.beta`,返回当前模块的元信息。 +开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。现在有一个[提案](https://github.com/tc39/proposal-import-meta),为 import 命令添加了一个元属性`import.meta`,返回当前模块的元信息。 `import.meta`只能在模块内部使用,如果在模块外部使用会报错。 From 0402e4705b3b66442b46eb5d6af57f03b4f95e95 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 12 Feb 2020 18:50:35 +0800 Subject: [PATCH 045/280] refactor: edit ditto.js --- js/ditto.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 03135258e..d928fb085 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -225,11 +225,11 @@ function create_banner(element) { 'color: #333333' ].join(';'); - var text = '《ES6 实战教程》 ' + - '深入学习一线大厂必备 ES6 技能。VIP 教程限时免费领取。' + - ' ⇐ 立即查看'; + var text = '【开课吧】' + + '拿不到 Offer 退全款,廖雪峰的' + + '《Web 全栈架构师》开班了!'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } From 86ad600682f1bc3afe2107541c009331b16f6f8a Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 12 Feb 2020 21:47:21 +0800 Subject: [PATCH 046/280] refactor: edit ditto.js --- js/ditto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ditto.js b/js/ditto.js index d928fb085..0828dc996 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -225,7 +225,7 @@ function create_banner(element) { 'color: #333333' ].join(';'); - var text = '【开课吧】' + + var text = '【课程学习】' + '拿不到 Offer 退全款,廖雪峰的' + '《Web 全栈架构师》开班了!'; From 0daab0192dac5ccee5d379088cc66a398aad4b85 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 13 Feb 2020 15:22:09 +0800 Subject: [PATCH 047/280] =?UTF-8?q?docs(regexp):=20add=20=E6=AD=A3?= =?UTF-8?q?=E5=88=99=E5=8C=B9=E9=85=8D=E7=B4=A2=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/regex.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/regex.md b/docs/regex.md index a64ad1e65..1f8c1792a 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -594,6 +594,74 @@ RE_TWICE.test('abc!abc!abc') // true RE_TWICE.test('abc!abc!ab') // false ``` +## 正则匹配索引 + +正则匹配结果的开始位置和结束位置,目前获取并不是很方便。正则实例的`exex()`方法,返回结果有一个`index`属性,可以获取整个匹配结果的开始位置,但是如果包含组匹配,每个组匹配的开始位置,很难拿到。 + +现在有一个[第三阶段提案](https://github.com/tc39/proposal-regexp-match-Indices),为`exec()`方法的返回结果加上`indices`属性,在这个属性上面可以拿到匹配的开始位置和结束位置。 + +```javascript +const text = 'zabbcdef'; +const re = /ab/; +const result = re.exec(text); + +result.index // 1 +result.indices // [ [1, 3] ] +``` + +上面例子中,`exec()`方法的返回结果`result`,它的`index`属性是整个匹配结果(`ab`)的开始位置,而它的`indices`属性是一个数组,成员是每个匹配的开始位置和结束位置的数组。由于该例子的正则表达式没有组匹配,所以`indices`数组只有一个成员,表示整个匹配的开始位置是`1`,结束位置是`3`。 + +注意,开始位置包含在匹配结果之中,但是结束位置不包含在匹配结果之中。比如,匹配结果为`ab`,分别是原始字符串的第1位和第2位,那么结束位置就是第3位。 + +如果正则表达式包含组匹配,那么`indices`属性对应的数组就会包含多个成员,提供每个组匹配的开始位置和结束位置。 + +```javascript +const text = 'zabbcdef'; +const re = /ab+(cd)/; +const result = re.exec(text); + +result.indices // [ [ 1, 6 ], [ 4, 6 ] ] +``` + +上面例子中,正则表达式包含一个组匹配,那么`indices`属性数组就有两个成员,第一个成员是整个匹配结果(`abbcd`)的开始位置和结束位置,第二个成员是组匹配(`cd`)的开始位置和结束位置。 + +下面是多个组匹配的例子。 + +```javascript +const text = 'zabbcdef'; +const re = /ab+(cd(ef))/; +const result = re.exec(text); + +result.indices // [ [1, 8], [4, 8], [6, 8] ] +``` + +上面例子中,正则表达式包含两个组匹配,所以`indices`属性数组就有三个成员。 + +如果正则表达式包含具名组匹配,`indices`属性数组还会有一个`groups`属性。该属性是一个对象,可以从该对象获取具名组匹配的开始位置和结束位置。 + +```javascript +const text = 'zabbcdef'; +const re = /ab+(?cd)/; +const result = re.exec(text); + +result.indices.groups // { Z: [ 4, 6 ] } +``` + +上面例子中,`exec()`方法返回结果的`indices.groups`属性是一个对象,提供具名组匹配`Z`的开始位置和结束位置。 + +如果如何获取成功组匹配,`indices`属性数组的对应成员则为`undefined`,`indices.groups`属性对象的对应成员也是`undefined`。 + +```javascript +const text = 'zabbcdef'; +const re = /ab+(?ce)?/; +const result = re.exec(text); + +result.indices[1] // undefined +result.indices.groups['Z'] // undefined +``` + +上面例子中,由于组匹配不成功,所以`indices`属性数组和`indices.groups`属性对象对应的组匹配成员都是`undefined`。 + ## String.prototype.matchAll() 如果一个正则表达式在字符串里面有多个匹配,现在一般使用`g`修饰符或`y`修饰符,在循环里面逐一取出。 From 412b29860516ab843f2123229ceedbf1efa4ff94 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 13 Feb 2020 18:23:59 +0800 Subject: [PATCH 048/280] docs(map): fix #949 --- docs/set-map.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/set-map.md b/docs/set-map.md index 25eac94ee..9733ee4e6 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -833,6 +833,15 @@ strMapToObj(myMap) **(4)对象转为 Map** +对象转为 Map 可以通过`Object.entries()`。 + +```javascript +let obj = {"a":1, "b":2}; +let map = new Map(Object.entries(obj)); +``` + +此外,也可以自己实现一个转换函数。 + ```javascript function objToStrMap(obj) { let strMap = new Map(); From a6e8a400bc1e539c8ec46e76cbf87da559ad6155 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 23 Feb 2020 12:41:58 +0800 Subject: [PATCH 049/280] refactor: add tencent.txt --- tencent9908947001655912037.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 tencent9908947001655912037.txt diff --git a/tencent9908947001655912037.txt b/tencent9908947001655912037.txt new file mode 100644 index 000000000..ca2c43365 --- /dev/null +++ b/tencent9908947001655912037.txt @@ -0,0 +1 @@ +4096418148424150100 From b0b10a308e73d4b13712ffc71ac14dfaa18b8af6 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 23 Feb 2020 12:45:18 +0800 Subject: [PATCH 050/280] refactor: add tencent.txt --- tencent9908947001655912037.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tencent9908947001655912037.txt b/tencent9908947001655912037.txt index ca2c43365..c216cc792 100644 --- a/tencent9908947001655912037.txt +++ b/tencent9908947001655912037.txt @@ -1 +1,2 @@ 4096418148424150100 + From 717f4e4218520e076a7dc7d0ce9a8958fb67e1ef Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 23 Feb 2020 14:13:54 +0800 Subject: [PATCH 051/280] docs: fix #952 --- docs/intro.md | 180 ++------------------------------------------------ 1 file changed, 4 insertions(+), 176 deletions(-) diff --git a/docs/intro.md b/docs/intro.md index 59f418747..db626c680 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -68,11 +68,9 @@ ES6 从开始制定到最后发布,整整用了 15 年。 2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。从 2000 年算起,这时已经过去了 15 年。 -## 部署进度 +目前,各大浏览器对 ES6 的支持可以查看[kangax.github.io/compat-table/es6/](https://kangax.github.io/compat-table/es6/)。 -各大浏览器的最新版本,对 ES6 的支持可以查看[kangax.github.io/compat-table/es6/](https://kangax.github.io/compat-table/es6/)。随着时间的推移,支持度已经越来越高了,超过 90%的 ES6 语法特性都实现了。 - -Node 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持度更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node 已经实现的 ES6 特性。 +Node.js 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持度更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node.js 默认没有打开的 ES6 实验性语法。 ```bash // Linux & Mac @@ -82,21 +80,9 @@ $ node --v8-options | grep harmony $ node --v8-options | findstr harmony ``` -我写了一个工具 [ES-Checker](https://github.com/ruanyf/es-checker),用来检查各种运行环境对 ES6 的支持情况。访问[ruanyf.github.io/es-checker](http://ruanyf.github.io/es-checker),可以看到您的浏览器支持 ES6 的程度。运行下面的命令,可以查看你正在使用的 Node 环境对 ES6 的支持程度。 - -```bash -$ npm install -g es-checker -$ es-checker - -========================================= -Passes 24 feature Detections -Your runtime supports 57% of ECMAScript 6 -========================================= -``` - ## Babel 转码器 -[Babel](https://babeljs.io/) 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在现有环境执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。 +[Babel](https://babeljs.io/) 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。 ```javascript // 转码前 @@ -300,7 +286,7 @@ import '@babel/polyfill'; require('@babel/polyfill'); ``` -Babel 默认不转码的 API 非常多,详细清单可以查看`babel-plugin-transform-runtime`模块的[definitions.js](https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-runtime/src/definitions.js)文件。 +Babel 默认不转码的 API 非常多,详细清单可以查看`babel-plugin-transform-runtime`模块的[definitions.js](https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-runtime/src/runtime-corejs3-definitions.js)文件。 ### 浏览器环境 @@ -317,161 +303,3 @@ Babel 也可以用于浏览器环境,使用[@babel/standalone](https://babeljs Babel 提供一个[REPL 在线编译器](https://babeljs.io/repl/),可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。 -## Traceur 转码器 - -Google 公司的[Traceur](https://github.com/google/traceur-compiler)转码器,也可以将 ES6 代码转为 ES5 代码。 - -### 直接插入网页 - -Traceur 允许将 ES6 代码直接插入网页。首先,必须在网页头部加载 Traceur 库文件。 - -```html - - - - -``` - -上面代码中,一共有 4 个`script`标签。第一个是加载 Traceur 的库文件,第二个和第三个是将这个库文件用于浏览器环境,第四个则是加载用户脚本,这个脚本里面可以使用 ES6 代码。 - -注意,第四个`script`标签的`type`属性的值是`module`,而不是`text/javascript`。这是 Traceur 编译器识别 ES6 代码的标志,编译器会自动将所有`type=module`的代码编译为 ES5,然后再交给浏览器执行。 - -除了引用外部 ES6 脚本,也可以直接在网页中放置 ES6 代码。 - -```javascript - -``` - -正常情况下,上面代码会在控制台打印出`9`。 - -如果想对 Traceur 的行为有精确控制,可以采用下面参数配置的写法。 - -```javascript - -``` - -上面代码中,首先生成 Traceur 的全局对象`window.System`,然后`System.import`方法可以用来加载 ES6。加载的时候,需要传入一个配置对象`metadata`,该对象的`traceurOptions`属性可以配置支持 ES6 功能。如果设为`experimental: true`,就表示除了 ES6 以外,还支持一些实验性的新功能。 - -### 在线转换 - -Traceur 也提供一个[在线编译器](http://google.github.io/traceur-compiler/demo/repl.html),可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。 - -上面的例子转为 ES5 代码运行,就是下面这个样子。 - -```javascript - - - - -``` - -### 命令行转换 - -作为命令行工具使用时,Traceur 是一个 Node 的模块,首先需要用 npm 安装。 - -```bash -$ npm install -g traceur -``` - -安装成功后,就可以在命令行下使用 Traceur 了。 - -Traceur 直接运行 ES6 脚本文件,会在标准输出显示运行结果,以前面的`calc.js`为例。 - -```bash -$ traceur calc.js -Calc constructor -9 -``` - -如果要将 ES6 脚本转为 ES5 保存,要采用下面的写法。 - -```bash -$ traceur --script calc.es6.js --out calc.es5.js -``` - -上面代码的`--script`选项表示指定输入文件,`--out`选项表示指定输出文件。 - -为了防止有些特性编译不成功,最好加上`--experimental`选项。 - -```bash -$ traceur --script calc.es6.js --out calc.es5.js --experimental -``` - -命令行下转换生成的文件,就可以直接放到浏览器中运行。 - -### Node 环境的用法 - -Traceur 的 Node 用法如下(假定已安装`traceur`模块)。 - -```javascript -var traceur = require('traceur'); -var fs = require('fs'); - -// 将 ES6 脚本转为字符串 -var contents = fs.readFileSync('es6-file.js').toString(); - -var result = traceur.compile(contents, { - filename: 'es6-file.js', - sourceMap: true, - // 其他设置 - modules: 'commonjs' -}); - -if (result.error) - throw result.error; - -// result 对象的 js 属性就是转换后的 ES5 代码 -fs.writeFileSync('out.js', result.js); -// sourceMap 属性对应 map 文件 -fs.writeFileSync('out.js.map', result.sourceMap); -``` From 296159a4bc93a34fa5108bceab188f4799d4af7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=A9=E4=BA=8C=E8=9B=8B?= Date: Tue, 25 Feb 2020 20:27:01 +0800 Subject: [PATCH 052/280] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E5=A4=84?= =?UTF-8?q?=E5=B0=8F=E5=9C=B0=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 强迫症患者看着有点变扭。 --- docs/function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/function.md b/docs/function.md index 03dcece48..7dbf10798 100644 --- a/docs/function.md +++ b/docs/function.md @@ -197,7 +197,7 @@ function f(x = 1, y) { } f() // [1, undefined] -f(2) // [2, undefined]) +f(2) // [2, undefined] f(, 1) // 报错 f(undefined, 1) // [1, 1] From 57701fcc2e4c972b5fa6be236362f0b57b97bab4 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 27 Feb 2020 11:37:31 +0800 Subject: [PATCH 053/280] docs(class): fix typo --- docs/class.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/class.md b/docs/class.md index a235dd689..5d8b2e9e7 100644 --- a/docs/class.md +++ b/docs/class.md @@ -975,7 +975,7 @@ class Rectangle { } class Square extends Rectangle { - constructor(length) { + constructor(length, width) { super(length, width); } } From b4ec05645d258102c75a267826209c0c3cd6d4b3 Mon Sep 17 00:00:00 2001 From: AnHongpeng Date: Sat, 29 Feb 2020 14:52:30 +0800 Subject: [PATCH 054/280] =?UTF-8?q?fix:=20=E4=BD=BF=E8=AF=AD=E5=8F=A5?= =?UTF-8?q?=E6=9B=B4=E9=80=9A=E9=A1=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/symbol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/symbol.md b/docs/symbol.md index e8d97cb66..75eb1517e 100644 --- a/docs/symbol.md +++ b/docs/symbol.md @@ -375,7 +375,7 @@ let s2 = Symbol.for('foo'); s1 === s2 // true ``` -上面代码中,`s1`和`s2`都是 Symbol 值,但是它们都是同样参数的`Symbol.for`方法生成的,所以实际上是同一个值。 +上面代码中,`s1`和`s2`都是 Symbol 值,但是它们都是由同样参数的`Symbol.for`方法生成的,所以实际上是同一个值。 `Symbol.for()`与`Symbol()`这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。`Symbol.for()`不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的`key`是否已经存在,如果不存在才会新建一个值。比如,如果你调用`Symbol.for("cat")`30 次,每次都会返回同一个 Symbol 值,但是调用`Symbol("cat")`30 次,会返回 30 个不同的 Symbol 值。 From 518f311c5a329c98540b6e83362d39a0e4174c51 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 3 Mar 2020 21:27:41 +0800 Subject: [PATCH 055/280] refactor: update disqus url to HTTPS --- js/ditto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 0828dc996..9442f9061 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -380,14 +380,14 @@ function router() { window.disqus_shortname = 'es6'; window.disqus_identifier = (location.hash ? location.hash.replace("#", "") : 'READEME'); window.disqus_title = $(ditto.content_id + " h1").text(); - window.disqus_url = 'http://es6.ruanyifeng.com/' + (location.hash ? location.hash.replace("#", "") : 'README'); + window.disqus_url = 'https://es6.ruanyifeng.com/' + (location.hash ? location.hash.replace("#", "") : 'README'); // http://docs.disqus.com/developers/universal/ (function() { var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = 'http://' + window.disqus_shortname + '.disqus.com/embed.js'; + dsq.src = 'https://' + window.disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })(); })(); From 6800796e357b574aaf2c0915b0b434bece866a78 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 3 Mar 2020 23:35:12 +0800 Subject: [PATCH 056/280] =?UTF-8?q?docs(async):=20=E5=A6=82=E4=BD=95?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=95=B0=E7=BB=84=E7=9A=84=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/async.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/async.md b/docs/async.md index 3c845b8a2..b3dfce791 100644 --- a/docs/async.md +++ b/docs/async.md @@ -482,6 +482,21 @@ async function dbFuc(db) { } ``` +另一种方法是使用数组的`reduce`方法。 + +```javascript +async function dbFuc(db) { + let docs = [{}, {}, {}]; + + await docs.reduce(async (_, doc) => { + await _; + await db.post(doc); + }, undefined); +} +``` + +上面例子中,`reduce`方法的第一个参数是`async`函数,导致该函数的第一个参数是前一步操作返回的 Promise 对象,所以必须使用`await`等待它操作结束。另外,`reduce`方法返回的是`docs`数组最后一个成员的`async`函数的执行结果,也是一个 Promise 对象,导致在它前面也必须加上`await`。 + 如果确实希望多个请求并发执行,可以使用`Promise.all`方法。当三个请求都会`resolved`时,下面两种写法效果相同。 ```javascript @@ -716,7 +731,7 @@ const data = await fetch('https://api.example.com'); 上面代码中,`await`命令独立使用,没有放在 async 函数里面,就会报错。 -目前,有一个[语法提案](https://github.com/tc39/proposal-top-level-await),允许在模块的顶层独立使用`await`命令。这个提案的目的,是借用`await`解决模块异步加载的问题。 +目前,有一个[语法提案](https://github.com/tc39/proposal-top-level-await),允许在模块的顶层独立使用`await`命令,使得上面那行代码不会报错了。这个提案的目的,是借用`await`解决模块异步加载的问题。 ```javascript // awaiting.js @@ -737,7 +752,7 @@ export { output }; ```javascript // awaiting.js let output; -(async function main() { +(async function1 main() { const dynamic = await import(someMission); const data = await fetch(url); output = someProcess(dynamic.default, data); From 6069e3f7e14d9d1028b88d525ebf54574f336f76 Mon Sep 17 00:00:00 2001 From: Mookiepiece <48076971+Mookiepiece@users.noreply.github.com> Date: Sat, 7 Mar 2020 20:03:45 +0800 Subject: [PATCH 057/280] =?UTF-8?q?=E4=BD=BF=E7=94=A8core-js=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=E8=BF=87=E6=97=B6=E7=9A=84@babel/polyfill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/intro.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/intro.md b/docs/intro.md index db626c680..77fde956b 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -266,26 +266,30 @@ console.log(es5Code); 上面代码中,`transform`方法的第一个参数是一个字符串,表示需要被转换的 ES6 代码,第二个参数是转换的配置对象。 -### @babel/polyfill +### polyfill Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如`Iterator`、`Generator`、`Set`、`Map`、`Proxy`、`Reflect`、`Symbol`、`Promise`等全局对象,以及一些定义在全局对象上的方法(比如`Object.assign`)都不会转码。 -举例来说,ES6 在`Array`对象上新增了`Array.from`方法。Babel 就不会转码这个方法。如果想让这个方法运行,必须使用`babel-polyfill`,为当前环境提供一个垫片。 +举例来说,ES6 在`Array`对象上新增了`Array.from`方法。Babel 就不会转码这个方法。如果想让这个方法运行,可以使用`core-js`和`regenerator-runtime`(后者提供generator函数的转码),为当前环境提供一个垫片。 安装命令如下。 ```bash -$ npm install --save-dev @babel/polyfill +$ npm install --save-dev core-js regenerator-runtime ``` -然后,在脚本头部,加入如下一行代码。 +然后,在脚本头部,加入如下两行代码。 ```javascript -import '@babel/polyfill'; +import 'core-js'; +import 'regenerator-runtime/runtime'; // 或者 -require('@babel/polyfill'); +require('core-js'); +require('regenerator-runtime/runtime); ``` +`@babel/polyfill` 在7.4.0后被废弃,如果选择`core-js`和`regenerator-runtime`则不必再引入`@babel/polyfill`,二者会冲突。 + Babel 默认不转码的 API 非常多,详细清单可以查看`babel-plugin-transform-runtime`模块的[definitions.js](https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-runtime/src/runtime-corejs3-definitions.js)文件。 ### 浏览器环境 From 428a74820894e3ab83d3e0111109775a237a55f6 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 10 Mar 2020 15:09:17 +0800 Subject: [PATCH 058/280] docs(intro): delete Babel API #958 --- docs/intro.md | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/docs/intro.md b/docs/intro.md index 77fde956b..488a87bb8 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -223,49 +223,6 @@ $ node index.js 需要注意的是,`@babel/register`只会对`require`命令加载的文件转码,而不会对当前文件转码。另外,由于它是实时转码,所以只适合在开发环境使用。 -### babel API - -如果某些代码需要调用 Babel 的 API 进行转码,就要使用`@babel/core`模块。 - -```javascript -var babel = require('@babel/core'); - -// 字符串转码 -babel.transform('code();', options); -// => { code, map, ast } - -// 文件转码(异步) -babel.transformFile('filename.js', options, function(err, result) { - result; // => { code, map, ast } -}); - -// 文件转码(同步) -babel.transformFileSync('filename.js', options); -// => { code, map, ast } - -// Babel AST转码 -babel.transformFromAst(ast, code, options); -// => { code, map, ast } -``` - -配置对象`options`,可以参看官方文档[http://babeljs.io/docs/usage/options/](http://babeljs.io/docs/usage/options/)。 - -下面是一个例子。 - -```javascript -var es6Code = 'let x = n => n + 1'; -var es5Code = require('@babel/core') - .transform(es6Code, { - presets: ['@babel/env'] - }) - .code; - -console.log(es5Code); -// '"use strict";\n\nvar x = function x(n) {\n return n + 1;\n};' -``` - -上面代码中,`transform`方法的第一个参数是一个字符串,表示需要被转换的 ES6 代码,第二个参数是转换的配置对象。 - ### polyfill Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如`Iterator`、`Generator`、`Set`、`Map`、`Proxy`、`Reflect`、`Symbol`、`Promise`等全局对象,以及一些定义在全局对象上的方法(比如`Object.assign`)都不会转码。 @@ -288,8 +245,6 @@ require('core-js'); require('regenerator-runtime/runtime); ``` -`@babel/polyfill` 在7.4.0后被废弃,如果选择`core-js`和`regenerator-runtime`则不必再引入`@babel/polyfill`,二者会冲突。 - Babel 默认不转码的 API 非常多,详细清单可以查看`babel-plugin-transform-runtime`模块的[definitions.js](https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-runtime/src/runtime-corejs3-definitions.js)文件。 ### 浏览器环境 From 0f6569795de19e42b17d50791ff82bea8b194517 Mon Sep 17 00:00:00 2001 From: jianjian Date: Fri, 13 Mar 2020 18:30:23 +0800 Subject: [PATCH 059/280] perf: remove unnecessary closure --- docs/proxy.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index 6c57552c0..03df972f3 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -214,24 +214,22 @@ arr[-1] // c 利用 Proxy,可以将读取属性的操作(`get`),转变为执行某个函数,从而实现属性的链式操作。 ```javascript -var pipe = (function () { - return function (value) { - var funcStack = []; - var oproxy = new Proxy({} , { - get : function (pipeObject, fnName) { - if (fnName === 'get') { - return funcStack.reduce(function (val, fn) { - return fn(val); - },value); - } - funcStack.push(window[fnName]); - return oproxy; +var pipe = function (value) { + var funcStack = []; + var oproxy = new Proxy({} , { + get : function (pipeObject, fnName) { + if (fnName === 'get') { + return funcStack.reduce(function (val, fn) { + return fn(val); + },value); } - }); + funcStack.push(window[fnName]); + return oproxy; + } + }); - return oproxy; - } -}()); + return oproxy; +} var double = n => n * 2; var pow = n => n * n; From e69230681e872422a23ff86308aa30250bd23721 Mon Sep 17 00:00:00 2001 From: wzx <308929264@qq.com> Date: Wed, 18 Mar 2020 17:21:22 +0800 Subject: [PATCH 060/280] fix: arraybuffer.md add `new` --- docs/arraybuffer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/arraybuffer.md b/docs/arraybuffer.md index 9eb73ee83..a2e455104 100644 --- a/docs/arraybuffer.md +++ b/docs/arraybuffer.md @@ -730,7 +730,7 @@ struct someStruct { `DataView`视图本身也是构造函数,接受一个`ArrayBuffer`对象作为参数,生成视图。 ```javascript -DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]); +new DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]); ``` 下面是一个例子。 From f18bd46755bfe80c19cfa54782a61b4cdab268f2 Mon Sep 17 00:00:00 2001 From: linmii Date: Sun, 22 Mar 2020 00:01:09 +0800 Subject: [PATCH 061/280] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A1=A8=E8=BF=B0?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复表述错误 --- docs/regex.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/regex.md b/docs/regex.md index 1f8c1792a..f33b4293c 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -649,7 +649,7 @@ result.indices.groups // { Z: [ 4, 6 ] } 上面例子中,`exec()`方法返回结果的`indices.groups`属性是一个对象,提供具名组匹配`Z`的开始位置和结束位置。 -如果如何获取成功组匹配,`indices`属性数组的对应成员则为`undefined`,`indices.groups`属性对象的对应成员也是`undefined`。 +如果获取组匹配不成功,`indices`属性数组的对应成员则为`undefined`,`indices.groups`属性对象的对应成员也是`undefined`。 ```javascript const text = 'zabbcdef'; From 2061ad95334c7ef2f01cff698cdf03c802fd618c Mon Sep 17 00:00:00 2001 From: Kid <44045911+kidonng@users.noreply.github.com> Date: Sun, 22 Mar 2020 21:00:07 +0800 Subject: [PATCH 062/280] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20`exec()`=20?= =?UTF-8?q?=E6=8B=BC=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/regex.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/regex.md b/docs/regex.md index 1f8c1792a..af89582d2 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -596,7 +596,7 @@ RE_TWICE.test('abc!abc!ab') // false ## 正则匹配索引 -正则匹配结果的开始位置和结束位置,目前获取并不是很方便。正则实例的`exex()`方法,返回结果有一个`index`属性,可以获取整个匹配结果的开始位置,但是如果包含组匹配,每个组匹配的开始位置,很难拿到。 +正则匹配结果的开始位置和结束位置,目前获取并不是很方便。正则实例的`exec()`方法,返回结果有一个`index`属性,可以获取整个匹配结果的开始位置,但是如果包含组匹配,每个组匹配的开始位置,很难拿到。 现在有一个[第三阶段提案](https://github.com/tc39/proposal-regexp-match-Indices),为`exec()`方法的返回结果加上`indices`属性,在这个属性上面可以拿到匹配的开始位置和结束位置。 From f71a2cefeaeab30ef05335b3f1ceb11f9e93a243 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 23 Mar 2020 19:57:55 +0800 Subject: [PATCH 063/280] docs(number): fix #965 --- docs/number.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/number.md b/docs/number.md index 25611e439..9205dc3d9 100644 --- a/docs/number.md +++ b/docs/number.md @@ -686,18 +686,6 @@ b **= 3; // 等同于 b = b * b * b; ``` -注意,V8 引擎的指数运算符与`Math.pow`的实现不相同,对于特别大的运算结果,两者会有细微的差异。 - -```javascript -Math.pow(99, 99) -// 3.697296376497263e+197 - -99 ** 99 -// 3.697296376497268e+197 -``` - -上面代码中,两个运算结果的最后一位有效数字是有差异的。 - ## BigInt 数据类型 ### 简介 From cd0310c978a3155177322eb5a33eab9109ee039f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 23 Mar 2020 20:19:08 +0800 Subject: [PATCH 064/280] =?UTF-8?q?docs(object):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=89=A9=E5=B1=95=E8=BF=90=E7=AE=97=E7=AC=A6=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E5=8F=96=E5=80=BC=E5=87=BD=E6=95=B0=20get=20?= =?UTF-8?q?=E7=9A=84=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/object.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/object.md b/docs/object.md index f353fef9c..b8f7511bf 100644 --- a/docs/object.md +++ b/docs/object.md @@ -693,25 +693,17 @@ const obj = { 扩展运算符的参数对象之中,如果有取值函数`get`,这个函数是会执行的。 ```javascript -// 并不会抛出错误,因为 x 属性只是被定义,但没执行 -let aWithXGetter = { - ...a, +let a = { get x() { throw new Error('not throw yet'); } -}; +} -// 会抛出错误,因为 x 属性被执行了 -let runtimeError = { - ...a, - ...{ - get x() { - throw new Error('throw now'); - } - } -}; +let aWithXGetter = { ...a }; // 报错 ``` +上面例子中,取值函数`get`在扩展`a`对象时会自动执行,导致报错。 + ## 链判断运算符 编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取`message.body.user.firstName`,安全的写法是写成下面这样。 From 31ab7b10d275e63fde94f5448df6c2a9d7592799 Mon Sep 17 00:00:00 2001 From: linmii Date: Tue, 24 Mar 2020 09:28:01 +0800 Subject: [PATCH 065/280] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E6=96=87=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 去除多余文字 --- docs/promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/promise.md b/docs/promise.md index bd2b631e4..6bb8976cd 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -776,7 +776,7 @@ try { } ``` -上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled`,`Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么就会`await`命令就会抛出错误。 +上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled`,`Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么`await`命令就会抛出错误。 `Promise.any()`抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。 From fe723778a0e32363c0e881c1d2f8ab2fd3611e15 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 25 Mar 2020 00:52:06 +0800 Subject: [PATCH 066/280] refactor: edit sponsor banner --- js/ditto.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 9442f9061..b78cf4ce9 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -210,8 +210,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2020年3月22日 - var deadline = new Date(2020, 2, 22); + // 2020年4月25日 + var deadline = new Date(2020, 3, 25); if (deadline - (new Date()) < 0) return; var styleStr = [ @@ -225,11 +225,10 @@ function create_banner(element) { 'color: #333333' ].join(';'); - var text = '【课程学习】' + - '拿不到 Offer 退全款,廖雪峰的' + - '《Web 全栈架构师》开班了!'; + var text = '【课程】' + + '开始学习《ES6 实战教程》,一线大厂前端必备技能。'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } From 0068aa51a6c07980337d82f9f2e79f60ab054c3c Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 25 Mar 2020 10:04:53 +0800 Subject: [PATCH 067/280] refactor: edit sponsor banner --- js/ditto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ditto.js b/js/ditto.js index b78cf4ce9..4960312e2 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -225,7 +225,7 @@ function create_banner(element) { 'color: #333333' ].join(';'); - var text = '【课程】' + + var text = '【免费课程】' + '开始学习《ES6 实战教程》,一线大厂前端必备技能。'; var banner = $('
    ' + text + '
    ') From 8b4db430afb432515e43b7b54fec592ded386b16 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 30 Mar 2020 15:02:12 +0800 Subject: [PATCH 068/280] docs: edit object-methods --- docs/object-methods.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/object-methods.md b/docs/object-methods.md index 1b901cbfd..20c7fd7c1 100644 --- a/docs/object-methods.md +++ b/docs/object-methods.md @@ -478,7 +478,7 @@ JavaScript 语言的对象继承是通过原型链实现的。ES6 提供了更 ### `__proto__`属性 -`__proto__`属性(前后各两个下划线),用来读取或设置当前对象的`prototype`对象。目前,所有浏览器(包括 IE11)都部署了这个属性。 +`__proto__`属性(前后各两个下划线),用来读取或设置当前对象的原型对象(prototype)。目前,所有浏览器(包括 IE11)都部署了这个属性。 ```javascript // es5 的写法 @@ -533,7 +533,7 @@ Object.getPrototypeOf({ __proto__: null }) ### Object.setPrototypeOf() -`Object.setPrototypeOf`方法的作用与`__proto__`相同,用来设置一个对象的`prototype`对象,返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。 +`Object.setPrototypeOf`方法的作用与`__proto__`相同,用来设置一个对象的原型对象(prototype),返回参数对象本身。它是 ES6 正式推荐的设置原型对象的方法。 ```javascript // 格式 From 73320217c2c0703c962d1314f2c0103319d29d0e Mon Sep 17 00:00:00 2001 From: zhangbao Date: Tue, 31 Mar 2020 10:43:21 +0800 Subject: [PATCH 069/280] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E2=80=9C=E6=8A=BD?= =?UTF-8?q?=E8=B1=A1=E6=93=8D=E4=BD=9C=E7=9A=84=E8=BF=90=E8=A1=8C=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E2=80=9D=E7=9A=84=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改了两处: 1)步骤描述 `ReturnIfAbrupt(result)` 应该描述为: > 1. If result is an abrupt completion, return result. > 2. Set result to result.[[Value]] 而非 > 1. If resultCompletionRecord is an abrupt completion, return resultCompletionRecord. > 2. Let result be resultCompletionRecord.[[Value]]. 改写之后,方便简写前后的对照,而不至于对不上号。 另外,第一步也修改为:Let result be AbstractOp() “Set result to result.[[Value]]”的写法参考了这里的:https://v8.dev/blog/understanding-ecmascript-part-1#completion-records 2) 简化了步骤之后的文字描述 --- docs/spec.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/spec.md b/docs/spec.md index f1c1e0eee..30aa2c44f 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -70,12 +70,12 @@ F.[[Call]](V, argumentsList) 抽象操作的运行流程,一般是下面这样。 -> 1. Let `resultCompletionRecord` be `AbstractOp()`. -> 1. If `resultCompletionRecord` is an abrupt completion, return `resultCompletionRecord`. -> 1. Let `result` be `resultCompletionRecord.[[Value]]`. +> 1. Let `result` be `AbstractOp()`. +> 1. If `result` is an abrupt completion, return `result`. +> 1. Set `result` to `result.[[Value]]`. > 1. return `result`. -上面的第一步是调用抽象操作`AbstractOp()`,得到`resultCompletionRecord`,这是一个 Completion Record。第二步,如果这个 Record 属于 abrupt completion,就将`resultCompletionRecord`返回给用户。如果此处没有返回,就表示运行结果正常,所得的值存放在`resultCompletionRecord.[[Value]]`属性。第三步,将这个值记为`result`。第四步,将`result`返回给用户。 +上面的第一步调用了抽象操作`AbstractOp()`,得到`result`,这是一个 Completion Record。第二步,如果`result`属于 abrupt completion,就直接返回。如果此处没有返回,表示`result`属于 normal completion。第三步,将`result`的值设置为`resultCompletionRecord.[[Value]]`。第四步,返回`result`。 ES6 规格将这个标准流程,使用简写的方式表达。 From 03c9e345a7d254043a748089868b27ba62772252 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 3 Apr 2020 20:45:39 +0800 Subject: [PATCH 070/280] docs(module): update es2020 --- docs/module.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/module.md b/docs/module.md index 62cffbe84..ce6c266f0 100644 --- a/docs/module.md +++ b/docs/module.md @@ -540,20 +540,20 @@ export default es6; export { default as es6 } from './someModule'; ``` -下面三种`import`语句,没有对应的复合写法。 +ES2020 之前,有一种`import`语句,没有对应的复合写法。 ```javascript import * as someIdentifier from "someModule"; -import someIdentifier from "someModule"; -import someIdentifier, { namedIdentifier } from "someModule"; ``` -为了做到形式的对称,现在有[提案](https://github.com/leebyron/ecmascript-export-default-from),提出补上这三种复合写法。 +[ES2020](https://github.com/tc39/proposal-export-ns-from)补上了这个写法。 ```javascript -export * as someIdentifier from "someModule"; -export someIdentifier from "someModule"; -export someIdentifier, { namedIdentifier } from "someModule"; +export * as ns from "mod"; + +// 等同于 +import * as ns from "mod"; +export {ns}; ``` ## 模块的继承 From ac50325b6ef029a47b624fb02321dccd2dbdd8a6 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 7 Apr 2020 01:22:55 +0800 Subject: [PATCH 071/280] docs: edit Promise --- docs/promise.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 6bb8976cd..8723e68c3 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -251,7 +251,7 @@ getJSON("/post/1.json").then( ## Promise.prototype.catch() -`Promise.prototype.catch`方法是`.then(null, rejection)`或`.then(undefined, rejection)`的别名,用于指定发生错误时的回调函数。 +`Promise.prototype.catch()`方法是`.then(null, rejection)`或`.then(undefined, rejection)`的别名,用于指定发生错误时的回调函数。 ```javascript getJSON('/posts.json').then(function(posts) { @@ -262,7 +262,7 @@ getJSON('/posts.json').then(function(posts) { }); ``` -上面代码中,`getJSON`方法返回一个 Promise 对象,如果该对象状态变为`resolved`,则会调用`then`方法指定的回调函数;如果异步操作抛出错误,状态就会变为`rejected`,就会调用`catch`方法指定的回调函数,处理这个错误。另外,`then`方法指定的回调函数,如果运行中抛出错误,也会被`catch`方法捕获。 +上面代码中,`getJSON()`方法返回一个 Promise 对象,如果该对象状态变为`resolved`,则会调用`then()`方法指定的回调函数;如果异步操作抛出错误,状态就会变为`rejected`,就会调用`catch()`方法指定的回调函数,处理这个错误。另外,`then()`方法指定的回调函数,如果运行中抛出错误,也会被`catch()`方法捕获。 ```javascript p.then((val) => console.log('fulfilled:', val)) @@ -285,7 +285,7 @@ promise.catch(function(error) { // Error: test ``` -上面代码中,`promise`抛出一个错误,就被`catch`方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。 +上面代码中,`promise`抛出一个错误,就被`catch()`方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。 ```javascript // 写法一 @@ -309,7 +309,7 @@ promise.catch(function(error) { }); ``` -比较上面两种写法,可以发现`reject`方法的作用,等同于抛出错误。 +比较上面两种写法,可以发现`reject()`方法的作用,等同于抛出错误。 如果 Promise 状态已经变成`resolved`,再抛出错误是无效的。 @@ -338,9 +338,9 @@ getJSON('/post/1.json').then(function(post) { }); ``` -上面代码中,一共有三个 Promise 对象:一个由`getJSON`产生,两个由`then`产生。它们之中任何一个抛出的错误,都会被最后一个`catch`捕获。 +上面代码中,一共有三个 Promise 对象:一个由`getJSON()`产生,两个由`then()`产生。它们之中任何一个抛出的错误,都会被最后一个`catch()`捕获。 -一般来说,不要在`then`方法里面定义 Reject 状态的回调函数(即`then`的第二个参数),总是使用`catch`方法。 +一般来说,不要在`then()`方法里面定义 Reject 状态的回调函数(即`then`的第二个参数),总是使用`catch`方法。 ```javascript // bad @@ -361,9 +361,9 @@ promise }); ``` -上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面`then`方法执行中的错误,也更接近同步的写法(`try/catch`)。因此,建议总是使用`catch`方法,而不使用`then`方法的第二个参数。 +上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面`then`方法执行中的错误,也更接近同步的写法(`try/catch`)。因此,建议总是使用`catch()`方法,而不使用`then()`方法的第二个参数。 -跟传统的`try/catch`代码块不同的是,如果没有使用`catch`方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。 +跟传统的`try/catch`代码块不同的是,如果没有使用`catch()`方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。 ```javascript const someAsyncThing = function() { @@ -382,9 +382,9 @@ setTimeout(() => { console.log(123) }, 2000); // 123 ``` -上面代码中,`someAsyncThing`函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示`ReferenceError: x is not defined`,但是不会退出进程、终止脚本执行,2 秒之后还是会输出`123`。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。 +上面代码中,`someAsyncThing()`函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示`ReferenceError: x is not defined`,但是不会退出进程、终止脚本执行,2 秒之后还是会输出`123`。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。 -这个脚本放在服务器执行,退出码就是`0`(即表示执行成功)。不过,Node 有一个`unhandledRejection`事件,专门监听未捕获的`reject`错误,上面的脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。 +这个脚本放在服务器执行,退出码就是`0`(即表示执行成功)。不过,Node.js 有一个`unhandledRejection`事件,专门监听未捕获的`reject`错误,上面的脚本会触发这个事件的监听函数,可以在监听函数里面抛出错误。 ```javascript process.on('unhandledRejection', function (err, p) { @@ -410,7 +410,7 @@ promise.then(function (value) { console.log(value) }); 上面代码中,Promise 指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise 的运行已经结束了,所以这个错误是在 Promise 函数体外抛出的,会冒泡到最外层,成了未捕获的错误。 -一般总是建议,Promise 对象后面要跟`catch`方法,这样可以处理 Promise 内部发生的错误。`catch`方法返回的还是一个 Promise 对象,因此后面还可以接着调用`then`方法。 +一般总是建议,Promise 对象后面要跟`catch()`方法,这样可以处理 Promise 内部发生的错误。`catch()`方法返回的还是一个 Promise 对象,因此后面还可以接着调用`then()`方法。 ```javascript const someAsyncThing = function() { @@ -431,7 +431,7 @@ someAsyncThing() // carry on ``` -上面代码运行完`catch`方法指定的回调函数,会接着运行后面那个`then`方法指定的回调函数。如果没有报错,则会跳过`catch`方法。 +上面代码运行完`catch()`方法指定的回调函数,会接着运行后面那个`then()`方法指定的回调函数。如果没有报错,则会跳过`catch()`方法。 ```javascript Promise.resolve() @@ -444,9 +444,9 @@ Promise.resolve() // carry on ``` -上面的代码因为没有报错,跳过了`catch`方法,直接执行后面的`then`方法。此时,要是`then`方法里面报错,就与前面的`catch`无关了。 +上面的代码因为没有报错,跳过了`catch()`方法,直接执行后面的`then()`方法。此时,要是`then()`方法里面报错,就与前面的`catch()`无关了。 -`catch`方法之中,还能再抛出错误。 +`catch()`方法之中,还能再抛出错误。 ```javascript const someAsyncThing = function() { @@ -468,7 +468,7 @@ someAsyncThing().then(function() { // oh no [ReferenceError: x is not defined] ``` -上面代码中,`catch`方法抛出一个错误,因为后面没有别的`catch`方法了,导致这个错误不会被捕获,也不会传递到外层。如果改写一下,结果就不一样了。 +上面代码中,`catch()`方法抛出一个错误,因为后面没有别的`catch()`方法了,导致这个错误不会被捕获,也不会传递到外层。如果改写一下,结果就不一样了。 ```javascript someAsyncThing().then(function() { @@ -484,11 +484,11 @@ someAsyncThing().then(function() { // carry on [ReferenceError: y is not defined] ``` -上面代码中,第二个`catch`方法用来捕获前一个`catch`方法抛出的错误。 +上面代码中,第二个`catch()`方法用来捕获前一个`catch()`方法抛出的错误。 ## Promise.prototype.finally() -`finally`方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。 +`finally()`方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。 ```javascript promise From 1474f82f17f9c77d1acef3b0d012b7c9b5a2f170 Mon Sep 17 00:00:00 2001 From: waiting <1661926154@qq.com> Date: Tue, 7 Apr 2020 10:27:28 +0800 Subject: [PATCH 072/280] fix(proposals): shell exec style Executing file directly must start with (absolute/relative) path under Linux --- docs/proposals.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposals.md b/docs/proposals.md index aa077ac3a..543905570 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -596,7 +596,7 @@ console.log(1); $ node hello.js # hashbang 的方式 -$ hello.js +$ ./hello.js ``` 对于 JavaScript 引擎来说,会把`#!`理解成注释,忽略掉这一行。 From 7076dd5e4cbb9d180ce4d64f0b10e2889a4acd14 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 9 Apr 2020 14:15:26 +0800 Subject: [PATCH 073/280] docs(math): fix #973 --- docs/number.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/number.md b/docs/number.md index 9205dc3d9..0aca19309 100644 --- a/docs/number.md +++ b/docs/number.md @@ -397,16 +397,16 @@ Math.sign = Math.sign || function(x) { ### Math.cbrt() -`Math.cbrt`方法用于计算一个数的立方根。 +`Math.cbrt()`方法用于计算一个数的立方根。 ```javascript Math.cbrt(-1) // -1 Math.cbrt(0) // 0 Math.cbrt(1) // 1 -Math.cbrt(2) // 1.2599210498948734 +Math.cbrt(2) // 1.2599210498948732 ``` -对于非数值,`Math.cbrt`方法内部也是先使用`Number`方法将其转为数值。 +对于非数值,`Math.cbrt()`方法内部也是先使用`Number()`方法将其转为数值。 ```javascript Math.cbrt('8') // 2 From c3dd90d54343d38693d8625a6503c4c16af466d9 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 13 Apr 2020 18:45:20 +0800 Subject: [PATCH 074/280] refactor: edit ditto/support --- js/ditto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ditto.js b/js/ditto.js index 4960312e2..8631cee60 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -228,7 +228,7 @@ function create_banner(element) { var text = '【免费课程】' + '开始学习《ES6 实战教程》,一线大厂前端必备技能。'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } From e11dc14db1eb8621f511f72727725c9b95ed8488 Mon Sep 17 00:00:00 2001 From: suzhongping <55773520+suzhongping@users.noreply.github.com> Date: Fri, 24 Apr 2020 14:04:25 +0800 Subject: [PATCH 075/280] Update module.md lowercase "readfile" --- docs/module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module.md b/docs/module.md index ce6c266f0..dac58ff69 100644 --- a/docs/module.md +++ b/docs/module.md @@ -10,7 +10,7 @@ ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模 ```javascript // CommonJS模块 -let { stat, exists, readFile } = require('fs'); +let { stat, exists, readfile } = require('fs'); // 等同于 let _fs = require('fs'); From 2f908a58ba3b09bc233c9880cb14e8f57bdb5274 Mon Sep 17 00:00:00 2001 From: Ricardo Chan Date: Fri, 8 May 2020 15:20:46 +0800 Subject: [PATCH 076/280] =?UTF-8?q?=E5=AE=8C=E5=96=84Reflect.ownkeys()?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E7=9A=84=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` Reflect.ownKeys(target) Returns an array of the target object's own (not inherited) property keys. ``` Refrence: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index b8f7511bf..ab7a5e99d 100644 --- a/docs/object.md +++ b/docs/object.md @@ -376,7 +376,7 @@ ES6 一共有 5 种方法可以遍历对象的属性。 **(5)Reflect.ownKeys(obj)** -`Reflect.ownKeys`返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。 +`Reflect.ownKeys`返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。 以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。 From 6f59bc38e18f29552d14e5a661f3aaaf8d95c041 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 13 May 2020 13:35:10 +0800 Subject: [PATCH 077/280] docs(proxy): edit defineProperty --- docs/proxy.md | 62 +++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index 03df972f3..e0dfb38da 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -673,7 +673,7 @@ delete proxy._prop ### defineProperty() -`defineProperty`方法拦截了`Object.defineProperty`操作。 +`defineProperty()`方法拦截了`Object.defineProperty()`操作。 ```javascript var handler = { @@ -686,13 +686,13 @@ var proxy = new Proxy(target, handler); proxy.foo = 'bar' // 不会生效 ``` -上面代码中,`defineProperty`方法返回`false`,导致添加新属性总是无效。 +上面代码中,`defineProperty()`方法内部没有任何操作,只返回`false`,导致添加新属性总是无效。注意,这里的`false`只是用来提示操作失败,本身并不能阻止添加新属性。 -注意,如果目标对象不可扩展(non-extensible),则`defineProperty`不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则`defineProperty`方法不得改变这两个设置。 +注意,如果目标对象不可扩展(non-extensible),则`defineProperty()`不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则`defineProperty()`方法不得改变这两个设置。 ### getOwnPropertyDescriptor() -`getOwnPropertyDescriptor`方法拦截`Object.getOwnPropertyDescriptor()`,返回一个属性描述对象或者`undefined`。 +`getOwnPropertyDescriptor()`方法拦截`Object.getOwnPropertyDescriptor()`,返回一个属性描述对象或者`undefined`。 ```javascript var handler = { @@ -713,11 +713,11 @@ Object.getOwnPropertyDescriptor(proxy, 'baz') // { value: 'tar', writable: true, enumerable: true, configurable: true } ``` -上面代码中,`handler.getOwnPropertyDescriptor`方法对于第一个字符为下划线的属性名会返回`undefined`。 +上面代码中,`handler.getOwnPropertyDescriptor()`方法对于第一个字符为下划线的属性名会返回`undefined`。 ### getPrototypeOf() -`getPrototypeOf`方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。 +`getPrototypeOf()`方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。 - `Object.prototype.__proto__` - `Object.prototype.isPrototypeOf()` @@ -737,13 +737,13 @@ var p = new Proxy({}, { Object.getPrototypeOf(p) === proto // true ``` -上面代码中,`getPrototypeOf`方法拦截`Object.getPrototypeOf()`,返回`proto`对象。 +上面代码中,`getPrototypeOf()`方法拦截`Object.getPrototypeOf()`,返回`proto`对象。 -注意,`getPrototypeOf`方法的返回值必须是对象或者`null`,否则报错。另外,如果目标对象不可扩展(non-extensible), `getPrototypeOf`方法必须返回目标对象的原型对象。 +注意,`getPrototypeOf()`方法的返回值必须是对象或者`null`,否则报错。另外,如果目标对象不可扩展(non-extensible), `getPrototypeOf()`方法必须返回目标对象的原型对象。 ### isExtensible() -`isExtensible`方法拦截`Object.isExtensible`操作。 +`isExtensible()`方法拦截`Object.isExtensible()`操作。 ```javascript var p = new Proxy({}, { @@ -758,7 +758,7 @@ Object.isExtensible(p) // true ``` -上面代码设置了`isExtensible`方法,在调用`Object.isExtensible`时会输出`called`。 +上面代码设置了`isExtensible()`方法,在调用`Object.isExtensible`时会输出`called`。 注意,该方法只能返回布尔值,否则返回值会被自动转为布尔值。 @@ -783,7 +783,7 @@ Object.isExtensible(p) ### ownKeys() -`ownKeys`方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。 +`ownKeys()`方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。 - `Object.getOwnPropertyNames()` - `Object.getOwnPropertySymbols()` @@ -835,7 +835,7 @@ for (let key of Object.keys(proxy)) { // "baz" ``` -注意,使用`Object.keys`方法时,有三类属性会被`ownKeys`方法自动过滤,不会返回。 +注意,使用`Object.keys()`方法时,有三类属性会被`ownKeys()`方法自动过滤,不会返回。 - 目标对象上不存在的属性 - 属性名为 Symbol 值 @@ -868,9 +868,9 @@ Object.keys(proxy) // ['a'] ``` -上面代码中,`ownKeys`方法之中,显式返回不存在的属性(`d`)、Symbol 值(`Symbol.for('secret')`)、不可遍历的属性(`key`),结果都被自动过滤掉。 +上面代码中,`ownKeys()`方法之中,显式返回不存在的属性(`d`)、Symbol 值(`Symbol.for('secret')`)、不可遍历的属性(`key`),结果都被自动过滤掉。 -`ownKeys`方法还可以拦截`Object.getOwnPropertyNames()`。 +`ownKeys()`方法还可以拦截`Object.getOwnPropertyNames()`。 ```javascript var p = new Proxy({}, { @@ -883,7 +883,7 @@ Object.getOwnPropertyNames(p) // [ 'a', 'b', 'c' ] ``` -`for...in`循环也受到`ownKeys`方法的拦截。 +`for...in`循环也受到`ownKeys()`方法的拦截。 ```javascript const obj = { hello: 'world' }; @@ -898,9 +898,9 @@ for (let key in proxy) { } ``` -上面代码中,`ownkeys`指定只返回`a`和`b`属性,由于`obj`没有这两个属性,因此`for...in`循环不会有任何输出。 +上面代码中,`ownkeys()`指定只返回`a`和`b`属性,由于`obj`没有这两个属性,因此`for...in`循环不会有任何输出。 -`ownKeys`方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。 +`ownKeys()`方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。 ```javascript var obj = {}; @@ -915,9 +915,9 @@ Object.getOwnPropertyNames(p) // Uncaught TypeError: 123 is not a valid property name ``` -上面代码中,`ownKeys`方法虽然返回一个数组,但是每一个数组成员都不是字符串或 Symbol 值,因此就报错了。 +上面代码中,`ownKeys()`方法虽然返回一个数组,但是每一个数组成员都不是字符串或 Symbol 值,因此就报错了。 -如果目标对象自身包含不可配置的属性,则该属性必须被`ownKeys`方法返回,否则报错。 +如果目标对象自身包含不可配置的属性,则该属性必须被`ownKeys()`方法返回,否则报错。 ```javascript var obj = {}; @@ -937,9 +937,9 @@ Object.getOwnPropertyNames(p) // Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'a' ``` -上面代码中,`obj`对象的`a`属性是不可配置的,这时`ownKeys`方法返回的数组之中,必须包含`a`,否则会报错。 +上面代码中,`obj`对象的`a`属性是不可配置的,这时`ownKeys()`方法返回的数组之中,必须包含`a`,否则会报错。 -另外,如果目标对象是不可扩展的(non-extensible),这时`ownKeys`方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。 +另外,如果目标对象是不可扩展的(non-extensible),这时`ownKeys()`方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。 ```javascript var obj = { @@ -958,11 +958,11 @@ Object.getOwnPropertyNames(p) // Uncaught TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible ``` -上面代码中,`obj`对象是不可扩展的,这时`ownKeys`方法返回的数组之中,包含了`obj`对象的多余属性`b`,所以导致了报错。 +上面代码中,`obj`对象是不可扩展的,这时`ownKeys()`方法返回的数组之中,包含了`obj`对象的多余属性`b`,所以导致了报错。 ### preventExtensions() -`preventExtensions`方法拦截`Object.preventExtensions()`。该方法必须返回一个布尔值,否则会被自动转为布尔值。 +`preventExtensions()`方法拦截`Object.preventExtensions()`。该方法必须返回一个布尔值,否则会被自动转为布尔值。 这个方法有一个限制,只有目标对象不可扩展时(即`Object.isExtensible(proxy)`为`false`),`proxy.preventExtensions`才能返回`true`,否则会报错。 @@ -977,9 +977,9 @@ Object.preventExtensions(proxy) // Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible ``` -上面代码中,`proxy.preventExtensions`方法返回`true`,但这时`Object.isExtensible(proxy)`会返回`true`,因此报错。 +上面代码中,`proxy.preventExtensions()`方法返回`true`,但这时`Object.isExtensible(proxy)`会返回`true`,因此报错。 -为了防止出现这个问题,通常要在`proxy.preventExtensions`方法里面,调用一次`Object.preventExtensions`。 +为了防止出现这个问题,通常要在`proxy.preventExtensions()`方法里面,调用一次`Object.preventExtensions()`。 ```javascript var proxy = new Proxy({}, { @@ -997,7 +997,7 @@ Object.preventExtensions(proxy) ### setPrototypeOf() -`setPrototypeOf`方法主要用来拦截`Object.setPrototypeOf`方法。 +`setPrototypeOf()`方法主要用来拦截`Object.setPrototypeOf()`方法。 下面是一个例子。 @@ -1016,11 +1016,11 @@ Object.setPrototypeOf(proxy, proto); 上面代码中,只要修改`target`的原型对象,就会报错。 -注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),`setPrototypeOf`方法不得改变目标对象的原型。 +注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),`setPrototypeOf()`方法不得改变目标对象的原型。 ## Proxy.revocable() -`Proxy.revocable`方法返回一个可取消的 Proxy 实例。 +`Proxy.revocable()`方法返回一个可取消的 Proxy 实例。 ```javascript let target = {}; @@ -1035,9 +1035,9 @@ revoke(); proxy.foo // TypeError: Revoked ``` -`Proxy.revocable`方法返回一个对象,该对象的`proxy`属性是`Proxy`实例,`revoke`属性是一个函数,可以取消`Proxy`实例。上面代码中,当执行`revoke`函数之后,再访问`Proxy`实例,就会抛出一个错误。 +`Proxy.revocable()`方法返回一个对象,该对象的`proxy`属性是`Proxy`实例,`revoke`属性是一个函数,可以取消`Proxy`实例。上面代码中,当执行`revoke`函数之后,再访问`Proxy`实例,就会抛出一个错误。 -`Proxy.revocable`的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。 +`Proxy.revocable()`的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。 ## this 问题 @@ -1093,7 +1093,7 @@ proxy.getDate(); // TypeError: this is not a Date object. ``` -上面代码中,`getDate`方法只能在`Date`对象实例上面拿到,如果`this`不是`Date`对象实例就会报错。这时,`this`绑定原始对象,就可以解决这个问题。 +上面代码中,`getDate()`方法只能在`Date`对象实例上面拿到,如果`this`不是`Date`对象实例就会报错。这时,`this`绑定原始对象,就可以解决这个问题。 ```javascript const target = new Date('2015-01-01'); From 3832b6a691daa4b52ec912616375def6f5f2bab3 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 13 May 2020 13:51:58 +0800 Subject: [PATCH 078/280] refactor: adjust support date --- js/ditto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 8631cee60..e870748de 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -210,8 +210,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2020年4月25日 - var deadline = new Date(2020, 3, 25); + // 2020年8月15日 + var deadline = new Date(2020, 7, 15); if (deadline - (new Date()) < 0) return; var styleStr = [ From 55cbbb8da9d604001f2886bbeeb52dda65ffb2b2 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 14 May 2020 16:44:18 +0800 Subject: [PATCH 079/280] fix: li tag click bug #984 --- js/ditto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ditto.js b/js/ditto.js index e870748de..6daa3b3ae 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -178,7 +178,7 @@ function replace_symbols(text) { // replace symbols with underscore return text .replace(/, /g, ',') - .replace(/[&\/\\#,.+=$~%'":*?<>{}\ \]\[]/g, "-") + .replace(/[&\!\/\\#,.+=$~%'":*?<>{}\ \]\[]/g, "-") .replace(/[()]/g, ''); } From 323e9620a78167f0feee1ca9905180a33d3ab77f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 31 May 2020 20:17:06 +0800 Subject: [PATCH 080/280] docs(set): fix #991 --- docs/async.md | 2 +- docs/set-map.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/async.md b/docs/async.md index b3dfce791..53f1ccfba 100644 --- a/docs/async.md +++ b/docs/async.md @@ -225,7 +225,7 @@ f().then(v => console.log(v)) 上面代码中,`await`命令的参数是数值`123`,这时等同于`return 123`。 -另一种情况是,`await`命令后面是一个`thenable`对象(即定义`then`方法的对象),那么`await`会将其等同于 Promise 对象。 +另一种情况是,`await`命令后面是一个`thenable`对象(即定义了`then`方法的对象),那么`await`会将其等同于 Promise 对象。 ```javascript class Sleep { diff --git a/docs/set-map.md b/docs/set-map.md index 9733ee4e6..a934cc3a4 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -281,7 +281,7 @@ let union = new Set([...a, ...b]); let intersect = new Set([...a].filter(x => b.has(x))); // set {2, 3} -// 差集 +// (a 相对于 b 的)差集 let difference = new Set([...a].filter(x => !b.has(x))); // Set {1} ``` From f0a92d5ff0184f145b42cfcd4783dec615d1b44b Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 12 Jun 2020 17:42:44 +0800 Subject: [PATCH 081/280] docs(regex): fixed matchall() #992 --- docs/regex.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/regex.md b/docs/regex.md index 6e0631cb2..2cd8a15da 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -690,8 +690,6 @@ matches ```javascript const string = 'test1test2test3'; - -// g 修饰符加不加都可以 const regex = /t(e)(st(\d?))/g; for (const match of string.matchAll(regex)) { @@ -707,9 +705,10 @@ for (const match of string.matchAll(regex)) { 遍历器转为数组是非常简单的,使用`...`运算符和`Array.from()`方法就可以了。 ```javascript -// 转为数组方法一 +// 转为数组的方法一 [...string.matchAll(regex)] -// 转为数组方法二 +// 转为数组的方法二 Array.from(string.matchAll(regex)) ``` + From 0219f20c072780d1702a64e8073a686c4bbba50a Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 21 Jun 2020 19:58:46 +0800 Subject: [PATCH 082/280] =?UTF-8?q?docs(object):=20edit=20=E9=93=BE?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E8=BF=90=E7=AE=97=E7=AC=A6=20=3F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/object.md | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/docs/object.md b/docs/object.md index ab7a5e99d..698df6676 100644 --- a/docs/object.md +++ b/docs/object.md @@ -709,19 +709,27 @@ let aWithXGetter = { ...a }; // 报错 编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取`message.body.user.firstName`,安全的写法是写成下面这样。 ```javascript +// 错误的写法 +const firstName = message.body.user.firstName; + +// 正确的写法 const firstName = (message && message.body && message.body.user && message.body.user.firstName) || 'default'; ``` -或者使用三元运算符`?:`,判断一个对象是否存在。 +上面例子中,`firstName`属性在对象的第四层,所以需要判断四次,每一层是否有值。 + +三元运算符`?:`也常用于判断对象是否存在。 ```javascript const fooInput = myForm.querySelector('input[name=foo]') const fooValue = fooInput ? fooInput.value : undefined ``` +上面例子中,必须先判断`fooInput`是否存在,才能读取`fooInput.value`。 + 这样的层层判断非常麻烦,因此 [ES2020](https://github.com/tc39/proposal-optional-chaining) 引入了“链判断运算符”(optional chaining operator)`?.`,简化上面的写法。 ```javascript @@ -731,19 +739,13 @@ const fooValue = myForm.querySelector('input[name=foo]')?.value 上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null`或`undefined`。如果是的,就不再往下运算,而是返回`undefined`。 -链判断运算符有三种用法。 - -- `obj?.prop` // 对象属性 -- `obj?.[expr]` // 同上 -- `func?.(...args)` // 函数或对象方法的调用 - 下面是判断对象方法是否存在,如果存在就立即执行的例子。 ```javascript iterator.return?.() ``` -上面代码中,`iterator.return`如果有定义,就会调用该方法,否则直接返回`undefined`。 +上面代码中,`iterator.return`如果有定义,就会调用该方法,否则`iterator.return`直接返回`undefined`,不再执行`?.`后面的部分。 对于那些可能没有实现的方法,这个运算符尤其有用。 @@ -756,7 +758,21 @@ if (myForm.checkValidity?.() === false) { 上面代码中,老式浏览器的表单可能没有`checkValidity`这个方法,这时`?.`运算符就会返回`undefined`,判断语句就变成了`undefined === false`,所以就会跳过下面的代码。 -下面是这个运算符常见的使用形式,以及不使用该运算符时的等价形式。 +链判断运算符有三种用法。 + +- `obj?.prop` // 对象属性 +- `obj?.[expr]` // 同上 +- `func?.(...args)` // 函数或对象方法的调用 + +下面是`obj?.[expr]`用法的一个例子。 + +```bash +let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1]; +``` + +上面例子中,字符串的`match()`方法,如果没有发现匹配会返回`null`,如果发现匹配会返回一个数组,`?.`运算符起到了判断作用。 + +下面是`?.`运算符常见形式,以及不使用该运算符时的等价形式。 ```javascript a?.b @@ -782,6 +798,8 @@ a == null ? undefined : a() (1)短路机制 +`?.`运算符相当于一种短路机制,只要不满足条件,就不再往下执行。 + ```javascript a?.[++x] // 等同于 @@ -859,7 +877,7 @@ const animationDuration = response.settings.animationDuration ?? 300; const showSplashScreen = response.settings.showSplashScreen ?? true; ``` -上面代码中,默认值只有在属性值为`null`或`undefined`时,才会生效。 +上面代码中,默认值只有在左侧属性值为`null`或`undefined`时,才会生效。 这个运算符的一个目的,就是跟链判断运算符`?.`配合使用,为`null`或`undefined`的值设置默认值。 From d59b30173d31e697b53865fbe776c824ba0aed26 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 7 Jul 2020 07:47:01 +0800 Subject: [PATCH 083/280] docs(number): edit BigInt --- docs/number.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/number.md b/docs/number.md index 0aca19309..250e7e735 100644 --- a/docs/number.md +++ b/docs/number.md @@ -700,7 +700,7 @@ Math.pow(2, 53) === Math.pow(2, 53) + 1 // true Math.pow(2, 1024) // Infinity ``` -[ES2020](https://github.com/tc39/proposal-bigint) 引入了一种新的数据类型 BigInt(大整数),来解决这个问题。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。 +[ES2020](https://github.com/tc39/proposal-bigint) 引入了一种新的数据类型 BigInt(大整数),来解决这个问题,这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。 ```javascript const a = 2172141653n; From 4425866c1011e83817179b04cb831bdbc409e1d3 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 8 Jul 2020 00:19:02 +0800 Subject: [PATCH 084/280] =?UTF-8?q?docs(object):=20edit=20null=20=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E8=BF=90=E7=AE=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index 698df6676..295608cee 100644 --- a/docs/object.md +++ b/docs/object.md @@ -896,7 +896,7 @@ function Component(props) { } ``` -上面代码判断`props`参数的`enabled`属性是否赋值,等同于下面的写法。 +上面代码判断`props`参数的`enabled`属性是否赋值,基本等同于下面的写法。 ```javascript function Component(props) { From 064ee65efc665759a4074f6b533c4883a6f83e8b Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 8 Jul 2020 00:37:57 +0800 Subject: [PATCH 085/280] docs(object-methods): edit Object.assign() --- docs/object-methods.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/object-methods.md b/docs/object-methods.md index 20c7fd7c1..a9d8200f3 100644 --- a/docs/object-methods.md +++ b/docs/object-methods.md @@ -47,7 +47,7 @@ Object.defineProperty(Object, 'is', { ### 基本用法 -`Object.assign`方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。 +`Object.assign()`方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。 ```javascript const target = { a: 1 }; @@ -59,7 +59,7 @@ Object.assign(target, source1, source2); target // {a:1, b:2, c:3} ``` -`Object.assign`方法的第一个参数是目标对象,后面的参数都是源对象。 +`Object.assign()`方法的第一个参数是目标对象,后面的参数都是源对象。 注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。 @@ -73,7 +73,7 @@ Object.assign(target, source1, source2); target // {a:1, b:2, c:3} ``` -如果只有一个参数,`Object.assign`会直接返回该参数。 +如果只有一个参数,`Object.assign()`会直接返回该参数。 ```javascript const obj = {a: 1}; @@ -120,9 +120,9 @@ Object(10) // {[[PrimitiveValue]]: 10} Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"} ``` -上面代码中,布尔值、数值、字符串分别转成对应的包装对象,可以看到它们的原始值都在包装对象的内部属性`[[PrimitiveValue]]`上面,这个属性是不会被`Object.assign`拷贝的。只有字符串的包装对象,会产生可枚举的实义属性,那些属性则会被拷贝。 +上面代码中,布尔值、数值、字符串分别转成对应的包装对象,可以看到它们的原始值都在包装对象的内部属性`[[PrimitiveValue]]`上面,这个属性是不会被`Object.assign()`拷贝的。只有字符串的包装对象,会产生可枚举的实义属性,那些属性则会被拷贝。 -`Object.assign`拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(`enumerable: false`)。 +`Object.assign()`拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(`enumerable: false`)。 ```javascript Object.assign({b: 'c'}, @@ -134,9 +134,9 @@ Object.assign({b: 'c'}, // { b: 'c' } ``` -上面代码中,`Object.assign`要拷贝的对象只有一个不可枚举属性`invisible`,这个属性并没有被拷贝进去。 +上面代码中,`Object.assign()`要拷贝的对象只有一个不可枚举属性`invisible`,这个属性并没有被拷贝进去。 -属性名为 Symbol 值的属性,也会被`Object.assign`拷贝。 +属性名为 Symbol 值的属性,也会被`Object.assign()`拷贝。 ```javascript Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) @@ -147,7 +147,7 @@ Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' }) **(1)浅拷贝** -`Object.assign`方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。 +`Object.assign()`方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。 ```javascript const obj1 = {a: {b: 1}}; @@ -157,11 +157,11 @@ obj1.a.b = 2; obj2.a.b // 2 ``` -上面代码中,源对象`obj1`的`a`属性的值是一个对象,`Object.assign`拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。 +上面代码中,源对象`obj1`的`a`属性的值是一个对象,`Object.assign()`拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。 **(2)同名属性的替换** -对于这种嵌套的对象,一旦遇到同名属性,`Object.assign`的处理方法是替换,而不是添加。 +对于这种嵌套的对象,一旦遇到同名属性,`Object.assign()`的处理方法是替换,而不是添加。 ```javascript const target = { a: { b: 'c', d: 'e' } } @@ -172,22 +172,22 @@ Object.assign(target, source) 上面代码中,`target`对象的`a`属性被`source`对象的`a`属性整个替换掉了,而不会得到`{ a: { b: 'hello', d: 'e' } }`的结果。这通常不是开发者想要的,需要特别小心。 -一些函数库提供`Object.assign`的定制版本(比如 Lodash 的`_.defaultsDeep`方法),可以得到深拷贝的合并。 +一些函数库提供`Object.assign()`的定制版本(比如 Lodash 的`_.defaultsDeep()`方法),可以得到深拷贝的合并。 **(3)数组的处理** -`Object.assign`可以用来处理数组,但是会把数组视为对象。 +`Object.assign()`可以用来处理数组,但是会把数组视为对象。 ```javascript Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3] ``` -上面代码中,`Object.assign`把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性`4`覆盖了目标数组的 0 号属性`1`。 +上面代码中,`Object.assign()`把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性`4`覆盖了目标数组的 0 号属性`1`。 **(4)取值函数的处理** -`Object.assign`只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。 +`Object.assign()`只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。 ```javascript const source = { @@ -199,11 +199,11 @@ Object.assign(target, source) // { foo: 1 } ``` -上面代码中,`source`对象的`foo`属性是一个取值函数,`Object.assign`不会复制这个取值函数,只会拿到值以后,将这个值复制过去。 +上面代码中,`source`对象的`foo`属性是一个取值函数,`Object.assign()`不会复制这个取值函数,只会拿到值以后,将这个值复制过去。 ### 常见用途 -`Object.assign`方法有很多用处。 +`Object.assign()`方法有很多用处。 **(1)为对象添加属性** @@ -215,7 +215,7 @@ class Point { } ``` -上面方法通过`Object.assign`方法,将`x`属性和`y`属性添加到`Point`类的对象实例。 +上面方法通过`Object.assign()`方法,将`x`属性和`y`属性添加到`Point`类的对象实例。 **(2)为对象添加方法** @@ -238,7 +238,7 @@ SomeClass.prototype.anotherMethod = function () { }; ``` -上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用`assign`方法添加到`SomeClass.prototype`之中。 +上面代码使用了对象属性的简洁表示法,直接将两个函数放在大括号中,再使用`assign()`方法添加到`SomeClass.prototype`之中。 **(3)克隆对象** @@ -290,7 +290,7 @@ function processContent(options) { } ``` -上面代码中,`DEFAULTS`对象是默认值,`options`对象是用户提供的参数。`Object.assign`方法将`DEFAULTS`和`options`合并成一个新对象,如果两者有同名属性,则`options`的属性值会覆盖`DEFAULTS`的属性值。 +上面代码中,`DEFAULTS`对象是默认值,`options`对象是用户提供的参数。`Object.assign()`方法将`DEFAULTS`和`options`合并成一个新对象,如果两者有同名属性,则`options`的属性值会覆盖`DEFAULTS`的属性值。 注意,由于存在浅拷贝的问题,`DEFAULTS`对象和`options`对象的所有属性的值,最好都是简单类型,不要指向另一个对象。否则,`DEFAULTS`对象的该属性很可能不起作用。 From e78513744c80d552fb9c2268f09b2d15f6181eff Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 6 Aug 2020 19:56:54 +0800 Subject: [PATCH 086/280] docs(let): fix #1001 --- docs/let.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/let.md b/docs/let.md index b92887beb..e5a7c8b3e 100644 --- a/docs/let.md +++ b/docs/let.md @@ -601,7 +601,7 @@ JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作 同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用`this`变量,但是有局限性。 -- 全局环境中,`this`会返回顶层对象。但是,Node 模块和 ES6 模块中,`this`返回的是当前模块。 +- 全局环境中,`this`会返回顶层对象。但是,Node.js 模块中`this`返回的是当前模块,ES6 模块中`this`返回的是`undefined`。 - 函数里面的`this`,如果函数不是作为对象的方法运行,而是单纯作为函数运行,`this`会指向顶层对象。但是,严格模式下,这时`this`会返回`undefined`。 - 不管是严格模式,还是普通模式,`new Function('return this')()`,总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么`eval`、`new Function`这些方法都可能无法使用。 From 9461a922f6e8ab6ad10ed351007a4418d42d8c6d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 14 Aug 2020 22:35:52 +0800 Subject: [PATCH 087/280] docs: edit README --- README.md | 2 ++ js/ditto.js | 4 ++-- tencent9908947001655912037.txt | 2 -- 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 tencent9908947001655912037.txt diff --git a/README.md b/README.md index 0e80abe03..2fbff3ee7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # ES6 入门教程 +官方镜像:[网道(WangDoc.com)](https://wangdoc.com/es6/) + 《ECMAScript 6 入门教程》是一本开源的 JavaScript 语言教程,全面介绍 ECMAScript 6 新引入的语法特性。 [![cover](images/cover_thumbnail_3rd.jpg)](images/cover-3rd.jpg) diff --git a/js/ditto.js b/js/ditto.js index 6daa3b3ae..d694a063e 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -210,8 +210,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2020年8月15日 - var deadline = new Date(2020, 7, 15); + // 2020年12月1日 + var deadline = new Date(2020, 11, 1); if (deadline - (new Date()) < 0) return; var styleStr = [ diff --git a/tencent9908947001655912037.txt b/tencent9908947001655912037.txt deleted file mode 100644 index c216cc792..000000000 --- a/tencent9908947001655912037.txt +++ /dev/null @@ -1,2 +0,0 @@ -4096418148424150100 - From 266199c2d2db82e0ae32134b71ab194485b43b9d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 19 Aug 2020 20:51:55 +0800 Subject: [PATCH 088/280] =?UTF-8?q?docs(module-loader):=20edit=20Node.js?= =?UTF-8?q?=20=E7=9A=84=E6=A8=A1=E5=9D=97=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/async.md | 2 + docs/module-loader.md | 93 ++++++++++++++++++++++++------------------- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/docs/async.md b/docs/async.md index 53f1ccfba..d109a395b 100644 --- a/docs/async.md +++ b/docs/async.md @@ -833,6 +833,8 @@ setTimeout(() => console.log(outputPlusValue(100), 1000); 这时,模块的加载会等待依赖模块(上例是`awaiting.js`)的异步操作完成,才执行后面的代码,有点像暂停在那里。所以,它总是会得到正确的`output`,不会因为加载时机的不同,而得到不一样的值。 +注意,顶层`await`只能用在 ES6 模块,不能用在 CommonJS 模块。这是因为 CommonJS 模块的`require()`是同步加载,如果有顶层`await`,就没法处理加载了。 + 下面是顶层`await`的一些使用场景。 ```javascript diff --git a/docs/module-loader.md b/docs/module-loader.md index 0bbfe3e74..ee37ca240 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -110,10 +110,11 @@ const isNotModuleScript = this !== undefined; 讨论 Node.js 加载 ES6 模块之前,必须了解 ES6 模块与 CommonJS 模块完全不同。 -它们有两个重大差异。 +它们有三个重大差异。 - CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 - CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 +- CommonJS 模块的`require()`是同步加载模块,ES6 模块的`import`命令是异步加载,有一个独立模块依赖的解析阶段。 第二个差异是因为 CommonJS 加载的是一个对象(即`module.exports`属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。 @@ -271,11 +272,15 @@ $ babel-node main.js 这就证明了`x.js`和`y.js`加载的都是`C`的同一个实例。 -## Node.js 加载 +## Node.js 的模块加载方法 ### 概述 -Node.js 对 ES6 模块的处理比较麻烦,因为它有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。从 v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。 +JavaScript 现在有两种模块。一种是 ES6 模块,简称 ESM;另一种是 CommonJS 模块,简称 CJS。 + +CommonJS 模块是 Node.js 专用的,与 ES6 模块不兼容。语法上面,两者最明显的差异是,CommonJS 模块使用`require()`和`module.exports`,ES6 模块使用`import`和`export`。 + +它们采用不同的加载方案。从 Node.js v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。 Node.js 要求 ES6 模块采用`.mjs`后缀文件名。也就是说,只要脚本文件里面使用`import`或者`export`命令,那么就必须采用`.mjs`后缀名。Node.js 遇到`.mjs`文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定`"use strict"`。 @@ -300,7 +305,7 @@ $ node my-app.js 注意,ES6 模块与 CommonJS 模块尽量不要混用。`require`命令不能加载`.mjs`文件,会报错,只有`import`命令才可以加载`.mjs`文件。反过来,`.mjs`文件里面也不能使用`require`命令,必须使用`import`。 -### main 字段 +### package.json 的 main 字段 `package.json`文件有两个字段可以指定模块的入口文件:`main`和`exports`。比较简单的模块,可以只使用`main`字段,指定模块加载的入口文件。 @@ -327,7 +332,7 @@ import { something } from 'es-module-package'; 这时,如果用 CommonJS 模块的`require()`命令去加载`es-module-package`模块会报错,因为 CommonJS 模块不能处理`export`命令。 -### exports 字段 +### package.json 的 exports 字段 `exports`字段的优先级高于`main`字段。它有多种用法。 @@ -427,7 +432,7 @@ import submodule from './node_modules/es-module-package/private-module.js'; ```javascript { - "exports": { + "exports": {package.json 的 "require": "./main.cjs", "default": "./main.js" } @@ -447,40 +452,23 @@ import submodule from './node_modules/es-module-package/private-module.js'; } ``` -### ES6 模块加载 CommonJS 模块 - -目前,一个模块同时支持 ES6 和 CommonJS 两种格式的常见方法是,`package.json`文件的`main`字段指定 CommonJS 入口,给 Node.js 使用;`module`字段指定 ES6 模块入口,给打包工具使用,因为 Node.js 不认识`module`字段。 +### CommonJS 模块加载 ES6 模块 -有了上一节的条件加载以后,Node.js 本身就可以同时处理两种模块。 +CommonJS 的`require()`命令不能加载 ES6 模块,会报错,只能使用`import()`这个方法加载。 ```javascript -// ./node_modules/pkg/package.json -{ - "type": "module", - "main": "./index.cjs", - "exports": { - "require": "./index.cjs", - "default": "./wrapper.mjs" - } -} +(async () => { + await import('./my-app.mjs'); +})(); ``` -上面代码指定了 CommonJS 入口文件`index.cjs`,下面是这个文件的代码。 - -```javascript -// ./node_modules/pkg/index.cjs -exports.name = 'value'; -``` +上面代码可以在 CommonJS 模块中运行。 -然后,ES6 模块可以加载这个文件。 +`require()`不支持 ES6 模块的一个原因是,它是同步加载,而 ES6 模块内部可以使用顶层`await`命令,导致无法被同步加载。 -```javascript -// ./node_modules/pkg/wrapper.mjs -import cjsModule from './index.cjs'; -export const name = cjsModule.name; -``` +### ES6 模块加载 CommonJS 模块 -注意,`import`命令加载 CommonJS 模块,只能整体加载,不能只加载单一的输出项。 +ES6 模块的`import`命令可以加载 CommonJS 模块,但是只能整体加载,不能只加载单一的输出项。 ```javascript // 正确 @@ -490,6 +478,15 @@ import packageMain from 'commonjs-package'; import { method } from 'commonjs-package'; ``` +这是因为 ES6 模块需要支持静态代码分析,而 CommonJS 模块的输出接口是`module.exports`,无法被静态分析,所以只能整体加载。 + +加载单一的输出项,可以写成下面这样。 + +```bash +import packageMain from 'commonjs-package'; +const { method } = packageMain;:w +``` + 还有一种变通的加载方法,就是使用 Node.js 内置的`module.createRequire()`方法。 ```javascript @@ -505,19 +502,33 @@ const cjs = require('./cjs.cjs'); cjs === 'cjs'; // true ``` -上面代码中,ES6 模块通过`module.createRequire()`方法可以加载 CommonJS 模块 +上面代码中,ES6 模块通过`module.createRequire()`方法可以加载 CommonJS 模块。但是,这种写法等于将 ES6 和 CommonJS 混在一起了,所以不建议使用。 -### CommonJS 模块加载 ES6 模块 +## 同时支持两种格式的模块 + +一个模块同时要支持 CommonJS 和 ES6 两种格式,也很容易。 -CommonJS 的`require`命令不能加载 ES6 模块,会报错,只能使用`import()`这个方法加载。 +如果原始模块是 ES6 格式,那么需要给出一个整体输出接口,比如`export default obj`,使得 CommonJS 可以用`import()`进行加载。 + +如果原始模块是 CommonJS 格式,那么可以加一个包装层。 ```javascript -(async () => { - await import('./my-app.mjs'); -})(); +import cjsModule from '../index.js'; +export const foo = cjsModule.foo; ``` -上面代码可以在 CommonJS 模块中运行。 +上面代码先整体输入 CommonJS 模块,然后再根据需要输出具名接口。最后,可以将它放在一个子目录,再放一个单独的`package.json`文件,指明`{ module: "type" }`。 + +另一种做法是在`package.json`文件的`exports`字段,指明两种格式模块各自的加载入口。 + +```bash +"exports":{ + "require": "./index.js", + "import": "./esm/wrapper.js" +} +``` + +上面代码指定`require()`和`import`,加载该模块会自动切换到不一样的入口文件。 ### Node.js 的内置模块 @@ -558,11 +569,9 @@ import './foo.mjs?query=1'; // 加载 ./foo 传入参数 ?query=1 目前,Node.js 的`import`命令只支持加载本地模块(`file:`协议)和`data:`协议,不支持加载远程模块。另外,脚本路径只支持相对路径,不支持绝对路径(即以`/`或`//`开头的路径)。 -最后,Node 的`import`命令是异步加载,这一点与浏览器的处理方法相同。 - ### 内部变量 -ES6 模块应该是通用的,同一个模块不用修改,就可以用在浏览器环境和服务器环境。为了达到这个目标,Node 规定 ES6 模块之中不能使用 CommonJS 模块的特有的一些内部变量。 +ES6 模块应该是通用的,同一个模块不用修改,就可以用在浏览器环境和服务器环境。为了达到这个目标,Node.js 规定 ES6 模块之中不能使用 CommonJS 模块的特有的一些内部变量。 首先,就是`this`关键字。ES6 模块之中,顶层的`this`指向`undefined`;CommonJS 模块的顶层`this`指向当前模块,这是两者的一个重大差异。 From 09accf90d958db8f31dd930801fb509905f8b5b6 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 19 Aug 2020 21:03:52 +0800 Subject: [PATCH 089/280] docs(module-loader): fix typo --- docs/module-loader.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index ee37ca240..dbf26114e 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -504,7 +504,7 @@ cjs === 'cjs'; // true 上面代码中,ES6 模块通过`module.createRequire()`方法可以加载 CommonJS 模块。但是,这种写法等于将 ES6 和 CommonJS 混在一起了,所以不建议使用。 -## 同时支持两种格式的模块 +### 同时支持两种格式的模块 一个模块同时要支持 CommonJS 和 ES6 两种格式,也很容易。 From 7b8c0632f62f3da9310f6fe13e8d6c96c72111e5 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 20 Aug 2020 14:19:23 +0800 Subject: [PATCH 090/280] =?UTF-8?q?docs(module-loader):=20edit=20=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E6=94=AF=E6=8C=81=E4=B8=A4=E7=A7=8D=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E7=9A=84=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/module-loader.md | 14 ++++++++------ docs/reference.md | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index dbf26114e..d1a4fbaef 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -478,7 +478,7 @@ import packageMain from 'commonjs-package'; import { method } from 'commonjs-package'; ``` -这是因为 ES6 模块需要支持静态代码分析,而 CommonJS 模块的输出接口是`module.exports`,无法被静态分析,所以只能整体加载。 +这是因为 ES6 模块需要支持静态代码分析,而 CommonJS 模块的输出接口是`module.exports`,是一个对象,无法被静态分析,所以只能整体加载。 加载单一的输出项,可以写成下面这样。 @@ -514,17 +514,19 @@ cjs === 'cjs'; // true ```javascript import cjsModule from '../index.js'; -export const foo = cjsModule.foo; +export const foo = cjsModule.foo; ``` -上面代码先整体输入 CommonJS 模块,然后再根据需要输出具名接口。最后,可以将它放在一个子目录,再放一个单独的`package.json`文件,指明`{ module: "type" }`。 +上面代码先整体输入 CommonJS 模块,然后再根据需要输出具名接口。 + +你可以把这个文件的后缀名改为`.mjs`,或者将它放在一个子目录,再在这个子目录里面放一个单独的`package.json`文件,指明`{ module: "type" }`。 另一种做法是在`package.json`文件的`exports`字段,指明两种格式模块各自的加载入口。 ```bash -"exports":{ - "require": "./index.js", - "import": "./esm/wrapper.js" +"exports":{ + "require": "./index.js", + "import": "./esm/wrapper.js" } ``` diff --git a/docs/reference.md b/docs/reference.md index 1391174fd..909c21cc0 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -227,6 +227,7 @@ - Axel Rauschmayer, [Making transpiled ES modules more spec-compliant](http://www.2ality.com/2017/01/babel-esm-spec-mode.html): ES6 模块编译成 CommonJS 模块的详细介绍 - Axel Rauschmayer, [ES proposal: import() – dynamically importing ES modules](http://www.2ality.com/2017/01/import-operator.html): import() 的用法 - Node EPS, [ES Module Interoperability](https://github.com/nodejs/node-eps/blob/master/002-es-modules.md): Node 对 ES6 模块的处理规格 +- Dan Fabulich, [Why CommonJS and ES Modules Can’t Get Along](https://redfin.engineering/node-modules-at-war-why-commonjs-and-es-modules-cant-get-along-9617135eeca1): Node.js 对 ES6 模块的处理 ## 二进制数组 From a841acdca5fe465f5f6548d407bf5d7192bd276d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 20 Aug 2020 16:36:45 +0800 Subject: [PATCH 091/280] docs(module-loader): fix typo --- docs/module-loader.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index d1a4fbaef..8ec507962 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -482,7 +482,7 @@ import { method } from 'commonjs-package'; 加载单一的输出项,可以写成下面这样。 -```bash +```javascript import packageMain from 'commonjs-package'; const { method } = packageMain;:w ``` @@ -523,7 +523,7 @@ export const foo = cjsModule.foo; 另一种做法是在`package.json`文件的`exports`字段,指明两种格式模块各自的加载入口。 -```bash +```javascript "exports":{ "require": "./index.js", "import": "./esm/wrapper.js" From 2613635b774184b9661f3ead26e385769c078c01 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 20 Aug 2020 20:26:45 +0800 Subject: [PATCH 092/280] docs(module-loader): fix typo --- docs/module-loader.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index 8ec507962..2c0d046ee 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -484,7 +484,7 @@ import { method } from 'commonjs-package'; ```javascript import packageMain from 'commonjs-package'; -const { method } = packageMain;:w +const { method } = packageMain; ``` 还有一种变通的加载方法,就是使用 Node.js 内置的`module.createRequire()`方法。 From dfe3373143a539f9a9d7b5518868be4d19faa8a4 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 21 Aug 2020 12:09:30 +0800 Subject: [PATCH 093/280] feat: add change theme --- css/app.css | 26 ++++++++++++++++++++++++++ index.html | 8 ++++++++ js/ditto.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/css/app.css b/css/app.css index a2e2f3c51..7938b0963 100644 --- a/css/app.css +++ b/css/app.css @@ -340,6 +340,32 @@ input.searchButton { cursor: pointer; } +#theme { + display: none; + position: fixed; + + height: 17px; + width: 70px; + top: 70px; + + margin-left: 930px; + margin-top: 0px; + + color: #FFF; + line-height: 17px; + text-align: center; + font-size: 10px; + + + border-radius: 5px; + background-color: #AAA; +} + +#theme:hover { + background-color: #444; + cursor: pointer; +} + #loading, #error { display: none; position: fixed; diff --git a/index.html b/index.html index 76253f553..7cf302be0 100644 --- a/index.html +++ b/index.html @@ -1,3 +1,10 @@ + @@ -22,6 +29,7 @@
    back to top
    edit
    +
    theme
    Loading ...
    Oops! ... File not found!
    上一章
    下一章
    diff --git a/js/ditto.js b/js/ditto.js index d694a063e..c633dd449 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -4,6 +4,7 @@ var ditto = { sidebar_id: "#sidebar", edit_id: "#edit", back_to_top_id: "#back_to_top", + theme_id: "#theme", loading_id: "#loading", error_id: "#error", @@ -11,6 +12,7 @@ var ditto = { sidebar: true, edit_button: true, back_to_top_button: true, + theme_button: true, save_progress: true, // 保存阅读进度 search_bar: true, @@ -58,6 +60,10 @@ function initialize() { if (ditto.edit_button) { init_edit_button(); } + + if (ditto.theme_button) { + init_theme_button(); + } // page router router(); @@ -133,12 +139,39 @@ function searchbar_listener(event) { */ } +function init_theme_button() { + $(ditto.theme_id).show(); + // 默认主题 + var currfontColor = localStorage.getItem('fontColor') || '#0d141e'; + var currbgColor = localStorage.getItem('bgColor') || '#ffffff'; + $('body').css({ + color: currfontColor, + backgroundColor: currbgColor + }) + $(ditto.theme_id).on('click', changeTheme); +} function init_back_to_top_button() { $(ditto.back_to_top_id).show(); $(ditto.back_to_top_id).on('click', goTop); } +// 改变主题 +function changeTheme() { + var fontColor = localStorage.getItem('fontColor') || '#0d141e'; + var bgColor = localStorage.getItem('bgColor') || '#ffffff'; + var fontColors = ['#0d141e', '#020000', '#020702', '#d0d3d8']; + var bgColors = ['#ffffff', '#f6f0da', '#c0edc6', '#1f2022']; + var currIndex = bgColors.indexOf(bgColor); + var nextIndex = (currIndex + 1) >= bgColors.length ? 0 : currIndex + 1; + $('body').css({ + color: fontColors[nextIndex], + backgroundColor: bgColors[nextIndex], + }); + localStorage.setItem('fontColor', fontColors[nextIndex]); + localStorage.setItem('bgColor', bgColors[nextIndex]); +} + function goTop(e) { if(e) e.preventDefault(); $('html, body').animate({ From e33a66895df25191222a424e6b9e3aae0649f172 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 25 Aug 2020 09:45:02 +0800 Subject: [PATCH 094/280] upt: delete note --- index.html | 7 ------- js/ditto.js | 8 ++++---- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/index.html b/index.html index 7cf302be0..0f88f503c 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,3 @@ - diff --git a/js/ditto.js b/js/ditto.js index c633dd449..3f8e1219e 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -142,11 +142,11 @@ function searchbar_listener(event) { function init_theme_button() { $(ditto.theme_id).show(); // 默认主题 - var currfontColor = localStorage.getItem('fontColor') || '#0d141e'; - var currbgColor = localStorage.getItem('bgColor') || '#ffffff'; + var currFontColor = localStorage.getItem('fontColor') || '#0d141e'; + var currBgColor = localStorage.getItem('bgColor') || '#ffffff'; $('body').css({ - color: currfontColor, - backgroundColor: currbgColor + color: currFontColor, + backgroundColor: currBgColor }) $(ditto.theme_id).on('click', changeTheme); } From adb49644ad8016b06ee1f3b417b5d958f4dcf90d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 29 Aug 2020 11:24:38 +0800 Subject: [PATCH 095/280] docs(module-loader): fix typo --- docs/module-loader.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index 2c0d046ee..0d1e14c53 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -519,7 +519,7 @@ export const foo = cjsModule.foo; 上面代码先整体输入 CommonJS 模块,然后再根据需要输出具名接口。 -你可以把这个文件的后缀名改为`.mjs`,或者将它放在一个子目录,再在这个子目录里面放一个单独的`package.json`文件,指明`{ module: "type" }`。 +你可以把这个文件的后缀名改为`.mjs`,或者将它放在一个子目录,再在这个子目录里面放一个单独的`package.json`文件,指明`{ type: "module" }`。 另一种做法是在`package.json`文件的`exports`字段,指明两种格式模块各自的加载入口。 From 4e14b40bfcde1aae70edbed62d6c561073546a43 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 2 Sep 2020 00:53:24 +0800 Subject: [PATCH 096/280] docs(object): fix #1004 --- docs/object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/object.md b/docs/object.md index 295608cee..5b8395bc7 100644 --- a/docs/object.md +++ b/docs/object.md @@ -885,7 +885,7 @@ const showSplashScreen = response.settings.showSplashScreen ?? true; const animationDuration = response.settings?.animationDuration ?? 300; ``` -上面代码中,`response.settings`如果是`null`或`undefined`,就会返回默认值300。 +上面代码中,如果`response.settings`是`null`或`undefined`,或者`response.settings.animationDuration`是`null`或`undefined`,就会返回默认值300。也就是说,这一行代码包括了两级属性的判断。 这个运算符很适合判断函数参数是否赋值。 From 2f76a9f55f8f7d330ad1c01d43ab9ddee6b50cc9 Mon Sep 17 00:00:00 2001 From: hehe1111 <2908749709@qq.com> Date: Wed, 9 Sep 2020 11:29:06 +0800 Subject: [PATCH 097/280] (docs): fix typo --- docs/module-loader.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index 0d1e14c53..8b0fe33ac 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -432,7 +432,7 @@ import submodule from './node_modules/es-module-package/private-module.js'; ```javascript { - "exports": {package.json 的 + "exports": { "require": "./main.cjs", "default": "./main.js" } From 76fc255da46beb06e52c393a01544765c82c510d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 10 Sep 2020 20:53:57 +0800 Subject: [PATCH 098/280] docs(promise): edit Promise.reject() --- docs/promise.md | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index 8723e68c3..baa4287fa 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -836,7 +836,7 @@ Promise.resolve('foo') new Promise(resolve => resolve('foo')) ``` -`Promise.resolve`方法的参数分成四种情况。 +`Promise.resolve()`方法的参数分成四种情况。 **(1)参数是一个 Promise 实例** @@ -854,7 +854,7 @@ let thenable = { }; ``` -`Promise.resolve`方法会将这个对象转为 Promise 对象,然后就立即执行`thenable`对象的`then`方法。 +`Promise.resolve()`方法会将这个对象转为 Promise 对象,然后就立即执行`thenable`对象的`then()`方法。 ```javascript let thenable = { @@ -864,27 +864,27 @@ let thenable = { }; let p1 = Promise.resolve(thenable); -p1.then(function(value) { +p1.then(function (value) { console.log(value); // 42 }); ``` -上面代码中,`thenable`对象的`then`方法执行后,对象`p1`的状态就变为`resolved`,从而立即执行最后那个`then`方法指定的回调函数,输出 42。 +上面代码中,`thenable`对象的`then()`方法执行后,对象`p1`的状态就变为`resolved`,从而立即执行最后那个`then()`方法指定的回调函数,输出42。 -**(3)参数不是具有`then`方法的对象,或根本就不是对象** +**(3)参数不是具有`then()`方法的对象,或根本就不是对象** -如果参数是一个原始值,或者是一个不具有`then`方法的对象,则`Promise.resolve`方法返回一个新的 Promise 对象,状态为`resolved`。 +如果参数是一个原始值,或者是一个不具有`then()`方法的对象,则`Promise.resolve()`方法返回一个新的 Promise 对象,状态为`resolved`。 ```javascript const p = Promise.resolve('Hello'); -p.then(function (s){ +p.then(function (s) { console.log(s) }); // Hello ``` -上面代码生成一个新的 Promise 对象的实例`p`。由于字符串`Hello`不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是`resolved`,所以回调函数会立即执行。`Promise.resolve`方法的参数,会同时传给回调函数。 +上面代码生成一个新的 Promise 对象的实例`p`。由于字符串`Hello`不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是`resolved`,所以回调函数会立即执行。`Promise.resolve()`方法的参数,会同时传给回调函数。 **(4)不带有任何参数** @@ -939,23 +939,17 @@ p.then(null, function (s) { 上面代码生成一个 Promise 对象的实例`p`,状态为`rejected`,回调函数会立即执行。 -注意,`Promise.reject()`方法的参数,会原封不动地作为`reject`的理由,变成后续方法的参数。这一点与`Promise.resolve`方法不一致。 +`Promise.reject()`方法的参数,会原封不动地作为`reject`的理由,变成后续方法的参数。 ```javascript -const thenable = { - then(resolve, reject) { - reject('出错了'); - } -}; - -Promise.reject(thenable) +Promise.reject('出错了') .catch(e => { - console.log(e === thenable) + console.log(e === '出错了') }) // true ``` -上面代码中,`Promise.reject`方法的参数是一个`thenable`对象,执行以后,后面`catch`方法的参数不是`reject`抛出的“出错了”这个字符串,而是`thenable`对象。 +上面代码中,`Promise.reject()`方法的参数是一个字符串,后面`catch()`方法的参数`e`就是这个字符串。 ## 应用 From d4952d6d796d606947cef12149bd383be401fa2d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 10 Sep 2020 21:24:39 +0800 Subject: [PATCH 099/280] docs(module-loader): fix typo --- docs/module-loader.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index 0d1e14c53..de3813a03 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -114,7 +114,7 @@ const isNotModuleScript = this !== undefined; - CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 - CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 -- CommonJS 模块的`require()`是同步加载模块,ES6 模块的`import`命令是异步加载,有一个独立模块依赖的解析阶段。 +- CommonJS 模块的`require()`是同步加载模块,ES6 模块的`import`命令是异步加载,有一个独立的模块依赖的解析阶段。 第二个差异是因为 CommonJS 加载的是一个对象(即`module.exports`属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。 From 6dc250f739200e5f8a4f542b1fc0e870c0c40f24 Mon Sep 17 00:00:00 2001 From: Careteen <15074806497@163.com> Date: Sat, 12 Sep 2020 17:45:34 +0800 Subject: [PATCH 100/280] =?UTF-8?q?docs(decorator):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E6=8F=90=E5=8D=87=E6=97=B6=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=AE=9E=E9=99=85=E6=89=A7=E8=A1=8C=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/decorator.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/decorator.md b/docs/decorator.md index 262016f7d..cb8517a90 100644 --- a/docs/decorator.md +++ b/docs/decorator.md @@ -309,13 +309,13 @@ function foo() { 上面的代码,意图是执行后`counter`等于 1,但是实际上结果是`counter`等于 0。因为函数提升,使得实际执行的代码是下面这样。 ```javascript +var counter; +var add; + @add function foo() { } -var counter; -var add; - counter = 0; add = function () { From c43225319371a27984822c4038404d5292507378 Mon Sep 17 00:00:00 2001 From: Jacty Date: Sun, 13 Sep 2020 01:11:40 +0800 Subject: [PATCH 101/280] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=20async=20?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E5=86=85=E9=83=A8=E6=8A=A5=E9=94=99=E4=BC=9A?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=20Promise=20=E5=AF=B9=E8=B1=A1=E5=8F=98?= =?UTF-8?q?=E4=B8=BA`reject`=E7=8A=B6=E6=80=81=E7=9A=84=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原来的例子并不能很明显的表示打印的内容来自于 reject 的回调。 --- docs/async.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/async.md b/docs/async.md index d109a395b..1bb0dd7e2 100644 --- a/docs/async.md +++ b/docs/async.md @@ -184,10 +184,10 @@ async function f() { } f().then( - v => console.log(v), - e => console.log(e) + v => console.log(‘resolve’,v), + e => console.log('reject',e) ) -// Error: 出错了 +//reject Error: 出错了 ``` ### Promise 对象的状态变化 From f81d202a097e85d31fdae5e898b30b42395dcc74 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 15 Sep 2020 11:11:57 +0800 Subject: [PATCH 102/280] docs(proxy): fix constrct() --- docs/proxy.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index e0dfb38da..3f5b84102 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -601,7 +601,7 @@ for (let b in oproxy2) { ### construct() -`construct`方法用于拦截`new`命令,下面是拦截对象的写法。 +`construct()`方法用于拦截`new`命令,下面是拦截对象的写法。 ```javascript var handler = { @@ -611,11 +611,11 @@ var handler = { }; ``` -`construct`方法可以接受三个参数。 +`construct()`方法可以接受三个参数。 -- `target`:目标对象 -- `args`:构造函数的参数对象 -- `newTarget`:创造实例对象时,`new`命令作用的构造函数(下面例子的`p`) +- `target`:目标对象。 +- `args`:构造函数的参数数组。 +- `newTarget`:创造实例对象时,`new`命令作用的构造函数(下面例子的`p`)。 ```javascript var p = new Proxy(function () {}, { @@ -630,7 +630,7 @@ var p = new Proxy(function () {}, { // 10 ``` -`construct`方法返回的必须是一个对象,否则会报错。 +`construct()`方法返回的必须是一个对象,否则会报错。 ```javascript var p = new Proxy(function() {}, { From 0f1acd319a870cb7380c5ca7a1b6bb6d24b322c4 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 15 Sep 2020 11:36:32 +0800 Subject: [PATCH 103/280] docs(iterable): fix typo --- docs/iterator.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/iterator.md b/docs/iterator.md index a39352c4a..68fba5009 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -441,7 +441,7 @@ str // "hi" ## Iterator 接口与 Generator 函数 -`Symbol.iterator`方法的最简单实现,还是使用下一章要介绍的 Generator 函数。 +`Symbol.iterator()`方法的最简单实现,还是使用下一章要介绍的 Generator 函数。 ```javascript let myIterable = { @@ -450,7 +450,7 @@ let myIterable = { yield 2; yield 3; } -} +}; [...myIterable] // [1, 2, 3] // 或者采用下面的简洁写法 @@ -469,13 +469,13 @@ for (let x of obj) { // "world" ``` -上面代码中,`Symbol.iterator`方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可。 +上面代码中,`Symbol.iterator()`方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可。 ## 遍历器对象的 return(),throw() -遍历器对象除了具有`next`方法,还可以具有`return`方法和`throw`方法。如果你自己写遍历器对象生成函数,那么`next`方法是必须部署的,`return`方法和`throw`方法是否部署是可选的。 +遍历器对象除了具有`next()`方法,还可以具有`return()`方法和`throw()`方法。如果你自己写遍历器对象生成函数,那么`next()`方法是必须部署的,`return()`方法和`throw()`方法是否部署是可选的。 -`return`方法的使用场合是,如果`for...of`循环提前退出(通常是因为出错,或者有`break`语句),就会调用`return`方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署`return`方法。 +`return()`方法的使用场合是,如果`for...of`循环提前退出(通常是因为出错,或者有`break`语句),就会调用`return()`方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署`return()`方法。 ```javascript function readLinesSync(file) { @@ -495,7 +495,7 @@ function readLinesSync(file) { } ``` -上面代码中,函数`readLinesSync`接受一个文件对象作为参数,返回一个遍历器对象,其中除了`next`方法,还部署了`return`方法。下面的两种情况,都会触发执行`return`方法。 +上面代码中,函数`readLinesSync`接受一个文件对象作为参数,返回一个遍历器对象,其中除了`next()`方法,还部署了`return()`方法。下面的两种情况,都会触发执行`return()`方法。 ```javascript // 情况一 @@ -511,11 +511,11 @@ for (let line of readLinesSync(fileName)) { } ``` -上面代码中,情况一输出文件的第一行以后,就会执行`return`方法,关闭这个文件;情况二会在执行`return`方法关闭文件之后,再抛出错误。 +上面代码中,情况一输出文件的第一行以后,就会执行`return()`方法,关闭这个文件;情况二会在执行`return()`方法关闭文件之后,再抛出错误。 -注意,`return`方法必须返回一个对象,这是 Generator 规格决定的。 +注意,`return()`方法必须返回一个对象,这是 Generator 语法决定的。 -`throw`方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。请参阅《Generator 函数》一章。 +`throw()`方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。请参阅《Generator 函数》一章。 ## for...of 循环 From 10defdbcacf524ec938969ad341670391d677a9d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 15 Sep 2020 20:23:25 +0800 Subject: [PATCH 104/280] docs(async): fix typo --- docs/async.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/async.md b/docs/async.md index 1bb0dd7e2..bdfd6e41f 100644 --- a/docs/async.md +++ b/docs/async.md @@ -184,8 +184,8 @@ async function f() { } f().then( - v => console.log(‘resolve’,v), - e => console.log('reject',e) + v => console.log('resolve', v), + e => console.log('reject', e) ) //reject Error: 出错了 ``` From 9f1f925fc1fd48a85b8bc5c9211229fbfe6a209d Mon Sep 17 00:00:00 2001 From: chua Date: Tue, 15 Sep 2020 21:02:41 +0800 Subject: [PATCH 105/280] Update proxy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加handler.construct中对于this的描述 --- docs/proxy.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/proxy.md b/docs/proxy.md index 3f5b84102..5e76b9e2f 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -643,6 +643,18 @@ new p() // 报错 // Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1') ``` +`construct()`方法中的`this`指向的是handler +``` +var handler = { + construct: function(target, args) { + console.log(this === handler); + return new target(...args); + } +} +var p = new Proxy(function() {}, handler); +new p() // true +``` + ### deleteProperty() `deleteProperty`方法用于拦截`delete`操作,如果这个方法抛出错误或者返回`false`,当前属性就无法被`delete`命令删除。 From 48e318b12be1531b53de3306b5a669f0101cc3af Mon Sep 17 00:00:00 2001 From: chua Date: Tue, 15 Sep 2020 21:11:55 +0800 Subject: [PATCH 106/280] Update proxy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this问题中添加对handler.construct钩子函数中this的描述 --- docs/proxy.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/proxy.md b/docs/proxy.md index 5e76b9e2f..3b36fe51b 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -1053,6 +1053,8 @@ proxy.foo // TypeError: Revoked ## this 问题 +正常情况下,Proxy代理的钩子函数中的`this`指向的是Proxy代理实例(construct钩子函数除外,该钩子函数中`this`指向的是handler) + 虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的`this`关键字会指向 Proxy 代理。 ```javascript From 746a9188b61dbdcb8c36286b1db800276b83f137 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 16 Sep 2020 09:13:54 +0800 Subject: [PATCH 107/280] docs(proxy): edit construct() --- docs/proxy.md | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index 3b36fe51b..e5b1823cb 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -517,11 +517,11 @@ Reflect.apply(proxy, null, [9, 10]) // 38 ### has() -`has`方法用来拦截`HasProperty`操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是`in`运算符。 +`has()`方法用来拦截`HasProperty`操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是`in`运算符。 -`has`方法可以接受两个参数,分别是目标对象、需查询的属性名。 +`has()`方法可以接受两个参数,分别是目标对象、需查询的属性名。 -下面的例子使用`has`方法隐藏某些属性,不被`in`运算符发现。 +下面的例子使用`has()`方法隐藏某些属性,不被`in`运算符发现。 ```javascript var handler = { @@ -537,9 +537,9 @@ var proxy = new Proxy(target, handler); '_prop' in proxy // false ``` -上面代码中,如果原对象的属性名的第一个字符是下划线,`proxy.has`就会返回`false`,从而不会被`in`运算符发现。 +上面代码中,如果原对象的属性名的第一个字符是下划线,`proxy.has()`就会返回`false`,从而不会被`in`运算符发现。 -如果原对象不可配置或者禁止扩展,这时`has`拦截会报错。 +如果原对象不可配置或者禁止扩展,这时`has()`拦截会报错。 ```javascript var obj = { a: 10 }; @@ -554,11 +554,11 @@ var p = new Proxy(obj, { 'a' in p // TypeError is thrown ``` -上面代码中,`obj`对象禁止扩展,结果使用`has`拦截就会报错。也就是说,如果某个属性不可配置(或者目标对象不可扩展),则`has`方法就不得“隐藏”(即返回`false`)目标对象的该属性。 +上面代码中,`obj`对象禁止扩展,结果使用`has`拦截就会报错。也就是说,如果某个属性不可配置(或者目标对象不可扩展),则`has()`方法就不得“隐藏”(即返回`false`)目标对象的该属性。 -值得注意的是,`has`方法拦截的是`HasProperty`操作,而不是`HasOwnProperty`操作,即`has`方法不判断一个属性是对象自身的属性,还是继承的属性。 +值得注意的是,`has()`方法拦截的是`HasProperty`操作,而不是`HasOwnProperty`操作,即`has()`方法不判断一个属性是对象自身的属性,还是继承的属性。 -另外,虽然`for...in`循环也用到了`in`运算符,但是`has`拦截对`for...in`循环不生效。 +另外,虽然`for...in`循环也用到了`in`运算符,但是`has()`拦截对`for...in`循环不生效。 ```javascript let stu1 = {name: '张三', score: 59}; @@ -597,14 +597,14 @@ for (let b in oproxy2) { // 99 ``` -上面代码中,`has`拦截只对`in`运算符生效,对`for...in`循环不生效,导致不符合要求的属性没有被`for...in`循环所排除。 +上面代码中,`has()`拦截只对`in`运算符生效,对`for...in`循环不生效,导致不符合要求的属性没有被`for...in`循环所排除。 ### construct() `construct()`方法用于拦截`new`命令,下面是拦截对象的写法。 ```javascript -var handler = { +const handler = { construct (target, args, newTarget) { return new target(...args); } @@ -618,7 +618,7 @@ var handler = { - `newTarget`:创造实例对象时,`new`命令作用的构造函数(下面例子的`p`)。 ```javascript -var p = new Proxy(function () {}, { +const p = new Proxy(function () {}, { construct: function(target, args) { console.log('called: ' + args.join(', ')); return { value: args[0] * 10 }; @@ -633,7 +633,7 @@ var p = new Proxy(function () {}, { `construct()`方法返回的必须是一个对象,否则会报错。 ```javascript -var p = new Proxy(function() {}, { +const p = new Proxy(function() {}, { construct: function(target, argumentsList) { return 1; } @@ -643,15 +643,32 @@ new p() // 报错 // Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1') ``` -`construct()`方法中的`this`指向的是handler +另外,由于`construct()`拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错。 + +```javascript +const p = new Proxy({}, { + construct: function(target, argumentsList) { + return {}; + } +}); + +new p() // 报错 +// Uncaught TypeError: p is not a constructor ``` -var handler = { + +上面例子中,拦截的目标对象不是一个函数,而是一个对象(`new Proxy()`的第一个参数),导致报错。 + +注意,`construct()`方法中的`this`指向的是`handler`,而不是实例对象。 + +```javascript +const handler = { construct: function(target, args) { console.log(this === handler); return new target(...args); } } -var p = new Proxy(function() {}, handler); + +let p = new Proxy(function () {}, handler); new p() // true ``` From 6ff7cb8a2f61d31d86159e2d87256fab33ea59af Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 17 Sep 2020 14:18:50 +0800 Subject: [PATCH 108/280] docs(class): fix private property --- docs/class.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/class.md b/docs/class.md index 5d8b2e9e7..70917bcdd 100644 --- a/docs/class.md +++ b/docs/class.md @@ -850,7 +850,7 @@ class Foo { this.#b = b; } #sum() { - return #a + #b; + return this.#a + this.#b; } printSum() { console.log(this.#sum()); From c2d1792c62ff7e805a9705d341629511338fa24b Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 23 Sep 2020 01:15:20 +0800 Subject: [PATCH 109/280] docs(set-map): edit WeakMap --- docs/set-map.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/set-map.md b/docs/set-map.md index a934cc3a4..b2e14d13e 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -1087,6 +1087,8 @@ undefined 上面代码中,只要外部的引用消失,WeakMap 内部的引用,就会自动被垃圾回收清除。由此可见,有了 WeakMap 的帮助,解决内存泄漏就会简单很多。 +Chrome 浏览器的 Dev Tools 的 Memory 面板,有一个垃圾桶的按钮,可以强制垃圾回收(garbage collect)。这个按钮也能用来观察 WeakMap 里面的引用是否消失。 + ### WeakMap 的用途 前文说过,WeakMap 应用的典型场合就是 DOM 节点作为键名。下面是一个例子。 @@ -1137,3 +1139,4 @@ c.dec() ``` 上面代码中,`Countdown`类的两个内部属性`_counter`和`_action`,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。 + From 4bd4d771518c81b839f4a666514d99cff23ff4b7 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 2 Oct 2020 18:38:14 +0800 Subject: [PATCH 110/280] docs(function/arrow fucntion): edit #150 --- docs/function.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/function.md b/docs/function.md index 7dbf10798..fe6b10750 100644 --- a/docs/function.md +++ b/docs/function.md @@ -731,7 +731,7 @@ foo.call({ id: 42 }); // id: 42 ``` -上面代码中,`setTimeout`的参数是一个箭头函数,这个箭头函数的定义生效是在`foo`函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时`this`应该指向全局对象`window`,这时应该输出`21`。但是,箭头函数导致`this`总是指向函数定义生效时所在的对象(本例是`{id: 42}`),所以输出的是`42`。 +上面代码中,`setTimeout()`的参数是一个箭头函数,这个箭头函数的定义生效是在`foo`函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时`this`应该指向全局对象`window`,这时应该输出`21`。但是,箭头函数导致`this`总是指向函数定义生效时所在的对象(本例是`{id: 42}`),所以打印出来的是`42`。 箭头函数可以让`setTimeout`里面的`this`,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子。 From 2e082a334320716c81cb9e24cf4cd634e133d088 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 23 Oct 2020 22:20:10 +0800 Subject: [PATCH 111/280] docs(module): fix module suffix --- docs/module.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/module.md b/docs/module.md index dac58ff69..040cbb2f5 100644 --- a/docs/module.md +++ b/docs/module.md @@ -223,10 +223,10 @@ a.foo = 'hello'; // 合法操作 上面代码中,`a`的属性可以成功改写,并且其他模块也可以读到改写后的值。不过,这种写法很难查错,建议凡是输入的变量,都当作完全只读,不要轻易改变它的属性。 -`import`后面的`from`指定模块文件的位置,可以是相对路径,也可以是绝对路径,`.js`后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。 +`import`后面的`from`指定模块文件的位置,可以是相对路径,也可以是绝对路径。如果不带有路径,只是一个模块名,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。 ```javascript -import {myMethod} from 'util'; +import { myMethod } from 'util'; ``` 上面代码中,`util`是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块。 From 8f852c0425814d80d6a6108632ef3ce79cda1b36 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 24 Oct 2020 13:40:48 +0800 Subject: [PATCH 112/280] docs(Atomics): Atomics.wake() rename to Atomics.notify() --- docs/arraybuffer.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/arraybuffer.md b/docs/arraybuffer.md index a2e455104..449476596 100644 --- a/docs/arraybuffer.md +++ b/docs/arraybuffer.md @@ -1215,9 +1215,11 @@ self.addEventListener('message', (event) => { 上面代码将共享内存的偶数位置的值改成`1`,奇数位置的值改成`2`。 -**(3)Atomics.wait(),Atomics.wake()** +**(3)Atomics.wait(),Atomics.notify()** -使用`while`循环等待主线程的通知,不是很高效,如果用在主线程,就会造成卡顿,`Atomics`对象提供了`wait()`和`wake()`两个方法用于等待通知。这两个方法相当于锁内存,即在一个线程进行操作时,让其他线程休眠(建立锁),等到操作结束,再唤醒那些休眠的线程(解除锁)。 +使用`while`循环等待主线程的通知,不是很高效,如果用在主线程,就会造成卡顿,`Atomics`对象提供了`wait()`和`notify()`两个方法用于等待通知。这两个方法相当于锁内存,即在一个线程进行操作时,让其他线程休眠(建立锁),等到操作结束,再唤醒那些休眠的线程(解除锁)。 + +`Atomics.notify()`方法以前叫做`Atomics.wake()`,后来进行了改名。 ```javascript // Worker 线程 @@ -1240,10 +1242,10 @@ const newArrayValue = 100; Atomics.store(sharedArray, 0, newArrayValue); const arrayIndex = 0; const queuePos = 1; -Atomics.wake(sharedArray, arrayIndex, queuePos); +Atomics.notify(sharedArray, arrayIndex, queuePos); ``` -上面代码中,`sharedArray`的`0`号位置改为`100`,然后就执行`Atomics.wake()`方法,唤醒在`sharedArray`的`0`号位置休眠队列里的一个线程。 +上面代码中,`sharedArray`的`0`号位置改为`100`,然后就执行`Atomics.notify()`方法,唤醒在`sharedArray`的`0`号位置休眠队列里的一个线程。 `Atomics.wait()`方法的使用格式如下。 @@ -1256,14 +1258,14 @@ Atomics.wait(sharedArray, index, value, timeout) - sharedArray:共享内存的视图数组。 - index:视图数据的位置(从0开始)。 - value:该位置的预期值。一旦实际值等于预期值,就进入休眠。 -- timeout:整数,表示过了这个时间以后,就自动唤醒,单位毫秒。该参数可选,默认值是`Infinity`,即无限期的休眠,只有通过`Atomics.wake()`方法才能唤醒。 +- timeout:整数,表示过了这个时间以后,就自动唤醒,单位毫秒。该参数可选,默认值是`Infinity`,即无限期的休眠,只有通过`Atomics.notify()`方法才能唤醒。 -`Atomics.wait()`的返回值是一个字符串,共有三种可能的值。如果`sharedArray[index]`不等于`value`,就返回字符串`not-equal`,否则就进入休眠。如果`Atomics.wake()`方法唤醒,就返回字符串`ok`;如果因为超时唤醒,就返回字符串`timed-out`。 +`Atomics.wait()`的返回值是一个字符串,共有三种可能的值。如果`sharedArray[index]`不等于`value`,就返回字符串`not-equal`,否则就进入休眠。如果`Atomics.notify()`方法唤醒,就返回字符串`ok`;如果因为超时唤醒,就返回字符串`timed-out`。 -`Atomics.wake()`方法的使用格式如下。 +`Atomics.notify()`方法的使用格式如下。 ```javascript -Atomics.wake(sharedArray, index, count) +Atomics.notify(sharedArray, index, count) ``` 它的三个参数含义如下。 @@ -1272,7 +1274,7 @@ Atomics.wake(sharedArray, index, count) - index:视图数据的位置(从0开始)。 - count:需要唤醒的 Worker 线程的数量,默认为`Infinity`。 -`Atomics.wake()`方法一旦唤醒休眠的 Worker 线程,就会让它继续往下运行。 +`Atomics.notify()`方法一旦唤醒休眠的 Worker 线程,就会让它继续往下运行。 请看一个例子。 @@ -1280,16 +1282,16 @@ Atomics.wake(sharedArray, index, count) // 主线程 console.log(ia[37]); // 163 Atomics.store(ia, 37, 123456); -Atomics.wake(ia, 37, 1); +Atomics.notify(ia, 37, 1); // Worker 线程 Atomics.wait(ia, 37, 163); console.log(ia[37]); // 123456 ``` -上面代码中,视图数组`ia`的第 37 号位置,原来的值是`163`。Worker 线程使用`Atomics.wait()`方法,指定只要`ia[37]`等于`163`,就进入休眠状态。主线程使用`Atomics.store()`方法,将`123456`写入`ia[37]`,然后使用`Atomics.wake()`方法唤醒 Worker 线程。 +上面代码中,视图数组`ia`的第 37 号位置,原来的值是`163`。Worker 线程使用`Atomics.wait()`方法,指定只要`ia[37]`等于`163`,就进入休眠状态。主线程使用`Atomics.store()`方法,将`123456`写入`ia[37]`,然后使用`Atomics.notify()`方法唤醒 Worker 线程。 -另外,基于`wait`和`wake`这两个方法的锁内存实现,可以看 Lars T Hansen 的 [js-lock-and-condition](https://github.com/lars-t-hansen/js-lock-and-condition) 这个库。 +另外,基于`wait`和`notify`这两个方法的锁内存实现,可以看 Lars T Hansen 的 [js-lock-and-condition](https://github.com/lars-t-hansen/js-lock-and-condition) 这个库。 注意,浏览器的主线程不宜设置休眠,这会导致用户失去响应。而且,主线程实际上会拒绝进入休眠。 @@ -1335,3 +1337,4 @@ Atomics.xor(sharedArray, index, value) - `Atomics.isLockFree(size)`:返回一个布尔值,表示`Atomics`对象是否可以处理某个`size`的内存锁定。如果返回`false`,应用程序就需要自己来实现锁定。 `Atomics.compareExchange`的一个用途是,从 SharedArrayBuffer 读取一个值,然后对该值进行某个操作,操作结束以后,检查一下 SharedArrayBuffer 里面原来那个值是否发生变化(即被其他线程改写过)。如果没有改写过,就将它写回原来的位置,否则读取新的值,再重头进行一次操作。 + From d1f96feee0279ee4c1fe83a62bd6c17b3175fed0 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 24 Oct 2020 19:30:43 +0800 Subject: [PATCH 113/280] docs(proxy): fix this #1014 --- docs/proxy.md | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index e5b1823cb..e705fba45 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -663,7 +663,7 @@ new p() // 报错 ```javascript const handler = { construct: function(target, args) { - console.log(this === handler); + console.log(this === handler); return new target(...args); } } @@ -1070,8 +1070,6 @@ proxy.foo // TypeError: Revoked ## this 问题 -正常情况下,Proxy代理的钩子函数中的`this`指向的是Proxy代理实例(construct钩子函数除外,该钩子函数中`this`指向的是handler) - 虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的`this`关键字会指向 Proxy 代理。 ```javascript @@ -1088,7 +1086,7 @@ target.m() // false proxy.m() // true ``` -上面代码中,一旦`proxy`代理`target.m`,后者内部的`this`就是指向`proxy`,而不是`target`。 +上面代码中,一旦`proxy`代理`target`,`target.m()`内部的`this`就是指向`proxy`,而不是`target`。 下面是一个例子,由于`this`指向的变化,导致 Proxy 无法代理目标对象。 @@ -1141,6 +1139,33 @@ const proxy = new Proxy(target, handler); proxy.getDate() // 1 ``` +另外,Proxy 拦截函数内部的`this`,指向的是`handler`对象。 + +```javascript +const handler = { + get: function (target, key, receiver) { + console.log(this === handler); + return 'Hello, ' + key; + }, + set: function (target, key, value) { + console.log(this === handler); + target[key] = value; + return true; + } +}; + +const proxy = new Proxy({}, handler); + +proxy.foo +// true +// Hello, foo + +proxy.foo = 1 +// true +``` + +上面例子中,`get()`和`set()`拦截函数内部的`this`,指向的都是`handler`对象。 + ## 实例:Web 服务的客户端 Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。 From 356b0db1b0095a4e80b516a05cdf69f8bbdc0cad Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 25 Oct 2020 12:33:16 +0800 Subject: [PATCH 114/280] docs(string): add replaceAll() --- docs/string-methods.md | 85 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/docs/string-methods.md b/docs/string-methods.md index 99233f310..8b1a5d508 100644 --- a/docs/string-methods.md +++ b/docs/string-methods.md @@ -329,3 +329,88 @@ s.trimEnd() // " abc" `matchAll()`方法返回一个正则表达式在当前字符串的所有匹配,详见《正则的扩展》的一章。 +## 实例方法:replaceAll() + +历史上,字符串的实例方法`replace()`只能替换第一个匹配。 + +```javascript +'aabbcc'.replace('b', '_') +// 'aa_bcc' +``` + +上面例子中,`replace()`只将第一个`b`替换成了下划线。 + +如果要替换所有的匹配,不得不使用正则表达式的`g`修饰符。 + +```javascript +'aabbcc'.replace(/b/g, '_') +// 'aa__cc' +``` + +正则表达式毕竟不是那么方便和直观,[ES2021](https://github.com/tc39/proposal-string-replaceall) 引入了`replaceAll()`方法,可以一次性替换所有匹配。 + +```javascript +'aabbcc'.replaceAll('b', '_') +// 'aa__cc' +``` + +它的用法与`replace()`相同,返回一个新字符串,不会改变原字符串。 + +```javascript +String.prototype.replaceAll(searchValue, replacement) +``` + +上面代码中,`searchValue`是搜索模式,可以是一个字符串,也可以是一个全局的正则表达式(带有`g`修饰符)。 + +如果`searchValue`是一个不带有`g`修饰符的正则表达式,`replaceAll()`会报错。这一点跟`replace()`不同。 + +```javascript +// 不报错 +'aabbcc'.replace(/b/, '_') + +// 报错 +'aabbcc'.replaceAll(/b/, '_') +``` + +上面例子中,`/b/`不带有`g`修饰符,会导致`replaceAll()`报错。 + +`replaceAll()`的第二个参数`replacement`是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。 + +- `$&`:匹配的子字符串。 +- `` `$` ``:匹配结果前面的文本。 +- `$'`:匹配结果后面的文本。 +- `$n`:匹配成功的第`n`组内容,`n`是从1开始的自然数。 +- `$$`:指代美元符号`$`。 + +```javascript +'abc'.replaceAll('b', '$$') +// 'a$c' +``` + +上面例子中,`$$`表示替换的文本是单个美元符号`$`。 + +`replaceAll()`的第二个参数`replacement`除了为字符串,也可以是一个函数,该函数的返回值将替换掉第一个参数`searchValue`匹配的文本。 + +```javascript +'aabbcc'.replaceAll('b', () => '_') +// 'aa__cc' +``` + +上面例子中,`replaceAll()`的第二个参数是一个函数,该函数的返回值会替换掉所有`b`的匹配。 + +这个替换函数可以接受多个参数。第一个参数是捕捉到的匹配内容,第二个参数捕捉到是组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置,最后一个参数是原字符串。 + +```javascript +const str = '123abc456'; +const regex = /(\d+)([a-z]+)(\d+)/g; + +function replacer(match, p1, p2, p3, offset, string) { + return [p1, p2, p3].join(' - '); +} + +str.replaceAll(regex, replacer) +// 123 - abc - 456 +``` + +上面例子中,正则表达式有三个组匹配,所以`replacer()`函数的第一个参数`match`是捕捉到的匹配内容(即字符串`123abc456`),后面三个参数`p1`、`p2`、`p3`则依次为三个组匹配。 + From 492fb25238677ab3e69e376f70dbe3604814fb35 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 27 Oct 2020 22:56:35 +0800 Subject: [PATCH 115/280] docs(string-methods): add replaceAll() --- js/ditto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 3f8e1219e..e3e69c923 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -243,8 +243,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2020年12月1日 - var deadline = new Date(2020, 11, 1); + // 2020年11月1日 + var deadline = new Date(2020, 10, 1); if (deadline - (new Date()) < 0) return; var styleStr = [ From 2ed3a6ba3b455045ff84d350f8a34e9790658a7d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 1 Nov 2020 00:03:02 +0800 Subject: [PATCH 116/280] docs(promise): edit Promise.any() --- docs/promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/promise.md b/docs/promise.md index baa4287fa..f6717616b 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -758,7 +758,7 @@ try { ## Promise.any() -`Promise.any()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。该方法目前是一个第三阶段的[提案](https://github.com/tc39/proposal-promise-any) 。 +ES2021 引入了[`Promise.any()`方法](https://github.com/tc39/proposal-promise-any)。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。 `Promise.any()`跟`Promise.race()`方法很像,只有一点不同,就是不会因为某个 Promise 变成`rejected`状态而结束。 From 04dddb2d7c34cbcd90a0bd1f027835bd744bf827 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 1 Nov 2020 01:33:11 +0800 Subject: [PATCH 117/280] docs: fix wangdoc-es6 #4 --- docs/arraybuffer.md | 8 ++++---- docs/async-iterator.md | 2 +- docs/async.md | 2 +- docs/class.md | 6 +++--- docs/module.md | 2 +- docs/style.md | 5 +++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/arraybuffer.md b/docs/arraybuffer.md index 449476596..4f25ff179 100644 --- a/docs/arraybuffer.md +++ b/docs/arraybuffer.md @@ -1141,14 +1141,14 @@ Atomics.add(ia, 112, 1); // 正确 `store()`方法用来向共享内存写入数据,`load()`方法用来从共享内存读出数据。比起直接的读写操作,它们的好处是保证了读写操作的原子性。 -此外,它们还用来解决一个问题:多个线程使用共享内存的某个位置作为开关(flag),一旦该位置的值变了,就执行特定操作。这时,必须保证该位置的赋值操作,一定是在它前面的所有可能会改写内存的操作结束后执行;而该位置的取值操作,一定是在它后面所有可能会读取该位置的操作开始之前执行。`store`方法和`load`方法就能做到这一点,编译器不会为了优化,而打乱机器指令的执行顺序。 +此外,它们还用来解决一个问题:多个线程使用共享内存的某个位置作为开关(flag),一旦该位置的值变了,就执行特定操作。这时,必须保证该位置的赋值操作,一定是在它前面的所有可能会改写内存的操作结束后执行;而该位置的取值操作,一定是在它后面所有可能会读取该位置的操作开始之前执行。`store()`方法和`load()`方法就能做到这一点,编译器不会为了优化,而打乱机器指令的执行顺序。 ```javascript -Atomics.load(array, index) -Atomics.store(array, index, value) +Atomics.load(typedArray, index) +Atomics.store(typedArray, index, value) ``` -`store`方法接受三个参数:SharedBuffer 的视图、位置索引和值,返回`sharedArray[index]`的值。`load`方法只接受两个参数:SharedBuffer 的视图和位置索引,也是返回`sharedArray[index]`的值。 +`store`方法接受三个参数:SharedArrayBuffer 的视图、位置索引和值,返回`sharedArrayBuffer[index]`的值。`load`方法只接受两个参数:SharedArrayBuffer 的视图和位置索引,也是返回`sharedArrayBuffer[index]`的值。 ```javascript // 主线程 main.js diff --git a/docs/async-iterator.md b/docs/async-iterator.md index 6e5d70a6b..e229c1b8d 100644 --- a/docs/async-iterator.md +++ b/docs/async-iterator.md @@ -63,9 +63,9 @@ function idMaker() { const it = idMaker(); +it.next().value.then(o => console.log(o)) // 0 it.next().value.then(o => console.log(o)) // 1 it.next().value.then(o => console.log(o)) // 2 -it.next().value.then(o => console.log(o)) // 3 // ... ``` diff --git a/docs/async.md b/docs/async.md index bdfd6e41f..b305d3735 100644 --- a/docs/async.md +++ b/docs/async.md @@ -752,7 +752,7 @@ export { output }; ```javascript // awaiting.js let output; -(async function1 main() { +(async function main() { const dynamic = await import(someMission); const data = await fetch(url); output = someProcess(dynamic.default, data); diff --git a/docs/class.md b/docs/class.md index 70917bcdd..994ba1ae3 100644 --- a/docs/class.md +++ b/docs/class.md @@ -735,9 +735,9 @@ class Widget { } ``` -上面代码中,`_bar`方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。 +上面代码中,`_bar()`方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。 -另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。 +另一种方法就是索性将私有方法移出类,因为类内部的所有方法都是对外可见的。 ```javascript class Widget { @@ -753,7 +753,7 @@ function bar(baz) { } ``` -上面代码中,`foo`是公开方法,内部调用了`bar.call(this, baz)`。这使得`bar`实际上成为了当前模块的私有方法。 +上面代码中,`foo`是公开方法,内部调用了`bar.call(this, baz)`。这使得`bar()`实际上成为了当前类的私有方法。 还有一种方法是利用`Symbol`值的唯一性,将私有方法的名字命名为一个`Symbol`值。 diff --git a/docs/module.md b/docs/module.md index 040cbb2f5..6c773c4cc 100644 --- a/docs/module.md +++ b/docs/module.md @@ -286,7 +286,7 @@ import { bar } from 'my_module'; import { foo, bar } from 'my_module'; ``` -上面代码中,虽然`foo`和`bar`在两个语句中加载,但是它们对应的是同一个`my_module`实例。也就是说,`import`语句是 Singleton 模式。 +上面代码中,虽然`foo`和`bar`在两个语句中加载,但是它们对应的是同一个`my_module`模块。也就是说,`import`语句是 Singleton 模式。 目前阶段,通过 Babel 转码,CommonJS 模块的`require`命令和 ES6 模块的`import`命令,可以写在同一个模块里面,但是最好不要这样做。因为`import`在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。 diff --git a/docs/style.md b/docs/style.md index 0a18f9d30..db0506740 100644 --- a/docs/style.md +++ b/docs/style.md @@ -497,7 +497,7 @@ $ npm i -g eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react `index.js`文件的代码如下。 ```javascript -var unusued = 'I have no purpose!'; +var unused = 'I have no purpose!'; function greet() { var message = 'Hello, World!'; @@ -513,7 +513,7 @@ greet(); $ eslint index.js index.js 1:1 error Unexpected var, use let or const instead no-var - 1:5 error unusued is defined but never used no-unused-vars + 1:5 error unused is defined but never used no-unused-vars 4:5 error Expected indentation of 2 characters but found 4 indent 4:5 error Unexpected var, use let or const instead no-var 5:5 error Expected indentation of 2 characters but found 4 indent @@ -522,3 +522,4 @@ index.js ``` 上面代码说明,原文件有五个错误,其中两个是不应该使用`var`命令,而要使用`let`或`const`;一个是定义了变量,却没有使用;另外两个是行首缩进为 4 个空格,而不是规定的 2 个空格。 + From 861dd41fc542c11b991c1b472cc82a454f4abbaf Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 6 Nov 2020 22:36:42 +0800 Subject: [PATCH 118/280] docs(arraybuffer): edit Atomics.load() --- docs/arraybuffer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/arraybuffer.md b/docs/arraybuffer.md index 4f25ff179..61325fd7d 100644 --- a/docs/arraybuffer.md +++ b/docs/arraybuffer.md @@ -1148,7 +1148,7 @@ Atomics.load(typedArray, index) Atomics.store(typedArray, index, value) ``` -`store`方法接受三个参数:SharedArrayBuffer 的视图、位置索引和值,返回`sharedArrayBuffer[index]`的值。`load`方法只接受两个参数:SharedArrayBuffer 的视图和位置索引,也是返回`sharedArrayBuffer[index]`的值。 +`store()`方法接受三个参数:`typedArray`对象(SharedArrayBuffer 的视图)、位置索引和值,返回`typedArray[index]`的值。`load()`方法只接受两个参数:`typedArray`对象(SharedArrayBuffer 的视图)和位置索引,也是返回`typedArray[index]`的值。 ```javascript // 主线程 main.js @@ -1161,7 +1161,7 @@ console.log(ia[37]); // 123456 console.log(ia[42]); // 314159 ``` -上面代码中,主线程的`Atomics.store`向 42 号位置的赋值,一定是早于 37 位置的赋值。只要 37 号位置等于 163,Worker 线程就不会终止循环,而对 37 号位置和 42 号位置的取值,一定是在`Atomics.load`操作之后。 +上面代码中,主线程的`Atomics.store()`向 42 号位置的赋值,一定是早于 37 位置的赋值。只要 37 号位置等于 163,Worker 线程就不会终止循环,而对 37 号位置和 42 号位置的取值,一定是在`Atomics.load()`操作之后。 下面是另一个例子。 From f8171643cdfaa826fe1b21e589757a4e953a30b8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 17 Nov 2020 22:32:59 +0800 Subject: [PATCH 119/280] docs(string-methods): edit replaceAll(), fixed #1018 --- docs/string-methods.md | 31 +++++++++++++++++++++++++++---- docs/string.md | 3 ++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/docs/string-methods.md b/docs/string-methods.md index 8b1a5d508..2fbb76174 100644 --- a/docs/string-methods.md +++ b/docs/string-methods.md @@ -377,18 +377,41 @@ String.prototype.replaceAll(searchValue, replacement) `replaceAll()`的第二个参数`replacement`是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。 - `$&`:匹配的子字符串。 -- `` `$` ``:匹配结果前面的文本。 +- `` $` ``:匹配结果前面的文本。 - `$'`:匹配结果后面的文本。 -- `$n`:匹配成功的第`n`组内容,`n`是从1开始的自然数。 +- `$n`:匹配成功的第`n`组内容,`n`是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。 - `$$`:指代美元符号`$`。 +下面是一些例子。 + ```javascript +// $& 表示匹配的字符串,即`b`本身 +// 所以返回结果与原字符串一致 +'abbc'.replaceAll('b', '$&') +// 'abbc' + +// $` 表示匹配结果之前的字符串 +// 对于第一个`b`,$` 指代`a` +// 对于第二个`b`,$` 指代`ab` +'abbc'.replaceAll('b', '$`') +// 'aaabc' + +// $' 表示匹配结果之后的字符串 +// 对于第一个`b`,$' 指代`bc` +// 对于第二个`b`,$' 指代`c` +'abbc'.replaceAll('b', `$'`) +// 'abccc' + +// $1 表示正则表达式的第一个组匹配,指代`ab` +// $2 表示正则表达式的第二个组匹配,指代`bc` +'abbc'.replaceAll(/(ab)(bc)/g, '$2$1') +// 'bcab' + +// $$ 指代 $ 'abc'.replaceAll('b', '$$') // 'a$c' ``` -上面例子中,`$$`表示替换的文本是单个美元符号`$`。 - `replaceAll()`的第二个参数`replacement`除了为字符串,也可以是一个函数,该函数的返回值将替换掉第一个参数`searchValue`匹配的文本。 ```javascript diff --git a/docs/string.md b/docs/string.md index 3a3d78560..97b3ddc22 100644 --- a/docs/string.md +++ b/docs/string.md @@ -505,10 +505,11 @@ function passthru(literals) { let i = 0; while (i < literals.length) { - result += literals[i++]; + result += literals[i]; if (i < arguments.length) { result += arguments[i]; } + i++; } return result; From a326217687f9cc8335d3cf9ba9147bb9a7e335a8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 17 Nov 2020 22:39:58 +0800 Subject: [PATCH 120/280] docs(string): fix typo --- docs/string.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/string.md b/docs/string.md index 97b3ddc22..3a3d78560 100644 --- a/docs/string.md +++ b/docs/string.md @@ -505,11 +505,10 @@ function passthru(literals) { let i = 0; while (i < literals.length) { - result += literals[i]; + result += literals[i++]; if (i < arguments.length) { result += arguments[i]; } - i++; } return result; From ff4778487cf48986cb43df000c7e568f83abf27d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 25 Nov 2020 06:57:53 +0800 Subject: [PATCH 121/280] docs(style): fixed #1020 --- docs/style.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/style.md b/docs/style.md index db0506740..42fd61da6 100644 --- a/docs/style.md +++ b/docs/style.md @@ -471,17 +471,17 @@ export default StyleGuide; ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。 -首先,安装 ESLint。 +首先,在项目的根目录安装 ESLint。 ```bash -$ npm i -g eslint +$ npm install --save-dev eslint ``` 然后,安装 Airbnb 语法规则,以及 import、a11y、react 插件。 ```bash -$ npm i -g eslint-config-airbnb -$ npm i -g eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react +$ npm install --save-dev eslint-config-airbnb +$ npm install --save-dev eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react ``` 最后,在项目的根目录下新建一个`.eslintrc`文件,配置 ESLint。 @@ -501,7 +501,7 @@ var unused = 'I have no purpose!'; function greet() { var message = 'Hello, World!'; - alert(message); + console.log(message); } greet(); @@ -510,7 +510,7 @@ greet(); 使用 ESLint 检查这个文件,就会报出错误。 ```bash -$ eslint index.js +$ npx eslint index.js index.js 1:1 error Unexpected var, use let or const instead no-var 1:5 error unused is defined but never used no-unused-vars From 584d5d93321cf780a9d51e2e0f787052f2e3a2fa Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 25 Nov 2020 07:24:09 +0800 Subject: [PATCH 122/280] docs(generator): edit style --- docs/generator.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/generator.md b/docs/generator.md index c0683c6b4..e2f7d18ee 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -667,7 +667,7 @@ log(g()); ## Generator.prototype.return() -Generator 函数返回的遍历器对象,还有一个`return`方法,可以返回给定的值,并且终结遍历 Generator 函数。 +Generator 函数返回的遍历器对象,还有一个`return()`方法,可以返回给定的值,并且终结遍历 Generator 函数。 ```javascript function* gen() { @@ -683,9 +683,9 @@ g.return('foo') // { value: "foo", done: true } g.next() // { value: undefined, done: true } ``` -上面代码中,遍历器对象`g`调用`return`方法后,返回值的`value`属性就是`return`方法的参数`foo`。并且,Generator 函数的遍历就终止了,返回值的`done`属性为`true`,以后再调用`next`方法,`done`属性总是返回`true`。 +上面代码中,遍历器对象`g`调用`return()`方法后,返回值的`value`属性就是`return()`方法的参数`foo`。并且,Generator 函数的遍历就终止了,返回值的`done`属性为`true`,以后再调用`next()`方法,`done`属性总是返回`true`。 -如果`return`方法调用时,不提供参数,则返回值的`value`属性为`undefined`。 +如果`return()`方法调用时,不提供参数,则返回值的`value`属性为`undefined`。 ```javascript function* gen() { @@ -700,7 +700,7 @@ g.next() // { value: 1, done: false } g.return() // { value: undefined, done: true } ``` -如果 Generator 函数内部有`try...finally`代码块,且正在执行`try`代码块,那么`return`方法会导致立刻进入`finally`代码块,执行完以后,整个函数才会结束。 +如果 Generator 函数内部有`try...finally`代码块,且正在执行`try`代码块,那么`return()`方法会导致立刻进入`finally`代码块,执行完以后,整个函数才会结束。 ```javascript function* numbers () { From 201eebe08fa11f992196a9a40a98ba3be2a48d8d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 29 Nov 2020 12:23:59 +0800 Subject: [PATCH 123/280] docs(async): fixed #1023 --- docs/async.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/async.md b/docs/async.md index b305d3735..c28439df8 100644 --- a/docs/async.md +++ b/docs/async.md @@ -470,7 +470,7 @@ function dbFuc(db) { //这里不需要 async } ``` -上面代码可能不会正常工作,原因是这时三个`db.post`操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用`for`循环。 +上面代码可能不会正常工作,原因是这时三个`db.post()`操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用`for`循环。 ```javascript async function dbFuc(db) { @@ -482,7 +482,7 @@ async function dbFuc(db) { } ``` -另一种方法是使用数组的`reduce`方法。 +另一种方法是使用数组的`reduce()`方法。 ```javascript async function dbFuc(db) { @@ -495,7 +495,9 @@ async function dbFuc(db) { } ``` -上面例子中,`reduce`方法的第一个参数是`async`函数,导致该函数的第一个参数是前一步操作返回的 Promise 对象,所以必须使用`await`等待它操作结束。另外,`reduce`方法返回的是`docs`数组最后一个成员的`async`函数的执行结果,也是一个 Promise 对象,导致在它前面也必须加上`await`。 +上面例子中,`reduce()`方法的第一个参数是`async`函数,导致该函数的第一个参数是前一步操作返回的 Promise 对象,所以必须使用`await`等待它操作结束。另外,`reduce()`方法返回的是`docs`数组最后一个成员的`async`函数的执行结果,也是一个 Promise 对象,导致在它前面也必须加上`await`。 + +这个例子的`reduce()`的第二个参数是`undefined`,并且第一个参数函数里面没有`return`语句,原因是这个例子没有用到累积变量,而且每一轮`async`函数不管有没有`return`语句,总是返回一个 Promise 对象。换句话说,这里的`reduce()`方法不是为了求值,而是为了遍历操作。 如果确实希望多个请求并发执行,可以使用`Promise.all`方法。当三个请求都会`resolved`时,下面两种写法效果相同。 From bc0ba763f7c15ec2a505ab864ba3cb0c2a087f00 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 29 Nov 2020 13:04:11 +0800 Subject: [PATCH 124/280] docs(async): edit reduce() --- docs/async.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/async.md b/docs/async.md index c28439df8..532c1a366 100644 --- a/docs/async.md +++ b/docs/async.md @@ -497,7 +497,7 @@ async function dbFuc(db) { 上面例子中,`reduce()`方法的第一个参数是`async`函数,导致该函数的第一个参数是前一步操作返回的 Promise 对象,所以必须使用`await`等待它操作结束。另外,`reduce()`方法返回的是`docs`数组最后一个成员的`async`函数的执行结果,也是一个 Promise 对象,导致在它前面也必须加上`await`。 -这个例子的`reduce()`的第二个参数是`undefined`,并且第一个参数函数里面没有`return`语句,原因是这个例子没有用到累积变量,而且每一轮`async`函数不管有没有`return`语句,总是返回一个 Promise 对象。换句话说,这里的`reduce()`方法不是为了求值,而是为了遍历操作。 +上面的`reduce()`的参数函数里面没有`return`语句,原因是这个函数的主要目的是`db.post()`操作,不是返回值。而且`async`函数不管有没有`return`语句,总是返回一个 Promise 对象,所以这里的`return`是不必要的。 如果确实希望多个请求并发执行,可以使用`Promise.all`方法。当三个请求都会`resolved`时,下面两种写法效果相同。 From 038a080f7b08181d3f2d17d5756f985665225dbf Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 2 Dec 2020 17:39:44 +0800 Subject: [PATCH 125/280] refactor: edit support banner --- js/ditto.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index e3e69c923..de35e8b72 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -243,8 +243,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2020年11月1日 - var deadline = new Date(2020, 10, 1); + // 2021年1月1日 + var deadline = new Date(2021, 0, 1); if (deadline - (new Date()) < 0) return; var styleStr = [ @@ -259,7 +259,7 @@ function create_banner(element) { ].join(';'); var text = '【免费课程】' + - '开始学习《ES6 实战教程》,一线大厂前端必备技能。'; + '《Vue进阶攻略》领取,Vue 3.0 新知识点讲解。'; var banner = $('
    ' + text + '
    ') .insertAfter(element); From 761d1075223cc931a28ff08c5b709969e5ce8606 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 2 Dec 2020 18:18:40 +0800 Subject: [PATCH 126/280] refactor: edit support banner --- js/ditto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ditto.js b/js/ditto.js index de35e8b72..7f2a404dc 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -261,7 +261,7 @@ function create_banner(element) { var text = '【免费课程】' + '《Vue进阶攻略》领取,Vue 3.0 新知识点讲解。'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } From 56d15b327aab1005adc52375a84c2c901761422c Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 3 Dec 2020 23:17:05 +0800 Subject: [PATCH 127/280] refactor: edit support banner --- js/ditto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 7f2a404dc..c5eb80e0e 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -243,8 +243,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2021年1月1日 - var deadline = new Date(2021, 0, 1); + // 2021年2月3日 + var deadline = new Date(2021, 1, 3); if (deadline - (new Date()) < 0) return; var styleStr = [ From d04189323a2d6ed96b261ccf22f08e5f47f00cf8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 9 Dec 2020 13:10:24 +0800 Subject: [PATCH 128/280] docs(class): edit class intro --- docs/class.md | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/class.md b/docs/class.md index 994ba1ae3..528fac0bd 100644 --- a/docs/class.md +++ b/docs/class.md @@ -38,9 +38,9 @@ class Point { } ``` -上面代码定义了一个“类”,可以看到里面有一个`constructor`方法,这就是构造方法,而`this`关键字则代表实例对象。也就是说,ES5 的构造函数`Point`,对应 ES6 的`Point`类的构造方法。 +上面代码定义了一个“类”,可以看到里面有一个`constructor()`方法,这就是构造方法,而`this`关键字则代表实例对象。这种新的 Class 写法,本质上与本章开头的 ES5 的构造函数`Point`是一致的。 -`Point`类除了构造方法,还定义了一个`toString`方法。注意,定义“类”的方法的时候,前面不需要加上`function`这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。 +`Point`类除了构造方法,还定义了一个`toString()`方法。注意,定义`toString()`方法的时候,前面不需要加上`function`这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加了会报错。 ES6 的类,完全可以看作构造函数的另一种写法。 @@ -64,7 +64,7 @@ class Bar { } } -var b = new Bar(); +const b = new Bar(); b.doStuff() // "stuff" ``` @@ -94,18 +94,20 @@ Point.prototype = { }; ``` -在类的实例上面调用方法,其实就是调用原型上的方法。 +上面代码中,`constructor()`、`toString()`、`toValue()`这三个方法,其实都是定义在`Point.prototype`上面。 + +因此,在类的实例上面调用方法,其实就是调用原型上的方法。 ```javascript class B {} -let b = new B(); +const b = new B(); b.constructor === B.prototype.constructor // true ``` -上面代码中,`b`是`B`类的实例,它的`constructor`方法就是`B`类原型的`constructor`方法。 +上面代码中,`b`是`B`类的实例,它的`constructor()`方法就是`B`类原型的`constructor()`方法。 -由于类的方法都定义在`prototype`对象上面,所以类的新方法可以添加在`prototype`对象上面。`Object.assign`方法可以很方便地一次向类添加多个方法。 +由于类的方法都定义在`prototype`对象上面,所以类的新方法可以添加在`prototype`对象上面。`Object.assign()`方法可以很方便地一次向类添加多个方法。 ```javascript class Point { @@ -120,7 +122,7 @@ Object.assign(Point.prototype, { }); ``` -`prototype`对象的`constructor`属性,直接指向“类”的本身,这与 ES5 的行为是一致的。 +`prototype`对象的`constructor()`属性,直接指向“类”的本身,这与 ES5 的行为是一致的。 ```javascript Point.prototype.constructor === Point // true @@ -145,14 +147,14 @@ Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"] ``` -上面代码中,`toString`方法是`Point`类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。 +上面代码中,`toString()`方法是`Point`类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。 ```javascript var Point = function (x, y) { // ... }; -Point.prototype.toString = function() { +Point.prototype.toString = function () { // ... }; @@ -162,11 +164,11 @@ Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"] ``` -上面代码采用 ES5 的写法,`toString`方法就是可枚举的。 +上面代码采用 ES5 的写法,`toString()`方法就是可枚举的。 ### constructor 方法 -`constructor`方法是类的默认方法,通过`new`命令生成对象实例时,自动调用该方法。一个类必须有`constructor`方法,如果没有显式定义,一个空的`constructor`方法会被默认添加。 +`constructor()`方法是类的默认方法,通过`new`命令生成对象实例时,自动调用该方法。一个类必须有`constructor()`方法,如果没有显式定义,一个空的`constructor()`方法会被默认添加。 ```javascript class Point { @@ -178,9 +180,9 @@ class Point { } ``` -上面代码中,定义了一个空的类`Point`,JavaScript 引擎会自动为它添加一个空的`constructor`方法。 +上面代码中,定义了一个空的类`Point`,JavaScript 引擎会自动为它添加一个空的`constructor()`方法。 -`constructor`方法默认返回实例对象(即`this`),完全可以指定返回另外一个对象。 +`constructor()`方法默认返回实例对象(即`this`),完全可以指定返回另外一个对象。 ```javascript class Foo { @@ -193,7 +195,7 @@ new Foo() instanceof Foo // false ``` -上面代码中,`constructor`函数返回一个全新的对象,结果导致实例对象不是`Foo`类的实例。 +上面代码中,`constructor()`函数返回一个全新的对象,结果导致实例对象不是`Foo`类的实例。 类必须使用`new`调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用`new`也可以执行。 @@ -251,7 +253,7 @@ point.hasOwnProperty('toString') // false point.__proto__.hasOwnProperty('toString') // true ``` -上面代码中,`x`和`y`都是实例对象`point`自身的属性(因为定义在`this`变量上),所以`hasOwnProperty`方法返回`true`,而`toString`是原型对象的属性(因为定义在`Point`类上),所以`hasOwnProperty`方法返回`false`。这些都与 ES5 的行为保持一致。 +上面代码中,`x`和`y`都是实例对象`point`自身的属性(因为定义在`this`变量上),所以`hasOwnProperty()`方法返回`true`,而`toString()`是原型对象的属性(因为定义在`Point`类上),所以`hasOwnProperty()`方法返回`false`。这些都与 ES5 的行为保持一致。 与 ES5 一样,类的所有实例共享一个原型对象。 @@ -282,7 +284,7 @@ var p3 = new Point(4,2); p3.printName() // "Oops" ``` -上面代码在`p1`的原型上添加了一个`printName`方法,由于`p1`的原型就是`p2`的原型,因此`p2`也可以调用这个方法。而且,此后新建的实例`p3`也可以调用这个方法。这意味着,使用实例的`__proto__`属性改写原型,必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。 +上面代码在`p1`的原型上添加了一个`printName()`方法,由于`p1`的原型就是`p2`的原型,因此`p2`也可以调用这个方法。而且,此后新建的实例`p3`也可以调用这个方法。这意味着,使用实例的`__proto__`属性改写原型,必须相当谨慎,不推荐使用,因为这会改变“类”的原始定义,影响到所有实例。 ### 取值函数(getter)和存值函数(setter) From cc2420e644113f25b4598b940dd321a0b5e3b09f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 17 Dec 2020 15:49:41 +0800 Subject: [PATCH 129/280] docs(function): edit arrow function --- docs/function.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/function.md b/docs/function.md index fe6b10750..792a45856 100644 --- a/docs/function.md +++ b/docs/function.md @@ -869,6 +869,35 @@ const cat = { 上面代码中,`cat.jumps()`方法是一个箭头函数,这是错误的。调用`cat.jumps()`时,如果是普通函数,该方法内部的`this`指向`cat`;如果写成上面那样的箭头函数,使得`this`指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致`jumps`箭头函数定义时的作用域就是全局作用域。 +再看一个例子。 + +```javascript +globalThis.s = 21; + +const obj = { + s: 42, + m: () => console.log(this.s) +}; + +obj.m() // 21 +``` + +上面例子中,`obj.m()`使用箭头函数定义。JavaScript 引擎的处理方法是,先在全局空间生成这个箭头函数,然后赋值给`obj.m`,这导致箭头函数内部的`this`指向全局对象,所以`obj.m()`输出的是全局空间的`21`,而不是对象内部的`42`。上面的代码实际上等同于下面的代码。 + +```javascript +globalThis.s = 21; +globalThis.m = () => console.log(this.s); + +const obj = { + s: 42, + m: globalThis.m +}; + +obj.m() // 21 +``` + +由于上面这个原因,对象的属性建议使用传统的写法定义,不要用箭头函数定义。 + 第二个场合是需要动态`this`的时候,也不应使用箭头函数。 ```javascript From 1c99572bc7be46aece9cea600f42ab0a9a55a79b Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 22 Dec 2020 23:41:42 +0800 Subject: [PATCH 130/280] docs(promise): fix #1028 --- docs/promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/promise.md b/docs/promise.md index f6717616b..c3eee3907 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -52,7 +52,7 @@ promise.then(function(value) { }); ``` -`then`方法可以接受两个回调函数作为参数。第一个回调函数是`Promise`对象的状态变为`resolved`时调用,第二个回调函数是`Promise`对象的状态变为`rejected`时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受`Promise`对象传出的值作为参数。 +`then`方法可以接受两个回调函数作为参数。第一个回调函数是`Promise`对象的状态变为`resolved`时调用,第二个回调函数是`Promise`对象的状态变为`rejected`时调用。这两个函数都是可选的,不一定要提供。它们都接受`Promise`对象传出的值作为参数。 下面是一个`Promise`对象的简单例子。 From 1d556be8a7e396608639b7263f7ad4b7a5054fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AB=A5=E6=AC=A7=E5=B7=B4?= <349247397@qq.com> Date: Wed, 23 Dec 2020 00:04:15 +0800 Subject: [PATCH 131/280] Update promise.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这里也需要同步下,阮大。 --- docs/promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/promise.md b/docs/promise.md index c3eee3907..02c1d0611 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -210,7 +210,7 @@ new Promise((resolve, reject) => { ## Promise.prototype.then() -Promise 实例具有`then`方法,也就是说,`then`方法是定义在原型对象`Promise.prototype`上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,`then`方法的第一个参数是`resolved`状态的回调函数,第二个参数(可选)是`rejected`状态的回调函数。 +Promise 实例具有`then`方法,也就是说,`then`方法是定义在原型对象`Promise.prototype`上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,`then`方法的第一个参数是`resolved`状态的回调函数,第二个参数是`rejected`状态的回调函数,它们都是可选的。 `then`方法返回的是一个新的`Promise`实例(注意,不是原来那个`Promise`实例)。因此可以采用链式写法,即`then`方法后面再调用另一个`then`方法。 From 4309e6aad345a473be5bdf4ca01f140e58232ad1 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 7 Jan 2021 00:12:47 +0800 Subject: [PATCH 132/280] docs(regex): edit regex --- docs/regex.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/regex.md b/docs/regex.md index 2cd8a15da..c14811110 100644 --- a/docs/regex.md +++ b/docs/regex.md @@ -38,7 +38,7 @@ new RegExp(/abc/ig, 'i').flags ## 字符串的正则方法 -字符串对象共有 4 个方法,可以使用正则表达式:`match()`、`replace()`、`search()`和`split()`。 +ES6 出现之前,字符串对象共有 4 个方法,可以使用正则表达式:`match()`、`replace()`、`search()`和`split()`。 ES6 将这 4 个方法,在语言内部全部调用`RegExp`的实例方法,从而做到所有与正则相关的方法,全都定义在`RegExp`对象上。 @@ -507,9 +507,9 @@ ES2018 引入了[具名组匹配](https://github.com/tc39/proposal-regexp-named- const RE_DATE = /(?\d{4})-(?\d{2})-(?\d{2})/; const matchObj = RE_DATE.exec('1999-12-31'); -const year = matchObj.groups.year; // 1999 -const month = matchObj.groups.month; // 12 -const day = matchObj.groups.day; // 31 +const year = matchObj.groups.year; // "1999" +const month = matchObj.groups.month; // "12" +const day = matchObj.groups.day; // "31" ``` 上面代码中,“具名组匹配”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(`?`),然后就可以在`exec`方法返回结果的`groups`属性上引用该组名。同时,数字序号(`matchObj[1]`)依然有效。 From b45161a0f6a0658029432d38b6526a58c3b428b4 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 22 Jan 2021 23:09:50 +0800 Subject: [PATCH 133/280] docs(proxy): fix #1032 --- docs/proxy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/proxy.md b/docs/proxy.md index e705fba45..7a761c0ad 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -418,13 +418,13 @@ myObj.foo === myObj // true 上面代码中,设置`myObj.foo`属性的值时,`myObj`并没有`foo`属性,因此引擎会到`myObj`的原型链去找`foo`属性。`myObj`的原型对象`proxy`是一个 Proxy 实例,设置它的`foo`属性会触发`set`方法。这时,第四个参数`receiver`就指向原始赋值行为所在的对象`myObj`。 -注意,如果目标对象自身的某个属性,不可写且不可配置,那么`set`方法将不起作用。 +注意,如果目标对象自身的某个属性不可写,那么`set`方法将不起作用。 ```javascript const obj = {}; Object.defineProperty(obj, 'foo', { value: 'bar', - writable: false, + writable: false }); const handler = { From 94616305e53401ad24daec046d9c302c2eee726f Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 5 Feb 2021 13:57:28 +0800 Subject: [PATCH 134/280] refactor: edit support js --- js/ditto.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index c5eb80e0e..d20a7eb64 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -243,8 +243,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2021年2月3日 - var deadline = new Date(2021, 1, 3); + // 2021年3月18日 + var deadline = new Date(2021, 2, 18); if (deadline - (new Date()) < 0) return; var styleStr = [ @@ -258,10 +258,10 @@ function create_banner(element) { 'color: #333333' ].join(';'); - var text = '【免费课程】' + - '《Vue进阶攻略》领取,Vue 3.0 新知识点讲解。'; + var text = '【免费资料】' + + '开课吧《数据分析全套讲解》视频,讲解统计建模与分析工具(Excel/Python/SQL等)基础知识。'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } From 543678f9ddcdf3b5ee670dde8aeb624d07669055 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 11 Feb 2021 15:53:32 +0800 Subject: [PATCH 135/280] docs(array): fix #1037 --- docs/array.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/array.md b/docs/array.md index 3dc145483..e69830951 100644 --- a/docs/array.md +++ b/docs/array.md @@ -507,7 +507,7 @@ function countSymbols(string) { ## Array.of() -`Array.of`方法用于将一组值,转换为数组。 +`Array.of()`方法用于将一组值,转换为数组。 ```javascript Array.of(3, 11, 8) // [3,11,8] @@ -523,9 +523,9 @@ Array(3) // [, , ,] Array(3, 11, 8) // [3, 11, 8] ``` -上面代码中,`Array`方法没有参数、一个参数、三个参数时,返回结果都不一样。只有当参数个数不少于 2 个时,`Array()`才会返回由参数组成的新数组。参数个数只有一个时,实际上是指定数组的长度。 +上面代码中,`Array()`方法没有参数、一个参数、三个参数时,返回的结果都不一样。只有当参数个数不少于 2 个时,`Array()`才会返回由参数组成的新数组。参数只有一个正整数时,实际上是指定数组的长度。 -`Array.of`基本上可以用来替代`Array()`或`new Array()`,并且不存在由于参数不同而导致的重载。它的行为非常统一。 +`Array.of()`基本上可以用来替代`Array()`或`new Array()`,并且不存在由于参数不同而导致的重载。它的行为非常统一。 ```javascript Array.of() // [] @@ -534,9 +534,9 @@ Array.of(1) // [1] Array.of(1, 2) // [1, 2] ``` -`Array.of`总是返回参数值组成的数组。如果没有参数,就返回一个空数组。 +`Array.of()`总是返回参数值组成的数组。如果没有参数,就返回一个空数组。 -`Array.of`方法可以用下面的代码模拟实现。 +`Array.of()`方法可以用下面的代码模拟实现。 ```javascript function ArrayOf(){ From ec9db66106511904747de9ef7bd590e6935e10e8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 20 Feb 2021 19:40:37 +0800 Subject: [PATCH 136/280] refactor: edit support banner --- js/ditto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index d20a7eb64..95f2c99fb 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -259,9 +259,9 @@ function create_banner(element) { ].join(';'); var text = '【免费资料】' + - '开课吧《数据分析全套讲解》视频,讲解统计建模与分析工具(Excel/Python/SQL等)基础知识。'; + 'Excel + Python + SQL 数据处理入门,课程资料下载。'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } From 631a66ea8f89fff4547cef0ae9fb6970d9fb628d Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 20 Feb 2021 19:44:10 +0800 Subject: [PATCH 137/280] refactor: edit support banner --- js/ditto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ditto.js b/js/ditto.js index 95f2c99fb..186a85c1f 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -259,7 +259,7 @@ function create_banner(element) { ].join(';'); var text = '【免费资料】' + - 'Excel + Python + SQL 数据处理入门,课程资料下载。'; + 'Excel + Python + SQL 数据处理入门,课程资料下载。'; var banner = $('
    ' + text + '
    ') .insertAfter(element); From 3929f4f21148dcd2a10d2ebc722323a5dbd473f4 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 23 Feb 2021 16:32:04 +0800 Subject: [PATCH 138/280] refactor: edit support banner --- js/ditto.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 186a85c1f..417363ea1 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -243,8 +243,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2021年3月18日 - var deadline = new Date(2021, 2, 18); + // 2021年2月18日 + var deadline = new Date(2021, 1, 18); if (deadline - (new Date()) < 0) return; var styleStr = [ From e9fd28bf6460140cdc652ff50755bcfbcb68012b Mon Sep 17 00:00:00 2001 From: An Hongpeng <79783808+roc-an@users.noreply.github.com> Date: Sun, 18 Apr 2021 21:32:40 +0800 Subject: [PATCH 139/280] fix: remove 'else' --- docs/iterator.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/iterator.md b/docs/iterator.md index 68fba5009..4d75784f8 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -209,9 +209,8 @@ Obj.prototype[Symbol.iterator] = function() { var value = current.value; current = current.next; return { done: false, value: value }; - } else { - return { done: true }; } + return { done: true }; } return iterator; } @@ -245,9 +244,8 @@ let obj = { value: self.data[index++], done: false }; - } else { - return { value: undefined, done: true }; } + return { value: undefined, done: true }; } }; } From d09058f44ede41cbb44612d0817a53d4ca5a65fb Mon Sep 17 00:00:00 2001 From: An Hongpeng <79783808+roc-an@users.noreply.github.com> Date: Tue, 20 Apr 2021 20:03:08 +0800 Subject: [PATCH 140/280] =?UTF-8?q?fix:=20=E4=B8=BA=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/iterator.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/iterator.md b/docs/iterator.md index 68fba5009..74056b400 100644 --- a/docs/iterator.md +++ b/docs/iterator.md @@ -745,6 +745,8 @@ for (var key of Object.keys(someObject)) { 另一个方法是使用 Generator 函数将对象重新包装一下。 ```javascript +const obj = { a: 1, b: 2, c: 3 } + function* entries(obj) { for (let key of Object.keys(obj)) { yield [key, obj[key]]; From fa03d29d6410caa1074382a0cfe8650222ed2220 Mon Sep 17 00:00:00 2001 From: An Hongpeng <79783808+roc-an@users.noreply.github.com> Date: Sat, 24 Apr 2021 23:22:28 +0800 Subject: [PATCH 141/280] =?UTF-8?q?fix:=20=E5=BC=BA=E8=BF=AB=E7=97=87?= =?UTF-8?q?=E7=8A=AF=E4=BA=86=E6=83=B3=E5=8E=BB=E6=8E=89=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=A9=BA=E6=A0=BC=E5=A5=BD=E7=9C=8B=E4=B8=80=E4=BA=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/generator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/generator.md b/docs/generator.md index e2f7d18ee..7d18278ca 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -696,7 +696,7 @@ function* gen() { var g = gen(); -g.next() // { value: 1, done: false } +g.next() // { value: 1, done: false } g.return() // { value: undefined, done: true } ``` From 59146b4806f42ad9bb1587651493ceabaf28f0c3 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 26 Apr 2021 10:04:13 +0800 Subject: [PATCH 142/280] docs(proxy): fix set return value #1047 --- docs/proxy.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/proxy.md b/docs/proxy.md index 7a761c0ad..33492c1ba 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -344,6 +344,7 @@ let validator = { // 对于满足条件的 age 属性以及其他属性,直接保存 obj[prop] = value; + return true; } }; @@ -393,6 +394,7 @@ proxy._prop = 'c' const handler = { set: function(obj, prop, value, receiver) { obj[prop] = receiver; + return true; } }; const proxy = new Proxy({}, handler); @@ -406,6 +408,7 @@ proxy.foo === proxy // true const handler = { set: function(obj, prop, value, receiver) { obj[prop] = receiver; + return true; } }; const proxy = new Proxy({}, handler); @@ -430,6 +433,7 @@ Object.defineProperty(obj, 'foo', { const handler = { set: function(obj, prop, value, receiver) { obj[prop] = 'baz'; + return true; } }; @@ -440,7 +444,7 @@ proxy.foo // "bar" 上面代码中,`obj.foo`属性不可写,Proxy 对这个属性的`set`代理将不会生效。 -注意,严格模式下,`set`代理如果没有返回`true`,就会报错。 +注意,`set`代理应当返回一个布尔值。严格模式下,`set`代理如果没有返回`true`,就会报错。 ```javascript 'use strict'; From 85b8174db3bd7b2e59a370bca4841f01941ca25f Mon Sep 17 00:00:00 2001 From: daungui Date: Sat, 8 May 2021 22:36:36 +0800 Subject: [PATCH 143/280] =?UTF-8?q?docs(set-map):=20=E6=94=B9=E8=BF=9B=20W?= =?UTF-8?q?eakSet=20=E5=92=8C=20WeakMap=20=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/set-map.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/set-map.md b/docs/set-map.md index b2e14d13e..cafd91141 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -322,7 +322,7 @@ ws.add(Symbol()) 其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。 -这是因为垃圾回收机制依赖引用计数,如果一个值的引用次数不为`0`,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。 +这是因为垃圾回收机制根据对象的可达性(Reachability)来判断回收,如果对象还能被访问到即仍然可达,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。 由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。 @@ -981,9 +981,9 @@ wm.set(element, 'some information'); wm.get(element) // "some information" ``` -上面代码中,先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对`element`的引用就是弱引用,不会被计入垃圾回收机制。 +上面代码中,先新建一个 WeakMap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对`element`的引用就是弱引用,不会被计入垃圾回收机制。 -也就是说,上面的 DOM 节点对象的引用计数是`1`,而不是`2`。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。 +也就是说,上面的 DOM 节点对象除了 WeakMap 的弱引用外,只有一处引用。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。WeakMap 保存的这个键值对,也会自动消失。 总之,`WeakMap`的专用场合就是,它的键所对应的对象,可能会在将来消失。`WeakMap`结构有助于防止内存泄漏。 From 71af30def18573f73e6d75df7c288cabd2ce04e0 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 12 May 2021 01:07:14 +0800 Subject: [PATCH 144/280] =?UTF-8?q?docs(function):=20fixed=20=E7=AE=AD?= =?UTF-8?q?=E5=A4=B4=E5=87=BD=E6=95=B0=E7=9A=84this=20#1050?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/function.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/function.md b/docs/function.md index 792a45856..a826c8814 100644 --- a/docs/function.md +++ b/docs/function.md @@ -708,15 +708,15 @@ headAndTail(1, 2, 3, 4, 5) 箭头函数有几个使用注意点。 -(1)函数体内的`this`对象,就是定义时所在的对象,而不是使用时所在的对象。 +(1)箭头函数没有自己的`this`对象(详见下文)。 -(2)不可以当作构造函数,也就是说,不可以使用`new`命令,否则会抛出一个错误。 +(2)不可以当作构造函数,也就是说,不可以对箭头函数使用`new`命令,否则会抛出一个错误。 (3)不可以使用`arguments`对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 (4)不可以使用`yield`命令,因此箭头函数不能用作 Generator 函数。 -上面四点中,第一点尤其值得注意。`this`对象的指向是可变的,但是在箭头函数中,它是固定的。 +上面四点中,最重要的是第一点。对于普通函数来说,内部的`this`代表函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的`this`对象,内部的`this`就是一个普通变量,指向定义时上层函数所在的对象。也就是说,箭头函数内部的`this`指向是固定的,相比之下,普通函数的`this`指向是可变的。 ```javascript function foo() { @@ -733,7 +733,7 @@ foo.call({ id: 42 }); 上面代码中,`setTimeout()`的参数是一个箭头函数,这个箭头函数的定义生效是在`foo`函数生成时,而它的真正执行要等到 100 毫秒后。如果是普通函数,执行时`this`应该指向全局对象`window`,这时应该输出`21`。但是,箭头函数导致`this`总是指向函数定义生效时所在的对象(本例是`{id: 42}`),所以打印出来的是`42`。 -箭头函数可以让`setTimeout`里面的`this`,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子。 +下面例子是回调函数分别为箭头函数和普通函数,对比它们内部的`this`指向。 ```javascript function Timer() { @@ -757,7 +757,7 @@ setTimeout(() => console.log('s2: ', timer.s2), 3100); 上面代码中,`Timer`函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的`this`绑定定义时所在的作用域(即`Timer`函数),后者的`this`指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,`timer.s1`被更新了 3 次,而`timer.s2`一次都没更新。 -箭头函数可以让`this`指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。 +箭头函数实际上可以让`this`指向固定化,绑定`this`使得它不再可变,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。 ```javascript var handler = { @@ -774,11 +774,11 @@ var handler = { }; ``` -上面代码的`init`方法中,使用了箭头函数,这导致这个箭头函数里面的`this`,总是指向`handler`对象。否则,回调函数运行时,`this.doSomething`这一行会报错,因为此时`this`指向`document`对象。 +上面代码的`init()`方法中,使用了箭头函数,这导致这个箭头函数里面的`this`,总是指向`handler`对象。如果回调函数是普通函数,那么运行`this.doSomething()`这一行会报错,因为此时`this`指向`document`对象。 -`this`指向的固定化,并不是因为箭头函数内部有绑定`this`的机制,实际原因是箭头函数根本没有自己的`this`,导致内部的`this`就是外层代码块的`this`。正是因为它没有`this`,所以也就不能用作构造函数。 +总之,箭头函数根本没有自己的`this`,导致内部的`this`就是外层代码块的`this`。正是因为它没有`this`,所以也就不能用作构造函数。 -所以,箭头函数转成 ES5 的代码如下。 +下面是 Babel 转箭头函数产生的 ES5 代码,就能清楚地说明`this`的指向。 ```javascript // ES6 @@ -800,7 +800,7 @@ function foo() { 上面代码中,转换后的 ES5 版本清楚地说明了,箭头函数里面根本没有自己的`this`,而是引用外层的`this`。 -请问下面的代码之中有几个`this`? +请问下面的代码之中,`this`的指向有几个? ```javascript function foo() { @@ -820,7 +820,7 @@ var t2 = f().call({id: 3})(); // id: 1 var t3 = f()().call({id: 4}); // id: 1 ``` -上面代码之中,只有一个`this`,就是函数`foo`的`this`,所以`t1`、`t2`、`t3`都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的`this`,它们的`this`其实都是最外层`foo`函数的`this`。 +答案是`this`的指向只有一个,就是函数`foo`的`this`,这是因为所有的内层函数都是箭头函数,都没有自己的`this`,它们的`this`其实都是最外层`foo`函数的`this`。所以不管怎么嵌套,`t1`、`t2`、`t3`都输出同样的结果。如果这个例子的所有内层函数都写成普通函数,那么每个函数的`this`都指向运行时所在的不同对象。 除了`this`,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:`arguments`、`super`、`new.target`。 From ee1b51ecd175675595edc685f68d1f56009b171e Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 12 May 2021 08:42:53 +0800 Subject: [PATCH 145/280] docs(set-map): edit text --- docs/set-map.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/set-map.md b/docs/set-map.md index cafd91141..0fcfde883 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -322,7 +322,7 @@ ws.add(Symbol()) 其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。 -这是因为垃圾回收机制根据对象的可达性(Reachability)来判断回收,如果对象还能被访问到即仍然可达,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。 +这是因为垃圾回收机制根据对象的可达性(reachability)来判断回收,如果对象还能被访问到,垃圾回收机制就不会释放这块内存。结束使用该值之后,有时会忘记取消引用,导致内存无法释放,进而可能会引发内存泄漏。WeakSet 里面的引用,都不计入垃圾回收机制,所以就不存在这个问题。因此,WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。 由于上面这个特点,WeakSet 的成员是不适合引用的,因为它会随时消失。另外,由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。 @@ -983,7 +983,7 @@ wm.get(element) // "some information" 上面代码中,先新建一个 WeakMap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对`element`的引用就是弱引用,不会被计入垃圾回收机制。 -也就是说,上面的 DOM 节点对象除了 WeakMap 的弱引用外,只有一处引用。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。WeakMap 保存的这个键值对,也会自动消失。 +也就是说,上面的 DOM 节点对象除了 WeakMap 的弱引用外,其他位置对该对象的引用一旦消除,该对象占用的内存就会被垃圾回收机制释放。WeakMap 保存的这个键值对,也会自动消失。 总之,`WeakMap`的专用场合就是,它的键所对应的对象,可能会在将来消失。`WeakMap`结构有助于防止内存泄漏。 From d41bf835a189a2f93b5881e720c35196f6590a1b Mon Sep 17 00:00:00 2001 From: daungui Date: Fri, 14 May 2021 23:27:51 +0800 Subject: [PATCH 146/280] =?UTF-8?q?docs(function):=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E4=B8=8D=E5=90=88=E9=80=82=E7=9A=84=E5=87=BD=E6=95=B0=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/function.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/function.md b/docs/function.md index a826c8814..d5a8e4eda 100644 --- a/docs/function.md +++ b/docs/function.md @@ -669,7 +669,7 @@ const square = n => n * n; 箭头函数的一个用处是简化回调函数。 ```javascript -// 正常函数写法 +// 普通函数写法 [1,2,3].map(function (x) { return x * x; }); @@ -681,7 +681,7 @@ const square = n => n * n; 另一个例子是 ```javascript -// 正常函数写法 +// 普通函数写法 var result = values.sort(function (a, b) { return a - b; }); @@ -716,7 +716,7 @@ headAndTail(1, 2, 3, 4, 5) (4)不可以使用`yield`命令,因此箭头函数不能用作 Generator 函数。 -上面四点中,最重要的是第一点。对于普通函数来说,内部的`this`代表函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的`this`对象,内部的`this`就是一个普通变量,指向定义时上层函数所在的对象。也就是说,箭头函数内部的`this`指向是固定的,相比之下,普通函数的`this`指向是可变的。 +上面四点中,最重要的是第一点。对于普通函数来说,内部的`this`指向函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的`this`对象,内部的`this`就是定义时上层作用域中的`this`。也就是说,箭头函数内部的`this`指向是固定的,相比之下,普通函数的`this`指向是可变的。 ```javascript function foo() { From 53cb4d3ef806575686238cf74df611fe04fe9f5e Mon Sep 17 00:00:00 2001 From: daungui Date: Sun, 16 May 2021 00:39:45 +0800 Subject: [PATCH 147/280] =?UTF-8?q?docs:=20=E4=BF=AE=E6=AD=A3=E5=B7=B2?= =?UTF-8?q?=E7=9F=A5=E7=9A=84=20this=20=E6=9C=AF=E8=AF=AD=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 2 +- docs/let.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/class.md b/docs/class.md index 528fac0bd..f613a60e2 100644 --- a/docs/class.md +++ b/docs/class.md @@ -253,7 +253,7 @@ point.hasOwnProperty('toString') // false point.__proto__.hasOwnProperty('toString') // true ``` -上面代码中,`x`和`y`都是实例对象`point`自身的属性(因为定义在`this`变量上),所以`hasOwnProperty()`方法返回`true`,而`toString()`是原型对象的属性(因为定义在`Point`类上),所以`hasOwnProperty()`方法返回`false`。这些都与 ES5 的行为保持一致。 +上面代码中,`x`和`y`都是实例对象`point`自身的属性(因为定义在`this`对象上),所以`hasOwnProperty()`方法返回`true`,而`toString()`是原型对象的属性(因为定义在`Point`类上),所以`hasOwnProperty()`方法返回`false`。这些都与 ES5 的行为保持一致。 与 ES5 一样,类的所有实例共享一个原型对象。 diff --git a/docs/let.md b/docs/let.md index e5a7c8b3e..9bb8782d1 100644 --- a/docs/let.md +++ b/docs/let.md @@ -599,7 +599,7 @@ JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作 - 浏览器和 Web Worker 里面,`self`也指向顶层对象,但是 Node 没有`self`。 - Node 里面,顶层对象是`global`,但其他环境都不支持。 -同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用`this`变量,但是有局限性。 +同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用`this`关键字,但是有局限性。 - 全局环境中,`this`会返回顶层对象。但是,Node.js 模块中`this`返回的是当前模块,ES6 模块中`this`返回的是`undefined`。 - 函数里面的`this`,如果函数不是作为对象的方法运行,而是单纯作为函数运行,`this`会指向顶层对象。但是,严格模式下,这时`this`会返回`undefined`。 From 89370afc1ce03750af7f3eb4469c9e6deec0d65b Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 24 May 2021 19:06:00 +0800 Subject: [PATCH 148/280] =?UTF-8?q?docs(class):=20=E6=B7=BB=E5=8A=A0=20in?= =?UTF-8?q?=20=E8=BF=90=E7=AE=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/docs/class.md b/docs/class.md index f613a60e2..391194087 100644 --- a/docs/class.md +++ b/docs/class.md @@ -924,6 +924,117 @@ FakeMath.#computeRandomNumber() // 报错 上面代码中,`#totallyRandomNumber`是私有属性,`#computeRandomNumber()`是私有方法,只能在`FakeMath`这个类的内部调用,外部调用就会报错。 +### in 运算符 + +`try...catch`结构可以用来判断是否存在某个私有属性。 + +```javascript +class A { + use(obj) { + try { + obj.#foo; + } catch { + // 私有属性 #foo 不存在 + } + } +} + +const a = new A(); +a.use(a); // 报错 +``` + +上面示例中,类`A`并不存在私有属性`#foo`,所以`try...catch`报错了。 + +这样的写法很麻烦,可读性很差,V8 引擎改进了`in`运算符,使它也可以用来判断私有属性。 + +```javascript +class A { + use(obj) { + if (#foo in obj) { + // 私有属性 #foo 存在 + } else { + // 私有属性 #foo 不存在 + } + } +} +``` + +上面示例中,`in`运算符判断当前类`A`的实例,是否有私有属性`#foo`,如果有返回`true`,否则返回`false`。 + +`in`也可以跟`this`一起配合使用。 + +```javascript +class A { + #foo = 0; + m() { + console.log(#foo in this); // true + console.log(#bar in this); // false + } +} +``` + +注意,判断私有属性时,`in`只能用在定义该私有属性的类的内部。 + +```javascript +class A { + #foo = 0; + static test(obj) { + console.log(#foo in obj); + } +} + +A.test(new A()) // true +A.test({}) // false + +class B { + #foo = 0; +} + +A.test(new B()) // false +``` + +上面示例中,类`A`的私有属性`#foo`,只能在类`A`内部使用`in`运算符判断,而且只对`A`的实例返回`true`,对于其他对象都返回`false`。 + +子类从父类继承的私有属性,也可以使用`in`运算符来判断。 + +```javascript +class A { + #foo = 0; + static test(obj) { + console.log(#foo in obj); + } +} + +class SubA extend A {}; + +A.test(new SubA()) // true +``` + +上面示例中,`SubA`从父类继承了私有属性`#foo`,`in`运算符也有效。 + +注意,`in`运算符对于`Object.create()`、`Object.setPrototypeOf`形成的继承,是无效的,因为这种继承不会传递私有属性。 + +```javascript +class A { + #foo = 0; + static test(obj) { + console.log(#foo in obj); + } +} +const a = new A(); + +const o1 = Object.create(a); +A.test(o1) // false +A.test(o1.__proto__) // true + +const o2 = {}; +Object.setPrototypeOf(o2, A); +A.test(o2) // false +A.test(o2.__proto__) // true +``` + +上面示例中,对于修改原型链形成的继承,子类都取不到父类的私有属性,所以`in`运算符无效。 + ## new.target 属性 `new`是从构造函数生成实例对象的命令。ES6 为`new`命令引入了一个`new.target`属性,该属性一般用在构造函数之中,返回`new`命令作用于的那个构造函数。如果构造函数不是通过`new`命令或`Reflect.construct()`调用的,`new.target`会返回`undefined`,因此这个属性可以用来确定构造函数是怎么调用的。 From 8e236cb8b2584ea7b2e4eb2f754155f8ef28571c Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 2 Jun 2021 20:26:21 +0800 Subject: [PATCH 149/280] refactor: edit support banner --- js/ditto.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 417363ea1..fd92bf0bc 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -243,8 +243,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2021年2月18日 - var deadline = new Date(2021, 1, 18); + // 2021年6月7日 + var deadline = new Date(2021, 5, 7); if (deadline - (new Date()) < 0) return; var styleStr = [ @@ -258,10 +258,10 @@ function create_banner(element) { 'color: #333333' ].join(';'); - var text = '【免费资料】' + - 'Excel + Python + SQL 数据处理入门,课程资料下载。'; + var text = '【前端课程】' + + '《React Hooks 核心原理与实战》通过实战案例,详细讲解 Hooks,现在优惠中。'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } From fdac6ccb2e93dc4956e8b8ab10cdfaa912b4ca73 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 10 Jul 2021 14:58:15 +0800 Subject: [PATCH 150/280] docs: add ES2021 --- docs/number.md | 124 ++++++++++++++----- docs/object.md | 229 ---------------------------------- docs/operator.md | 309 ++++++++++++++++++++++++++++++++++++++++++++++ docs/promise.md | 39 +++--- docs/proposals.md | 77 ------------ docs/set-map.md | 148 ++++++++++++++++++++++ 6 files changed, 571 insertions(+), 355 deletions(-) create mode 100644 docs/operator.md diff --git a/docs/number.md b/docs/number.md index 250e7e735..c21067e00 100644 --- a/docs/number.md +++ b/docs/number.md @@ -31,6 +31,97 @@ Number('0b111') // 7 Number('0o10') // 8 ``` +## 数值分隔符 + +欧美语言中,较长的数值允许每三位添加一个分隔符(通常是一个逗号),增加数值的可读性。比如,`1000`可以写作`1,000`。 + +[ES2021](https://github.com/tc39/proposal-numeric-separator),允许 JavaScript 的数值使用下划线(`_`)作为分隔符。 + +```javascript +let budget = 1_000_000_000_000; +budget === 10 ** 12 // true +``` + +这个数值分隔符没有指定间隔的位数,也就是说,可以每三位添加一个分隔符,也可以每一位、每两位、每四位添加一个。 + +```javascript +123_00 === 12_300 // true + +12345_00 === 123_4500 // true +12345_00 === 1_234_500 // true +``` + +小数和科学计数法也可以使用数值分隔符。 + +```javascript +// 小数 +0.000_001 + +// 科学计数法 +1e10_000 +``` + +数值分隔符有几个使用注意点。 + +- 不能放在数值的最前面(leading)或最后面(trailing)。 +- 不能两个或两个以上的分隔符连在一起。 +- 小数点的前后不能有分隔符。 +- 科学计数法里面,表示指数的`e`或`E`前后不能有分隔符。 + +下面的写法都会报错。 + +```javascript +// 全部报错 +3_.141 +3._141 +1_e12 +1e_12 +123__456 +_1464301 +1464301_ +``` + +除了十进制,其他进制的数值也可以使用分隔符。 + +```javascript +// 二进制 +0b1010_0001_1000_0101 +// 十六进制 +0xA0_B0_C0 +``` + +可以看到,数值分隔符可以按字节顺序分隔数值,这在操作二进制位时,非常有用。 + +注意,分隔符不能紧跟着进制的前缀`0b`、`0B`、`0o`、`0O`、`0x`、`0X`。 + +```javascript +// 报错 +0_b111111000 +0b_111111000 +``` + +数值分隔符只是一种书写便利,对于 JavaScript 内部数值的存储和输出,并没有影响。 + +```javascript +let num = 12_345; + +num // 12345 +num.toString() // 12345 +``` + +上面示例中,变量`num`的值为`12_345`,但是内部存储和输出的时候,都不会有数值分隔符。 + +下面三个将字符串转成数值的函数,不支持数值分隔符。主要原因是语言的设计者认为,数值分隔符主要是为了编码时书写数值的方便,而不是为了处理外部输入的数据。 + +- Number() +- parseInt() +- parseFloat() + +```javascript +Number('123_456') // NaN +parseInt('123_456') // 123 +``` + ## Number.isFinite(), Number.isNaN() ES6 在`Number`对象上,新提供了`Number.isFinite()`和`Number.isNaN()`两个方法。 @@ -655,42 +746,11 @@ ES6 新增了 6 个双曲函数方法。 - `Math.acosh(x)` 返回`x`的反双曲余弦(inverse hyperbolic cosine) - `Math.atanh(x)` 返回`x`的反双曲正切(inverse hyperbolic tangent) -## 指数运算符 - -ES2016 新增了一个指数运算符(`**`)。 - -```javascript -2 ** 2 // 4 -2 ** 3 // 8 -``` - -这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。 - -```javascript -// 相当于 2 ** (3 ** 2) -2 ** 3 ** 2 -// 512 -``` - -上面代码中,首先计算的是第二个指数运算符,而不是第一个。 - -指数运算符可以与等号结合,形成一个新的赋值运算符(`**=`)。 - -```javascript -let a = 1.5; -a **= 2; -// 等同于 a = a * a; - -let b = 4; -b **= 3; -// 等同于 b = b * b * b; -``` - ## BigInt 数据类型 ### 简介 -JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示的,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回`Infinity`。 +JavaScript 所有数字都保存成 64 位浮点数,这给数值的表示带来了两大限制。一是数值的精度只能到 53 个二进制位(相当于 16 个十进制位),大于这个范围的整数,JavaScript 是无法精确表示,这使得 JavaScript 不适合进行科学和金融方面的精确计算。二是大于或等于2的1024次方的数值,JavaScript 无法表示,会返回`Infinity`。 ```javascript // 超过 53 个二进制位的数值,无法保持精度 diff --git a/docs/object.md b/docs/object.md index 5b8395bc7..d2965e818 100644 --- a/docs/object.md +++ b/docs/object.md @@ -704,232 +704,3 @@ let aWithXGetter = { ...a }; // 报错 上面例子中,取值函数`get`在扩展`a`对象时会自动执行,导致报错。 -## 链判断运算符 - -编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取`message.body.user.firstName`,安全的写法是写成下面这样。 - -```javascript -// 错误的写法 -const firstName = message.body.user.firstName; - -// 正确的写法 -const firstName = (message - && message.body - && message.body.user - && message.body.user.firstName) || 'default'; -``` - -上面例子中,`firstName`属性在对象的第四层,所以需要判断四次,每一层是否有值。 - -三元运算符`?:`也常用于判断对象是否存在。 - -```javascript -const fooInput = myForm.querySelector('input[name=foo]') -const fooValue = fooInput ? fooInput.value : undefined -``` - -上面例子中,必须先判断`fooInput`是否存在,才能读取`fooInput.value`。 - -这样的层层判断非常麻烦,因此 [ES2020](https://github.com/tc39/proposal-optional-chaining) 引入了“链判断运算符”(optional chaining operator)`?.`,简化上面的写法。 - -```javascript -const firstName = message?.body?.user?.firstName || 'default'; -const fooValue = myForm.querySelector('input[name=foo]')?.value -``` - -上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null`或`undefined`。如果是的,就不再往下运算,而是返回`undefined`。 - -下面是判断对象方法是否存在,如果存在就立即执行的例子。 - -```javascript -iterator.return?.() -``` - -上面代码中,`iterator.return`如果有定义,就会调用该方法,否则`iterator.return`直接返回`undefined`,不再执行`?.`后面的部分。 - -对于那些可能没有实现的方法,这个运算符尤其有用。 - -```javascript -if (myForm.checkValidity?.() === false) { - // 表单校验失败 - return; -} -``` - -上面代码中,老式浏览器的表单可能没有`checkValidity`这个方法,这时`?.`运算符就会返回`undefined`,判断语句就变成了`undefined === false`,所以就会跳过下面的代码。 - -链判断运算符有三种用法。 - -- `obj?.prop` // 对象属性 -- `obj?.[expr]` // 同上 -- `func?.(...args)` // 函数或对象方法的调用 - -下面是`obj?.[expr]`用法的一个例子。 - -```bash -let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1]; -``` - -上面例子中,字符串的`match()`方法,如果没有发现匹配会返回`null`,如果发现匹配会返回一个数组,`?.`运算符起到了判断作用。 - -下面是`?.`运算符常见形式,以及不使用该运算符时的等价形式。 - -```javascript -a?.b -// 等同于 -a == null ? undefined : a.b - -a?.[x] -// 等同于 -a == null ? undefined : a[x] - -a?.b() -// 等同于 -a == null ? undefined : a.b() - -a?.() -// 等同于 -a == null ? undefined : a() -``` - -上面代码中,特别注意后两种形式,如果`a?.b()`里面的`a.b`不是函数,不可调用,那么`a?.b()`是会报错的。`a?.()`也是如此,如果`a`不是`null`或`undefined`,但也不是函数,那么`a?.()`会报错。 - -使用这个运算符,有几个注意点。 - -(1)短路机制 - -`?.`运算符相当于一种短路机制,只要不满足条件,就不再往下执行。 - -```javascript -a?.[++x] -// 等同于 -a == null ? undefined : a[++x] -``` - -上面代码中,如果`a`是`undefined`或`null`,那么`x`不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。 - -(2)delete 运算符 - -```javascript -delete a?.b -// 等同于 -a == null ? undefined : delete a.b -``` - -上面代码中,如果`a`是`undefined`或`null`,会直接返回`undefined`,而不会进行`delete`运算。 - -(3)括号的影响 - -如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。 - -```javascript -(a?.b).c -// 等价于 -(a == null ? undefined : a.b).c -``` - -上面代码中,`?.`对圆括号外部没有影响,不管`a`对象是否存在,圆括号后面的`.c`总是会执行。 - -一般来说,使用`?.`运算符的场合,不应该使用圆括号。 - -(4)报错场合 - -以下写法是禁止的,会报错。 - -```javascript -// 构造函数 -new a?.() -new a?.b() - -// 链判断运算符的右侧有模板字符串 -a?.`{b}` -a?.b`{c}` - -// 链判断运算符的左侧是 super -super?.() -super?.foo - -// 链运算符用于赋值运算符左侧 -a?.b = c -``` - -(5)右侧不得为十进制数值 - -为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。 - -## Null 判断运算符 - -读取对象属性的时候,如果某个属性的值是`null`或`undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。 - -```javascript -const headerText = response.settings.headerText || 'Hello, world!'; -const animationDuration = response.settings.animationDuration || 300; -const showSplashScreen = response.settings.showSplashScreen || true; -``` - -上面的三行代码都通过`||`运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为`null`或`undefined`,默认值就会生效,但是属性的值如果为空字符串或`false`或`0`,默认值也会生效。 - -为了避免这种情况,[ES2020](https://github.com/tc39/proposal-nullish-coalescing) 引入了一个新的 Null 判断运算符`??`。它的行为类似`||`,但是只有运算符左侧的值为`null`或`undefined`时,才会返回右侧的值。 - -```javascript -const headerText = response.settings.headerText ?? 'Hello, world!'; -const animationDuration = response.settings.animationDuration ?? 300; -const showSplashScreen = response.settings.showSplashScreen ?? true; -``` - -上面代码中,默认值只有在左侧属性值为`null`或`undefined`时,才会生效。 - -这个运算符的一个目的,就是跟链判断运算符`?.`配合使用,为`null`或`undefined`的值设置默认值。 - -```javascript -const animationDuration = response.settings?.animationDuration ?? 300; -``` - -上面代码中,如果`response.settings`是`null`或`undefined`,或者`response.settings.animationDuration`是`null`或`undefined`,就会返回默认值300。也就是说,这一行代码包括了两级属性的判断。 - -这个运算符很适合判断函数参数是否赋值。 - -```javascript -function Component(props) { - const enable = props.enabled ?? true; - // … -} -``` - -上面代码判断`props`参数的`enabled`属性是否赋值,基本等同于下面的写法。 - -```javascript -function Component(props) { - const { - enabled: enable = true, - } = props; - // … -} -``` - -`??`有一个运算优先级问题,它与`&&`和`||`的优先级孰高孰低。现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。 - -```javascript -// 报错 -lhs && middle ?? rhs -lhs ?? middle && rhs -lhs || middle ?? rhs -lhs ?? middle || rhs -``` - -上面四个表达式都会报错,必须加入表明优先级的括号。 - -```javascript -(lhs && middle) ?? rhs; -lhs && (middle ?? rhs); - -(lhs ?? middle) && rhs; -lhs ?? (middle && rhs); - -(lhs || middle) ?? rhs; -lhs || (middle ?? rhs); - -(lhs ?? middle) || rhs; -lhs ?? (middle || rhs); -``` - diff --git a/docs/operator.md b/docs/operator.md new file mode 100644 index 000000000..e3958b43a --- /dev/null +++ b/docs/operator.md @@ -0,0 +1,309 @@ +# 运算符的扩展 + +本章介绍 ES6 后续标准添加的一些运算符。 + +## 指数运算符 + +ES2016 新增了一个指数运算符(`**`)。 + +```javascript +2 ** 2 // 4 +2 ** 3 // 8 +``` + +这个运算符的一个特点是右结合,而不是常见的左结合。多个指数运算符连用时,是从最右边开始计算的。 + +```javascript +// 相当于 2 ** (3 ** 2) +2 ** 3 ** 2 +// 512 +``` + +上面代码中,首先计算的是第二个指数运算符,而不是第一个。 + +指数运算符可以与等号结合,形成一个新的赋值运算符(`**=`)。 + +```javascript +let a = 1.5; +a **= 2; +// 等同于 a = a * a; + +let b = 4; +b **= 3; +// 等同于 b = b * b * b; +``` + +## 链判断运算符 + +编程实务中,如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取`message.body.user.firstName`这个属性,安全的写法是写成下面这样。 + +```javascript +// 错误的写法 +const firstName = message.body.user.firstName || 'default'; + +// 正确的写法 +const firstName = (message + && message.body + && message.body.user + && message.body.user.firstName) || 'default'; +``` + +上面例子中,`firstName`属性在对象的第四层,所以需要判断四次,每一层是否有值。 + +三元运算符`?:`也常用于判断对象是否存在。 + +```javascript +const fooInput = myForm.querySelector('input[name=foo]') +const fooValue = fooInput ? fooInput.value : undefined +``` + +上面例子中,必须先判断`fooInput`是否存在,才能读取`fooInput.value`。 + +这样的层层判断非常麻烦,因此 [ES2020](https://github.com/tc39/proposal-optional-chaining) 引入了“链判断运算符”(optional chaining operator)`?.`,简化上面的写法。 + +```javascript +const firstName = message?.body?.user?.firstName || 'default'; +const fooValue = myForm.querySelector('input[name=foo]')?.value +``` + +上面代码使用了`?.`运算符,直接在链式调用的时候判断,左侧的对象是否为`null`或`undefined`。如果是的,就不再往下运算,而是返回`undefined`。 + +下面是判断对象方法是否存在,如果存在就立即执行的例子。 + +```javascript +iterator.return?.() +``` + +上面代码中,`iterator.return`如果有定义,就会调用该方法,否则`iterator.return`直接返回`undefined`,不再执行`?.`后面的部分。 + +对于那些可能没有实现的方法,这个运算符尤其有用。 + +```javascript +if (myForm.checkValidity?.() === false) { + // 表单校验失败 + return; +} +``` + +上面代码中,老式浏览器的表单对象可能没有`checkValidity()`这个方法,这时`?.`运算符就会返回`undefined`,判断语句就变成了`undefined === false`,所以就会跳过下面的代码。 + +链判断运算符`?.`有三种写法。 + +- `obj?.prop` // 对象属性是否存在 +- `obj?.[expr]` // 同上 +- `func?.(...args)` // 函数或对象方法是否存在 + +下面是`obj?.[expr]`用法的一个例子。 + +```bash +let hex = "#C0FFEE".match(/#([A-Z]+)/i)?.[1]; +``` + +上面例子中,字符串的`match()`方法,如果没有发现匹配会返回`null`,如果发现匹配会返回一个数组,`?.`运算符起到了判断作用。 + +下面是`?.`运算符常见形式,以及不使用该运算符时的等价形式。 + +```javascript +a?.b +// 等同于 +a == null ? undefined : a.b + +a?.[x] +// 等同于 +a == null ? undefined : a[x] + +a?.b() +// 等同于 +a == null ? undefined : a.b() + +a?.() +// 等同于 +a == null ? undefined : a() +``` + +上面代码中,特别注意后两种形式,如果`a?.b()`和`a?.()`。如果`a?.b()`里面的`a.b`有值,但不是函数,不可调用,那么`a?.b()`是会报错的。`a?.()`也是如此,如果`a`不是`null`或`undefined`,但也不是函数,那么`a?.()`会报错。 + +使用这个运算符,有几个注意点。 + +(1)短路机制 + +本质上,`?.`运算符相当于一种短路机制,只要不满足条件,就不再往下执行。 + +```javascript +a?.[++x] +// 等同于 +a == null ? undefined : a[++x] +``` + +上面代码中,如果`a`是`undefined`或`null`,那么`x`不会进行递增运算。也就是说,链判断运算符一旦为真,右侧的表达式就不再求值。 + +(2)括号的影响 + +如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。 + +```javascript +(a?.b).c +// 等价于 +(a == null ? undefined : a.b).c +``` + +上面代码中,`?.`对圆括号外部没有影响,不管`a`对象是否存在,圆括号后面的`.c`总是会执行。 + +一般来说,使用`?.`运算符的场合,不应该使用圆括号。 + +(3)报错场合 + +以下写法是禁止的,会报错。 + +```javascript +// 构造函数 +new a?.() +new a?.b() + +// 链判断运算符的右侧有模板字符串 +a?.`{b}` +a?.b`{c}` + +// 链判断运算符的左侧是 super +super?.() +super?.foo + +// 链运算符用于赋值运算符左侧 +a?.b = c +``` + +(4)右侧不得为十进制数值 + +为了保证兼容以前的代码,允许`foo?.3:0`被解析成`foo ? .3 : 0`,因此规定如果`?.`后面紧跟一个十进制数字,那么`?.`不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。 + +## Null 判断运算符 + +读取对象属性的时候,如果某个属性的值是`null`或`undefined`,有时候需要为它们指定默认值。常见做法是通过`||`运算符指定默认值。 + +```javascript +const headerText = response.settings.headerText || 'Hello, world!'; +const animationDuration = response.settings.animationDuration || 300; +const showSplashScreen = response.settings.showSplashScreen || true; +``` + +上面的三行代码都通过`||`运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为`null`或`undefined`,默认值就会生效,但是属性的值如果为空字符串或`false`或`0`,默认值也会生效。 + +为了避免这种情况,[ES2020](https://github.com/tc39/proposal-nullish-coalescing) 引入了一个新的 Null 判断运算符`??`。它的行为类似`||`,但是只有运算符左侧的值为`null`或`undefined`时,才会返回右侧的值。 + +```javascript +const headerText = response.settings.headerText ?? 'Hello, world!'; +const animationDuration = response.settings.animationDuration ?? 300; +const showSplashScreen = response.settings.showSplashScreen ?? true; +``` + +上面代码中,默认值只有在左侧属性值为`null`或`undefined`时,才会生效。 + +这个运算符的一个目的,就是跟链判断运算符`?.`配合使用,为`null`或`undefined`的值设置默认值。 + +```javascript +const animationDuration = response.settings?.animationDuration ?? 300; +``` + +上面代码中,如果`response.settings`是`null`或`undefined`,或者`response.settings.animationDuration`是`null`或`undefined`,就会返回默认值300。也就是说,这一行代码包括了两级属性的判断。 + +这个运算符很适合判断函数参数是否赋值。 + +```javascript +function Component(props) { + const enable = props.enabled ?? true; + // … +} +``` + +上面代码判断`props`参数的`enabled`属性是否赋值,基本等同于下面的写法。 + +```javascript +function Component(props) { + const { + enabled: enable = true, + } = props; + // … +} +``` + +`??`本质上是逻辑运算,它与其他两个逻辑运算符`&&`和`||`有一个优先级问题,它们之间的优先级到底孰高孰低。优先级的不同,往往会导致逻辑运算的结果不同。 + +现在的规则是,如果多个逻辑运算符一起使用,必须用括号表明优先级,否则会报错。 + +```javascript +// 报错 +lhs && middle ?? rhs +lhs ?? middle && rhs +lhs || middle ?? rhs +lhs ?? middle || rhs +``` + +上面四个表达式都会报错,必须加入表明优先级的括号。 + +```javascript +(lhs && middle) ?? rhs; +lhs && (middle ?? rhs); + +(lhs ?? middle) && rhs; +lhs ?? (middle && rhs); + +(lhs || middle) ?? rhs; +lhs || (middle ?? rhs); + +(lhs ?? middle) || rhs; +lhs ?? (middle || rhs); +``` + +## 逻辑赋值运算符 + +ES2021 引入了三个新的[逻辑赋值运算符](https://github.com/tc39/proposal-logical-assignment)(logical assignment operators),将逻辑运算符与赋值运算符进行结合。 + +```javascript +// 或赋值运算符 +x ||= y +// 等同于 +x || (x = y) + +// 与赋值运算符 +x &&= y +// 等同于 +x && (x = y) + +// Null 赋值运算符 +x ??= y +// 等同于 +x ?? (x = y) +``` + +这三个运算符`||=`、`&&=`、`??=`相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。 + +它们的一个用途是,为变量或属性设置默认值。 + +```javascript +// 老的写法 +user.id = user.id || 1; + +// 新的写法 +user.id ||= 1; +``` + +上面示例中,`user.id`属性如果不存在,则设为`1`,新的写法比老的写法更紧凑一些。 + +下面是另一个例子。 + +```javascript +function example(opts) { + opts.foo = opts.foo ?? 'bar'; + opts.baz ?? (opts.baz = 'qux'); +} +``` + +上面示例中,参数对象`opts`如果不存在属性`foo`和属性`bar`,则为这两个属性设置默认值。有了“Null 赋值运算符”以后,就可以统一写成下面这样。 + +```javascript +function example(opts) { + opts.foo ??= 'bar'; + opts.baz ??= 'qux'; +} +``` + diff --git a/docs/promise.md b/docs/promise.md index 02c1d0611..ea5b04758 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -758,9 +758,25 @@ try { ## Promise.any() -ES2021 引入了[`Promise.any()`方法](https://github.com/tc39/proposal-promise-any)。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。 +ES2021 引入了[`Promise.any()`方法](https://github.com/tc39/proposal-promise-any)。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。 -`Promise.any()`跟`Promise.race()`方法很像,只有一点不同,就是不会因为某个 Promise 变成`rejected`状态而结束。 +```javascript +Promise.any([ + fetch('https://v8.dev/').then(() => 'home'), + fetch('https://v8.dev/blog').then(() => 'blog'), + fetch('https://v8.dev/docs').then(() => 'docs') +]).then((first) => { // 只要有一个 fetch() 请求成功 + console.log(first); +}).catch((error) => { // 所有三个 fetch() 全部请求失败 + console.log(error); +}); +``` + +只要参数实例有一个变成`fulfilled`状态,包装实例就会变成`fulfilled`状态;如果所有参数实例都变成`rejected`状态,包装实例就会变成`rejected`状态。 + +`Promise.any()`跟`Promise.race()`方法很像,只有一点不同,就是`Promise.any()`不会因为某个 Promise 变成`rejected`状态而结束,必须等到所有参数 Promise 变成`rejected`状态才会结束。 + +下面是`Promise()`与`await`命令结合使用的例子。 ```javascript const promises = [ @@ -768,6 +784,7 @@ const promises = [ fetch('/endpoint-b').then(() => 'b'), fetch('/endpoint-c').then(() => 'c'), ]; + try { const first = await Promise.any(promises); console.log(first); @@ -778,30 +795,18 @@ try { 上面代码中,`Promise.any()`方法的参数数组包含三个 Promise 操作。其中只要有一个变成`fulfilled`,`Promise.any()`返回的 Promise 对象就变成`fulfilled`。如果所有三个操作都变成`rejected`,那么`await`命令就会抛出错误。 -`Promise.any()`抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。 +`Promise.any()`抛出的错误,不是一个一般的 Error 错误对象,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被`rejected`的操作所抛出的错误。下面是 AggregateError 的实现示例。 ```javascript -new AggregateError() extends Array -> AggregateError +// new AggregateError() extends Array const err = new AggregateError(); err.push(new Error("first error")); err.push(new Error("second error")); +// ... throw err; ``` -捕捉错误时,如果不用`try...catch`结构和 await 命令,可以像下面这样写。 - -```javascript -Promise.any(promises).then( - (first) => { - // Any of the promises was fulfilled. - }, - (error) => { - // All of the promises were rejected. - } -); -``` - 下面是一个例子。 ```javascript diff --git a/docs/proposals.md b/docs/proposals.md index 543905570..4fb68488b 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -314,83 +314,6 @@ const userAge = userId |> await fetchUserById |> getAgeFromUser; const userAge = getAgeFromUser(await fetchUserById(userId)); ``` -## 数值分隔符 - -欧美语言中,较长的数值允许每三位添加一个分隔符(通常是一个逗号),增加数值的可读性。比如,`1000`可以写作`1,000`。 - -现在有一个[提案](https://github.com/tc39/proposal-numeric-separator),允许 JavaScript 的数值使用下划线(`_`)作为分隔符。 - -```javascript -let budget = 1_000_000_000_000; -budget === 10 ** 12 // true -``` - -JavaScript 的数值分隔符没有指定间隔的位数,也就是说,可以每三位添加一个分隔符,也可以每一位、每两位、每四位添加一个。 - -```javascript -123_00 === 12_300 // true - -12345_00 === 123_4500 // true -12345_00 === 1_234_500 // true -``` - -小数和科学计数法也可以使用数值分隔符。 - -```javascript -// 小数 -0.000_001 -// 科学计数法 -1e10_000 -``` - -数值分隔符有几个使用注意点。 - -- 不能在数值的最前面(leading)或最后面(trailing)。 -- 不能两个或两个以上的分隔符连在一起。 -- 小数点的前后不能有分隔符。 -- 科学计数法里面,表示指数的`e`或`E`前后不能有分隔符。 - -下面的写法都会报错。 - -```javascript -// 全部报错 -3_.141 -3._141 -1_e12 -1e_12 -123__456 -_1464301 -1464301_ -``` - -除了十进制,其他进制的数值也可以使用分隔符。 - -```javascript -// 二进制 -0b1010_0001_1000_0101 -// 十六进制 -0xA0_B0_C0 -``` - -注意,分隔符不能紧跟着进制的前缀`0b`、`0B`、`0o`、`0O`、`0x`、`0X`。 - -```javascript -// 报错 -0_b111111000 -0b_111111000 -``` - -下面三个将字符串转成数值的函数,不支持数值分隔符。主要原因是提案的设计者认为,数值分隔符主要是为了编码时书写数值的方便,而不是为了处理外部输入的数据。 - -- Number() -- parseInt() -- parseFloat() - -```javascript -Number('123_456') // NaN -parseInt('123_456') // 123 -``` - ## Math.signbit() `Math.sign()`用来判断一个值的正负,但是如果参数是`-0`,它会返回`-0`。 diff --git a/docs/set-map.md b/docs/set-map.md index 0fcfde883..ac2ca3e79 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -1140,3 +1140,151 @@ c.dec() 上面代码中,`Countdown`类的两个内部属性`_counter`和`_action`,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。 +## WeakRef + +WeakSet 和 WeakMap 是基于弱引用的数据结构,[ES2021](https://github.com/tc39/proposal-weakrefs) 更进一步,提供了 WeakRef 对象,用于直接创建对象的弱引用。 + +```javascript +let target = {}; +let wr = new WeakRef(target); +``` + +上面示例中,`target`是原始对象,构造函数`WeakRef()`创建了一个基于`target`的新对象`wr`。这里,`wr`就是一个 WeakRef 的示例,属于对`target`的弱引用,垃圾回收机制不会计入这个引用,也就是说,`wr`的引入不会妨碍原始对象`target`被垃圾回收机制清除。 + +WeakRef 实例对象有一个`deref()`方法,如果原始对象存在,该方法返回原始对象;如果原始对象已经被垃圾回收机制清除,该方法返回`undefined`。 + +```javascript +let target = {}; +let wr = new WeakRef(target); + +let obj = wr.deref(); +if (obj) { // target 未被垃圾回收机制清除 + // ... +} +``` + +上面示例中,`deref()`方法可以判断原始对象是否已被清除。 + +弱引用对象的一大用处,就是作为缓存,未被清除时可以从缓存取值,一旦清除缓存就自动失效。 + +```javascript +function makeWeakCached(f) { + const cache = new Map(); + return key => { + const ref = cache.get(key); + if (ref) { + const cached = ref.deref(); + if (cached !== undefined) return cached; + } + + const fresh = f(key); + cache.set(key, new WeakRef(fresh)); + return fresh; + }; +} + +const getImageCached = makeWeakCached(getImage); +``` + +上面示例中,`makeWeakCached()`用于建立一个缓存,缓存里面保存对原始文件的弱引用。 + +注意,标准规定,一旦使用`WeakRef()`创建了原始对象的弱引用,那么在本轮事件循环(event loop),原始对象肯定不会被清除,只会在后面的事件循环才会被清除。 + +## FinalizationRegistry + +[ES2021](https://github.com/tc39/proposal-weakrefs#finalizers) 引入了清理器注册表功能 FinalizationRegistry,用来指定目标对象被垃圾回收机制清除以后,所要执行的回调函数。 + +首先,新建一个注册表实例。 + +```javascript +const registry = new FinalizationRegistry(heldValue => { + // .... +}); +``` + +上面代码中,`FinalizationRegistry()`是系统提供的构造函数,返回一个清理器注册表实例,里面登记了所要执行的回调函数。回调函数作为`FinalizationRegistry()`的参数传入,它本身有一个参数`heldValue`。 + +然后,注册表实例的`register()`方法,用来注册所要观察的目标对象。 + +```javascript +registry.register(theObject, "some value"); +``` + +上面示例中,`theObject`就是所要观察的目标对象,一旦该对象被垃圾回收机制清除,注册表就会在清除完成后,调用早前注册的回调函数,并将`some value`作为参数(前面的`heldValue`)传入回调函数。 + +注意,注册表不对目标对象`theObject`构成强引用,属于弱引用。因为强引用的话,原始对象就不会被垃圾回收机制清除,这就失去使用注册表的意义了。 + +回调函数的参数`heldValue`可以是任意类型的值,字符串、数值、布尔值、对象,甚至可以是`undefined`。 + +最后,如果以后还想取消已经注册的回调函数,则要向`register()`传入第三个参数,作为标记值。这个标记值必须是对象,一般都用原始对象。接着,再使用注册表实例对象的`unregister()`方法取消注册。 + +```javascript +registry.register(theObject, "some value", theObject); +// ...其他操作... +registry.unregister(theObject); +``` + +上面代码中,`register()`方法的第三个参数就是标记值`theObject`。取消回调函数时,要使用`unregister()`方法,并将标记值作为该方法的参数。这里`register()`方法对第三个参数的引用,也属于弱引用。如果没有这个参数,则回调函数无法取消。 + +由于回调函数被调用以后,就不再存在于注册表之中了,所以执行`unregister()`应该是在回调函数还没被调用之前。 + +下面使用`FinalizationRegistry`,对前一节的缓存函数进行增强。 + +```javascript +function makeWeakCached(f) { + const cache = new Map(); + const cleanup = new FinalizationRegistry(key => { + const ref = cache.get(key); + if (ref && !ref.deref()) cache.delete(key); + }); + + return key => { + const ref = cache.get(key); + if (ref) { + const cached = ref.deref(); + if (cached !== undefined) return cached; + } + + const fresh = f(key); + cache.set(key, new WeakRef(fresh)); + cleanup.register(fresh, key); + return fresh; + }; +} + +const getImageCached = makeWeakCached(getImage); +``` + +上面示例与前一节的例子相比,就是增加一个清理器注册表,一旦缓存的原始对象被垃圾回收机制清除,会自动执行一个回调函数。该回调函数会清除缓存里面已经失效的键。 + +下面是另一个例子。 + +```javascript +class Thingy { + #file; + #cleanup = file => { + console.error( + `The \`release\` method was never called for the \`Thingy\` for the file "${file.name}"` + ); + }; + #registry = new FinalizationRegistry(this.#cleanup); + + constructor(filename) { + this.#file = File.open(filename); + this.#registry.register(this, this.#file, this.#file); + } + + release() { + if (this.#file) { + this.#registry.unregister(this.#file); + File.close(this.#file); + this.#file = null; + } + } +} +``` + +上面示例中,如果由于某种原因,`Thingy`类的实例对象没有调用`release()`方法,就被垃圾回收机制清除了,那么清理器就会调用回调函数`#cleanup()`,输出一条错误信息。 + +由于无法知道清理器何时会执行,所以最好避免使用它。另外,如果浏览器窗口关闭或者进程意外退出,清理器则不会运行。 + From 9181f78036646474aa597ae91542b411bf1bc4f2 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sat, 10 Jul 2021 14:59:18 +0800 Subject: [PATCH 151/280] refactor: update chapters --- sidebar.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sidebar.md b/sidebar.md index 8d3579f54..1d167e9c4 100644 --- a/sidebar.md +++ b/sidebar.md @@ -17,6 +17,7 @@ 1. [数组的扩展](#docs/array) 1. [对象的扩展](#docs/object) 1. [对象的新增方法](#docs/object-methods) +1. [运算符的扩展](#docs/operator) 1. [Symbol](#docs/symbol) 1. [Set 和 Map 数据结构](#docs/set-map) 1. [Proxy](#docs/proxy) From 145e84021d12ece8be356760efefaaf58bbb407b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B0=B8=E8=BE=89?= <39959340+yonghui-wang@users.noreply.github.com> Date: Wed, 14 Jul 2021 17:02:26 +0800 Subject: [PATCH 152/280] Update async.md console.log missing a ) --- docs/async.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/async.md b/docs/async.md index 532c1a366..5d9376a79 100644 --- a/docs/async.md +++ b/docs/async.md @@ -771,7 +771,7 @@ import { output } from "./awaiting.js"; function outputPlusValue(value) { return output + value } console.log(outputPlusValue(100)); -setTimeout(() => console.log(outputPlusValue(100), 1000); +setTimeout(() => console.log(outputPlusValue(100)), 1000); ``` 上面代码中,`outputPlusValue()`的执行结果,完全取决于执行的时间。如果`awaiting.js`里面的异步操作没执行完,加载进来的`output`的值就是`undefined`。 @@ -801,7 +801,7 @@ function outputPlusValue(value) { return output + value } promise.then(() => { console.log(outputPlusValue(100)); - setTimeout(() => console.log(outputPlusValue(100), 1000); + setTimeout(() => console.log(outputPlusValue(100)), 1000); }); ``` @@ -828,7 +828,7 @@ import { output } from "./awaiting.js"; function outputPlusValue(value) { return output + value } console.log(outputPlusValue(100)); -setTimeout(() => console.log(outputPlusValue(100), 1000); +setTimeout(() => console.log(outputPlusValue(100)), 1000); ``` 上面代码的写法,与普通的模块加载完全一样。也就是说,模块的使用者完全不用关心,依赖模块的内部有没有异步操作,正常加载即可。 From 6a972dfdee91c752709089cdb5ecb16c72db2ad8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 15 Jul 2021 00:12:22 +0800 Subject: [PATCH 153/280] docs(operator): fix WeakRef typo --- docs/set-map.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/set-map.md b/docs/set-map.md index ac2ca3e79..cc6159db1 100644 --- a/docs/set-map.md +++ b/docs/set-map.md @@ -1149,7 +1149,7 @@ let target = {}; let wr = new WeakRef(target); ``` -上面示例中,`target`是原始对象,构造函数`WeakRef()`创建了一个基于`target`的新对象`wr`。这里,`wr`就是一个 WeakRef 的示例,属于对`target`的弱引用,垃圾回收机制不会计入这个引用,也就是说,`wr`的引入不会妨碍原始对象`target`被垃圾回收机制清除。 +上面示例中,`target`是原始对象,构造函数`WeakRef()`创建了一个基于`target`的新对象`wr`。这里,`wr`就是一个 WeakRef 的实例,属于对`target`的弱引用,垃圾回收机制不会计入这个引用,也就是说,`wr`的引用不会妨碍原始对象`target`被垃圾回收机制清除。 WeakRef 实例对象有一个`deref()`方法,如果原始对象存在,该方法返回原始对象;如果原始对象已经被垃圾回收机制清除,该方法返回`undefined`。 From 9d51292b694a15eb19f8864c6660f3e01fde2a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?bieyanghong=E3=80=82?= <41629560+ThroughTheNight@users.noreply.github.com> Date: Thu, 22 Jul 2021 17:11:21 +0800 Subject: [PATCH 154/280] Update let.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加一个解释,方便刚入门的同学理解。 --- docs/let.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/let.md b/docs/let.md index 9bb8782d1..8a7c3ea17 100644 --- a/docs/let.md +++ b/docs/let.md @@ -71,7 +71,7 @@ for (let i = 0; i < 3; i++) { // abc ``` -上面代码正确运行,输出了 3 次`abc`。这表明函数内部的变量`i`与循环变量`i`不在同一个作用域,有各自单独的作用域。 +上面代码正确运行,输出了 3 次`abc`。这表明函数内部的变量`i`与循环变量`i`不在同一个作用域,有各自单独的作用域(同一个作用域不可使用 `let` 重复声明同一个变量)。 ### 不存在变量提升 From 864c2c09aaf364fe90b4b3fe58001cf72d54fad2 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 22 Jul 2021 17:17:02 +0800 Subject: [PATCH 155/280] =?UTF-8?q?docs(proposals):=20JSON=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/proposals.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/proposals.md b/docs/proposals.md index 4fb68488b..c565d01be 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -555,3 +555,47 @@ import.meta.scriptElement.dataset.foo // "abc" ``` +## JSON 模块 + +import 命令目前只能用于加载 ES 模块,现在有一个[提案](https://github.com/tc39/proposal-json-modules),允许加载 JSON 模块。 + +假定有一个 JSON 模块文件`config.json`。 + +```javascript +{ + "appName": "My App" +} +``` + +目前,只能使用`fetch()`加载 JSON 模块。 + +```javascript +const response = await fetch('./config.json'); +const json = await response.json(); +``` + +import 命令能够直接加载 JSON 模块以后,就可以像下面这样写。 + +```javascript +import configData from './config.json' assert { type: "json" }; +console.log(configData.appName); +``` + +上面示例中,整个 JSON 对象被导入为`configData`对象,然后就可以从该对象获取 JSON 数据。 + +`import`命令导入 JSON 模块时,命令结尾的`assert {type: "json"}`不可缺少。这叫做导入断言,用来告诉 JavaScript 引擎,现在加载的是 JSON 模块。你可能会问,为什么不通过`.json`后缀名判断呢?因为浏览器的传统是不通过后缀名判断文件类型,标准委员会希望遵循这种做法,这样也可以避免一些安全问题。 + +导入断言是 JavaScript 导入其他格式模块的标准写法,JSON 模块将是第一个使用这种语法导入的模块。以后,还会支持导入 CSS 模块、HTML 模块等等。 + +动态加载模块的`import()`函数也支持加载 JSON 模块。 + +```javascript +import('./config.json', { assert: { type: 'json' } }) +``` + +脚本加载 JSON 模块以后,还可以再用 export 命令输出。这时,可以将 export 和 import 结合成一个语句。 + +```javascript +export { config } from './config.json' assert { type: 'json' }; +``` + From 4ead6a067fbf4f9e8592167fd2988aa29096a8e6 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 28 Jul 2021 01:32:44 +0800 Subject: [PATCH 156/280] docs(class): fix typo #1065 --- docs/class.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/class.md b/docs/class.md index 391194087..868ea8359 100644 --- a/docs/class.md +++ b/docs/class.md @@ -1005,7 +1005,7 @@ class A { } } -class SubA extend A {}; +class SubA extends A {}; A.test(new SubA()) // true ``` From 1f57596bb112291093c6a05982bdf6a7d2f2db1f Mon Sep 17 00:00:00 2001 From: "Mr.Care" <50878376+MrCare@users.noreply.github.com> Date: Sat, 31 Jul 2021 14:38:19 +0800 Subject: [PATCH 157/280] =?UTF-8?q?=E4=B8=80=E5=A4=84=E7=AC=94=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在字符串 replaceAll()方法中的第二个参数的说明部分,按照笔者的意思`$&` 应当代表匹配的“字符串”,而不是匹配的“子字符串” --- docs/string-methods.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/string-methods.md b/docs/string-methods.md index 2fbb76174..1d4b3fc06 100644 --- a/docs/string-methods.md +++ b/docs/string-methods.md @@ -376,7 +376,7 @@ String.prototype.replaceAll(searchValue, replacement) `replaceAll()`的第二个参数`replacement`是一个字符串,表示替换的文本,其中可以使用一些特殊字符串。 -- `$&`:匹配的子字符串。 +- `$&`:匹配的字符串。 - `` $` ``:匹配结果前面的文本。 - `$'`:匹配结果后面的文本。 - `$n`:匹配成功的第`n`组内容,`n`是从1开始的自然数。这个参数生效的前提是,第一个参数必须是正则表达式。 From 9cf2cca1a8e15f735f5056537fc164948bcf7721 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 4 Aug 2021 16:27:37 +0800 Subject: [PATCH 158/280] refactor: edit support banner --- js/ditto.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index fd92bf0bc..0d1e7218e 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -243,8 +243,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2021年6月7日 - var deadline = new Date(2021, 5, 7); + // 2021年9月6日 + var deadline = new Date(2021, 8, 6); if (deadline - (new Date()) < 0) return; var styleStr = [ @@ -258,10 +258,10 @@ function create_banner(element) { 'color: #333333' ].join(';'); - var text = '【前端课程】' + - '《React Hooks 核心原理与实战》通过实战案例,详细讲解 Hooks,现在优惠中。'; + var text = '【免费课程】' + + '开课吧《深度理解 Vue 3.0 核心源码》精品课领取,讲解 Vue 3.0 源码,动手制作实战项目,快速上手Vue 3.0。'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } From 562c688e3d654f85af523438858472e11fba8700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?bieyanghong=E3=80=82?= <41629560+ThroughTheNight@users.noreply.github.com> Date: Fri, 6 Aug 2021 15:13:37 +0800 Subject: [PATCH 159/280] Update function.md --- docs/function.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/function.md b/docs/function.md index d5a8e4eda..e59b7bda1 100644 --- a/docs/function.md +++ b/docs/function.md @@ -418,7 +418,7 @@ add(2, 5, 3) // 10 ```javascript // arguments变量的写法 function sortNumbers() { - return Array.prototype.slice.call(arguments).sort(); + return Array.from(arguments).sort(); } // rest参数的写法 @@ -427,7 +427,7 @@ const sortNumbers = (...numbers) => numbers.sort(); 上面代码的两种写法,比较后可以发现,rest 参数的写法更自然也更简洁。 -`arguments`对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用`Array.prototype.slice.call`先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组`push`方法的例子。 +`arguments`对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用`Array.from`先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。下面是一个利用 rest 参数改写数组`push`方法的例子。 ```javascript function push(array, ...items) { From 0549983c536f4d5cda1760b8874d163403be3dfa Mon Sep 17 00:00:00 2001 From: An Hongpeng <79783808+roc-an@users.noreply.github.com> Date: Fri, 6 Aug 2021 17:22:59 +0800 Subject: [PATCH 160/280] =?UTF-8?q?fix:=20BigInt=20=E4=B8=8D=E6=98=AF?= =?UTF-8?q?=E6=9E=84=E9=80=A0=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/number.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/number.md b/docs/number.md index c21067e00..e9904c567 100644 --- a/docs/number.md +++ b/docs/number.md @@ -830,9 +830,9 @@ for (let i = 1n; i <= 70n; i++) { console.log(p); // 11978571...00000000n ``` -### BigInt 对象 +### BigInt 函数 -JavaScript 原生提供`BigInt`对象,可以用作构造函数生成 BigInt 类型的数值。转换规则基本与`Number()`一致,将其他类型的值转为 BigInt。 +JavaScript 原生提供`BigInt`函数,可以用它生成 BigInt 类型的数值。转换规则基本与`Number()`一致,将其他类型的值转为 BigInt。 ```javascript BigInt(123) // 123n @@ -841,7 +841,7 @@ BigInt(false) // 0n BigInt(true) // 1n ``` -`BigInt()`构造函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。 +`BigInt()`函数必须有参数,而且参数必须可以正常转为数值,下面的用法都会报错。 ```javascript new BigInt() // TypeError @@ -860,7 +860,7 @@ BigInt(1.5) // RangeError BigInt('1.5') // SyntaxError ``` -BigInt 对象继承了 Object 对象的两个实例方法。 +BigInt 继承了 Object 对象的两个实例方法。 - `BigInt.prototype.toString()` - `BigInt.prototype.valueOf()` From a802e37d78d8c11be6b6c9fb8c5c0848a923c575 Mon Sep 17 00:00:00 2001 From: newbeman Date: Sat, 7 Aug 2021 17:01:04 +0800 Subject: [PATCH 161/280] Update operator.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 参数对象`opts`如果不存在属性`foo`和属性`bar` 变更为 参数对象`opts`如果不存在属性`foo`和属性`baz` --- docs/operator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/operator.md b/docs/operator.md index e3958b43a..6d72c666c 100644 --- a/docs/operator.md +++ b/docs/operator.md @@ -298,7 +298,7 @@ function example(opts) { } ``` -上面示例中,参数对象`opts`如果不存在属性`foo`和属性`bar`,则为这两个属性设置默认值。有了“Null 赋值运算符”以后,就可以统一写成下面这样。 +上面示例中,参数对象`opts`如果不存在属性`foo`和属性`baz`,则为这两个属性设置默认值。有了“Null 赋值运算符”以后,就可以统一写成下面这样。 ```javascript function example(opts) { From de8fad91c77948e28dd08451c5205baa0b571101 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Mon, 9 Aug 2021 17:20:00 +0800 Subject: [PATCH 162/280] docs(promise): edit Promise.allSettled() --- docs/promise.md | 58 +++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/docs/promise.md b/docs/promise.md index ea5b04758..0474ceed5 100644 --- a/docs/promise.md +++ b/docs/promise.md @@ -691,7 +691,27 @@ p ## Promise.allSettled() -`Promise.allSettled()`方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是`fulfilled`还是`rejected`,包装实例才会结束。该方法由 [ES2020](https://github.com/tc39/proposal-promise-allSettled) 引入。 +有时候,我们希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。但是,现有的 Promise 方法很难实现这个要求。 + +`Promise.all()`方法只适合所有异步操作都成功的情况,如果有一个操作失败,就无法满足要求。 + +```javascript +const urls = [url_1, url_2, url_3]; +const requests = urls.map(x => fetch(x)); + +try { + await Promise.all(requests); + console.log('所有请求都成功。'); +} catch { + console.log('至少一个请求失败,其他请求可能还没结束。'); +} +``` + +上面示例中,`Promise.all()`可以确定所有请求都成功了,但是只要有一个请求失败,它就会报错,而不管另外的请求是否结束。 + +为了解决这个问题,[ES2020](https://github.com/tc39/proposal-promise-allSettled) 引入了`Promise.allSettled()`方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。 + +`Promise.allSettled()`方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是`fulfilled`还是`rejected`),返回的 Promise 对象才会发生状态变更。 ```javascript const promises = [ @@ -704,9 +724,9 @@ await Promise.allSettled(promises); removeLoadingIndicator(); ``` -上面代码对服务器发出三个请求,等到三个请求都结束,不管请求成功还是失败,加载的滚动图标就会消失。 +上面示例中,数组`promises`包含了三个请求,只有等到这三个请求都结束了(不管请求成功还是失败),`removeLoadingIndicator()`才会执行。 -该方法返回的新的 Promise 实例,一旦结束,状态总是`fulfilled`,不会变成`rejected`。状态变成`fulfilled`后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入`Promise.allSettled()`的 Promise 实例。 +该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是`fulfilled`,不会变成`rejected`。状态变成`fulfilled`后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。 ```javascript const resolved = Promise.resolve(42); @@ -723,9 +743,21 @@ allSettledPromise.then(function (results) { // ] ``` -上面代码中,`Promise.allSettled()`的返回值`allSettledPromise`,状态只可能变成`fulfilled`。它的监听函数接收到的参数是数组`results`。该数组的每个成员都是一个对象,对应传入`Promise.allSettled()`的两个 Promise 实例。每个对象都有`status`属性,该属性的值只可能是字符串`fulfilled`或字符串`rejected`。`fulfilled`时,对象有`value`属性,`rejected`时有`reason`属性,对应两种状态的返回值。 +上面代码中,`Promise.allSettled()`的返回值`allSettledPromise`,状态只可能变成`fulfilled`。它的回调函数接收到的参数是数组`results`。该数组的每个成员都是一个对象,对应传入`Promise.allSettled()`的数组里面的两个 Promise 对象。 + +`results`的每个成员是一个对象,对象的格式是固定的,对应异步操作的结果。 + +```javascript +// 异步操作成功时 +{status: 'fulfilled', value: value} + +// 异步操作失败时 +{status: 'rejected', reason: reason} +``` + +成员对象的`status`属性的值只可能是字符串`fulfilled`或字符串`rejected`,用来区分异步操作是成功还是失败。如果是成功(`fulfilled`),对象会有`value`属性,如果是失败(`rejected`),会有`reason`属性,对应两种状态时前面异步操作的返回值。 -下面是返回值用法的例子。 +下面是返回值的用法例子。 ```javascript const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ]; @@ -740,22 +772,6 @@ const errors = results .map(p => p.reason); ``` -有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,`Promise.allSettled()`方法就很有用。如果没有这个方法,想要确保所有操作都结束,就很麻烦。`Promise.all()`方法无法做到这一点。 - -```javascript -const urls = [ /* ... */ ]; -const requests = urls.map(x => fetch(x)); - -try { - await Promise.all(requests); - console.log('所有请求都成功。'); -} catch { - console.log('至少一个请求失败,其他请求可能还没结束。'); -} -``` - -上面代码中,`Promise.all()`无法确定所有请求都结束。想要达到这个目的,写起来很麻烦,有了`Promise.allSettled()`,这就很容易了。 - ## Promise.any() ES2021 引入了[`Promise.any()`方法](https://github.com/tc39/proposal-promise-any)。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。 From 47dec55486be2010a16a41cc0e8af6bc9e42e4f2 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 2 Sep 2021 08:29:12 +0800 Subject: [PATCH 163/280] =?UTF-8?q?docs(class):=20=E5=8A=A0=E5=85=A5=20sta?= =?UTF-8?q?tic=20block?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/class.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/docs/class.md b/docs/class.md index 868ea8359..69cff93c1 100644 --- a/docs/class.md +++ b/docs/class.md @@ -1035,6 +1035,87 @@ A.test(o2.__proto__) // true 上面示例中,对于修改原型链形成的继承,子类都取不到父类的私有属性,所以`in`运算符无效。 +## 静态块 + +静态属性的一个问题是,它的初始化要么写在类的外部,要么写在`constructor()`方法里面。 + +```javascript +class C { + static x = 234; + static y; + static z; +} + +try { + const obj = doSomethingWith(C.x); + C.y = obj.y + C.z = obj.z; +} catch { + C.y = ...; + C.z = ...; +} +``` + +上面示例中,静态属性`y`和`z`的值依赖静态属性`x`,它们的初始化写在类的外部(上例的`try...catch`代码块)。另一种方法是写到类的`constructor()`方法里面。这两种方法都不是很理想,前者是将类的内部逻辑写到了外部,后者则是每次新建实例都会运行一次。 + +为了解决这个问题,ES2022 引入了[静态块](https://github.com/tc39/proposal-class-static-block)(static block),允许在类的内部设置一个代码块,在类生成时运行一次,主要作用是对静态属性进行初始化。 + +```javascript +class C { + static x = ...; + static y; + static z; + + static { + try { + const obj = doSomethingWith(this.x); + this.y = obj.y; + this.z = obj.z; + } + catch { + this.y = ...; + this.z = ...; + } + } +} +``` + +上面代码中,类的内部有一个 static 代码块,这就是静态块。它的好处是将静态属性`y`和`z`的初始化逻辑,写入了类的内部,而且只运行一次。 + +每个类只能有一个静态块,在静态属性声明后运行。静态块的内部不能有`return`语句。 + +静态块内部可以使用类名或`this`,指代当前类。 + +```c +class C { + static x = 1; + static { + this.x; // 1 + // 或者 + C.x; // 1 + } +} +``` + +上面示例中,`this.x`和`C.x`都能获取静态属性`x`。 + +除了静态属性的初始化,静态块还有一个作用,就是将私有属性与类的外部代码分享。 + +```javascript +let getX; + +export class C { + #x = 1; + static { + getX = obj => obj.#x; + } +} + +console.log(getX(new C())); // 1 +``` + +上面示例中,`#x`是类的私有属性,如果类外部的`getX()`方法希望获取这个属性,以前是要写在类的`constructor()`方法里面,这样的话,每次新建实例都会定义一次`getX()`方法。现在可以写在静态块里面,这样的话,只在类生成时定义一次。 + ## new.target 属性 `new`是从构造函数生成实例对象的命令。ES6 为`new`命令引入了一个`new.target`属性,该属性一般用在构造函数之中,返回`new`命令作用于的那个构造函数。如果构造函数不是通过`new`命令或`Reflect.construct()`调用的,`new.target`会返回`undefined`,因此这个属性可以用来确定构造函数是怎么调用的。 From d50868c4bb65d760e07eb8fa9fb3b5976b98d6a1 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Thu, 2 Sep 2021 20:03:21 +0800 Subject: [PATCH 164/280] refactor: edit support banner --- js/ditto.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/ditto.js b/js/ditto.js index 0d1e7218e..f0565c2d0 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -243,8 +243,8 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { - // 2021年9月6日 - var deadline = new Date(2021, 8, 6); + // 2021年10月9日 + var deadline = new Date(2021, 9, 9); if (deadline - (new Date()) < 0) return; var styleStr = [ @@ -261,7 +261,7 @@ function create_banner(element) { var text = '【免费课程】' + '开课吧《深度理解 Vue 3.0 核心源码》精品课领取,讲解 Vue 3.0 源码,动手制作实战项目,快速上手Vue 3.0。'; - var banner = $('
    ' + text + '
    ') + var banner = $('
    ' + text + '
    ') .insertAfter(element); } From 84dba5689b5b718a445ea6b9992ad787e9143d4e Mon Sep 17 00:00:00 2001 From: ruanyf Date: Wed, 8 Sep 2021 18:37:50 +0800 Subject: [PATCH 165/280] docs(style): fixed #1076 --- docs/style.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/style.md b/docs/style.md index 42fd61da6..45f7d88c0 100644 --- a/docs/style.md +++ b/docs/style.md @@ -437,16 +437,6 @@ export default Breadcrumbs; 如果模块只有一个输出值,就使用`export default`,如果模块有多个输出值,就不使用`export default`,`export default`与普通的`export`不要同时使用。 -不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。 - -```javascript -// bad -import * as myObject from './importModule'; - -// good -import myObject from './importModule'; -``` - 如果模块默认输出一个函数,函数名的首字母应该小写。 ```javascript From 248b51a5df069e6dfdc774b199a911d8ce5768e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Wed, 8 Sep 2021 21:25:53 +0800 Subject: [PATCH 166/280] =?UTF-8?q?fix=20typo:=20=E6=88=96=20->=20?= =?UTF-8?q?=E4=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/module-loader.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module-loader.md b/docs/module-loader.md index 22a2e5047..18b4f4c47 100644 --- a/docs/module-loader.md +++ b/docs/module-loader.md @@ -439,7 +439,7 @@ import submodule from './node_modules/es-module-package/private-module.js'; } ``` -注意,如果同时还有其他别名,就不能采用简写,否则或报错。 +注意,如果同时还有其他别名,就不能采用简写,否则会报错。 ```javascript { From daa455fb2ac2db87f424d42ad9b7849fc7f54449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Thu, 9 Sep 2021 09:14:43 +0800 Subject: [PATCH 167/280] use https for some links --- 404.html | 2 +- README.md | 2 +- book.json | 2 +- docs/class-extends.md | 2 +- docs/let.md | 2 +- docs/proposals.md | 2 +- docs/reference.md | 82 +++++++++++++++++++++---------------------- docs/spec.md | 10 +++--- sidebar.md | 6 ++-- 9 files changed, 55 insertions(+), 55 deletions(-) diff --git a/404.html b/404.html index 66b119741..f70820f17 100644 --- a/404.html +++ b/404.html @@ -2,7 +2,7 @@ ES6标准参考教程 + +// my-module.js 内部执行下面的代码 +import.meta.scriptElement.dataset.foo +// "abc" +``` + diff --git a/docs/proposals.md b/docs/proposals.md index d33632185..447151a5a 100644 --- a/docs/proposals.md +++ b/docs/proposals.md @@ -524,37 +524,6 @@ $ ./hello.js 对于 JavaScript 引擎来说,会把`#!`理解成注释,忽略掉这一行。 -## import.meta - -开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。现在有一个[提案](https://github.com/tc39/proposal-import-meta),为 import 命令添加了一个元属性`import.meta`,返回当前模块的元信息。 - -`import.meta`只能在模块内部使用,如果在模块外部使用会报错。 - -这个属性返回一个对象,该对象的各种属性就是当前运行的脚本的元信息。具体包含哪些属性,标准没有规定,由各个运行环境自行决定。一般来说,`import.meta`至少会有下面两个属性。 - -**(1)import.meta.url** - -`import.meta.url`返回当前模块的 URL 路径。举例来说,当前模块主文件的路径是`https://foo.com/main.js`,`import.meta.url`就返回这个路径。如果模块里面还有一个数据文件`data.txt`,那么就可以用下面的代码,获取这个数据文件的路径。 - -```javascript -new URL('data.txt', import.meta.url) -``` - -注意,Node.js 环境中,`import.meta.url`返回的总是本地路径,即是`file:URL`协议的字符串,比如`file:///home/user/foo.js`。 - -**(2)import.meta.scriptElement** - -`import.meta.scriptElement`是浏览器特有的元属性,返回加载模块的那个` - -// my-module.js 内部执行下面的代码 -import.meta.scriptElement.dataset.foo -// "abc" -``` - ## JSON 模块 import 命令目前只能用于加载 ES 模块,现在有一个[提案](https://github.com/tc39/proposal-json-modules),允许加载 JSON 模块。 From 406ba6da6d6996ae8d6a48bb915c1522d16c9dbf Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 23 Oct 2022 15:12:22 +0800 Subject: [PATCH 225/280] docs(module): fix import.meta typo --- docs/module.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/module.md b/docs/module.md index 7e97c43ea..0f2481587 100644 --- a/docs/module.md +++ b/docs/module.md @@ -824,7 +824,7 @@ main(); ## import.meta -开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。[ES2020](https://github.com/tc39/proposal-import-meta),为 import 命令添加了一个元属性`import.meta`,返回当前模块的元信息。 +开发者使用一个模块时,有时需要知道模板本身的一些信息(比如模块的路径)。[ES2020](https://github.com/tc39/proposal-import-meta) 为 import 命令添加了一个元属性`import.meta`,返回当前模块的元信息。 `import.meta`只能在模块内部使用,如果在模块外部使用会报错。 From ef2f8784732df5483c49c72f470d3f0177a2e7f8 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 23 Oct 2022 18:00:26 +0800 Subject: [PATCH 226/280] docs(decorator): edit text --- docs/decorator.md | 78 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/docs/decorator.md b/docs/decorator.md index b0db3e43a..a5b90603d 100644 --- a/docs/decorator.md +++ b/docs/decorator.md @@ -2,9 +2,18 @@ [说明] Decorator 提案经历了重大的语法变化,目前处于第三阶段,定案之前不知道是否还有变化。本章现在属于草稿阶段,凡是标注“新语法”的章节,都是基于当前的语法,不过没有详细整理,只是一些原始材料;未标注“新语法”的章节基于以前的语法,是过去遗留的稿子。之所以保留以前的内容,有两个原因,一是 TypeScript 装饰器会用到这些语法,二是里面包含不少有价值的内容。等到标准完全定案,本章将彻底重写:删去过时内容,补充材料,增加解释。(2022年6月) +## 简介(新语法) + 装饰器(Decorator)用来增强 JavaScript 类(class)的功能,许多面向对象的语言都有这种语法,目前有一个[提案](https://github.com/tc39/proposal-decorators)将其引入了 ECMAScript。 -装饰器是一种函数,写成`@ + 函数名`。它可以放在类和类方法的定义前面。 +装饰器是一种函数,写成`@ + 函数名`,可以用来装饰四种类型的值。 + +- 类 +- 类的属性 +- 类的方法 +- 属性存取器(accessor) + +下面的例子是装饰器放在类名和类方法名之前,大家可以感受一下写法。 ```javascript @frozen class Foo { @@ -17,16 +26,7 @@ } ``` -上面代码一共使用了四个装饰器,一个用在类本身,另外三个用在类方法。它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能。 - -## 装饰器的种类(新语法) - -装饰器可以用来装饰四种类型的值。 - -- 类 -- 类的属性(public, private, and static) -- 类的方法(public, private, and static) -- 属性存取器(accessor)(public, private, and static) +上面代码一共使用了四个装饰器,一个用在类本身(@frozen),另外三个用在类方法(@configurable()、@enumerable()、@throttle())。它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能。 ## 装饰器 API(新语法) @@ -46,16 +46,16 @@ type Decorator = (value: Input, context: { }) => Output | void; ``` -装饰器函数调用时,会接收到两个参数。 +装饰器函数有两个参数。运行时,JavaScript 引擎会提供这两个参数。 -- `value`:被装饰的值,某些情况下可能是`undefined`(装饰属性时)。 -- `context`:一个对象,包含了被装饰的值的上下文信息。 +- `value`:所要装饰的值,某些情况下可能是`undefined`(装饰属性时)。 +- `context`:上下文信息对象。 -另外,`input`和`output`表示输入的值和输出的值,每种装饰器都不一样,放在后面介绍。所有装饰器都可以不返回任何值。 +装饰器函数的返回值,是一个新版本的装饰对象,但也可以不返回任何值(void)。 -`context`对象的属性如下。 +`context`对象有很多属性,其中`kind`属性表示属于哪一种装饰,其他属性的含义如下。 -- `kind`:字符串,表示装饰类型,可能取值有`class`、`method`、`getter`、`setter`、`field`、`accessor`。 +- `kind`:字符串,表示装饰类型,可能的取值有`class`、`method`、`getter`、`setter`、`field`、`accessor`。 - `name`:被装饰的值的名称: The name of the value, or in the case of private elements the description of it (e.g. the readable name). - `access`:对象,包含访问这个值的方法,即存值器和取值器。 - `static`: 布尔值,该值是否为静态元素。 @@ -257,6 +257,22 @@ new C(1); ## 方法装饰器(新语法) +方法装饰器会修改类的方法。 + +```javascript +class C { + @trace + toString() { + return 'C'; + } +} + +// 相当于 +C.prototype.toString = trace(C.prototype.toString); +``` + +上面示例中,`@trace`装饰`toString()`方法,就相当于修改了该方法。 + 方式装饰器使用 TypeScript 描述类型如下。 ```typescript @@ -272,9 +288,31 @@ type ClassMethodDecorator = (value: Function, context: { 方法装饰器的第一个参数`value`,就是所要装饰的方法。 -方法装饰器可以返回一个新方法,取代原来的方法,也可以不返回值,表示依然使用原来的方法。如果返回其他类型的值,就会报错。 +方法装饰器可以返回一个新函数,取代原来的方法,也可以不返回值,表示依然使用原来的方法。如果返回其他类型的值,就会报错。下面是一个例子。 -下面是一个例子。 +```javascript +function replaceMethod() { + return function () { + return `How are you, ${this.name}?`; + } +} + +class Person { + constructor(name) { + this.name = name; + } + @replaceMethod + hello() { + return `Hi ${this.name}!`; + } +} + +const robin = new Person('Robin'); + +robin.hello(), 'How are you, Robin?' +``` + +上面示例中,`@replaceMethod`返回了一个新函数,取代了原来的`hello()`方法。 ```typescript function logged(value, { kind, name }) { @@ -626,7 +664,7 @@ type ClassFieldDecorator = (value: undefined, context: { }) => (initialValue: unknown) => unknown | void; ``` -属性装饰器的第一个参数是`undefined`,即没有一个直接的输入值。用户可以选择让装饰器返回一个初始化函数,当该属性被赋值时,这个初始化函数会自动运行,它会收到属性的初始值,然后返回一个新的初始值。属性装饰器也可以不返回任何值。只要返回的不是函数,而是其他类型的值,就会报错。 +属性装饰器的第一个参数是`undefined`,即不输入值。用户可以选择让装饰器返回一个初始化函数,当该属性被赋值时,这个初始化函数会自动运行,它会收到属性的初始值,然后返回一个新的初始值。属性装饰器也可以不返回任何值。除了这两种情况,返回其他类型的值都会报错。 下面是一个例子。 From add0bba14eab2b26c2a9929f7a624a7d79fea215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97?= Date: Sun, 4 Dec 2022 11:04:22 +0800 Subject: [PATCH 227/280] docs(decorator): fix typo --- docs/decorator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/decorator.md b/docs/decorator.md index a5b90603d..0577d18b3 100644 --- a/docs/decorator.md +++ b/docs/decorator.md @@ -273,7 +273,7 @@ C.prototype.toString = trace(C.prototype.toString); 上面示例中,`@trace`装饰`toString()`方法,就相当于修改了该方法。 -方式装饰器使用 TypeScript 描述类型如下。 +方法装饰器使用 TypeScript 描述类型如下。 ```typescript type ClassMethodDecorator = (value: Function, context: { From 90c0ea7cc872bae5d40442dac595f92ac1d6c6b7 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Fri, 23 Dec 2022 19:30:24 +0800 Subject: [PATCH 228/280] docs(class-extends): add new materials into super() --- docs/class-extends.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/class-extends.md b/docs/class-extends.md index 02028c216..5b1ff61fd 100644 --- a/docs/class-extends.md +++ b/docs/class-extends.md @@ -241,7 +241,7 @@ Object.getPrototypeOf(ColorPoint) === Point `super`这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。 -第一种情况,`super`作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次`super`函数。 +第一种情况,`super`作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次`super()`函数。 ```javascript class A {} @@ -253,9 +253,11 @@ class B extends A { } ``` -上面代码中,子类`B`的构造函数之中的`super()`,代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。 +上面代码中,子类`B`的构造函数之中的`super()`,代表调用父类的构造函数。这是必须的,否则报错。 -注意,`super`虽然代表了父类`A`的构造函数,但是返回的是子类`B`的实例,即`super`内部的`this`指的是`B`的实例,因此`super()`在这里相当于`A.prototype.constructor.call(this)`。 +调用`super()`的作用是形成子类的`this`对象,把父类的实例属性和方法放到这个`this`对象上面。子类在调用`super()`之前,是没有`this`对象的,任何对`this`的操作都要放在`super()`的后面。 + +注意,这里的`super`虽然代表了父类的构造函数,但是因为返回的是子类的`this`(即子类的实例对象),所以`super`内部的`this`代表子类的实例,而不是父类的实例,这里的`super()`相当于`A.prototype.constructor.call(this)`(在子类的`this`上运行父类的构造函数)。 ```javascript class A { @@ -272,7 +274,26 @@ new A() // A new B() // B ``` -上面代码中,`new.target`指向当前正在执行的函数。可以看到,在`super()`执行时,它指向的是子类`B`的构造函数,而不是父类`A`的构造函数。也就是说,`super()`内部的`this`指向的是`B`。 +上面示例中,`new.target`指向当前正在执行的函数。可以看到,在`super()`执行时(`new B()`),它指向的是子类`B`的构造函数,而不是父类`A`的构造函数。也就是说,`super()`内部的`this`指向的是`B`。 + +不过,由于`super()`在子类构造方法中执行时,子类的属性和方法还没有绑定到`this`,所以如果存在同名属性,此时拿到的是父类的属性。 + +```javascript +class A { + name = 'A'; + constructor() { + console.log('My name is ' + this.name); + } +} + +class B extends A { + name = 'B'; +} + +const b = new B(); // My name is A +``` + +上面示例中,最后一行输出的是`A`,而不是`B`,原因就在于`super()`执行时,`B`的`name`属性还没有绑定到`this`,所以`this.name`拿到的是`A`类的`name`属性。 作为函数时,`super()`只能用在子类的构造函数之中,用在其他地方就会报错。 From f9ef8fdae5bbd13a2e61a97534bc0218c4587c10 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 10 Jan 2023 16:09:45 +0800 Subject: [PATCH 229/280] refactor: add wwads banner --- js/ditto.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/js/ditto.js b/js/ditto.js index 40b9362fb..6da384699 100644 --- a/js/ditto.js +++ b/js/ditto.js @@ -243,6 +243,17 @@ function li_create_linkage(li_tag, header_level) { } function create_banner(element) { + (function() { + var wwads = document.createElement('script'); + wwads.type = 'text/javascript'; + wwads.async = true; + wwads.src = 'https://cdn.wwads.cn/js/makemoney.js'; + (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(wwads); + })(); + + var banner = $('
    ') + .insertAfter(element); +/* // 2022年8月25日 var deadline = new Date(2022, 7, 25); if (deadline - (new Date()) < 0) return; @@ -264,6 +275,7 @@ function create_banner(element) { var banner = $('
    ' + text + '
    ') .insertAfter(element); setTimeout(function () {if (banner.css('display') === 'none') {show_loading();show_error();} }, 500); +*/ } function create_page_anchors() { From c1a81dc51dcbe5ebc7ba30b16745cebc200dbaa5 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Tue, 10 Jan 2023 16:22:04 +0800 Subject: [PATCH 230/280] refactor: adjust wwads banner --- index.html | 1 + js/ditto.js | 10 +--------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/index.html b/index.html index 0f88f503c..6430cbbc0 100644 --- a/index.html +++ b/index.html @@ -29,6 +29,7 @@
    +