{"action":"created","issue":{"url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/82","repository_url":"https://api.github.com/repos/mqyqingfeng/Blog","labels_url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/82/labels{/name}","comments_url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/82/comments","events_url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/82/events","html_url":"https://github.com/mqyqingfeng/Blog/issues/82","id":324774130,"node_id":"MDU6SXNzdWUzMjQ3NzQxMzA=","number":82,"title":"ES6 系列之 let 和 const","user":{"login":"mqyqingfeng","id":11458263,"node_id":"MDQ6VXNlcjExNDU4MjYz","avatar_url":"https://avatars3.githubusercontent.com/u/11458263?v=4","gravatar_id":"","url":"https://api.github.com/users/mqyqingfeng","html_url":"https://github.com/mqyqingfeng","followers_url":"https://api.github.com/users/mqyqingfeng/followers","following_url":"https://api.github.com/users/mqyqingfeng/following{/other_user}","gists_url":"https://api.github.com/users/mqyqingfeng/gists{/gist_id}","starred_url":"https://api.github.com/users/mqyqingfeng/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mqyqingfeng/subscriptions","organizations_url":"https://api.github.com/users/mqyqingfeng/orgs","repos_url":"https://api.github.com/users/mqyqingfeng/repos","events_url":"https://api.github.com/users/mqyqingfeng/events{/privacy}","received_events_url":"https://api.github.com/users/mqyqingfeng/received_events","type":"User","site_admin":false},"labels":[{"id":939408132,"node_id":"MDU6TGFiZWw5Mzk0MDgxMzI=","url":"https://api.github.com/repos/mqyqingfeng/Blog/labels/ES6%E7%B3%BB%E5%88%97","name":"ES6系列","color":"F7E032","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":18,"created_at":"2018-05-21T03:10:57Z","updated_at":"2018-07-03T13:03:24Z","closed_at":null,"author_association":"OWNER","body":"## 块级作用域的出现\r\n\r\n通过 var 声明的变量存在变量提升的特性：\r\n\r\n```js\r\nif (condition) {\r\n    var value = 1;\r\n}\r\nconsole.log(value);\r\n```\r\n\r\n初学者可能会觉得只有 condition 为 true 的时候，才会创建 value，如果 condition 为 false，结果应该是报错，然而因为变量提升的原因，代码相当于：\r\n\r\n```js\r\nvar value;\r\nif (condition) {\r\n    value = 1;\r\n}\r\nconsole.log(value);\r\n```\r\n\r\n如果 condition 为 false，结果会是 undefined。\r\n\r\n除此之外，在 for 循环中：\r\n\r\n```js\r\nfor (var i = 0; i < 10; i++) {\r\n    ...\r\n}\r\nconsole.log(i); // 10\r\n```\r\n\r\n即便循环已经结束了，我们依然可以访问 i 的值。\r\n\r\n为了加强对变量生命周期的控制，ECMAScript 6 引入了块级作用域。\r\n\r\n块级作用域存在于：\r\n\r\n* 函数内部\r\n* 块中(字符 { 和 } 之间的区域)\r\n\r\n## let 和 const\r\n\r\n块级声明用于声明在指定块的作用域之外无法访问的变量。\r\n\r\nlet 和 const 都是块级声明的一种。\r\n\r\n我们来回顾下 let 和 const 的特点：\r\n\r\n**1.不会被提升**\r\n\r\n```js\r\nif (false) {\r\n    let value = 1;\r\n}\r\nconsole.log(value); // Uncaught ReferenceError: value is not defined\r\n```\r\n\r\n**2.重复声明报错**\r\n\r\n```js\r\nvar value = 1;\r\nlet value = 2; // Uncaught SyntaxError: Identifier 'value' has already been declared\r\n```\r\n\r\n**3.不绑定全局作用域**\r\n\r\n当在全局作用域中使用 var 声明的时候，会创建一个新的全局变量作为全局对象的属性。\r\n\r\n```js\r\nvar value = 1;\r\nconsole.log(window.value); // 1\r\n```\r\n\r\n然而 let 和 const 不会：\r\n\r\n```js\r\nlet value = 1;\r\nconsole.log(window.value); // undefined\r\n```\r\n\r\n再来说下 let 和 const 的区别：\r\n\r\nconst 用于声明常量，其值一旦被设定不能再被修改，否则会报错。\r\n\r\n值得一提的是：const 声明不允许修改绑定，但允许修改值。这意味着当用 const 声明对象时：\r\n\r\n```js\r\nconst data = {\r\n    value: 1\r\n}\r\n\r\n// 没有问题\r\ndata.value = 2;\r\ndata.num = 3;\r\n\r\n// 报错\r\ndata = {}; // Uncaught TypeError: Assignment to constant variable.\r\n```\r\n\r\n## 临时死区\r\n\r\n临时死区(Temporal Dead Zone)，简写为 TDZ。\r\n\r\nlet 和 const 声明的变量不会被提升到作用域顶部，如果在声明之前访问这些变量，会导致报错：\r\n\r\n```js\r\nconsole.log(typeof value); // Uncaught ReferenceError: value is not defined\r\nlet value = 1;\r\n```\r\n\r\n这是因为 JavaScript 引擎在扫描代码发现变量声明时，要么将它们提升到作用域顶部(遇到 var 声明)，要么将声明放在 TDZ 中(遇到 let 和 const 声明)。访问 TDZ 中的变量会触发运行时错误。只有执行过变量声明语句后，变量才会从 TDZ 中移出，然后方可访问。\r\n\r\n看似很好理解，不保证你不犯错：\r\n\r\n```js\r\nvar value = \"global\";\r\n\r\n// 例子1\r\n(function() {\r\n    console.log(value);\r\n\r\n    let value = 'local';\r\n}());\r\n\r\n// 例子2\r\n{\r\n    console.log(value);\r\n\r\n    const value = 'local';\r\n};\r\n```\r\n\r\n两个例子中，结果并不会打印 \"global\"，而是报错 `Uncaught ReferenceError: value is not defined`，就是因为 TDZ 的缘故。\r\n\r\n## 循环中的块级作用域\r\n\r\n```js\r\nvar funcs = [];\r\nfor (var i = 0; i < 3; i++) {\r\n    funcs[i] = function () {\r\n        console.log(i);\r\n    };\r\n}\r\nfuncs[0](); // 3\r\n```\r\n\r\n一个老生常谈的面试题，解决方案如下：\r\n\r\n```js\r\nvar funcs = [];\r\nfor (var i = 0; i < 3; i++) {\r\n    funcs[i] = (function(i){\r\n        return function() {\r\n            console.log(i);\r\n        }\r\n    }(i))\r\n}\r\nfuncs[0](); // 0\r\n```\r\n\r\nES6 的 let 为这个问题提供了新的解决方法：\r\n\r\n```js\r\nvar funcs = [];\r\nfor (let i = 0; i < 3; i++) {\r\n    funcs[i] = function () {\r\n        console.log(i);\r\n    };\r\n}\r\nfuncs[0](); // 0\r\n```\r\n\r\n问题在于，上面讲了 let 不提升，不能重复声明，不能绑定全局作用域等等特性，可是为什么在这里就能正确打印出 i 值呢？\r\n\r\n如果是不重复声明，在循环第二次的时候，又用 let 声明了 i，应该报错呀，就算因为某种原因，重复声明不报错，一遍一遍迭代，i 的值最终还是应该是 3 呀，还有人说 for 循环的\r\n设置循环变量的那部分是一个单独的作用域，就比如：\r\n\r\n```js\r\nfor (let i = 0; i < 3; i++) {\r\n  let i = 'abc';\r\n  console.log(i);\r\n}\r\n// abc\r\n// abc\r\n// abc\r\n```\r\n\r\n这个例子是对的，如果我们把 let 改成 var 呢？\r\n\r\n```js\r\nfor (var i = 0; i < 3; i++) {\r\n  var i = 'abc';\r\n  console.log(i);\r\n}\r\n// abc\r\n```\r\n\r\n为什么结果就不一样了呢，如果有单独的作用域，结果应该是相同的呀……\r\n\r\n如果要追究这个问题，就要抛弃掉之前所讲的这些特性！这是因为 let 声明在循环内部的行为是标准中专门定义的，不一定就与 let 的不提升特性有关，其实，在早期的 let 实现中就不包含这一行为。\r\n\r\n我们查看[ ECMAScript 规范第 13.7.4.7 节](http://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation):\r\n\r\n![let 规范](https://raw.githubusercontent.com/mqyqingfeng/Blog/master/Images/ES6/let/let-ecma.png)\r\n\r\n我们会发现，在 for 循环中使用 let 和 var，底层会使用不同的处理方式。\r\n\r\n那么当使用 let 的时候底层到底是怎么做的呢？\r\n\r\n简单的来说，就是在 `for (let i = 0; i < 3; i++)` 中，即圆括号之内建立一个隐藏的作用域，这就可以解释为什么:\r\n\r\n```js\r\nfor (let i = 0; i < 3; i++) {\r\n  let i = 'abc';\r\n  console.log(i);\r\n}\r\n// abc\r\n// abc\r\n// abc\r\n```\r\n\r\n然后**每次迭代循环时都创建一个新变量，并以之前迭代中同名变量的值将其初始化**。这样对于下面这样一段代码\r\n\r\n```js\r\nvar funcs = [];\r\nfor (let i = 0; i < 3; i++) {\r\n    funcs[i] = function () {\r\n        console.log(i);\r\n    };\r\n}\r\nfuncs[0](); // 0\r\n```\r\n\r\n就相当于：\r\n\r\n```js\r\n// 伪代码\r\n(let i = 0) {\r\n    funcs[0] = function() {\r\n        console.log(i)\r\n    };\r\n}\r\n\r\n(let i = 1) {\r\n    funcs[1] = function() {\r\n        console.log(i)\r\n    };\r\n}\r\n\r\n(let i = 2) {\r\n    funcs[2] = function() {\r\n        console.log(i)\r\n    };\r\n};\r\n```\r\n\r\n当执行函数的时候，根据词法作用域就可以找到正确的值，其实你也可以理解为 let 声明模仿了闭包的做法来简化循环过程。\r\n\r\n## 循环中的 let 和 const\r\n\r\n不过到这里还没有结束，如果我们把 let 改成 const 呢？\r\n\r\n```js\r\nvar funcs = [];\r\nfor (const i = 0; i < 10; i++) {\r\n    funcs[i] = function () {\r\n        console.log(i);\r\n    };\r\n}\r\nfuncs[0](); // Uncaught TypeError: Assignment to constant variable.\r\n```\r\n\r\n结果会是报错，因为虽然我们每次都创建了一个新的变量，然而我们却在迭代中尝试修改 const 的值，所以最终会报错。\r\n\r\n说完了普通的 for 循环，我们还有 for in 循环呢~\r\n\r\n那下面的结果是什么呢？\r\n\r\n```js\r\nvar funcs = [], object = {a: 1, b: 1, c: 1};\r\nfor (var key in object) {\r\n    funcs.push(function(){\r\n        console.log(key)\r\n    });\r\n}\r\n\r\nfuncs[0]()\r\n```\r\n\r\n结果是 'c';\r\n\r\n那如果把 var 改成 let 或者 const 呢？\r\n\r\n使用 let，结果自然会是 'a'，const 呢？ 报错还是 'a'?\r\n\r\n结果是正确打印 'a'，这是因为在 for in 循环中，每次迭代不会修改已有的绑定，而是会创建一个新的绑定。\r\n\r\n## Babel\r\n\r\n在 Babel 中是如何编译 let 和 const 的呢？我们来看看编译后的代码：\r\n\r\n```js\r\nlet value = 1;\r\n```\r\n\r\n编译为:\r\n\r\n```js\r\nvar value = 1;\r\n```\r\n\r\n我们可以看到 Babel 直接将 let 编译成了 var，如果是这样的话，那么我们来写个例子：\r\n\r\n```js\r\nif (false) {\r\n    let value = 1;\r\n}\r\nconsole.log(value); // Uncaught ReferenceError: value is not defined\r\n```\r\n\r\n如果还是直接编译成 var，打印的结果肯定是 undefined，然而 Babel 很聪明，它编译成了：\r\n\r\n```js\r\nif (false) {\r\n    var _value = 1;\r\n}\r\nconsole.log(value);\r\n```\r\n\r\n我们再写个直观的例子：\r\n\r\n```js\r\nlet value = 1;\r\n{\r\n    let value = 2;\r\n}\r\nvalue = 3;\r\n```\r\n\r\n```js\r\nvar value = 1;\r\n{\r\n    var _value = 2;\r\n}\r\nvalue = 3;\r\n```\r\n\r\n本质是一样的，就是改变量名，使内外层的变量名称不一样。\r\n\r\n那像 const 的修改值时报错，以及重复声明报错怎么实现的呢？\r\n\r\n其实就是在编译的时候直接给你报错……\r\n\r\n那循环中的 let 声明呢？\r\n\r\n```js\r\nvar funcs = [];\r\nfor (let i = 0; i < 10; i++) {\r\n    funcs[i] = function () {\r\n        console.log(i);\r\n    };\r\n}\r\nfuncs[0](); // 0\r\n```\r\n\r\nBabel 巧妙的编译成了：\r\n\r\n```js\r\nvar funcs = [];\r\n\r\nvar _loop = function _loop(i) {\r\n    funcs[i] = function () {\r\n        console.log(i);\r\n    };\r\n};\r\n\r\nfor (var i = 0; i < 10; i++) {\r\n    _loop(i);\r\n}\r\nfuncs[0](); // 0\r\n```\r\n\r\n## 最佳实践\r\n\r\n在我们开发的时候，可能认为应该默认使用 let 而不是 var ，这种情况下，对于需要写保护的变量要使用 const。然而另一种做法日益普及：默认使用 const，只有当确实需要改变变量的值的时候才使用 let。这是因为大部分的变量的值在初始化后不应再改变，而预料之外的变量之的改变是很多 bug 的源头。\r\n\r\n## ES6 系列\r\n\r\nES6 系列目录地址：https://github.com/mqyqingfeng/Blog\r\n\r\nES6 系列预计写二十篇左右，旨在加深 ES6 部分知识点的理解，重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。\r\n\r\n如果有错误或者不严谨的地方，请务必给予指正，十分感谢。如果喜欢或者有所启发，欢迎star，对作者也是一种鼓励。\r\n"},"comment":{"url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/comments/402149619","html_url":"https://github.com/mqyqingfeng/Blog/issues/82#issuecomment-402149619","issue_url":"https://api.github.com/repos/mqyqingfeng/Blog/issues/82","id":402149619,"node_id":"MDEyOklzc3VlQ29tbWVudDQwMjE0OTYxOQ==","user":{"login":"whwiGrado","id":8196617,"node_id":"MDQ6VXNlcjgxOTY2MTc=","avatar_url":"https://avatars3.githubusercontent.com/u/8196617?v=4","gravatar_id":"","url":"https://api.github.com/users/whwiGrado","html_url":"https://github.com/whwiGrado","followers_url":"https://api.github.com/users/whwiGrado/followers","following_url":"https://api.github.com/users/whwiGrado/following{/other_user}","gists_url":"https://api.github.com/users/whwiGrado/gists{/gist_id}","starred_url":"https://api.github.com/users/whwiGrado/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/whwiGrado/subscriptions","organizations_url":"https://api.github.com/users/whwiGrado/orgs","repos_url":"https://api.github.com/users/whwiGrado/repos","events_url":"https://api.github.com/users/whwiGrado/events{/privacy}","received_events_url":"https://api.github.com/users/whwiGrado/received_events","type":"User","site_admin":false},"created_at":"2018-07-03T13:03:24Z","updated_at":"2018-07-03T13:03:24Z","author_association":"NONE","body":"有两个疑问：\r\nvar value = \"global\";\r\n\r\n// 例子1\r\n(function() {\r\n    debugger // 在断点处访问 value，返回的是 undefined\r\n    console.log(value);\r\n    let value = 'local';\r\n}());\r\n\r\n这个例子中，自执行函数中\r\n1、先执行 console.log(value)，此时还没有执行 let value = 'local';\r\n那么 value 是怎么被放入 TDZ 的呢？\r\n2、如果加上了 debugger，在断点处访问 value，返回的是 undefined，又是为什么？"}}
{"actor":{"display_login":"whwiGrado"}}