es6有许多特别棒的特性,你可能对该语言的总体很是熟悉,可是你知道它在内部是如何工做的吗?当咱们知道它的内部原理之后,咱们使用起来也会更加的安心一些。这里咱们想逐步的引导你,让你对其有一个更深刻,更浅显的认识。让咱们就先从es6中的变量开始讲起吧。javascript
let和constjava
在es6中新引入了两种方式来申明变量,咱们仍然可使用广为传诵的var变量(然而你不该该继续使用它了,继续阅读来了解其中缘由),可是如今咱们有了两种更牛的工具去使用:let和const。es6
let数组
let和var很是的类似,在使用方面,你可使用彻底相同的方式来声明变量,例如:闭包
let myNewVariable = 2; var myOldVariable = 3; console.log(myNewVariable); // 2 console.log(myOldVariable); // 3
可是实际上,他们之间有几处明显的不一样。他们不只仅是关键字变了,并且实际上它还让会简化咱们的一些工做,防止一些奇怪的bug,其中这些不一样点是:函数
let是块状做用域(我将会在文章后面着重讲一下做用域相关的东西),而var是函数做用域。工具
let不能在定义以前访问该变量(var是能够的,它确实是js世界中许多bug和困扰的源头)。this
let不能被从新定义。es5
在咱们讲解这些不一样点以前,首先咱们看一个更酷的变量:const翻译
const
const和let很是像(跟var相比来讲,他们之间有许多相同点),可是他们有一个主要的不一样点:let能够从新赋值,可是const不能。所以const定义的变量只能有一个值,而且这个值在声明的时候就会被赋值。所以咱们来看下下面的例子。
const myConstVariable = 2; let myLetVariable = 3; console.log(myConstVariable); // 2 myLetVariable = 4; // ok myConstVariable = 5; //wrong - TypeError thrown
可是const是彻底不可变的吗?
有一个常见的问题:虽然变量不能被从新赋值,可是也不能让他们真正的变为不可变的状态。若是const变量有一个数组或者对象做为其值的话,你可能会像下面的代码同样改变它的值。
const myConstObject = {mutableProperty: 2}; // myConstObject = {}; - TypeError myConstObject.mutableProperty = 3; //ok console.log(myConstObject.mutableProperty); // 3 const myConstArray = [1]; // myConstArray = []; - TypeError myConstArray.push(2) //ok console.log(myConstArray); // [1, 2]
固然你不能用原始数据类型的值,好比string,number,boolean等,由于他们本质上是不可变的。
真正的不可变
若是你想让咱们的变量真正的不可变的话,可使用Object.freeze(), 它可让你的对象保持不可变。不幸的是,他仅仅是浅不可变,若是你对象里嵌套着对象的话,它依然是可变的。
const myConstNestedObject = { immutableObject: { mutableProperty: 1 } }; Object.freeze(myConstNestedObject); myConstNestedObject.immutableObject = 10; // won't change console.log(myConstNestedObject.immutableObject); // {mutableProperty: 1} myConstNestedObject.immutableObject.mutableProperty = 10; // ok console.log(myConstNestedObject.immutableObject.mutableProperty); // 10
变量的做用域
在介绍了一些基础知识之后,下面咱们要进入一个更高级的话题。如今咱们要开始讲解es5和es6变量中的第一个不一样-做用域
注意:下面的例子都用的是let,它的规则在const上一样也适用
全局变量和函数做用域变量
在js中,究竟什么是做用域呢?本文不会给出一个关于做用域的完整解释。简单来讲,变量的做用域决定了变量的可用位置。从不一样的角度来看,能够说做用域是你能够在特定区域内使用的那些变量(或者是函数)的声明。做用域能够是全局的(所以在全局做用域中定义的变量能够在你代码中任何部分访问)或者是局部的。
很显然,局部做用域只能在内部访问。在ES6之前,它仅仅容许一种方式来定义局部做用域 - function,我们来看一下下面的例子:
// global scope var globalVariable = 10; function functionWithVariable() { // local scope var localVariable = 5; console.log(globalVariable); // 10 console.log(localVariable); // 5 } functionWithVariable(); //global scope again console.log(globalVariable); // 10 console.log(localVariable); // undefined
上面的例子中,变量globalVariable是全局变量,因此它能够在咱们代码中的函数内或者是其余区域内被访问到,可是变量localVariable定义在函数内,因此它只在函数内可访问。
所以,全部在函数内建立的内容均可以在函数内被访问到,包括函数内部里全部的嵌套函数(可能会被嵌套多层)。在这里可能要感谢闭包了,可是在文章里咱们并不打算介绍它。不过请继续关注,由于咱们在将来的博文中,会更多的介绍它。
提高
简单来讲,提高是一种吧全部的变量和函数声明“移动”到做用域的最前面的机制。让咱们看一下下面的例子。
function func() { console.log(localVariable); // undefined var localVariable = 5; console.log(localVariable); // 5 } func();
它为何依然会正常工做呢?咱们尚未定义这个变量,可是它依然经过console.log()打印出了undefined。为何不会报出一个变量未定义的错误呢?让咱们再仔细看一下。
编译变量
Javascript解析器要遍历这个代码两次。第一次被称为编译状态,这一次的话,代码中定义的变量就会提高。在他以后,咱们的代码就变成相似于下面的这样子的(我已经作了一些简化,只展现出相关的部分)。
function func() { var localVariable = undefined; console.log(localVariable); // undefined localVariable = 5; console.log(localVariable); // 5 } func();
咱们看到的结果是,咱们的变量localVariable已经被移动到func函数的做用域的最前面。严格来讲,咱们变量的声明已经被移动了位置,而不是声明的相关代码被移动了位置。咱们使用这个变量并打印出来。它是undefined是由于咱们尚未定义它的值,它默认使用的undefined。
提高的例子 - 会出什么问题
来让咱们看一个使人讨厌的例子,咱们的做用域范围对于咱们来讲,是弊大于利的。也不是说函数做用域是很差的。而是说咱们必需要警戒一些因为提高而引发的一些陷进。咱们来看看下面的代码:
var callbacks = []; for (var i = 0; i < 4; i++) { callbacks.push(() => console.log(i)); } callbacks[0](); callbacks[1](); callbacks[2](); callbacks[3]();
你认为输出的值是多少呢?你猜多是0 1 2 3,是吗?若是是的话,对于你来讲,可能会有一些惊喜。实际上,他真实的结果是4 4 4 4。等等,它到底发生了什么?咱们来“编译”一下代码,代码如今看起来就像这样:
var callbacks; var i; callbacks = []; for (i = 0; i < 4; i++) { callbacks.push(() => console.log(i)); } callbacks[0](); callbacks[1](); callbacks[2](); callbacks[3]();
你看出问题所在了吗?变量i在整个做用域下都是能够被访问到的,它不会被从新定义。它的值只会在每次的迭代中不断地被改变。而后呢,当咱们随后想经过函数调用打印它的值得时候,他实际上只有一个值 - 就是在最后一次循环赋给的那个值。
咱们只能这样了吗?不是的
Let和Const的拯救
除了定义变量的新方式之外,还引入了一种新的做用域:块级做用域。块就是由花括号括起来的全部的内容。因此它能够是if,while或者是for声明中的花括号,也能够是单独的一个花括号甚至是一个函数(对,函数做用域是块状做用域)。let和const是块做用域。意味着不管你在块中不管定义了什么变量,何时定义的,它都不会跑到块做用域外面去。咱们来看一下下面的例子:
function func() { // function scope let localVariable = 5; var oldLocalVariable = 5; if (true) { // block scope let nestedLocalVariable = 6; var oldNestedLocalVariable = 6; console.log(nestedLocalVariable); // 6 console.log(oldNestedLocalVariable); // 6 } // those are stil valid console.log(localVariable); // 5 console.log(oldLocalVariable); // 5 // and this one as well console.log(oldNestedLocalVariable); // 6 // but this on isn't console.log(nestedLocalVariable); // ReferenceError: nestedLocalVariable is not defined
你能看出来差异吗?你能看出来怎么使用let来解决早些时候提出问题的吗?咱们的for循环包含一组花括号,因此它是块做用域。因此若是在定义循环变量的时候,使用的是let或者是const来代替var的话,代码会转为下面的形式。注意:我实际上已经简化了不少,不过我肯定你能理解个人意思。
let callbacks = []; for (; i < 4; i++) { let i = 0 //, 1, 2, 3 callbacks.push(() => console.log(i)); } callbacks[0](); callbacks[1](); callbacks[2](); callbacks[3]();
如今的每一次循环都有它本身的变量定义,因此变量不会被重写,咱们确信这行代码能够完成让他作的任何事情。
这是这一部分结束的例子,可是咱们再看一下下面的例子,我相信你明白打印出来的值的缘由,以及对应的表现是什么。
function func() { var functionScopedVariable = 10; let blockScopedVariable = 10; console.log(functionScopedVariable); // 10 console.log(blockScopedVariable); // 10 if (true) { var functionScopedVariable = 5; let blockScopedVariable = 5; console.log(functionScopedVariable); // 5 console.log(blockScopedVariable); // 5 } console.log(functionScopedVariable); // 5 console.log(blockScopedVariable); // 10 } func();
本文翻译自:
https://blog.pragmatists.com/let-your-javascript-variables-be-constant-1633e56a948d