let命令
基本使用
let
是ES6中新增的声明变量的命令,但是和var
不同,let
不可以被重复声明,let
只在其命令的代码块内有效,let
禁止变量出现变量提升现象,let
会通过暂时性死区的方式来避免程序上的错误
- 代码块:使用
{}
包括起来的内容 - 声明变量:分配一个存储空间
- 不可重复声明:
let
声明过后的变量无法再再次声明,而var
可以重复声明
{ let a = 10; var a = 1; // 'a' has already been declared } 复制代码
{ var a = 10 var a = 1 } console.log(a) // 1 复制代码
- 不同代码块可被重复声明:值得注意的是let的不可重复声明是作用在同一代码块中的,后面将会具体解释相关的概念即块级作用域
{ let a = 1 console.log(a) } { let a = 2 console.log(a) } 复制代码
- 有效区间的不同:
let
只在其命令的代码块内有效,而var
声明的变量可全局有效,下面例子说明两者不同。
{ let a = 10; var b = 1; } console.log(a) // a is not defined console.log(b) // 1 复制代码
- 变量提升:之前使用
var
声明变量,在声明前调用为undefined
,undefined
就是我们熟悉的声明但未赋值,这种现象叫做变量提升,但是let
禁止了这一行为,使用let
所声明的变量一定需要在声明后使用。
console.log(bar) // Cannot access 'bar' before initialization let bar = 2 复制代码
- 变量提升的编译:我们分别来看下变量提升现象在编译后的情况,就能彻底理解变量提升现象出现的原因,所有的声明变量语句在编译后都会将声明语句提到程序的最先级,所以才会出现声明但未赋值的现象。
// 编译前 console.log(a) var a = '猪痞恶霸' // 编译后 var a console.log(a) a = '猪痞恶霸' 复制代码
- 暂时性死区:在代码块中使用
let
声明变量前,该变量不可用,这种现象称为暂时性死区,其实这点和变量提升有些相似,我理解为:let
禁止变量提升现象的原因是在声明变量前的代码区域为暂时性死区
{ // 暂时性死区 tmp = 'abc'; console.log(tmp) // 暂时性死区 let tmp; // 声明tmp,结束暂时性死区 console.log(tmp) // undefined 声明未赋值 tmp = '123' console.log(tmp) // 123 } 复制代码
- 隐蔽的死区:有些暂时性死区会在我们注意不到的地方,比如
function bar(x = y, y = 2) { return [x,y] } bar() // Cannot access 'y' before initialization bar(2) // 无错误 复制代码
- 由于x的默认值为y,且y的声明还没开始,所以
x = y
处于死区中
适用场景
let
与var
根据其各自不同的特点有着不同的适用场景
- 计数器的使用:如果我们需要一个用来计数的变量,那么我们就可以使用
let
来声明。
var arr = [] for(let i = 0; i < 10; i++) { arr[i] = () => { console.log(i) } // 利用计数进行操作 } arr[7]() // 7 复制代码
for(var i = 0; i < 10; i++) { arr[i] = () => { console.log(i) } // 利用计数进行操作 } arr[7]() // 10 复制代码
- 根据适用
var
和let
的输出结果就能清楚两者的适用场景,而这也凸显了let
声明变量的不同之处,即声明的有效区间不同 - for循环中的父子关系:
for
循环中有两个作用域,即父作用域与子作用域,设置循环变量的部分为父作用域,循环体内部为子作用域
for(let i = 0; i < 3; i++) { let i = 'abc'; console.log(i) } // abc // abc // abc 复制代码
- 上面的例子循环打出了三个
abc
就说明了两个i的作用域不同,如果不在循环体内声明的话将会打印出0,1,2
块级作用域
ES6新增了块级作用域,此前ES5只有全局作用域与函数作用域
- 全局作用域:拥有全局作用域的变量可以在程序的任意位置访问,而window对象的所有属性都具有全局作用域。
- 函数作用域:又被称为局部作用域,每一个函数中都有一个函数作用域或者嵌套更多的函数作用域,在函数作用域中定义的变量只能在该函数作用域内部使用。
概念
块级作用域的出现其实是由新增声明let
产生的
let n = 5 function fn() { if(true) { let n = 10 } console.log(n) } fn() // 5 复制代码
上面的例子诠释了块级作用域的作用,即外层代码块不受内层代码块的影响,我们在if
函数体内声明的n
只有在if
函数体内可以访问,而fn
中访问n
不受if
内层的声明影响。
存在意义
var tmp = new Date(); function fn() { console.log(tmp); if(false) { var tmp = 'hello world' } } fn() 复制代码
如上,存在函数作用域与全局作用域,正常情况下fn()
函数体内if
函数体外是使用外层即var tmp = new Date();
但是由于变量提升现象的存在即if
函数体内的var tmp = 'hello world'
发生了变量提升,即fn()
函数体内的编译形式如下,所以输出结果为undefined
。
function fn() { var tmp console.log(tmp); tmp = 'hello world' } 复制代码
而块级作用域正是解决了这个问题
let n = 5 function fn() { if(true) { let n = 10 } console.log(n) } fn() // 5 复制代码
除此之外还可以防止局部作用域变量的泄露到全局当中
let arr = [1,2,3] for(var i = 0; i < arr.length; i++) { console.log(arr[i]) } { console.log(i) // 3 } 复制代码
如上就造成了变量泄露,i
变量被泄露到了全局当中,那么我们使用块级作用域来看一看i is not defined
,说明变量未被泄露到全局中。
let arr = [1,2,3] for(let i = 0; i < arr.length; i++) { console.log(arr[i]) } console.log(i) // i is not defined 复制代码
const命令
基本使用
const
声明的变量无法被再次赋值改变,且声明的时候必须赋值
const name = '猪痞恶霸' name = 'fzf404' // Assignment to constant variable. 复制代码
const name // Missing initializer in const declaration name = '猪痞恶霸' 复制代码
const
与let
的特性很像,比如只在其声明的块级作用域内有效,存在暂时性死区
不可被const的情况
javascript中有两种数据类型即:简单数据类型,复杂数据类型
简单数据类型:数值,字符串,布尔值
复杂数据类型:数组,对象
引出数据类型的原因正是因为两种数据类型的访问方式不同,简单数据类型的值直接保存变量所指向的内存地址,直接访问就可以拿到值而复杂数据类型的访问是通过变量指向的内存地址,内存地址保存的是另一个指针(引用)
const
声明是通过保证变量所指向的那个内存地址不能改动,所有说使用const
声明复杂数据类型,会出现数组的元素和对象的属性可以发生改变
const person = {} person.name = "猪痞恶霸" console.log(person.name) // 猪痞恶霸 复制代码
如果我们想要使我们的对象不可以发生改变那么我们可以使用Object.freeze
方法,详细使用参考这篇文章:冻结JS对象方法技术详解