文章目录
# let和const
es6
引入了两种方式来申明变量
我们仍然可以使用广为传诵的 var
变量(然而你<mark>不应该继续使用它</mark>了,继续阅读来了解其中原因)
因为,现在我们有了两种更牛的工具去使用: let
和 const
。
## let
let
和 var
非常的相似,在使用方面,你可以使用完全相同的方式来声明变量,例如:
let myNewVariable = 2;
var myOldVariable = 3;
console.log(myNewVariable); // 2
console.log(myOldVariable); // 3
但是实际上,他们之间有几处明显的不同。他们不仅仅是关键字变了,而且实际上它还让会简化我们的一些工作,防止一些奇怪的bug,其中这些<mark>不同点</mark>是:
let
是块状作用域
(我将会在文章后面着重讲一下作用域相关的东西),而var是函数作用域。let
不能在定义之前访问该变量
(var是可以的,它确实是js世界中许多bug和困扰的源头)。let
不能被重新定义。
在我们讲解这些不同点之前,首先我们看一个更酷的变量: const
## const
const
和 let
非常像(跟var相比来说,他们之间有许多相同点),但是他们有一个主要的不同点:
<mark>let可以重新赋值,但是const不能。</mark>
因此const定义的变量只能有一个值,并且这个值在声明的时候就会被赋值。因此我们来看下下面的例子。
const myConstVariable = 2;
let myLetVariable = 3;
console.log(myConstVariable); // 2
myLetVariable = 4; // ok
myConstVariable = 5; //wrong - TypeError thrown
但是const是完全不可变的吗? 不
<mark>有一个常见的问题:虽然变量不能被重新赋值,但是也不能让他们真正的变为不可变的状态</mark>。
如果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等,因为他们本质上是不可变的。
## 真正的不可变
如果你想让我们的<mark>变量真正的不可变</mark>的话,可以使用 Object.freeze()
, 它可以让你的对象保持不可变。
不幸的是,他仅仅是<mark>浅不可变</mark>,如果你对象里嵌套着对象的话,它依然是可变的。
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
变量中的<mark>第一个不同</mark> - 作用域
<mark>注意:下面的例子都用的是let,它的规则在const上同样也适用</mark>
## 全局变量和函数作用域变量
- 简单来说,变量的作用域决定了<mark>变量的可用位置</mark>。
- 从不同的角度来看,可以说作用域是你可以在<mark>特定区域</mark>内使用的那些变量(或者是函数)的<mark>声明</mark>。
- 作用域可以是<mark>全局</mark>的(因此在全局作用域中定义的变量可以在你代码中任何部分访问)或者是<mark>局部</mark>的。
很显然,局部作用域只能在内部访问。
在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定义在函数内,所以它只在函数内可访问。
因此,所有在函数内创建的内容都可以在函数内被访问到,包括函数内部里所有的嵌套函数(可能会被嵌套多层)。
<mark>在这里可能要感谢闭包了,但是在文章里我们并不打算介绍它。不过请继续关注,因为我们在未来的博文中,会更多的介绍它。</mark>
## 提升
简单来说,提升是一种把<mark>所有的变量和函数声明“移动”到作用域的最前面的机制</mark>。
让我们看一下下面的例子。
function func() {
console.log(localVariable); // undefined
var localVariable = 5;
console.log(localVariable); // 5
}
func();
它为什么依然会正常工作呢?我们还没有定义这个变量,但是它依然通过console.log()打印出了undefined。为什么不会报出一个变量未定义的错误呢?让我们再仔细看一下。
## 编译变量
<mark>Javascript解析器要遍历这个代码两次。</mark>
第一次被称为编译状态,这一次的话,代码中定义的变量就会提升。
在他之后,我们的代码就变成类似于下面的这样子的(我已经做了一些简化,只展示出相关的部分)。
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的拯救
除了定义变量的新方式以外,还引入了一种新的作用域:<mark>块级作用域</mark>
块就是由花括号括起来的所有的内容。{...............}
所以它可以是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
} func() ;
你能看出来差别吗?
你能看出来怎么使用let来解决早些时候提出问题的吗?
我们的for循环包含一组花括号,所以它是块作用域。
所以如果在定义循环变量的时候,使用的是let或者是const来代替var的话,代码会转为下面的形式。
注意:我实际上已经简化了很多,不过我确定你能理解我的意思。
let callbacks = [];
for ( let i = 0; i < 4; i++) {
//, 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();