├── .nojekyll ├── README.md ├── contents ├── advanced │ ├── Performance.md │ ├── assets │ │ ├── WebSocket.png │ │ └── encode.png │ ├── encode.md │ ├── firstStrToUppercase.md │ ├── heapAndStack.md │ ├── observable.md │ ├── socketVSwebscocket.md │ └── weakMap.md ├── basic │ ├── arrayn.md │ ├── ascii.md │ ├── assets │ │ ├── ascii.png │ │ └── result.png │ ├── badForInArray.md │ ├── checkInvalidDate.md │ ├── closures.md │ ├── emptyObject.md │ ├── enumerateObject.md │ ├── hexadecimal.md │ ├── integerVsFloat.md │ ├── loopArray.md │ ├── new.md │ ├── onclick.md │ ├── promise.md │ ├── promiseExecute.md │ ├── range.md │ ├── sleep.md │ ├── testKeyInBbject.md │ ├── this.md │ ├── use_strict.md │ └── variable.md ├── node │ ├── arrowFunction.md │ ├── buffer.md │ └── packageJSON.md ├── react │ ├── context.md │ ├── diffValues.md │ ├── stateVsProps.md │ ├── useEffect.md │ └── useHooks.md ├── typescript │ ├── index.md │ └── interface.md └── web │ └── urlLength.md └── index.html /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buynao/stackoverflow-js-top-qa/fff4cccbc9c603e7b9aff9fb86599a16570cd0d3/.nojekyll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stackoverflow-js-top-qa 2 | 3 | ## 背景 4 | 5 | 在学习过程中,如果能够针对一个问题着手,一直深入下去,可能会让你对这个知识点掌握的更深刻。 6 | 7 | 经过了一番筛选后,整理的很多问题,现在可能看起来比较过时,内容也没有一些现成的前端面试答案看起来更爽更舒服。 8 | 9 | 但是在整理翻译的过程中,还是会有一些让人觉得 “原来还能这样” 的深刻体会。 10 | 11 | 这些问题之所以之所以票数比较高,除了问题比较经典,时间久远积累的原因之外,更多的是因为很多答疑者们,在提供解决方案的同时,还能集思广益,引发更多角度的思考: 12 | 13 | - 比如通过ECMA标准规范,对问题刨根剖底:[this 的运行原理?](./contents/basic/this.md) 14 | - 比如针对 "老生常谈" 的问题,进行深度思考: [在 JavaScript 中如使字符串的第一个字母大写(含国际化方案讨论)?](./contents/advanced/firstStrToUppercase.md) 15 | - 比如会将解决方案的优缺点,进行一一对比:[在 JavaScript 中循环遍历数组](./contents/basic/loopArray.md) 16 | - 还有一些可能目前热度不高,但是却令人茅塞顿开... [JavaScript 在函数调用过程中是如何分配内存的?如何区分堆栈和堆?](./contents/advanced/heapAndStack.md) 17 | 18 | 总而言之,就是在解决问题的同时,还能够引导大家对问题多一些其他角度的思考~ 19 | 20 | 所以,我想对 stackoverflow 上 前端 相关(主要是JS,浏览器,框架,性能等)投票数较多且比较有意义的问题进行整理翻译! 21 | 22 | 为了让 翻译 更有意义,给阅读者带来更有效的收获,会做一些额外加工: 23 | 例如,删除过时问题、问题分类、聚合答案、删除冗余内容、加上自己的心得等等 24 | 25 | 由于筛选机制原因,票数最多的问题,一般提问时间也比较久远,对于一些已经明显过时的问题,不在进行翻译 26 | 27 | 只针对一些有意义的问题,进行整理翻译。 28 | 29 | 有意义的标准,可能是以下原因之一: 30 | 1. 票数多 31 | 2. 经典问题 32 | 3. 常见问题,但是解答内容仍然令人醍醐灌顶 33 | 4. 可能知道个大概,但是一下不知道如何具体表达的问题 34 | 5. 一些被人误解的问题 35 | 6. ... 36 | 37 | ## 在线阅读 38 | 39 | [https://stackoverflow-js-top-qa.vercel.app](https://stackoverflow-js-top-qa.vercel.app) 40 | 41 | ## 目录 42 | 43 | > 基础 44 | 45 | 1. [“use strict” 是如何工作的?](./contents/basic/use_strict.md) 46 | 2. [闭包 是如何工作的?](./contents/basic/closures.md) 47 | 3. [new 是如何工作的?](./contents/basic/new.md) 48 | 4. [this 的运行原理?](./contents/basic/this.md) 49 | 5. [在 JavaScript 中循环遍历数组](./contents/basic/loopArray.md) 50 | 6. [在 JavaScript 中循环枚举对象](./contents/basic/enumerateObject.md) 51 | 7. [sleep() 的 JavaScript 版本是什么?](./contents/basic/sleep.md) 52 | 8. [如何检查一个空的 JavaScript 对象?](./contents/basic/emptyObject.md) 53 | 9. [检查 JavaScript 对象中是否存在某个键?](./contents/basic/testKeyInBbject.md) 54 | 10. [为什么不能使用 “for...in” 进行数组迭代?](./contents/basic/badForInArray.md) 55 | 11. [在 JavaScript 中如何检测 "invalid date" 日期?](./contents/basic/checkInvalidDate.md) 56 | 12. [在 JavaScript 中如何将十进制转换为十六进制?](./contents/basic/hexadecimal.md) 57 | 13. [如何创建一个 [1,2,3,4,N...] 的数组?](./contents/basic/arrayn.md) 58 | 14. [JavaScript 是否有类似 range() 的方法,可以在边界内的生成一个范围?](./contents/basic/range.md) 59 | 15. [在 JavaScript 中如何把字符转换成ASCII编码?](./contents/basic/ascii.md) 60 | 16. [在 JavaScript 中 addEventListener 和 onclick 之间有什么区别?](./contents/basic/onclick.md) 61 | 17. [在 JavaScript 中如何判断一个数值是浮点数还是整数?](./contents/basic/integerVsFloat.md) 62 | 18. [为什么 ◎ܫ◎ 和 ☺ 不是有效的 JavaScript 变量名?](./contents/basic/variable.md) 63 | 19. [如何取消正在执行的 Promise ?](./contents/basic/promise.md) 64 | 20. [为什么 Promise 在 resolve 或 reject 后仍在继续执行?](./contents/basic/promiseExecute.md) 65 | 66 | - todo ... 67 | 68 | > 进阶 69 | 70 | 1. [在 JavaScript 中如使字符串的第一个字母大写(含国际化方案讨论)?](./contents/advanced/firstStrToUppercase.md) 71 | 2. [Javascript 在哪里为函数调用的结果分配内存?堆栈还是堆?](./contents/advanced/heapAndStack.md) 72 | 3. [什么时候应该使用encodeURI,而不是encodeURIComponent?](./contents/advanced/encode.md) 73 | 4. [WebSocket 和 Socket.IO 之间的区别](./contents/advanced/socketVSwebscocket.md) 74 | 5. [ES6 中 WeakMap 的实际用途是什么?](./contents/advanced/weakMap.md) 75 | 6. [Promise 和 Observables 之间的区别](./contents/advanced/observable.md) 76 | 7. [频繁调用 Performance API 会不会有性能问题?](./contents/advanced/Performance.md) 77 | 78 | - todo ... 79 | 80 | > Typescript 81 | 82 | 1. [TypeScript是什么,为什么要用它来代替JavaScript?](./contents/typescript/index.md) 83 | 2. [TypeScript中 接口(interface) 与 类型(type) 的区别是什么?](./contents/typescript/interface.md) 84 | 85 | - todo ... 86 | 87 | > React 88 | 89 | 1. [React 中, state 和 props 的区别是什么?应该怎么使用?](./contents/react/stateVsProps.md) 90 | 2. [React Context 和 React Redux 的区别是什么?应该怎么使用?](./contents/react/context.md) 91 | 3. [React Hooks 中,如何在 useEffect 里比较 oldValues 和 newValues ?](./contents/react/diffValues.md) 92 | 4. [在组件中应该使用一个 useEffect 还是多个?](./contents/react/useEffect.md) 93 | 5. [什么时候该使用 useImperativeHandle、useLayoutEffect 和 useDebugValue ?](./contents/react/useHooks.md) 94 | 95 | - todo ... 96 | 97 | > Node.js 98 | 99 | 1. [package.json 中,版本前的符号 ~ 和 ^ 有什么区别?](./contents/node/packageJSON.md) 100 | 2. [Node.js 中的 Buffer 最大容量限制是多少?](./contents/node/buffer.md) 101 | 3. [在 V8 中,箭头函数比普通的函数声明更快(性能更强、更轻)?](./contents/node/arrowFunction.md) 102 | 103 | - todo ... 104 | 105 | > 网络 106 | 107 | 1. [在不同的浏览器中,URL 的最大长度是多少?](./contents/web/urlLength.md) 108 | -------------------------------------------------------------------------------- /contents/advanced/Performance.md: -------------------------------------------------------------------------------- 1 | ## 问题 2 | 3 | 我想使用 `performance.memory` 来测量我的 `Web SPA` 的内存使用情况,主要是想检测是整个页面的生命周期内是否有内存泄漏。 4 | 5 | 出于这个原因,我需要在特定的时间间隔内调用这个 `API` -- 可能是每 3 秒,30 秒,或1分钟... 6 | 7 | 然后我的问题是--为了快速有效地检测可能会出现的问题,我想要将时间间隔尽可能地短一些,但我又担心是否会产生性能问题。 8 | 9 | 如果测量任务的开销比较昂贵,那么测量本身可能会影响到网络应用程序的性能。 10 | 11 | 有了以上的背景,我的问题如下: 12 | 13 | - `performance.memory` 这种方法是否会影响到浏览器主线程的性能 14 | 15 | - 有没有什么正确的方法或程序来确认 `Javascript` 的一些任务是否会影响设备的性能? 16 | 17 | ## 答案 18 | 19 | (我是 V8 的开发者) 20 | 21 | 调用 `performance.memory` 是相当快的。 22 | 23 | 你可以在一个快速测试中很容易地验证这一点:比如在一个循环中将其调用 1000 次,然后测量需要多长时间。 24 | 25 | 然而,这种观察结果并不能解决你的问题。`performance.memory` 之所以快,也是频繁调用它毫无意义的原因:**它只是返回一个缓存的值**,实际上并没有做任何工作来测量内存消耗。(如果它真的做了,那么调用它就会超级慢。) 26 | 27 | 下面是一个快速测试来证明这两点。 28 | 29 | ```js 30 | function f() { 31 | if (!performance.memory) { 32 | console.error("unsupported browser"); 33 | return; 34 | } 35 | let objects = []; 36 | for (let i = 0; i < 100; i++) { 37 | // We'd expect heap usage to increase by ~1MB per iteration. 38 | objects.push(new Array(256000)); 39 | let before = performance.now(); 40 | let memory = performance.memory.usedJSHeapSize; 41 | let after = performance.now(); 42 | console.log(`Took ${after - before} ms, result: ${memory}`); 43 | } 44 | } 45 | f(); 46 | // 输出 47 | // .... 48 | // Took 0 ms, result: 58817896 49 | // Took 0 ms, result: 58817896 50 | // Took 0 ms, result: 58817896 51 | // Took 0 ms, result: 58817896 52 | // Took 0 ms, result: 58817896 53 | // Took 0 ms, result: 58817896 54 | // Took 0 ms, result: 58817896 55 | // Took 0 ms, result: 58817896 56 | // Took 0 ms, result: 58817896 57 | // Took 0 ms, result: 58817896 58 | // Took 0 ms, result: 58817896 59 | // Took 0 ms, result: 58817896 60 | // ... 61 | ``` 62 | 63 | 你可以看到,浏览器出于安全考虑,对定时器的粒度进行了钳制:打印的执行时间要么是 0ms ,要么是 0.1ms ,从来没有介于两者之间,这并不是巧合。 64 | 65 | 然而,这并不是个问题,因为想要 "为了快速有效地检测可能会出现的问题,我想要将时间间隔尽可能地短" 的前提是被误导的: 66 | 67 | 在使用垃圾收集的语言中,内存使用量有一些上下波动是完全正常的。 68 | 69 | 这是因为寻找可以释放的对象是一项昂贵的工作,所以垃圾收集器被仔细地调整为一个很好的折衷方案:它们应该尽可能快地释放内存,而不把 CPU 周期浪费在无用的忙碌工作上。 70 | 71 | 检查你的应用程序内存消耗是一个好主意,你不是第一个这样做的人,而`performance.memory` 是目前最好的工具。 72 | 73 | **你要知道,你需要要找的是一个长期的上升趋势,而不是短期的波动。** 74 | 75 | 因此,每 10 分钟左右进行一次测量,就完全足够了。此外,你仍然需要大量的数据才能看到有统计意义的结果。这是因为你的任何一次测量都可能发生在垃圾收集周期之前或之后,这将导致一些数据偏差。 76 | 77 | 例如,如果你观察你的用户在 10 秒后的内存消耗比 5 秒后的高,那么这可能只是垃圾收集器在按计划工作,没什么大不了。 78 | 79 | 而如果你注意到 10 分钟后,读数在 100-300MB 的范围内,20 分钟后在 200-400MB 的范围内,1 小时后是 500-1000MB ,那么现在是时候去寻找这个漏洞了。 80 | 81 | > 问题来源:[https://stackoverflow.com/questions/71293653/would-calling-performance-api-frequently-be-causing-a-performance-issue/71295042#71295042](https://stackoverflow.com/questions/71293653/would-calling-performance-api-frequently-be-causing-a-performance-issue/71295042#71295042) -------------------------------------------------------------------------------- /contents/advanced/assets/WebSocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buynao/stackoverflow-js-top-qa/fff4cccbc9c603e7b9aff9fb86599a16570cd0d3/contents/advanced/assets/WebSocket.png -------------------------------------------------------------------------------- /contents/advanced/assets/encode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buynao/stackoverflow-js-top-qa/fff4cccbc9c603e7b9aff9fb86599a16570cd0d3/contents/advanced/assets/encode.png -------------------------------------------------------------------------------- /contents/advanced/encode.md: -------------------------------------------------------------------------------- 1 | ## 问题:什么时候应该使用encodeURI,而不是encodeURIComponent? 2 | 3 | 当需要对查询的参数进行编码时,应该使用 `encodeURI` 还是 `encodeURIComponent` ? 4 | 5 | ## 答案: 6 | 7 | ### 1. encodeURI() 8 | 9 | 当你需要一个完整有效的URL时,可以使用 `encodeURI` : 10 | 11 | ```js 12 | 13 | encodeURI("http://www.example.org/a file with spaces.html") 14 | 15 | // http://www.example.org/a%20file%20with%20spaces.html 16 | 17 | ``` 18 | 19 | 如果直接使用 `encodeURIComponent`,它将破坏 url 的完整性: 20 | 21 | ```js 22 | encodeURIComponent("http://www.example.org/a file with spaces.html") 23 | 24 | // 'http%3A%2F%2Fwww.example.org%2Fa%20file%20with%20spaces.html' 25 | 26 | ``` 27 | 28 | ### 2. encodeURIComponent() 29 | 30 | 如果需要对一个 URL 参数的值进行编码时,可以使用 `encodeURIComponent`。 31 | ```js 32 | 33 | // 查询参数 34 | var p1 = encodeURIComponent("http://example.org/?a=12&b=55"); 35 | 36 | // 通过查询参数p1,构建你的url 37 | var url = "http://example.net/?param1=" + p1 + "¶m2=99"; 38 | 39 | // url => http://example.net/?param1=http%3A%2F%2Fexample.org%2F%Ffa%3D12%26b%3D55¶m2=99 40 | 41 | 42 | ``` 43 | 44 | 注意,`encodeURIComponent` 并不转义 `'` 字符。 45 | 46 | 有种常见的错误用法是用它来创建 `html` 的属性,如 `href='MyUrl'`,这可能会出现注入错误。 47 | 48 | 在构建 `html` 时,最好使用双引号 `"` 而不是单引号 `'` 作为属性的引号,或者增加一层额外的编码( `'` 可以被编码为 `%27` )。 49 | 50 | 关于这种类型的编码的更多信息,你可以查看:[http://en.wikipedia.org/wiki/Percent-encoding](http://en.wikipedia.org/wiki/Percent-encoding) 51 | 52 | ## 其他答案 53 | 54 | `encodeURI` 会替换所有的字符,但不包括以下字符,即使它们具有适当的 `UTF-8` 转义序列: 55 | 56 | | 类型 | 包含 | 57 | | ---- | ---- | 58 | | 保留字符 | ; , / ? : @ & = + $ | 59 | | 非转义的字符 | 字母 数字 - _ . ! ~ * ' ( ) | 60 | | 数字符号 | # | 61 | 62 | 如果试图编码一个非高-低位完整的代理字符,`encodeURI` 和 `encodeURIComponent` 都将会抛出一个 `URIError` 错误,例如: 63 | 64 | ```js 65 | // 编码高-低位完整字符 ok 66 | console.log(encodeURI('\uD800\uDFFF')); 67 | 68 | // 编码单独的高位字符抛出 "Uncaught URIError: URI malformed" 69 | console.log(encodeURI('\uD800')); 70 | 71 | // 编码单独的低位字符抛出 "Uncaught URIError: URI malformed" 72 | console.log(encodeURI('\uDFFF')); 73 | ``` 74 | 75 | 并且需要注意,如果URL需要遵循较新的 [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986) 标准,那么方括号是被保留的(给 `IPv6` ),因此对于那些没有被编码的 URL 部分(例如主机),可以使用下面的代码: 76 | 77 | ```js 78 | function fixedEncodeURI (str) { 79 | return encodeURI(str).replace(/%5B/g, '[').replace(/%5D/g, ']'); 80 | } 81 | ``` 82 | 83 | `encodeURIComponent` 转义除了如下所示外的所有字符: 84 | 85 | 不转义的字符: `A-Z a-z 0-9 - _ . ! ~ * ' ( )` 86 | 87 | 通过以下方法测试,`encodeURIComponent()` 和 `encodeURI` 在编码字符时,有 11 个不同点: 88 | 89 | ```js 90 | var arr = []; 91 | for(var i=0;i<256;i++) { 92 | var char=String.fromCharCode(i); 93 | if(encodeURI(char)!==encodeURIComponent(char)) { 94 | arr.push({ 95 | character:char, 96 | encodeURI:encodeURI(char), 97 | encodeURIComponent:encodeURIComponent(char) 98 | }); 99 | } 100 | } 101 | console.table(arr); 102 | ``` 103 | 104 | 输出 105 | 106 | ![result](https://raw.githubusercontent.com/buynao/stackoverflow-js-top-qa/main/contents/advanced/assets/encode.png) 107 | 108 | 为了避免服务器收到不可预知的请求,对任何用户输入的作为URI部分的内容你都需要用 `encodeURIComponent` 进行转义。 109 | 110 | 比如,一个用户可能会输入`"Thyme &time=again"` 作为 `comment` 变量的一部分。如果不使用 `encodeURIComponent` 对此内容进行转义,服务器得到的将是`comment=Thyme%20&time=again`。请注意,`"&"` 符号和 `"="` 符号产生了一个新的键值对,所以服务器得到两个键值对(一个键值对是 `comment=Thyme`,另一个则是 `time=again`),而不是一个键值对。 111 | 112 | 对于` application/x-www-form-urlencoded (POST)` 这种数据方式,空格需要被替换成 `'+'`,所以通常使用 `encodeURIComponent` 的时候还会把 `"%20"` 替换为 `"+"`。 113 | 114 | 为了更严格的遵循 [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986) 标准(它保留 !, ', (, ), 和 *),即使这些字符并没有正式划定 URI 的用途,下面这种方式是比较安全的: 115 | 116 | ```js 117 | function fixedEncodeURIComponent (str) { 118 | return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { 119 | return '%' + c.charCodeAt(0).toString(16).toUpperCase(); 120 | }); 121 | } 122 | 123 | ``` 124 | 125 | > 问题来源:[https://stackoverflow.com/questions/75980/when-are-you-supposed-to-use-escape-instead-of-encodeuri-encodeuricomponent](https://stackoverflow.com/questions/75980/when-are-you-supposed-to-use-escape-instead-of-encodeuri-encodeuricomponent) -------------------------------------------------------------------------------- /contents/advanced/firstStrToUppercase.md: -------------------------------------------------------------------------------- 1 | ## 问题:JavaScript 中如使字符串的第一个字母大写? 2 | 3 | 如何让一个字符串的第一个字母大写,但不改变其他字母的大小写? 4 | 5 | 示例: 6 | 7 | ``` 8 | 9 | - "this is a test" → "This is a test" 10 | - "the Eiffel Tower" → "The Eiffel Tower" 11 | - "/index.html" → "/index.html" 12 | 13 | ``` 14 | 15 | ## 答案 16 | 17 | 基本解决方案是: 18 | 19 | ```js 20 | 21 | function capitalizeFirstLetter(string) { 22 | return string.charAt(0).toUpperCase() + string.slice(1); 23 | } 24 | 25 | console.log(capitalizeFirstLetter('foo')); // Foo 26 | 27 | ``` 28 | 29 | 其他一些答案修改了 `String.prototype`(这个答案以前也是如此),但我建议不要这样做,因为可维护性的问题(如果其他代码添加相同名称的本地函数,可能会导致代码冲突)。 30 | 31 | 如果你想用 `Unicode` 代码点而不是代码单元来工作,你可以使用 [String#[@iterator]](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/@@iterator),在通过 `toLocaleUpperCase` 来获得当前正确的大写字母。 32 | 33 | ```js 34 | 35 | const capitalizeFirstLetter = ([ first, ...rest ], locale = navigator.language) => 36 | first.toLocaleUpperCase(locale) + rest.join('') 37 | 38 | console.log( 39 | capitalizeFirstLetter('foo'), // Foo 40 | capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉"), // "𐐎𐐲𐑌𐐼𐐲𐑉" (correct!) 41 | capitalizeFirstLetter("italya", 'tr') // İtalya" (correct in Turkish Latin!) 42 | ) 43 | 44 | ``` 45 | 46 | 更多国际化方案选择,请参阅下面的其他答案。 47 | 48 | ## 其他答案 49 | 50 | 大多数建议的答案如下所示: 51 | 52 | ```js 53 | 54 | function capitalizeFirstLetter(str) { 55 | return str[0].toUpperCase() + str.slice(1); 56 | } 57 | 58 | ``` 59 | 60 | 但是,一些大小写字符不在 `BMP`([基本多文种平面](https://zh.wikipedia.org/wiki/Unicode%E5%AD%97%E7%AC%A6%E5%B9%B3%E9%9D%A2%E6%98%A0%E5%B0%84),代码点 `U+0` 到 `U+FFFF`)之外。例如,以这个 `西班牙语` 文本为例: 61 | 62 | ```js 63 | 64 | capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉"); // "𐐶𐐲𐑌𐐼𐐲𐑉" 65 | 66 | ``` 67 | 68 | 这里的第一个字符没有大写,因为字符串的数组索引属性不访问 `characters` 或 `代码单元*`。他们访问 `UTF-16` 代码单元。切片时也是如此——索引值指向代码单元。 69 | 70 | ```js 71 | 72 | '𐐶𐐲𐑌𐐼𐐲𐑉'[0] // '\uD801' 73 | 74 | ``` 75 | 76 | 碰巧 `UTF-16` 代码单位就是 `1:1`,`USV` 代码单位在两个范围内:`U+0` 到 `U+D7FF` 和 `U+E000` 到 `U+FFFF` 。大多数大小写字符都在这两个范围内,但并不是全部的。 77 | 78 | 从 `ES2015` 开始,处理这个变得更容易了。` String.prototype[@@iterator]` 方法返回一个新的`Iterator` 对象,它遍历字符串的代码点,返回每一个代码点的字符串值。 79 | 80 | ```js 81 | 82 | function capitalizeFirstLetter([ first, ...rest ]) { 83 | console.log(first) 84 | return [ first.toUpperCase(), ...rest ].join(''); 85 | } 86 | 87 | capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // 𐐶 and '𐐎𐐲𐑌𐐼𐐲𐑉' 88 | 89 | ``` 90 | 91 | 对于较长的字符串,这可能不是非常有效的。因为我们并不真的需要对剩余部分进行迭代。我们可以使用`String.prototype.codePointAt` 来获得第一个(可能的)字母,但是我们仍然需要确定切片应该从哪里开始。避免迭代余数的一个方法是测试第一个代码点是否在 `BMP` 之外;如果不是,分片从 1 开始,如果是,分片从 2 开始。 92 | 93 | ```js 94 | 95 | function capitalizeFirstLetter(str) { 96 | const firstCP = str.codePointAt(0); 97 | const index = firstCP > 0xFFFF ? 2 : 1; 98 | 99 | return String.fromCodePoint(firstCP).toUpperCase() + str.slice(index); 100 | } 101 | 102 | capitalizeFirstLetter("𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉" 103 | 104 | ``` 105 | 106 | 在开始的时候,我提到了国际化的考虑。其中有些是很难解释的,因为它们不仅需要了解使用的是什么语言,而且还可能需要了解该语言中的单词的具体知识。 107 | 108 | 例如,爱尔兰的数字符号 `mb` 在一个词的开头大写为 `mB`。 109 | 110 | 另一个例子,德语的 `eszett`,据说从来不会放在单词的开头。小写的 eszett `ß`,大写为 `SS`,但 `SS` 可以小写为 `ß` 或 `ss`。 111 | 112 | 你需要有德语的语言知识才能知道哪个才是正确的。 113 | 114 | 这类问题最著名的例子可能是土耳其语。在土耳其拉丁语中,`i` 的大写形式是 `İ`,而 `I` 的小写形式是 `ı`。 115 | 116 | 它们是两个不同的字母。幸运的是,我们确实有办法解决这个问题: 117 | 118 | ```js 119 | function capitalizeFirstLetter([ first, ...rest ], locale) { 120 | return [ first.toLocaleUpperCase(locale), ...rest ].join(''); 121 | } 122 | 123 | capitalizeFirstLetter("italy", "en") // "Italy" 124 | capitalizeFirstLetter("italya", "tr") // "İtalya" 125 | ``` 126 | 127 | 在浏览器中,用户最常用的语言标签由 `navigator.language` 标明。 128 | 129 | `navigator.languages` 中则也可以找到按优先顺序排列的语言标签列表。 130 | 131 | ```js 132 | navigator.language // 'zh-CN' 133 | navigator.languages // ['zh-CN', 'zh', 'en'] 134 | ``` 135 | 136 | 在 `ES2018` 中引入的 `RegExp` 中支持 `Unicode` 属性字符类的代理中,我们可以通过直接表达我们感兴趣的字符来进一步优化代码: 137 | 138 | ```js 139 | function capitalizeFirstLetter(str, locale=navigator.language) { 140 | return str.replace(/^\p{CWU}/u, char => char.toLocaleUpperCase(locale)); 141 | } 142 | ``` 143 | 144 | 优化过后,现在就能够比较精准地处理字符串中多个单词的大写。`CWU` 或 `Changes_When_Uppercased` 字符属性与所有代码点相匹配,这些代码点在大写时将会发生变化。 145 | 146 | 我们可以用诸如荷兰语 `ij` 这样的大写数字字符来试试。 147 | 148 | ```js 149 | capitalizeFirstLetter('ijsselmeer'); // "IJsselmeer" 150 | ``` 151 | 152 | ## 最后 153 | 154 | 在大多数情况下,问这个问题的人都不会关心 `Deseret` 的大写字母或国际化问题。但是,能搞提前意识到这些问题是很好的,因为你不知道什么时候就会遇到这些问题。它们并不是 `边缘` 案例,或者说,它们不是定义上的边缘案例,毕竟有一整个国家的大多数人都说土耳其语。 155 | 156 | 将代码单元与代码点混淆是一个相当常见的错误来源(特别是在表情符号方面),字符串和语言都是相当复杂的。 157 | 158 | > 问题来源:[https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript/53930826](https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript/53930826) -------------------------------------------------------------------------------- /contents/advanced/heapAndStack.md: -------------------------------------------------------------------------------- 1 | ## 问题:JavaScript 在函数调用过程中是如何分配内存的?如何区分堆栈和堆? 2 | 3 | 我查遍了整个互联网,似乎找不到这个问题的答案。 4 | 5 | - 我读到过 `栈(Stack)` 用于存储原始数据类型(number, string...),`堆(Heap)` 用于存储引用数据类型(object, array...)。 6 | - 我还读到过 `栈(Stack)` 的内存大小是固定的,一旦分配它就不会改变,那么它为什么还能用于原始数据类型的存储?因为 `JS` 在运行前并不一定知道每个变量的所占内存大小。 7 | - 原始数据类型是不可变的,所以如果我们尝试重新分配一个变量,它不会改变其内存地址的值,而是会在内存中分配新空间,存储新值并将其地址分配给变量。 8 | 9 | 如果以上是正确的,那么当我这样做时会发生什么? 10 | 11 | ```js 12 | 13 | let string = "Hello World"; // Memory allocated for "Hello World" 14 | string = string.ToUpperCase(); // New memory allocated for "HELLO WORLD" 15 | 16 | ``` 17 | 18 | `HELLO WORLD` 在内存中会如何分配? 19 | - 如果是在 `栈(Stack)` 中,是因为它是原始数据类型吗? 20 | - 还是在 `堆(Heap)` 中,因为 `JS` 在运行代码之前还不知道它的值。 21 | 22 | 如果它确实是在 `栈(Stack)` 中,那么旧的 `Hello World` 会发生什么变化? 23 | 24 | 只有 `堆(Heap)` 里的数据会被垃圾回收收集释放,这是否意味着旧的、未使用的字符串将一直保留着,直到程序运行结束? 25 | 26 | 如果我这样做会发生什么? 27 | 28 | ```js 29 | 30 | string = string.repeat(10000000000) 31 | 32 | ``` 33 | 34 | 如果 `栈(Stack)` 的内存空间是固定的,是否意味着,我如果让变量变得巨大,从而让 `栈(Stack)` 溢出? 35 | 36 | ## 答案 37 | 38 | 先说结论,答案因 `JS引擎` 而异,`EScript 标准规范` 中并没有对 `JS内存` 如何分配做出具体要求。 39 | 40 | 而且你没必要担心 `栈(Stack)` 用完的情况,这对你的代码来说真的不重要。 41 | 42 | `EScript 标准规范` 并不要求实现的每一步都很具体。它描述的是行为,而不是该行为的实现。 43 | 44 | 但从实际情况来看。一个 `JavaScript引擎` 似乎不太可能尝试把一个巨大的字符串放在 `栈(Stack)` 里。更有可能的情况,它将分配在 `堆(Heap)` 里。但这并不意味着它不能把其他字符串放在 `栈(Stack)` 中。 45 | 46 | 也许它是根据大小来决定的。 47 | 48 | 也许它是根据生命周期来决定的。 49 | 50 | 也许它会把一个只在函数中局部使用的小字符串放在 `栈(Stack)` 里,而把一个巨大的字符串,或者一个全局保留的字符串放在 `堆(Heap)` 里。 51 | 52 | 重要的是,现代的 `JavaScript引擎` 很复杂,而且每个阶段都会进行高度优化。 53 | 54 | 他们不可能采取: 55 | 56 | > `栈(Stack)` 用于存储原始数据类型(number, string...),`堆(Heap)` 用于存储引用数据类型(object, array...) 57 | 58 | 这样简单的观点。 59 | 60 | 引用数据类型也可以在 `栈(Stack)` 中分配,而且至少在历史上的不同时期,一些 `JavaScript引擎` 已经这样做过了。 61 | 62 | 如果一个对象不能在函数调用结束后继续存在,并且所占内存也不大的话,把它分配在 `栈(Stack)` 中就可以在函数调用结束后,很容易地回收它。 63 | 64 | 译者注: 65 | 66 | 同意上述观点,V8引擎的内存分配,是根据不同情况来分配的,并不是简单的 [原始数据存在堆栈上,引用数据类型存在堆上] 67 | 68 | 举两个小例子: 69 | 70 | V8引擎在分配 numer 类型时,如果是整数,number 类型就是Smi,如果是浮点数或者bigInt,number 类型就是heap number,从类型名可以推断,内部分配方式两者并不一样。 71 | 72 | V8引擎在分配 object 类型时,如果当前属性数量小于 10 个,会将数据直接分配在该对象上;超过 10 个,视不同属性名的类型,会在进行划分:快属性和慢属性,慢属性通过 map 隐藏类映射数据。 73 | 74 | 以上,V8引擎在内存分配中,做了大量性能优化工作,绝不仅仅只是 [原始数据存在堆栈上,引用数据类型存在堆上]. 75 | 76 | 参考资料: 77 | [v8 快属性](https://v8.js.cn/blog/fast-properties/) 78 | [v8 性能陷阱](https://siyuan.pub/2021/04/26/javascript-performance-pitfalls-v8/) 79 | 80 | > 问题来源:[https://stackoverflow.com/questions/67356107/where-does-javascript-allocate-memory-for-the-result-of-a-function-call-stack-o](https://stackoverflow.com/questions/67356107/where-does-javascript-allocate-memory-for-the-result-of-a-function-call-stack-o) -------------------------------------------------------------------------------- /contents/advanced/observable.md: -------------------------------------------------------------------------------- 1 | ## 问题:Promise 和 Observables 之间的区别 2 | 3 | 在 `Angular/RxJs` 中, `Promise` 和 `Observable` 的区别是什么? 4 | 5 | ## 答案 6 | 7 | ### Promise 8 | 9 | `Promise` 擅长处理单一的事件,比如等待一个异步操作,监听它的完成或失败。 10 | 11 | 注意:目前有一些支持取消的 `Promise` 库,但到目前为止还没有官方的取消规范。 12 | 13 | ### Observable 14 | 15 | `Observable` 就像 `Stream`,允许传递零或多个事件,每个事件的回调都会进行调用。 16 | 17 | 它也比 `Promise` 更灵活一些,因为它提供了比 `Promise` 更多的功能。 18 | 19 | 有了 `Observable` ,无论你想多少个事件都不重要,你可以使用相同的 `API` 处理不同的情况。 20 | 21 | 与 `Promise` 相比,`Observable` 还具有可取消的优势。 22 | 23 | `Observable` 的订阅是允许取消的,当你不再需要服务器的 `HTTP` 请求或其他昂贵的异步操作的响应结果时,随时可以取消。 24 | 25 | 而 `Promise` 一旦执行,始终会调用成功或失败,即使你不再需要它提供的结果。 26 | 27 | `Promise` 会立即启动,而 `Observable` 只有在你订阅它时才会启动,因为 `Observable` 是惰性的。 28 | 29 | `Observable` 提供了类似数组的一些操作符:像 map、forEach、reduce..., 方便你对异步的结果进行处理。当然,也有一些更强大的 [RxJS操作符](https://rxjs-dev.firebaseapp.com/guide/operators) 30 | 31 | 因其惰性执行的特性,可以在观察器被订阅执行之前建立一连串的操作符,以进行更加声明式的编程方式。 32 | 33 | > 问题来源:[https://stackoverflow.com/questions/37364973/what-is-the-difference-between-promises-and-observables](https://stackoverflow.com/questions/37364973/what-is-the-difference-between-promises-and-observables) -------------------------------------------------------------------------------- /contents/advanced/socketVSwebscocket.md: -------------------------------------------------------------------------------- 1 | ## 问题 2 | 3 | `node.js` 中的 `Socket.IO` 4 | 和 `WebSocket` 之间有什么区别? 它们都是关于服务器推送的技术吗? 5 | 6 | 我目前理解的区别是: 7 | 8 | - `Socket.IO` 允许我通过指定一个事件名来 `发送/触发(send/emit)` 消息。 9 | - 使用 `Socket.IO` ,来自服务器的消息会 `push` 到所有的客户端。 10 | - 使用 `WebSocket` ,我必须使用一个包含所有 `连接(connections)` 的数组,通过循环这个数组向所有客户端发送消息。 11 | 12 | 另外,我想知道为什么很多 网络开发工具(如 Chrome/firebug/fiddler )无法捕获来自服务器的这些消息(来自 socket.io/websocket)? 13 | 14 | ## 答案 15 | 16 | ### 误解 17 | 18 | 关于 `WebSocket` 和 `Socket.IO`,有一些常见的**误解**。 19 | 20 | 1. 使用 `Socket.IO` 比使用 `WebSocket` 更容易, 21 | 2. `WebSocket` 在浏览器中没有得到广泛支持。 22 | 3. `Socket.IO` 将连接降级作为老式浏览器的兼容方案。它将浏览器默认当做老式浏览器,并通过 `AJAX`与服务器进行连接,在检查浏览器支持 `WebSocket` 并交换了一些流量后,随后将连接进行升级。 23 | 24 | ### 我的实验 25 | 26 | 我写了一个 `npm` 模块来演示 `WebSocket` 和 `Socket.IO` 的区别。 27 | 28 | - [https://www.npmjs.com/package/websocket-vs-socket.io](https://www.npmjs.com/package/websocket-vs-socket.io) 29 | - [https://github.com/rsp/node-websocket-vs-socket.io](https://github.com/rsp/node-websocket-vs-socket.io) 30 | 31 | 这是个包含服务端和客户端代码的简单 demo -- 客户端使用 `WebSocket` 或 `Socket.IO` 连接到服务端,服务端以 `1s` 的间隔发送三条消息,这些消息被客户端添加到 `DOM` 中。 32 | 33 | ### 服务端写法上的比较 34 | 35 | 比较服务器端使用 `WebSocket` 和 `Socket.IO` 的例子,在 `Express.js` 应用程序中做同样的事情。 36 | 37 | #### WebSocket Server 38 | 39 | 使用 `Express.js` 的 `WebSocket` 服务器实例。 40 | 41 | 42 | ```js 43 | var path = require('path'); 44 | var app = require('express')(); 45 | var ws = require('express-ws')(app); 46 | 47 | app.get('/', (req, res) => { 48 | console.error('express connection'); 49 | res.sendFile(path.join(__dirname, 'ws.html')); 50 | }); 51 | 52 | app.ws('/', (s, req) => { 53 | console.error('websocket connection'); 54 | for (var t = 0; t < 3; t++) 55 | setTimeout(() => s.send('message from server', () => {}), 1000 * t); 56 | }); 57 | 58 | app.listen(3001, () => console.error('listening on http://localhost:3001/')); 59 | ``` 60 | 61 | #### Socket.IO Server 62 | 63 | 使用 `Express.js` 的 `Socket.IO` 服务器实例。 64 | 65 | ```js 66 | var path = require('path'); 67 | var app = require('express')(); 68 | var http = require('http').Server(app); 69 | var io = require('socket.io')(http); 70 | 71 | app.get('/', (req, res) => { 72 | console.error('express connection'); 73 | res.sendFile(path.join(__dirname, 'si.html')); 74 | }); 75 | 76 | io.on('connection', s => { 77 | console.error('socket.io connection'); 78 | for (var t = 0; t < 3; t++) 79 | setTimeout(() => s.emit('message', 'message from server'), 1000*t); 80 | }); 81 | 82 | http.listen(3002, () => console.error('listening on http://localhost:3002/')); 83 | ``` 84 | 85 | ### 客户端写法上的比较 86 | 87 | #### WebSocket Client 88 | 89 | 使用 `vanilla JavaScript` 的 `WebSocket` 客户端示例。 90 | 91 | ```js 92 | var l = document.getElementById('l'); 93 | 94 | var log = function (m) { 95 | var i = document.createElement('li'); 96 | i.innerText = new Date().toISOString()+' '+m; 97 | l.appendChild(i); 98 | } 99 | log('opening websocket connection'); 100 | 101 | var s = new WebSocket('ws://'+window.location.host+'/'); 102 | 103 | s.addEventListener('error', function (m) { log("error"); }); 104 | s.addEventListener('open', function (m) { log("websocket connection open"); }); 105 | s.addEventListener('message', function (m) { log(m.data); }); 106 | ``` 107 | 108 | #### Socket.IO Client 109 | 110 | 使用 `vanilla JavaScript` 的 `Socket.IO` 客户端示例。 111 | 112 | ```js 113 | var l = document.getElementById('l'); 114 | 115 | var log = function (m) { 116 | var i = document.createElement('li'); 117 | i.innerText = new Date().toISOString()+' '+m; 118 | l.appendChild(i); 119 | } 120 | log('opening socket.io connection'); 121 | 122 | var s = io(); 123 | 124 | s.on('connect_error', function (m) { log("error"); }); 125 | s.on('connect', function (m) { log("socket.io connection open"); }); 126 | s.on('message', function (m) { log(m); }); 127 | ``` 128 | 129 | ### 网络流量 Network traffic 130 | 131 | 要看到网络流量的差异,你可以运行我的测试。以下是我得到的结果。 132 | 133 | #### WebSocket 结果 134 | 135 | **2 requests, 1.50 KB, 0.05 s** 136 | 137 | 来自这两个请求 138 | 139 | 1. HTML page request 140 | 2. connection upgrade to WebSocket 141 | 142 | (连接升级的请求在开发者工具上可见,有个 101 交换协议的响应码)。 143 | 144 | #### Socket.IO 结果 145 | 146 | **6 requests, 181.56 KB, 0.25 s** 147 | 148 | 1. HTML page request 149 | 2. Socket.IO's JavaScript (180KB) 150 | 3. first long polling AJAX request 151 | 4. second long polling AJAX request 152 | 5. third long polling AJAX request 153 | 6. connection upgrade to WebSocket 154 | 155 | ### 浏览器兼容性 Browser compatibility 156 | 157 | ![result](https://raw.githubusercontent.com/buynao/stackoverflow-js-top-qa/main/contents/advanced/assets/WebSocket.png) 158 | 159 | ## 其他答案 160 | 161 | `Socket.IO` 的优点是简化了你在第 2 条中所描述的 `WebSockets` 的使用场景,而且更重要的是,在浏览器或服务器不支持 `WebSocket` 的情况下,它提供了对其他协议的故障转移。 162 | 163 | 通常情况下,建议避免直接使用 `WebSocket`,除非你已经有了,非常明确使用它的场景。 164 | 165 | ### SocketIO 166 | 167 | - 自动连接功能 168 | - 有命名空间 169 | - 有订阅服务 170 | - 有一个预先设计好的通信协议 171 | - 具有良好的兼容支持 172 | - 可与 `redis` 等服务进行良好的集成 173 | - 在不支持 `WebSocket` 的情况下有回退功能(好吧,虽然这种情况越来越少)。 174 | - 它是一个 `库(library)` 。也就是说,它已经在内部封装好了它的功能,来帮助你更简单的完成你的工作。`Websocket` 只是一个`协议(protocol)`,而 `SocketIO` 使用的是整个集成封装好了的 `库(library)`。 175 | - 整个架构是已经设计完成了的,你可以直接利用它去写你的业务代码,而不必在去花时间去设计和实现它的架构。 176 | - 因为它是一个 `库(library)` ,所以它有一个正在发展的社区(你不可能为 `HTTP` 或 `Websocket` 拥有一个社区 -- 因为它们只是标准/协议)。 177 | 178 | ### Websocket 179 | 180 | - 你对它有绝对的控制权,这可能是个好事,但也可能会带来一些麻烦 181 | - 最轻量的(记住,它是一个 `协议`,不是一个 `库(library)`)。 182 | - 你可以设计你想要的架构 & 协议 183 | - 没有自动连接,如果你想要,你只能自己实现 184 | - 没有订阅服务,同上,由你来设计 185 | - 没有日志记录,由你来设计 186 | - 没有回退兼容方案 187 | - 没有命名空间 188 | - 没有任何支持,你将是全部自己实现 189 | - 你要先专注于它的技术部分,在设计出所以你想要的功能 190 | - 接着要对你的设计进行调试,这将会花费你很长的时间 191 | 192 | **很明显,你可以看到我对 `SocketIO` 有偏好。我很想这么说,但我真的真的不是。** 193 | 194 | 其实,我有在为**不使用** `SocketIO` 而努力,我喜欢设计自己的东西,自己解决自己的问题。 195 | 196 | 如果你有一个大的业务,而不仅仅是一个 1000 行的项目,选择 `Websocket` 之后,你将不得不自己实现每一件事。你必须调试一切。你必须做你自己的订阅服务。你自己的协议。你需要的一切。你必须知道大部分的功能实现都是相当复杂的。 197 | 198 | 在这一过程中,你会犯很多的错误。你会花大量的时间来设计和调试一切。我就是这样做的,现在也是如此。我正在使用 `websocket`,我在这里的原因是,对于一个试图为他的创业公司解决业务的人来说,他们可能无法忍受你为 `Websocket` 所做的设计。 199 | 200 | 如果你是一个人或是一个试图实现复杂功能的小团队,为一个大型应用选择 `websocket` 并不明智。 201 | 202 | 我在 `websocket` 中写的代码比我过去用 `SocketIO` 写的更多,更复杂。 203 | 204 | 我只想说...如果你想要一个成品和设计,请选择 `SocketIO`。(除非你要实现一个功能非常简单的业务) 205 | 206 | > 问题来源:[https://stackoverflow.com/questions/10112178/differences-between-socket-io-and-websockets](https://stackoverflow.com/questions/10112178/differences-between-socket-io-and-websockets) -------------------------------------------------------------------------------- /contents/advanced/weakMap.md: -------------------------------------------------------------------------------- 1 | ## 问题:ES6 中 WeakMap 的实际用途是什么? 2 | 3 | `ECMAScript 6` 中引入了一种新的数据结构 `WeakMap`,它的实际用途是什么? 4 | 5 | 由于它的 `键 key` 是一个弱引用,而其 `值 value` 是一个强引用。因此只要它的 `键 key` 还在内存中"存在",其 `值 value` 就会在。所以不能安全的将其用于 `备忘录 memo tables` , `缓存 caches` 或者其他需要用到弱引用的场景中。 6 | 7 | 我认为这两种写法并没有什么大的区别, 8 | 9 | ```js 10 | weakmap.set(key, value); 11 | 12 | //or 13 | 14 | key.value = value; 15 | ``` 16 | 17 | 有没有更有用的场景? 18 | 19 | ## 答案 20 | 21 | `WeakMaps` 提供了一种从外部扩展对象而不影响垃圾回收的方法。 22 | 23 | 当你想扩展一个对象,但由于它是来自内部,或来自外部的以及其他情况,而不能扩展时,就可以应用 `WeakMap`。 24 | 25 | `WeakMap` 是个 `map 字典`,它的 `键 key` 是 `弱应用` 的。 26 | 27 | 也就是说,如果对 `键 key` 的所有引用都丢失了,并且也没有对 `值 value` 的引用 -- 那么其 `值 value` 可以被垃圾回收。 28 | 29 | 让我们先通过例子来了解一下 30 | 31 | 比方说,我正在使用一个 API,它给了我一个特定的对象。 32 | 33 | ```js 34 | var obj = getObjectFromLibrary(); 35 | ``` 36 | 37 | 现在,我有一个使用该对象的方法。 38 | 39 | ```js 40 | function useObj(obj){ 41 | doSomethingWith(obj); 42 | } 43 | ``` 44 | 45 | 我想跟踪某个对象调用该方法的次数,如果超过 N 次就上报。 46 | 47 | 大家通常会认为应该使用 `Map` 来作记录。 48 | 49 | ```js 50 | var map = new Map(); // maps can have object keys 51 | function useObj(obj){ 52 | doSomethingWith(obj); 53 | var called = map.get(obj) || 0; 54 | called++; // called one more time 55 | if(called > 10) report(); // Report called more than 10 times 56 | map.set(obj, called); 57 | } 58 | ``` 59 | 60 | 这确实有效,但它有 `内存泄漏`:现在要跟踪传递给函数的每一个 `obj`,这使得该对象永远不会被垃圾回收。 61 | 62 | 这种情况下,我们可以使用 `WeakMap` 来解决内存泄露的问题。 63 | 64 | ```js 65 | var map = new WeakMap(); // create a weak map 66 | function useObj(obj){ 67 | doSomethingWith(obj); 68 | var called = map.get(obj) || 0; 69 | called++; // called one more time 70 | if(called > 10) report(); // Report called more than 10 times 71 | map.set(obj, called); 72 | } 73 | ``` 74 | 75 | ### 使用案例 Use cases 76 | 77 | 应该使用 `WeakMaps`,不然会导致内存泄漏的一些用例: 78 | 79 | - 对特定对象的私有数据进行保存,并且只允许对该 Map 有权限的人可以访问。 80 | - 保留关于其他库中对象的数据,不会改变它们或者有其他开销 81 | - 保留关于浏览器中特有对象的数据,如 DOM 节点。 82 | - 从外部为一个对象添加其他特性 83 | 84 | ### 让我们来看看一个真实的用途 85 | 86 | `WeakMap` 可以从外部扩展一个对象。让我们从 `Node.js` 中举一个实际的例子。 87 | 88 | 假如你是 `Node.js`,你的 `Promise` 对象 -- 现在你想跟踪所有当前被 `reject` 的 `Promise` 。 89 | 90 | 由于显而易见的原因,你不想直接给本地对象添加属性。 91 | 92 | 如果你保留对 `Promise` 的引用,就会造成内存泄漏,因为不能发生垃圾回收。 93 | 94 | 如果你不保留引用,那么你就不能保存关于 `Promise` 的额外信息。 95 | 96 | 任何涉及保存 `Promise` 的 ID 的方案都意味着你需要一个对它的引用。 97 | 98 | ### 进入WeakMaps 99 | 100 | `WeakMaps` 的 所有 `键 key` 都是 `弱 weak` 引用。 101 | 102 | 没有办法枚举 `弱 weak` 引用,也没有办法枚举它的所有 `值 value`。 103 | 104 | 在 `弱引用 (weak map)` 中,你可以根据 `键 key` 来存储数据,当 `键 key` 被垃圾回收时,`值 value` 也会被垃圾回收。 105 | 106 | 这意味着,在需要特定处理的 `promise` 中,你可以创建一个 `obj 对象`,存储关于它的 `state 状态` -- 而且这个 `对象` 可以正常被垃圾回收,释放内存。 107 | 108 | 总而言之,可以设想将 `promise` 和其 `state状态` 等信息保存在 `WeakMaps` 中,有了这个映射关系,在添加一些扫描逻辑,就可以知道何时处理一个被 `reject` 的 `promise` 了。 109 | 110 | 针对该问题,此处没有具体代码,答者仅仅是给了一个思路 111 | 112 | 关于该问题,业界已经有所讨论,可以参考以下链接,去了解更多相关讨论: 113 | 114 | https://github.com/iojs/io.js/issues/256 115 | 116 | https://iojs.org/api/process.html#process_event_unhandledrejection 117 | 118 | > 问题来源:[https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap](https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap) -------------------------------------------------------------------------------- /contents/basic/arrayn.md: -------------------------------------------------------------------------------- 1 | ## 问题:如何创建一个 [1,2,3,4,N...] 的数组? 2 | 3 | 我想创建一个包含 1 到 N 的 JavaScript 数组,其中 N 是已知的。 4 | 5 | ```js 6 | var foo = []; 7 | 8 | for (var i = 1; i <= N; i++) { 9 | foo.push(i); 10 | } 11 | ``` 12 | 13 | 有没有一种办法,可以在 `没有循环` 的情况下做到这一点。 14 | 15 | ## 答案: 16 | 17 | **在 `ES6` 中使用 ` Array from()` 和 `keys()` 方法。** 18 | 19 | ```js 20 | Array.from(Array(10).keys()) 21 | //=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 22 | ``` 23 | 24 | **使用 `扩展运算符` 的简单版本。** 25 | 26 | ```js 27 | [...Array(10).keys()] 28 | //=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 29 | ``` 30 | 31 | **从 1 开始,将具有 `length` 属性的对象和 `map` 函数传递给 `Array from()` :** 32 | 33 | ```js 34 | Array.from({length: 10}, (_, i) => i + 1) 35 | //=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 36 | ``` 37 | 38 | > 问题来源:[https://stackoverflow.com/questions/3746725/how-to-create-an-array-containing-1-n](https://stackoverflow.com/questions/3746725/how-to-create-an-array-containing-1-n) -------------------------------------------------------------------------------- /contents/basic/ascii.md: -------------------------------------------------------------------------------- 1 | ## 问题:在JavaScript中把字符转换成ASCII编码? 2 | 3 | 如何用 JavaScript 将一个 `string` 转换成 `ASCII` 字符? 4 | 5 | 例如: 6 | 7 | ```js 8 | get 10 from "\n". 9 | ``` 10 | 11 | ## 答案: 12 | 13 | [String.prototype.charCodeAt()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt) 可以将 string 字符转换为 `ASCII` 字符。 14 | 15 | 相反,使用 [String.fromCharCode()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode) 可以将数字转换为相等的 `ASCII` 字符。这个函数可以接受多个数字并连接所有的字符然后返回字符串。 16 | 17 | 例子: 18 | 19 | ```js 20 | "\n".charCodeAt(0); // 10 21 | 22 | String.fromCharCode(10); // \n 23 | 24 | ``` 25 | 26 | 这里有一个快速的 `ASCII` 字符参考。 27 | 28 | ![result](https://raw.githubusercontent.com/buynao/stackoverflow-js-top-qa/main/contents/basic/assets/ascii.png) 29 | 30 | ## 其他 31 | 32 | `charCodeAt()` 方法返回 0 到 65535 之间的整数,表示给定索引处的 UTF-16 代码单元 33 | 34 | 请注意,答案中建议的 `String.prototype.charCodeAt()` 方法会返回 `UTF-16` 编码单元(由于历史原因,其并不是完整的正确的 `UTF-16` 编码)。只有前 128 个 `Unicode` 码位是直接匹配 `ASCII` 字符编码的。 35 | 36 | `UTF-16` 编码单元匹配能用一个 `UTF-16` 编码单元表示的 `Unicode` 码位。如果 `Unicode` 码位不能用一个 `UTF-16` 编码单元表示(因为它的值大于0xFFFF ),则所返回的编码单元会是这个码位代理对的第一个编码单元) 。如果你想要整个码位的值,使用 `codePointAt()` 。 37 | 38 | > 问题来源:[https://stackoverflow.com/questions/94037/convert-character-to-ascii-code-in-javascript](https://stackoverflow.com/questions/94037/convert-character-to-ascii-code-in-javascript) -------------------------------------------------------------------------------- /contents/basic/assets/ascii.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buynao/stackoverflow-js-top-qa/fff4cccbc9c603e7b9aff9fb86599a16570cd0d3/contents/basic/assets/ascii.png -------------------------------------------------------------------------------- /contents/basic/assets/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buynao/stackoverflow-js-top-qa/fff4cccbc9c603e7b9aff9fb86599a16570cd0d3/contents/basic/assets/result.png -------------------------------------------------------------------------------- /contents/basic/badForInArray.md: -------------------------------------------------------------------------------- 1 | ## 为什么不能使用“for...in”进行数组迭代? 2 | 3 | 为什么大家建议不要在 `JavaScript` 中使用 `for...in` 遍历数组。 4 | 5 | 什么原因? 6 | 7 | ## 答案 8 | 9 | 有一种情况,是来自下边这种构造数组的方式 10 | 11 | ```js 12 | var a = []; // 创建一个空数组。 13 | a[5] = 5; // 在 JavaScript 中,可以直接调整数组大小 14 | 15 | for (var i = 0; i < a.length; i++) { 16 | // 正如你所期望的那样,迭代从 0 到 5 的数字索引。 17 | console.log(a[i]); 18 | } 19 | 20 | /* 输出: 21 | undefined 22 | undefined 23 | undefined 24 | undefined 25 | undefined 26 | 5 27 | */ 28 | ``` 29 | 30 | 有时也可能完全不同: 31 | 32 | ```js 33 | var a = []; 34 | a[5] = 5; 35 | for (var x in a) { 36 | // 仅显示直接设置的索引 5,忽略了 0-4 37 | console.log(x); 38 | } 39 | 40 | /* 输出: 41 | 5 42 | */ 43 | ``` 44 | 45 | 还有一种情况,要考虑一些 `JavaScript` 库可能会做原型覆盖这样的事情,这将影响您创建的任何数组: 46 | 47 | ```js 48 | // 在你的 JavaScript 库深处的某个地方...... 49 | Array.prototype.foo = 1; 50 | 51 | // 现在你不知道下面的代码会做什么。 52 | var a = [1, 2, 3, 4, 5]; 53 | for (var x in a){ 54 | // 现在 foo 是每个数组的一部分, 55 | // 并且将在此处显示为 x 的值。 56 | console.log(x); 57 | } 58 | 59 | /* 输出: 60 | 0 61 | 1 62 | 2 63 | 3 64 | 4 65 | foo 66 | */ 67 | ``` 68 | 69 | ## 其他答案 70 | 71 | `for-in` 语句的目的是枚举对象属性。 72 | 73 | 该语句将在原型链中上升,还会枚举继承的属性,通常情况下,这不是我们所期望的。 74 | 75 | 此外,规范表面了它不能保证迭代的顺序,这意味着如果你想 `迭代` 一个数组对象,使用这个语句你不能确定属性(数组索引)一定将按数字顺序访问。 76 | 77 | 例如,在之前的 `IE` 浏览器中: 78 | ```js 79 | var array = []; 80 | array[2] = 'c'; 81 | array[1] = 'b'; 82 | array[0] = 'a'; 83 | 84 | for (var p in array) { 85 | console.log([]) 86 | //... p will be "2", "1" and "0" on IE 87 | } 88 | ``` 89 | 90 | > 问题来源:[https://stackoverflow.com/questions/500504/why-is-using-for-in-for-array-iteration-a-bad-idea](https://stackoverflow.com/questions/500504/why-is-using-for-in-for-array-iteration-a-bad-idea) -------------------------------------------------------------------------------- /contents/basic/checkInvalidDate.md: -------------------------------------------------------------------------------- 1 | ## 问题:在 JavaScript 中如何检测 "invalid date" 日期? 2 | 3 | 在 `JavaScript` 中,我想知道如何区分有效日期和无效日期 4 | 5 | ```js 6 | 7 | var d = new Date("foo"); 8 | console.log(d.toString()); // 'Invalid Date' 9 | console.log(typeof d); // 'object' 10 | console.log(d instanceof Date); // 'true' 11 | 12 | ``` 13 | 14 | ## 答案 15 | 16 | 我会这样做: 17 | 18 | ```js 19 | 20 | if (Object.prototype.toString.call(d) === "[object Date]") { 21 | // 是日期类型 22 | if (isNaN(d)) { // 也可以 isNaN(d.getTime()) or isNaN(d.valueOf()) 23 | // 无效日期 24 | } else { 25 | // 有效日期 26 | } 27 | } else { 28 | // 不是日期类型 29 | } 30 | 31 | ``` 32 | 33 | **Update [2018-05-31]:** 34 | 35 | 如果您不关心来自其他 `JavaScript` 上下文(外部窗口、框架或 iframe)的 `Date` 对象,可以使用这种更简单的方式: 36 | 37 | ```js 38 | function isValidDate(d) { 39 | return d instanceof Date && !isNaN(d); 40 | } 41 | ``` 42 | 43 | 请注意,`invalid dates` -> `2013-13-32` 和 `invalid date objects` -> `new Date('foo')` 之间存在本质的区别。 本答案不验证日期输入是否正常,仅判断 `Date` 实例是否是有效日期。 44 | 45 | 46 | > 问题来源:[https://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript](https://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript) -------------------------------------------------------------------------------- /contents/basic/closures.md: -------------------------------------------------------------------------------- 1 | ## 问题: JavaScript闭包是如何工作的? 2 | 3 | 你会如何向已经了解了一些基本语法概念(例如函数、变量等),但不了解 `闭包` 的人解释 `JavaScript 闭包`? 4 | 5 | ## 答案 6 | 7 | `闭包` 产生于一种组合:一个函数和对该函数外部范围 `词法环境` 的引用。 8 | 9 | `词法环境` 是每个 `执行上下文` 的一部分,是 `标识符(既变量名称)` 和 `值` 之间的映射集合,可以把它理解成一个对象。它的 `属性` 就是当前环境中的 `标识符(变量名称)`,`值` 就是变量的 `值`。 10 | 11 | `JavaScript` 中的每个函数都保持对其外部 `词法环境` 的引用。当函数被调用时,这个引用能够找到其当时创建的 `执行环境`。 12 | 13 | 不管当前的函数在任意时刻被调用,这个引用都可以使得该函数内部的代码能够 "看到" 函数外部声明的变量,从而进行引用。 14 | 15 | 如果一个 `函数A` 被另一个 `函数B` 包含,而这个 `函数B` 又被另一个 `函数C` 包含,那么从最底层的 `函数A` 内部就会产生一个指向外部一直到 `函数C` 的 `词法环境` 的 `引用链`,这就是我们经常说的 `作用域链`。 16 | 17 | 在下面的代码中,`inner` 与 `foo` 被调用时创建的 `执行上下文` 的 `词法环境` 形成一个 `闭包`,将变量 `secret` 进行了 [隐藏]。 18 | 19 | ```js 20 | 21 | function foo() { 22 | const secret = Math.trunc(Math.random() * 100) 23 | return function inner() { 24 | console.log(`The secret number is ${secret}.`) 25 | } 26 | } 27 | const f = foo() // `secret' 不能从 `foo` 外部直接访问 28 | f() // 取到 `secret` 的唯一方法是调用 `f()` 29 | 30 | 31 | ``` 32 | 33 | 换句话说: 34 | 35 | 在 `JavaScript` 中,函数带有一个私有 `状态容器(box of state)` 的引用,只有在当前 `词法环境` 中,或者在其中声明的 `其他函数` 才能对这个 `状态容器` 里的数据进行访问。 36 | 37 | `状态容器` 对函数的调用者来说是不可见的,这也为数据隐藏和封装提供了一个很好的机制。 38 | 39 | 请记住: 40 | 41 | `JavaScript` 中的函数是一等公民,他们也像变量一样可以随意传递。这也就意味着函数的功能和状态,可以在你的代码中随意传递。 42 | 43 | 如果 `JavaScript` 没有 `闭包`,则必须在函数之间 `显式传递` 这些状态,从而使参数列表更长且代码更嘈杂。 44 | 45 | 所以,如果您想让函数始终可以访问到其他可能的 `私有状态`,则可以使用 `闭包`。 46 | 47 | 在 `C` 和大多数其他常见语言中,在函数返回后,所有 `局部变量` 都不再可访问,因为 `堆栈帧` 被破坏了。 48 | 49 | 在 `JavaScript` 中,如果你在另一个 `函数A` 中声明一个 `函数B`,那么外部 `函数A` 的 `局部变量` 在从它返回后仍然可以在 `函数B` 中被访问到。 50 | 51 | 所以,在上面的代码中,`secret` 在从 `foo` 返回后,仍然可以被函数对象 `inner` 使用。 52 | 53 | ### 闭包的使用 54 | 55 | 每当您需要 `函数` 包含一些私有状态时,`闭包` 都是有用的。 56 | 57 | 请记住:`JavaScript` 直到 2015 年才有一个 `class`,而且它仍然是个没有私有属性的语法。而 `闭包` 可以满足这一需求。 58 | 59 | --- 60 | > 译者注: ES2022规范中,已经支持了,`class` 的私有属性。为了加深大家对闭包的理解,以下内容继续翻译,不做删除。更加现代化的方案可以参考一下文档: 61 | 62 | > [MDN 类私有域](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/Private_class_fields) 63 | 64 | > [ES2022 class-definitions](https://tc39.es/ecma262/multipage/ecmascript-language-functions-and-classes.html#sec-class-definitions) 65 | 66 | --- 67 | ### 私有实例变量 68 | 69 | 在下面的代码中,函数 `toString()` 隐藏了汽车的详细信息。 70 | 71 | ```js 72 | 73 | function Car(manufacturer, model, year, color) { 74 | return { 75 | toString() { 76 | return `hello : ${manufacturer} ${model} (${year}, ${color})` 77 | } 78 | } 79 | } 80 | 81 | const car = new Car('Aston Martin', 'V8 Vantage', '2012', 'Quantum Silver') 82 | console.log(car.toString()) // hello : Aston Martin V8 Vantage (2012, Quantum Silver) 83 | 84 | ``` 85 | 86 | ### 函数式编程 87 | 88 | 在下面的代码中,函数 `inner` [隐藏] 了 `fn` 和 `args` 。 89 | 90 | ```js 91 | 92 | function curry(fn) { 93 | const args = [] 94 | return function inner(arg) { 95 | if(args.length === fn.length) return fn(...args) 96 | args.push(arg) 97 | return inner 98 | } 99 | } 100 | 101 | function add(a, b) { 102 | return a + b 103 | } 104 | 105 | const curriedAdd = curry(add) 106 | console.log(curriedAdd(2)(3)()) // 5 107 | 108 | ``` 109 | 110 | ### 事件编程 111 | 112 | 在下面的代码中,函数 `onClick` [隐藏] 了变量 `BACKGROUND_COLOR` 113 | 114 | ```js 115 | 116 | const $ = document.querySelector.bind(document) 117 | const BACKGROUND_COLOR = 'rgba(200, 200, 242, 1)' 118 | 119 | function onClick() { 120 | $('body').style.background = BACKGROUND_COLOR 121 | } 122 | 123 | $('button').addEventListener('click', onClick) 124 | 125 | 126 | ``` 127 | 128 | 129 | ### 模块化 130 | 131 | 在下面的例子中,所有的执行细节都隐藏在一个 `立即执行` 的 `函数表达式` 里面。 132 | 133 | 函数 `tick` 和 `toString` 隐藏了它们完成工作所需的 `私有状态 numbers ` 和 `函数 format` 。`闭包` 使我们能够对代码进行模块化和封装。 134 | 135 | ```js 136 | 137 | let namespace = {}; 138 | 139 | (function foo(n) { 140 | let numbers = [] 141 | 142 | function format(n) { 143 | return Math.trunc(n) 144 | } 145 | 146 | function tick() { 147 | numbers.push(Math.random() * 100) 148 | } 149 | 150 | function toString() { 151 | return numbers.map(format) 152 | } 153 | 154 | n.counter = { 155 | tick, 156 | toString 157 | } 158 | }(namespace)) 159 | 160 | const counter = namespace.counter 161 | counter.tick() 162 | counter.tick() 163 | console.log(counter.toString()) 164 | 165 | ``` 166 | 167 | ## Examples 168 | 169 | ### Example 1 170 | 171 | 这个例子表明,局部变量在 `闭包` 中没有被复制:`闭包` 保持着对原始变量本身的引用。 172 | 173 | 这就好比 `堆栈(call stack)` 在内存中一直保持着活力,甚至在外层函数退出之后。 174 | 175 | ```js 176 | 177 | function foo() { 178 | let x = 42 179 | let inner = () => console.log(x) 180 | x = x + 1 181 | return inner 182 | } 183 | 184 | foo()() // logs 43 185 | 186 | ``` 187 | 188 | 189 | ### Example 2 190 | 191 | 在下面的代码中,三个方法 `log`、`increment` 和 `update` 都在同一个`词法环境`中关闭。 192 | 193 | 每当 `createObject` 被调用时,一个新的执行环境`(堆栈call stack)`被创建,一个全新的变量 `x` 和一组新的函数`(log等)`被创建,它们在这个`新变量 P`中隐藏着。 194 | 195 | ```js 196 | 197 | function createObject() { 198 | let x = 42; 199 | return { 200 | log() { console.log(x) }, 201 | increment() { x++ }, 202 | update(value) { x = value } 203 | } 204 | } 205 | 206 | const o = createObject() 207 | o.increment() 208 | o.log() // 43 209 | o.update(5) 210 | o.log() // 5 211 | const p = createObject() 212 | p.log() // 42 213 | 214 | ``` 215 | 216 | ### Example 3 217 | 218 | 如果你在使用 `var` 声明变量,你要明白这个变量的一些副作用: `var` 声明的变量是会 `声明提升` 的。 219 | 220 | 由于引入了 `let` 和 `const` ,这个问题在现代的 `JavaScript` 中要少得多。 221 | 222 | 在下面的代码中,每次循环都会创建一个新的函数 `inner`,它隐藏了 `i`。 223 | 224 | 但由于 `var i` 被 `声明提升` 在循环之外了,所以这些内部函数都在同一个变量中闭合了,这意味着 `i` 的最终值 `3` 被打印了三次。 225 | 226 | ```js 227 | 228 | function foo() { 229 | var result = [] 230 | for (var i = 0; i < 3; i++) { 231 | result.push(function inner() { console.log(i) } ) 232 | } 233 | 234 | return result 235 | } 236 | 237 | const result = foo() 238 | // 下面将打印 "3",三次...。 239 | for (var i = 0; i < 3; i++) { 240 | result[i]() 241 | } 242 | 243 | ``` 244 | 245 | ## 最后几点 246 | 247 | - 每当在 `JavaScript` 中声明函数时,都会产生 `闭包`。 248 | - `function` 从另一个函数内部返回 `a` 是 `闭包` 的经典案例,因为外部函数内部的状态对于返回的内部函数是隐式可用的,即使在外部函数完成执行之后也是如此。 249 | - 每当 `eval()` 在函数内部使用时,都会使用 `闭包`。文本 `eval` 可以引用函数的局部变量,在非严格模式下,甚至可以使用 `eval('var foo = …')`. 250 | - 当 `new Function(…)` 在函数内使用 `( Function 构造函数)` 时,它不会关闭其 `词法环境`:而是关闭全局上下文。新函数不能引用外部函数的局部变量。 251 | - `JavaScript` 中的 `闭包` 就像在函数声明时保留了对作用域的引用(而不是副本),而后者又保留对其外部作用域的引用,依此类推,一直到顶部的全局对象作用域链。 252 | 声明函数时会创建一个`闭包`;这个 `闭包` 用于在调用函数时配置执行上下文。 253 | - 每次调用函数时都会创建一组新的局部变量。 254 | 255 | ## 一些引用 256 | 257 | - Douglas Crockford's simulated private attributes and [private methods for an object](http://www.crockford.com/javascript/private.html), using closures. 258 | - A great explanation of how closures can cause [memory leaks in IE](https://www.codeproject.com/Articles/12231/Memory-Leakage-in-Internet-Explorer-revisited) if you are not careful. 259 | - MDN documentation on [JavaScript Closures.](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures) 260 | 261 | > 问题来源:[https://stackoverflow.com/questions/111102/how-do-javascript-closures-work](https://stackoverflow.com/questions/111102/how-do-javascript-closures-work) -------------------------------------------------------------------------------- /contents/basic/emptyObject.md: -------------------------------------------------------------------------------- 1 | ## 问题:如何测试一个空的 JavaScript 对象? 2 | 3 | 在 `AJAX` 请求之后,有时我的程序可能会返回一个空对象,我该如何对空对象进行检查? 4 | 5 | ```js 6 | var a = {}; 7 | ``` 8 | 9 | 10 | ## 答案 11 | 12 | ### ECMA 5之后: 13 | 14 | ```js 15 | // 因为 Object.keys(new Date()).length === 0; 16 | // 所以还得做一些额外的检查 17 | obj // 👈 null and undefined check 18 | && Reflect.ownKeys(obj).length === 0 // 译者补充:增加非枚举属性判断 19 | && Object.getPrototypeOf(obj) === Object.prototype 20 | ``` 21 | 22 | 但是请注意,这会创建一个不必要的数组(`key` 的返回值) 23 | 24 | ### ECMA 5以前: 25 | 26 | ```js 27 | function isEmpty(obj) { 28 | for(var prop in obj) { 29 | if(Object.prototype.hasOwnProperty.call(obj, prop)) { 30 | return false; 31 | } 32 | } 33 | 34 | return JSON.stringify(obj) === JSON.stringify({}); 35 | } 36 | ``` 37 | 38 | ### 其他 39 | 40 | ```js 41 | 42 | // lodash: 43 | _.isEmpty({}); // true 44 | 45 | // Underscore: 46 | _.isEmpty({}); // true 47 | 48 | // jquery 49 | jQuery.isEmptyObject({}); // true 50 | ``` 51 | > 问题来源:[https://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object](https://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object) 52 | -------------------------------------------------------------------------------- /contents/basic/enumerateObject.md: -------------------------------------------------------------------------------- 1 | ## 问题:在 JavaScript 中循环枚举对象 2 | 3 | 我有一个 JavaScript 对象,如下所示: 4 | 5 | ```js 6 | var p = { 7 | "p1": "value1", 8 | "p2": "value2", 9 | "p3": "value3" 10 | }; 11 | ``` 12 | 13 | 现在我想遍历 `p` 的所有属性 `p1、p2、p3...`,并获取它们的 `键` 和 `值`。 14 | 15 | 我该怎么做? 16 | 17 | ## 答案 18 | 19 | **1. ECMAScript 6 之前可以使用 `for-in` 循环** 20 | 21 | 但是,您必须得确保您获得的 `key` 是对象的实例属性,而不是来自原型。 22 | 23 | **代码片段:** 24 | 25 | ```js 26 | var p = { 27 | "p1": "value1", 28 | "p2": "value2", 29 | "p3": "value3" 30 | }; 31 | 32 | for (var key in p) { 33 | // 确保 key 来自实例 34 | if (p.hasOwnProperty(key)) { 35 | console.log(key + " -> " + p[key]); 36 | } 37 | } 38 | // p1 -> value1 39 | // p2 -> value2 40 | // p3 -> value3 41 | ``` 42 | 43 | **2. ECMAScript 6 可以使用 `for-of` 和 `Object.keys()`** 44 | 45 | ```js 46 | var p = { 47 | 0: "value1", 48 | "b": "value2", 49 | key: "value3" 50 | }; 51 | 52 | for (var key of Object.keys(p)) { 53 | console.log(key + " -> " + p[key]) 54 | } 55 | // 输出同上 56 | ``` 57 | 58 | 请注意上面使用的是 `for-of`,而不是 `for-in`。如果用错了,属性 `值` 返回的将是 `undefined`。 59 | 60 | `Object.keys()` 仅使用对象自己的属性而不会是整个原型链上的属性。 61 | 62 | **3. ECMAScript 8 可以使用`Object.entries()`** 63 | 64 | ```js 65 | const p = { 66 | "p1": "value1", 67 | "p2": "value2", 68 | "p3": "value3" 69 | }; 70 | 71 | for (let [key, value] of Object.entries(p)) { 72 | console.log(`${key}: ${value}`); 73 | } 74 | ``` 75 | 76 | ## 其他 77 | 78 | `Object.keys()` 和 `Object.entries()` 都以与 `for...in` 循环相同的顺序迭代属性,但前两者会忽略原型链上的属性。只有对象自己的可枚举属性被迭代。 79 | -------------------------------------------------------------------------------- /contents/basic/hexadecimal.md: -------------------------------------------------------------------------------- 1 | ## 在 JavaScript 中如何将十进制转换为十六进制 2 | 3 | ## 答案 4 | 5 | 将数字转换为十六进制字符串: 6 | 7 | ```js 8 | hexString = yourNumber.toString(16); 9 | ``` 10 | 11 | 并通过以下方式反转该过程: 12 | 13 | ```js 14 | yourNumber = parseInt(hexString, 16); 15 | ``` 16 | 17 | ## 其他答案 18 | 19 | 如果还需要处理 `位字段 (Bit field)`或 32 位颜色等内容,则需要考虑一些带有符号的数字类型。 20 | 直接使用 `toString(16)` 会返回一个负的十六进制数字,这通常不是我们想要的。 21 | 22 | 这个函数做了一些额外处理,使其成为一个正数,在进行转换... 23 | 24 | ```js 25 | 26 | function decimalToHexString(number) 27 | { 28 | if (number < 0) 29 | { 30 | number = 0xFFFFFFFF + number + 1; 31 | } 32 | 33 | return number.toString(16).toUpperCase(); 34 | } 35 | 36 | console.log(decimalToHexString(27)); // 1B 37 | console.log(decimalToHexString(48.6)); // 30.99999999999A 38 | 39 | ``` 40 | 41 | 译者注: 42 | 43 | 关于 `位字段` 的相关知识,[这篇文章](https://emergent.systems/posts/bit-fields/) 介绍的比较详细,有兴趣的同学可以看下。 44 | 45 | > 问题来源:[https://stackoverflow.com/questions/57803/how-to-convert-decimal-to-hexadecimal-in-javascript](https://stackoverflow.com/questions/57803/how-to-convert-decimal-to-hexadecimal-in-javascript) 46 | 47 | -------------------------------------------------------------------------------- /contents/basic/integerVsFloat.md: -------------------------------------------------------------------------------- 1 | ## 问题:如何判断一个 number 是浮点数还是整数? 2 | 3 | 如何判断一个 number 是浮点数还是整数? 4 | 5 | ```js 6 | 1.25 --> float 浮点数 7 | 1 --> integer 整数 8 | 0 --> integer 整数 9 | 0.25 --> float 浮点数 10 | ``` 11 | 12 | ## 答案 13 | 14 | ### ES6 之后 15 | 16 | > Number.isInteger 17 | 18 | ```js 19 | function fits(x, y) { 20 | if (Number.isInteger(y / x)) { 21 | return 'Fits!'; 22 | } 23 | return 'Does NOT fit!'; 24 | } 25 | 26 | console.log(fits(5, 10)); 27 | // expected output: "Fits!" 28 | 29 | console.log(fits(5, 11)); 30 | // expected output: "Does NOT fit!" 31 | 32 | ``` 33 | 34 | ### ES6 之前 35 | 36 | 在除以 1 时检查是否还有余数。 37 | 38 | ```js 39 | function isInt(n) { 40 | return n % 1 === 0; 41 | } 42 | ``` 43 | 44 | 如果你不知道参数是什么类型,你还需要在加个判断。 45 | 46 | ```js 47 | function isInt(n){ 48 | return Number(n) === n && n % 1 === 0; 49 | } 50 | 51 | function isFloat(n){ 52 | return Number(n) === n && n % 1 !== 0; 53 | } 54 | ``` 55 | 56 | > 问题来源:[https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer](https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer) -------------------------------------------------------------------------------- /contents/basic/loopArray.md: -------------------------------------------------------------------------------- 1 | ## 问题:在 JavaScript 中循环遍历数组 2 | 3 | 在 Java 中,可以使用 for 循环遍历数组中的对象,如下所示: 4 | 5 | ```java 6 | 7 | String[] myStringArray = {"Hello", "World"}; 8 | for (String s : myStringArray) 9 | { 10 | // Do something 11 | } 12 | 13 | ``` 14 | 15 | 在 JavaScript 中应该怎么做? 16 | 17 | ## 答案 18 | 19 | ### 三个主要选项: 20 | 21 | - `for (var i = 0; i < xs.length; i++) { console.log(xs[i]); }` 22 | - `xs.forEach((x, i) => console.log(x));` 23 | - `for (const x of xs) { console.log(x); } 24 | ` 25 | 26 | 详细示例如下: 27 | 28 | **1.顺序 for 循环:** 29 | 30 | 31 | ```js 32 | var myStringArray = ["Hello","World"]; 33 | var arrayLength = myStringArray.length; 34 | for (var i = 0; i < arrayLength; i++) { 35 | console.log(myStringArray[i]); 36 | //Do something 37 | } 38 | ``` 39 | 40 | 优点: 41 | 42 | - 适用于各种环境 43 | - 可以使用 `break` 和 `continue` 流控制语句 44 | 45 | 缺点: 46 | 47 | - 代码冗长 48 | - 容易出现一个 [错误(有时也称为栅栏柱错误)](https://en.wikipedia.org/wiki/Off-by-one_error#Looping_over_arrays) 49 | 50 | **2. Array.prototype.forEach:** 51 | 52 | `ES5` 规范引入了很多有益的数组方法。 53 | 54 | `Array.prototype.forEach` 就是其中之一,它为我们提供了一种迭代数组的简洁方法: 55 | 56 | ```js 57 | const array = ["one", "two", "three"] 58 | array.forEach(function (item, index) { 59 | console.log(item, index); 60 | }); 61 | 62 | // 使用 ES6 箭头函数语法,会让它更加简洁: 63 | 64 | array.forEach(item => console.log(item)); 65 | 66 | ``` 67 | 68 | 优点: 69 | 70 | - 非常简短和简洁 71 | - 声明式 72 | 73 | 缺点: 74 | 75 | - 对于迭代串行异步不太友好 76 | - 不能使用控制流语句,不能中断/继续 77 | 78 | 然而,您可以通过在迭代数组之前,先过滤它们,从而代替中断命令式循环的需要,例如: 79 | 80 | ```js 81 | array.filter(item => item.condition < 10) 82 | .forEach(item => console.log(item)) 83 | ``` 84 | 85 | 请记住,如果您正在迭代一个数组以从中构建另一个数组,您应该使用 `map`。我已经多次看到这种反模式。 86 | 87 | **反模式:** 88 | 89 | ```js 90 | const numbers = [1,2,3,4,5], doubled = []; 91 | 92 | numbers.forEach((n, i) => { doubled[i] = n * 2 }); 93 | 94 | ``` 95 | 96 | **正确用例:** 97 | 98 | ```js 99 | const numbers = [1,2,3,4,5]; 100 | const doubled = numbers.map(n => n * 2); 101 | ``` 102 | 103 | 如果您尝试将数组归约为一个 `值` ,例如,你想对一个数字数组求和,则应使用 `reduce` 方法。 104 | 105 | 106 | **反模式:** 107 | 108 | ```js 109 | const numbers = [1,2,3,4,5]; 110 | const sum = 0; 111 | numbers.forEach(num => { sum += num }); 112 | ``` 113 | 114 | **正确用例:** 115 | 116 | ```js 117 | const numbers = [1,2,3,4,5]; 118 | const sum = numbers.reduce((total, n) => total + n, 0); 119 | ``` 120 | 121 | **3. ES6 for-of :** 122 | 123 | `ES6` 标准引入了可迭代对象的概念,并定义了一种用于遍历数据的新结构,即 `for...of` 语句。 124 | 125 | 该语句适用于任何类型的可迭代对象,也适用于生成器(任何具有 `Symbol.iterator` 属性的对象)。 126 | 127 | `数组` 是 `ES6` 中定义的内置 `可迭代对象`,因此可以直接使用以下语句: 128 | 129 | ```js 130 | let colors = ['red', 'green', 'blue']; 131 | for (const color of colors){ 132 | console.log(color); 133 | } 134 | ``` 135 | 136 | **优点:** 137 | 138 | - 它可以迭代各种各样的对象 139 | - 可以使用正常的流控制语句 `break/continue` 140 | - 对于迭代串行异步很有用,可以使用 `async await 语句` 141 | 142 | **缺点:** 143 | 144 | - 无法获取当前索引 145 | 146 | ### 不要使用 `for...in` 147 | 148 | 对于迭代数组 `for-in` 应该避免,这个语句主要是用来 **枚举** 对象属性。 149 | 150 | 它不应该用于类似数组的对象,因为: 151 | 152 | - 它不能保证迭代的顺序;数组索引可能不会按数字顺序访问。 153 | - 还会枚举继承的属性。 154 | 155 | 尤其是第二点,它会带来很多问题。例如,如果你的代码库里扩展了 `Array.prototype` 对象,在其中添加了其他方法,那么这些添加的属性也会被枚举出来。 156 | 157 | ```js 158 | Array.prototype.foo = "foo!"; 159 | var array = ['a', 'b', 'c']; 160 | 161 | for (var i in array) { 162 | console.log(array[i]); 163 | } 164 | ``` 165 | 166 | 上面的代码将控制台记录`“a”、“b”、“c”和“foo!”`。 167 | 168 | 如果您使用一些严重依赖原生原型增强的库(例如 `MooTools`),这可能会变得很复杂。 169 | 170 | 正如我之前所说,`for-in` 语句用于枚举对象属性,例如: 171 | 172 | ```js 173 | var obj = { 174 | "a": 1, 175 | "b": 2, 176 | "c": 3 177 | }; 178 | 179 | for (var prop in obj) { 180 | if (obj.hasOwnProperty(prop)) { 181 | // 为了安全起见,也可以 182 | // if (Object.prototype.hasOwnProperty.call(obj,prop)) 183 | console.log("prop: " + prop + " value: " + obj[prop]) 184 | } 185 | } 186 | ``` 187 | 188 | 在上面的示例中, `hasOwnProperty` 方法允许你仅枚举自己的属性:只检索对象当前具有的属性,绕过继承的属性(原型上的属性)。 189 | 190 | 最后,我建议您阅读以下文章: 191 | 192 | [Enumeration VS Iteration 193 | ](http://web.archive.org/web/20101213150231/http://dhtmlkitchen.com/?category=/JavaScript/&date=2007/10/21/&entry=Iteration-Enumeration-Primitives-and-Objects) 194 | 195 | > 问题来源:[https://stackoverflow.com/questions/3010840/loop-through-an-array-in-javascript](https://stackoverflow.com/questions/3010840/loop-through-an-array-in-javascript) -------------------------------------------------------------------------------- /contents/basic/new.md: -------------------------------------------------------------------------------- 1 | ## 问题:怎么理解 JavaScript 中的 new 2 | 3 | 通常大家认为 `JavaScript` 不是一种面向对象的编程语言,所以,在遇到 `new` 这个关键字时,会有一些疑惑 4 | 5 | - 它是用来干什么的? 6 | - 它解决了哪些问题? 7 | - 什么时候用合适,什么时候不合适? 8 | 9 | ## 答案 10 | 11 | 它主要做了 5 件事: 12 | 13 | 1. 创建了一个新 `object`,这个 `object` 就是一个比较纯粹的 `{} object`。 14 | 2. 将这个 `object` 内部、不可访问的 `[[prototype]]`(即 `__proto__` )属性赋值为 `构造函数`的 `prototype`(每个函数对象都自动具有 `prototype` 属性)。 15 | 3. 使 `构造函数` 内部的 `this` 指向新创建的 `object` 。 16 | 4. 执行 `构造函数` 中的代码,只要执行到 `this` 相关,就将该属性赋值给新 `object` 。 17 | 5. 如果 `构造函数` 的执行结果返回的也是一个 `object`,则直接返回;否则,返回刚创建的 `object`。 18 | 19 | 注意:`构造函数` 是指 `new` 关键字后面的函数: 20 | 21 | ```js 22 | // 构造函数 = ConstructorFunction 23 | new ConstructorFunction(arg1, arg2) 24 | ``` 25 | 26 | 得到新 `object` 后,如果查询它的未知属性,将改为查询它的 `[[prototype]]` 对象上的属性。这就是在 `JavaScript` 中获得类似于传统类继承的方法。 27 | 28 | 其中最难理解的部分是第 2 点。每个对象(包括函数)都有一个内部属性,叫做 `[[prototype]]`。 29 | 30 | 它通常只在对象创建时被设置,要么用 `new`,要么用 `Object.create`,或者基于字面意思(函数默认为 `Function.prototype` ,数字为 `Number.prototype`,等等)。它只能通过 `Object.getPrototypeOf(someObject)` 读取。没有其他方法来设置或读取这个值。 31 | 32 | 函数除了隐藏的 `[[prototype]]` 属性外,还有一个叫做 `prototype` 的属性,你可以对其随时访问和修改,以便为你创建的对象提供继承的属性和方法。 33 | 34 | ### 示例 35 | 36 | ```js 37 | 38 | ObjMaker = function() { this.a = 'first'; }; 39 | // ObjMaker 只是一个函数,并没有什么特别的地方让它成为一个构造函数。 40 | // 它是一个构造器。 41 | 42 | ObjMaker.prototype.b = 'second'; 43 | // 像所有的函数一样,ObjMaker 有一个可访问的原型 prototype 属性。 44 | // 我们可以改变它。我只是给它添加了一个叫做'b'的属性。 45 | // 像所有的对象一样,ObjMaker 也有一个不可访问的 [[prototype]] 属性 46 | // 我们不能对它做任何事情 47 | 48 | obj1 = new ObjMaker(); 49 | // 刚刚发生了 3 件事。 50 | // 1. 一个新的、空的对象被创建,叫做 obj1。 起初 obj1 和 {} 是一样的,是一个空对象。 51 | // 2. 然后 obj1 的 [[prototype]] 属性被设置为当前的 ObjMaker.prototype 的对象值 52 | //( 如果 ObjMaker.prototype 后来被分配一个新的对象值,obj1 的 [[prototype]] 将不会变, 53 | // 但你可以改变 ObjMaker.prototype 的属性,将其添加到 prototype 和 [[prototype]] ) 54 | // 3. ObjMaker 函数被执行,其中 obj1 代替了 this ... 所以 obj1.a 被设置为 'first'。 55 | 56 | obj1.a; 57 | // returns 'first' 58 | 59 | obj1.b; 60 | // obj1 上没有一个叫 'b' 的属性,所以 JavaScript 查询了它的 [[prototype]]。 61 | // 它的 [[prototype]] 与 ObjMaker.prototype 相同。 62 | // ObjMaker.prototype 上有一个名为 'b' 的属性,值为 'second'。 63 | // 所以,obj1.b 返回'second'。 64 | 65 | ``` 66 | 67 | 这就像类继承,因为现在,您使用 `new ObjMaker()` 创建的任何对象,都继承了 `b` 属性。 68 | 69 | 如果你想要一个子类之类的东西,那么你可以这样做: 70 | 71 | ```js 72 | SubObjMaker = function () {}; 73 | SubObjMaker.prototype = new ObjMaker(); 74 | // 注意:这种模式已经被废弃了! 75 | // 因为我们使用了'new',所以 SubObjMaker.prototype 的 [[prototype]] 属性 76 | // 现在被设置为 ObjMaker.prototype 的对象值 77 | // 现代的方法是使用 Object.create(),这是在 ECMAScript 5 中加入的。 78 | // SubObjMaker.prototype = Object.create(ObjMaker.prototype); 79 | 80 | SubObjMaker.prototype.c = 'third'; 81 | obj2 = new SubObjMaker(); 82 | // bj2 的 [[prototype]] 属性现在被设置为 SubObjMaker.prototype。 83 | // 记住 SubObjMaker.prototype 的 [[prototype]] 属性是 ObjMaker.prototype。 84 | // 所以现在obj2有一个原型链! obj2 --> SubObjMaker.prototype --> ObjMaker.prototype 85 | 86 | obj2.c; 87 | // returns 'third', 来自 SubObjMaker.prototype 88 | 89 | obj2.b; 90 | // returns 'second', 来自 ObjMaker.prototype 91 | 92 | obj2.a; 93 | // returns 'first', 来自 SubObjMaker.prototype, 因为 SubObjMaker.prototype 94 | // 是用 ObjMaker 函数创建的,它为我们分配了 a 属性 95 | ``` 96 | 97 | 我在这个问题上读了很多浪费时间的文章,最后才找到[这篇](https://zeekat.nl/articles/constructors-considered-mildly-confusing.html),这里有很多漂亮的图表可以更好的解释这个问题。 98 | 99 | > 问题来源:[https://stackoverflow.com/questions/1646698/what-is-the-new-keyword-in-javascript](https://stackoverflow.com/questions/1646698/what-is-the-new-keyword-in-javascript) 100 | 101 | -------------------------------------------------------------------------------- /contents/basic/onclick.md: -------------------------------------------------------------------------------- 1 | ## 问题: 在 JavaScript 中 addEventListener 和 onclick 之间有什么区别? 2 | 3 | 下面的代码都能正常的执行,那么 `addEventListener` 和 `onclick` 之间有什么区别? 4 | 5 | ```js 6 | 7 | var h = document.getElementById("a"); 8 | h.onclick = dothing1; 9 | h.addEventListener("click", dothing2); 10 | 11 | ``` 12 | 13 | ## 答案 14 | 15 | 这两种方法都是正确的,但它们本身都不是 "最好的",而且开发选择使用这两种方法可能有不太原因。 16 | 17 | ### 事件监听器 (addEventListener and IE's attachEvent) 18 | 19 | 早期版本的 `Internet Explorer` 实现 `javascript` 的方式与其他浏览器都不太相同同。`IE9` 以下的版本,你可以使用`attachEvent` ,像这样: 20 | 21 | ```js 22 | element.attachEvent('onclick', function() { /* do stuff here*/ }); 23 | 24 | ``` 25 | 26 | 在大多数其他浏览器中(包括 `IE 9` 及以上版本),你使用`addEventListener`,像这样: 27 | 28 | ```js 29 | element.addEventListener('click', function() { /* do stuff here*/ }, false); 30 | ``` 31 | 32 | 使用([DOM Level 2 events](http://www.w3.org/wiki/Handling_events_with_JavaScript#The_evolution_of_events))里的方法,你可以为任意的单一元素添加无限量的事件。唯一的限制可能就是你的浏览器内存和一些性能问题。 33 | 34 | 上面举的例子,使用了一个匿名函数。你也可以使用一个具名函数或一个闭包来添加一个事件监听器。 35 | 36 | ```js 37 | var myFunctionReference = function() { /* do stuff here*/ } 38 | element.attachEvent('onclick', myFunctionReference); 39 | element.addEventListener('click', myFunctionReference , false); 40 | ``` 41 | 42 | `addEventListener` 的另一个重要特性是它的最后一个参数,最后的参数控制了监听器对冒泡事件的反应。 43 | 44 | 上述例子中,最终参数传递的都是 `false`,这也是目前 DOM 标准事件流的触发方式 - 事件冒泡。 45 | 46 | 而 `attachEvent`,或者使用内联事件时,则没有相应的参数来进行对监听器事件的控制。 47 | 48 | ### 内联事件(HTML onclick="" 属性和 element.onclick)。 49 | 50 | 在所有支持 `javascript` 的浏览器中,你可以通过内联的方式添加事件监听,也就是直接放在 HTML 代码中。 51 | 52 | ```js 53 | Click me 54 | ``` 55 | 56 | 大多数有经验的开发者都会回避这种方法,虽然它简单而直接,而且确实能完成工作。 57 | 58 | 但是你不能在这里使用闭包或匿名函数,而且你对范围的控制也是有限的。 59 | 60 | 你提到的另一种方法, 61 | 62 | ```js 63 | element.onclick = function () { /*do stuff here */ }; 64 | 65 | ``` 66 | 67 | ...相当于内联的 `javascript` ,只是你对范围有了更多的控制(因为你写的是脚本而不是 HTML ),也可以使用匿名函数、函数引用和/或闭包。 68 | 69 | 内联事件的显著缺点是,与上述的事件监听器(event listeners)不同,你只能分配一次内联事件。 70 | 71 | 内联事件是作为元素的属性 (attribute/property) 存在的,这意味着它可以被重写。 72 | 73 | ```js 74 | var element = document.getElementById('testing'); 75 | element.onclick = function () { alert('did stuff #1'); }; 76 | element.onclick = function () { alert('did stuff #2'); }; 77 | ``` 78 | 79 | ...当你点击该元素时,你只会看到 `"Did stuff #2"` 80 | 81 | 第二个值覆盖了 `onclick` 属性的第一次的分配,并且也覆盖了原始的内联 `HTML onclick` 属性。请看这里:[http://jsfiddle.net/jpgah/。](http://jsfiddle.net/jpgah/。) 82 | 83 | 通常来说,不要使用内联事件。可能你有特定的使用场景,但如果你不能 100% 确定这种特殊场景存在,那么你就不应该使用内联事件。 84 | 85 | ### 现代 Javascript(Angular 和 React 的框架) 86 | 87 | 自从这个答案最初发布以来,像 `React` 这样的 `javascript` 框架已经变得非常流行。你会在 `React` 模板中看到这样的代码。 88 | 89 | ```js 90 | 91 | ``` 92 | 93 | 这种写法看起来像是在写内联事件,但它并不是。 94 | 95 | 这种类型的写法,在框架中有着更复杂的处理,本质还是在底层使用了事件监听器。 96 | 97 | 我在上述写的关于事件的所有内容仍然适用于现在,你应该了解这些细节,起码能够帮你拨开一层关于事件的迷雾。但是如果你在现代 JS 框架中,写过这种内联代码,不要认为你在使用内联事件。 98 | 99 | ## 哪个是最好的? 100 | 101 | 这个问题是浏览器兼容性和必要性的问题。 102 | 103 | 你需要为一个元素附加一个以上的事件吗?现在不会,将来呢? 104 | 105 | 所以,`attachEvent` 和 `addEventListener` 是有必要的,如果不是,一个内联事件可能看起来就能解决这个问题,但你最好为未来做准备,开始使用事件监听器。 106 | 107 | > 问题来源: [https://stackoverflow.com/questions/6348494/addeventlistener-vs-onclick](https://stackoverflow.com/questions/6348494/addeventlistener-vs-onclick) -------------------------------------------------------------------------------- /contents/basic/promise.md: -------------------------------------------------------------------------------- 1 | ## 问题:如何取消正在执行的 Promise ? 2 | 3 | 有没有一种方法可以清除 `Promise` 实例的 `.thens` ? 4 | 5 | 我试着用 `Promise.race([result, at.promise])`,它好像没有起作用。 6 | 7 | 8 | ## 答案 9 | 10 | 暂时没有方法可以取消,至少在 `ECMAScript 6` 中没有。 11 | 12 | `Promise`(以及它们的处理程序)默认是不可取消的,在 [es-discuss](https://esdiscuss.org/topic/cancellation-architectural-observations) 上有一些关于如何取消的讨论; 13 | 14 | 目前的观点是:你可以通过 `子类化(subclassing)` 自己实现可取消的 `Promise` : 15 | 16 | ```js 17 | const makeCancelable = (promise) => { 18 | let hasCanceled_ = false; 19 | 20 | const wrappedPromise = new Promise((resolve, reject) => { 21 | promise.then((val) => 22 | hasCanceled_ ? reject({isCanceled: true}) : resolve(val) 23 | ); 24 | promise.catch((error) => 25 | hasCanceled_ ? reject({isCanceled: true}) : reject(error) 26 | ); 27 | }); 28 | 29 | return { 30 | promise: wrappedPromise, 31 | cancel() { 32 | hasCanceled_ = true; 33 | }, 34 | }; 35 | }; 36 | 37 | const cancelablePromise = makeCancelable( 38 | new Promise(r => component.setState({...}})) 39 | ); 40 | 41 | cancelablePromise 42 | .promise 43 | .then(() => console.log('resolved')) 44 | .catch((reason) => console.log('isCanceled', reason.isCanceled)); 45 | 46 | cancelablePromise.cancel(); // Cancel the promise 47 | ``` 48 | 49 | 相关标准讨论草案在这里 [https://github.com/bergus/promise-cancellation](https://github.com/bergus/promise-cancellation)。 50 | 51 | > 译者注:Promise 默认不可取消,即使如上面的自定义方法,也只是取消了 Promise 后续的默认标准行为,而并没有实际取消他的实际执行结果(该发的 http 请求还是会发出去~)。 52 | 53 | > 问题来源:[https://stackoverflow.com/questions/29478751/cancel-a-vanilla-ecmascript-6-promise-chain](https://stackoverflow.com/questions/29478751/cancel-a-vanilla-ecmascript-6-promise-chain) -------------------------------------------------------------------------------- /contents/basic/promiseExecute.md: -------------------------------------------------------------------------------- 1 | ## 问题:为什么 Promise 在 resolve 或 reject 后仍在继续执行? 2 | 3 | 我惊讶地发现,Promise 在被 resolve 或 reject 调用后,后续的代码仍在继续执行。 4 | 5 | 我认为 `resolve` 或 `reject` 应该会友好的 `exit` 或 `return`,接着将停止所有即时函数的执行。 6 | 7 | 谁能解释一下为什么? 8 | 9 | ```js 10 | return new Promise((resolve, reject) => { 11 | resolve(true) 12 | console.log(`---------`); // 也会继续输出 13 | }) 14 | ``` 15 | 16 | ## 答案 17 | 18 | `JavaScript` 有一个 ["Run-to-completion"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#run-to-completion) 的概念。 19 | 20 | 除非报错,否则一个函数会一直执行到 `return` 语句或到达终点。 21 | 22 | 函数之外的代码不能干扰它(除非再次抛出一个错误)。 23 | 24 | 如果你想让 `resolve()` 退出你的初始化函数,你必须在它前面加上 `return`: 25 | 26 | ```js 27 | return new Promise((resolve, reject) => { 28 | return resolve(true) 29 | console.log(`---------`); // 不会继续输出 30 | }) 31 | ``` 32 | 33 | 34 | > 问题来源:[https://stackoverflow.com/questions/28896280/why-does-javascript-es6-promises-continue-execution-after-a-resolve](https://stackoverflow.com/questions/28896280/why-does-javascript-es6-promises-continue-execution-after-a-resolve) -------------------------------------------------------------------------------- /contents/basic/range.md: -------------------------------------------------------------------------------- 1 | ## 问题:JavaScript是否有类似 range() 的方法,可以在边界内的生成一个范围? 2 | 3 | 在 PHP 语言中,有种 range 方法,通过传递上下界限值生成一个数字或字符的范围。 4 | 5 | 在 JavaScript 中是否有内置的方法可以实现这个功能?如果没有,我将如何实现它 6 | ? 7 | 8 | ```php 9 | range(1, 3); // Array(1, 2, 3) 10 | range("A", "C"); // Array("A", "B", "C") 11 | ``` 12 | 13 | ## 答案 14 | 15 | ### 生成数字 16 | 17 | ```js 18 | [...Array(5).keys()]; // => [0, 1, 2, 3, 4] 19 | ``` 20 | 21 | ### 生成字符 22 | 23 | ```js 24 | String.fromCharCode(...[...Array('D'.charCodeAt(0) - 'A'.charCodeAt(0) + 1).keys()].map(i => i + 'A'.charCodeAt(0))); 25 | // => 'ABCD' 26 | ``` 27 | 28 | ### 迭代 29 | 30 | ```js 31 | for (const x of Array(5).keys()) { 32 | console.log(x, String.fromCharCode('A'.charCodeAt(0) + x)); 33 | } 34 | => 0,"A" 1,"B" 2,"C" 3,"D" 4,"E" 35 | ``` 36 | 37 | ### 函数 38 | 39 | ```js 40 | function range(size, startAt = 0) { 41 | return [...Array(size).keys()].map(i => i + startAt); 42 | } 43 | 44 | function characterRange(startChar, endChar) { 45 | return String.fromCharCode(...range(endChar.charCodeAt(0) - 46 | startChar.charCodeAt(0), startChar.charCodeAt(0))) 47 | } 48 | ``` 49 | 50 | ### typescript 函数 51 | 52 | ```ts 53 | function range(size:number, startAt:number = 0):ReadonlyArray { 54 | return [...Array(size).keys()].map(i => i + startAt); 55 | } 56 | 57 | function characterRange(startChar:string, endChar:string):ReadonlyArray { 58 | return String.fromCharCode(...range(endChar.charCodeAt(0) - 59 | startChar.charCodeAt(0), startChar.charCodeAt(0))) 60 | } 61 | ``` 62 | 63 | ### lodash.js _.range()函数 64 | 65 | ```js 66 | _.range(10); 67 | => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 68 | _.range(1, 11); 69 | => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 70 | _.range(0, 30, 5); 71 | => [0, 5, 10, 15, 20, 25] 72 | _.range(0, -10, -1); 73 | => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] 74 | String.fromCharCode(..._.range('A'.charCodeAt(0), 'D'.charCodeAt(0) + 1)); 75 | => "ABCD" 76 | ``` 77 | 78 | ### es6之前 79 | 80 | ```js 81 | Array.apply(null, Array(5)).map(function (_, i) {return i;}); 82 | => [0, 1, 2, 3, 4] 83 | ``` 84 | 85 | 86 | > 问题来源:[https://stackoverflow.com/questions/3895478/does-javascript-have-a-method-like-range-to-generate-a-range-within-the-supp](https://stackoverflow.com/questions/3895478/does-javascript-have-a-method-like-range-to-generate-a-range-within-the-supp) -------------------------------------------------------------------------------- /contents/basic/sleep.md: -------------------------------------------------------------------------------- 1 | ## 问题:sleep() 的 JavaScript 版本是什么? 2 | 3 | 有没有比下面 `pausecomp` 更好的方法, 如何在 `JavaScript` 中设计一个优雅的 `sleep` ? 4 | 5 | ```js 6 | 7 | function pausecomp(millis) 8 | { 9 | var date = new Date(); 10 | var curDate = null; 11 | do { curDate = new Date(); } 12 | while(curDate-date < millis); 13 | } 14 | 15 | ``` 16 | 17 | ## 答案 18 | 19 | ### 2017 — 2021 更新 20 | 21 | 自 2009 年提出这个问题以来,`JavaScript` 发生了重大变化。其他很多的答案现在都已过时或过于复杂。 22 | 23 | 这种是当前的最佳实践: 24 | 25 | ```js 26 | 27 | function sleep(ms) { 28 | return new Promise(resolve => setTimeout(resolve, ms)); 29 | } 30 | 31 | // 作为单行 32 | await new Promise(r => setTimeout(r, 2000)); 33 | 34 | // 添加参数控制 35 | const sleep = ms => new Promise(r => setTimeout(r, ms)); 36 | 37 | await sleep(); 38 | 39 | ``` 40 | 41 | ### 使用 42 | 43 | ```js 44 | function sleep(ms) { 45 | return new Promise(resolve => setTimeout(resolve, ms)); 46 | } 47 | 48 | async function demo() { 49 | for (let i = 0; i < 5; i++) { 50 | console.log(`Waiting ${i} seconds...`); 51 | await sleep(i * 1000); 52 | } 53 | console.log('Done'); 54 | } 55 | 56 | demo(); 57 | 58 | ``` 59 | 60 | **请注意。** 61 | 62 | `await` 只能在以 `async` 关键字为前缀的函数中执行。`await` 只暂停当前的异步函数。 63 | 64 | 这意味着它不会阻止脚本其他部分的执行,这是你在绝大多数情况下想要的。 65 | 66 | 如果你确实想要一个`阻塞结构`,可以试下 `Atomics.wait`,它应该在 Node.js 9.3 或更高版本中工作。 67 | 68 | ```js 69 | let ms = 10000; 70 | Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); 71 | ``` 72 | 73 | 跑了几个 10 秒计时器。 74 | 75 | 使用 `setTimeout`,我会收到高达 7000 微秒(7 毫秒)的错误。 76 | 77 | 使用 `Atomics`,我的错误似乎保持在 600 微秒(0.6 毫秒)以下 78 | 79 | **总结:** 80 | 81 | ```js 82 | function sleep(millis){ // Need help of a server-side page 83 | let netMillis = Math.max(millis-5, 0); // Assuming 5 ms overhead 84 | let xhr = new XMLHttpRequest(); 85 | xhr.open('GET', '/sleep.jsp?millis=' + netMillis + '&rand=' + Math.random(), false); 86 | try{ 87 | xhr.send(); 88 | }catch(e){ 89 | } 90 | } 91 | 92 | function sleepAsync(millis){ // Use only in async function 93 | let netMillis = Math.max(millis-1, 0); // Assuming 1 ms overhead 94 | return new Promise((resolve) => { 95 | setTimeout(resolve, netMillis); 96 | }); 97 | } 98 | function sleepSync(millis){ // Use only in worker thread, currently Chrome-only 99 | Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, millis); 100 | } 101 | 102 | function sleepTest(){ 103 | console.time('sleep'); 104 | sleep(1000); 105 | console.timeEnd('sleep'); 106 | } 107 | 108 | async function sleepAsyncTest(){ 109 | console.time('sleepAsync'); 110 | await sleepAsync(1000); 111 | console.timeEnd('sleepAsync'); 112 | } 113 | 114 | function sleepSyncTest(){ 115 | let source = `${sleepSync.toString()} 116 | console.time('sleepSync'); 117 | sleepSync(1000); 118 | console.timeEnd('sleepSync');`; 119 | let src = 'data:text/javascript,' + encodeURIComponent(source); 120 | console.log(src); 121 | var worker = new Worker(src); 122 | } 123 | ``` 124 | 125 | > 问题来源:[https://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep](https://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep) -------------------------------------------------------------------------------- /contents/basic/testKeyInBbject.md: -------------------------------------------------------------------------------- 1 | ## 问题:判断 JavaScript 对象中是否存在某个 key? 2 | 3 | 如何判断 `JavaScript` 的对象或数组中是否存在某个 `key`? 4 | 5 | 如果`key`不存在,而我尝试访问它,它会返回 `false` 吗?还是抛出个错误? 6 | 7 | ## 答案 8 | 9 | 直接通过 `if (key === undefined) {} `, 并不能准确的判断这个 `key` 是否存在。 10 | 11 | 比如, `key` 确实存在,但如果它的 `值` 实际上却是 `undefined` 呢? 12 | 13 | ```js 14 | var obj = { key: undefined }; 15 | console.log(obj["key"] !== undefined); // false, 但 key 是存在的 16 | ``` 17 | 18 | **改用 `in` 运算符:** 19 | 20 | ```js 21 | var obj = { key: undefined }; 22 | console.log("key" in obj); // true, 与实际值无关 23 | ``` 24 | 25 | 注意,如果需要直接判断 `key` 是否存在,记得使用括号把判断条件包裹起来: 26 | 27 | ```js 28 | 29 | var obj = { not_key: undefined }; 30 | console.log(!("key" in obj)); // 如果对象中不存在“key”,则为 true 31 | console.log(!"key" in obj); // 不要这样做!程序会误以为这样进行判断: false in obj 32 | 33 | ``` 34 | 35 | 如果你想测试对象实例的属性(而不是继承的属性),使用 `hasOwnProperty` : 36 | 37 | ```js 38 | var obj = { key: undefined }; 39 | console.log(obj.hasOwnProperty("key")); // true 40 | ``` 41 | 42 | 关于 `in` , `hasOwnProperty` 和 `key === undefined` 三种方法之间的性能比较,请参阅此 [文档](https://jsben.ch/WqlIl) 。 43 | 44 | ![result](https://raw.githubusercontent.com/buynao/stackoverflow-js-top-qa/main/contents/basic/assets/result.png) 45 | -------------------------------------------------------------------------------- /contents/basic/this.md: -------------------------------------------------------------------------------- 1 | ## 问题: this 的运行原理 2 | 3 | 我看到过很多关于 `this` 的一些非常奇怪的行为,但却不明白为什么会发生这种情况。 4 | 5 | `this` 的运行原理是怎样的,应该怎么使用它? 6 | 7 | ## 答案 8 | 9 | `this` 是 JavaScript 中 `执行环境` 里的一个属性。主要用途通常是在函数和构造函数中。 10 | 11 | ### ECMA规范中对 this 的技术描述 12 | 13 | [ECMAScript标准](https://tc39.es/ecma262/) 通过 [ResolveThisBinding](https://tc39.es/ecma262/#sec-resolvethisbinding) AO(可以理解成是一种抽象方法,缩写为AO) 来定义 [this](https://tc39.es/ecma262/#sec-this-keyword) : 14 | 15 | ```text 16 | ResolveThisBinding 不需要任何参数。它使用运行中的执行环境的 [词法环境 LexicalEnvironment] 来完成 this 的绑定。在被调用时执行以下步骤。 17 | 18 | 1. Let envRec be GetThisEnvironment(). 19 | 2. Return ? envRec.GetThisBinding(). 20 | ``` 21 | 22 | [全局环境记录 Global Environment Records](https://tc39.es/ecma262/#sec-global-environment-records)、[模块环境记录 Module Environment Records](https://tc39.es/ecma262/#sec-module-environment-records) 和 [函数环境记录 Function Environment Records](https://tc39.es/ecma262/#sec-function-environment-records) 都有自己的 `GetThisBinding` 方法。 23 | 24 | 通过 `GetThisEnvironment` AO 找到当前执行环境中的 `词法环境 LexicalEnvironment`,并找到最接近的具有此绑定(即 `HasThisBinding` 返回 true )的环境记录(通过迭代访问其 `[[OuterEnv]]` 属性)。然后会产生上述 `三种环境类型` 中的一种环境记录。 25 | 26 | `this` 往往也取决于代码环境是否处于 `严格模式(strict mode)` 中。 27 | 28 | `GetThisBinding` 的返回值就是当前执行上下文的 `this`,所以每当建立一个新的执行上下文时,`this` 就会解析为一个不同的值。当当前执行上下文被修改时也会发生这种情况。 29 | 30 | 下面的例子列出了可能发生这种情况的五种场景: 31 | 32 | ### 1. script 中的执行上下文 33 | 34 | 例如,这是在最顶层执行的代码,直接在 ` 45 | ``` 46 | 47 | 当脚本代码在全局环境中执行时, `GetThisBinding` 的步骤如下: 48 | 49 | ```text 50 | The GetThisBinding concrete method of a global Environment Record envRec […] [does this]: 51 | 52 | Return envRec.[[GlobalThisValue]]. 53 | ``` 54 | 55 | `全局环境记录` 的 `[[GlobalThisValue]]` 属性始终被设置为当前环境的全局对象,可以直接通过 `globalThis`(Web 上的 `window`,Node.js 上的 `global`;[MDN上的文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/globalThis) )进行访问,也就是指向全局变量。 56 | 57 | 你可以访问 [InitializeHostDefinedRealm](https://tc39.es/ecma262/#sec-initializehostdefinedrealm) 进一步了解 `[[GlobalThisValue]]` 的属性是如何形成的。 58 | 59 | ### 2. module 中的执行上下文 60 | 61 | `module` 在 `ECMAScript 2015` 中被引入。 62 | 63 | 例子:直接在 ` 19 | 20 | 21 | 22 | 23 | --------------------------------------------------------------------------------