let和const命令

2.1.1 基本用法

ES6新增了let命令,用于生命变量。其用法类似于var,但是所声明的变量只在let命令所在的代码块内有效。

javascript 复制代码
{
 let a = 10;
 var b = 1;
}

a//ReferenceError:a is not defined
b//1

上面的代码在代码块中分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确得值,这表明,let声明的变量只在其所在的代码块内有效,

2.1.2 不存在变量提升

var命令会发生"变量提升"现象,即变量可以在声明之前使用,值为undefined。这种现象多少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则便会报错。

javascript 复制代码
// var的情况
console.log(foo); //输出undefined
var foo = 2;

// let 的情况
console.log(bar);// 报错ReferebceError
let bar = 2;

在以上代码中,变量foo用var命令声明会发生变量提升,即脚本开始运行时,变量foo便已经存在,但是没有值,所以会输出undefined。变量bar用let命令声明则不会发生变量提升。这表示在声明它之前,变量bar是不存在的,这是如果用到它,就会抛出一个错误。

2.1.3 暂时性死区

只要块级作用域内存在let命令,它所声明的变量就"绑定"(binding)这个区域,不在受外部影响,

javascript 复制代码
var tmp = 123;
if(true){
tmp = 'abc';//ReferenceError
let tmp;
}

上面的代码中存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

ES6明确规定,如果区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成封闭作用域。只要在声明之前就使用这些变量,就会报错。

总之,在代码快内,使用let命令声明变量之前,该变量都是不可用的。这在语法上称为"暂时性死区"(temporal dead zone,简称TDZ)。

javascript 复制代码
if(true){
// TDZ开始
tmp = 'abc';//ReferenceError
console.log(tmp);//ReferenceError

let tmp;// TDZ结束
console.log(tmp);//undefined
tmp = 123;
console.log(tmp);//123

上面的代码中,在let命令声明变量tmp之前,都属于变量tmp的"死区"。

"暂时性死区"也意味着typeof不再是一个百分之百安全的操作。

javascript 复制代码
typeof x;//ReferenceError
let x;

上面的代码中,变量x使用let命令声明,所以在声明之前都属于x的"死区",只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferebceError、

作为比较,如果一个变量根本没有被声明,使用typeof反而不会报错。

javascript 复制代码
typeof undeclared // "undefined"

上面的代码中,undeclared是一个不存在的变量名,结果返回"undefined"。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就会报错。

	有些"死区"比较隐蔽,不太容易发现。
javascript 复制代码
function bar(x=y,y=2){
	return [x,y];
	}
	bar();//报错

上面的代码中,调用bar函数之所以报错,是因为参数x的默认值等于另一个参数y,而此时y还没有声明,属于"死区"。如果y的默认值是x,就不会报错,因为此时x已声明。

javascript 复制代码
function bar(x = 2,y = x){
	return [x,y];
	}
bar();//[2,2]
另外,下面的代码也会报错,与var的行为不同。
//不报错
var x = x;
//报错
let x = x;
//ReferenceError:x is not defined

以上代码报错也是因为暂时性死区。使用let声明变量时,只要变量在还没有声明前使用,就会报错。以上示例就是属于这种情况,在变量x的声明语句还没有执行完成前就尝试获取x的值,导致出现"x未定义"的错误。

ES6规定暂时性死区和let,const语句不出现变量提升,主要是为了减少运行时错误,放置在变量声明前就是用这个变量,从而导致意料之外的行为。这样的错误在ES5中是很常见的,现在有了这种规定,避免此类错误就很容易了。

总之,暂时性死区的本质就是,只要进入当前作用域,所要使用的变量就已经存在,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

2.1.4 不允许重复声明

javascript 复制代码
let 不允许在相同作用域内重复声明同一个变量
//报错
function (){
	let a = 10;
	var a = 1;
}
//报错
function (){
	let a = 10;
	let a = 1;
}
因此不能在函数内部重新声明参数
function func(arg){
	let arg//报错
}
function func(arg){
	{
	let arg//报错
	}
}

2.2块级作用域

2.2.1为什么需要块级作用域

ES5只有全局作用域和函数作用域,没有块级作用域,这导致很多场景不合理。

第一种场景,内层变量可能会覆盖外层变量。

javascript 复制代码
var tmp = new Date();
function f(){
	console.log(tmp);
	if(false){	
		var tmp = 'hello world';
		}
}
f();//undefined

以上代码的愿意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量,但是,函数f执行后,输出结果为undefined,原因在于变量提升导致内层的tmp变量覆盖率外层的tmp变量。

第二种场景,用来计数的循环变量泄露为全局变量。

javascript 复制代码
var s = 'hello';
for (var i = 0;i<s.lengthl i++){
console.log(s[i]);
}
console.log(i)//5
上面的代码中,变量i只用来控制循环,但是循环结束后,他并没有消失,而是泄露成了全局变量。

2.2.2 ES6的块级作用域

javascript 复制代码
let 实际上为JacaScript新增了块级作用域。
function f1(){
let n = 5;
if(true){
	let n = 10;
	}
console.log(n);//5
}
上面的函数有两个代码块,都声明了变量n,运行后输出5.这表示外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10.
ES6允许块级作用域的任意嵌套。
{{{{{let insane = 'Hello Wprld'}}}}};
上面的代码使用了一个5层的块级作用域。外层作用域无法读取内层作用域的变量,
{{{{
{let insane = 'Hello Wprld'}
console.log(insane);//报错
}}}};

内层作用域可以定义外层作用域的同名变量。
{{{{
let insane = 'Hello Wprld'
{let insane = 'Hello Wprld'}
}}}};
块级作用域的出现,实际上使得获得广泛的应用的立即执行匿名函数(IIFE)不再必要了。
//IIFE写法
(function(){
	var tmp = ...;
	...
}());

//块级作用域写法
{
	let tmp = ...l
	...
}

2.2.3 块级作用域与函数声明

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。
    注意!上面3条规则只对ES6的浏览器实现有效,其他环境的实现不用遵守仍旧将块级作用域的函数声明当做let处理即可。

2.2.4 do表达式

本质上,块级作用域是一个语句,将多个操作封装在一起,没有返回值。

javascript 复制代码
{
 let  t = f();
 t = t*t+1;
}

上面的代码中,块级作用域将两个语句封装在一起。但是,在块级作用域以外,没有办法得到t的值,因为块级作用域不返回值,除非t是全局变量。

javascript 复制代码
let x = do {
	let t = f();
	t*t + 1;
};
上面的代码中,变量x会得到整个块级作用域的返回值

2.3 const 命令

const 声明一个只读的常亮。一旦声明,常量的值就不能改变。

const PI = 3.1415;

PI = 3;//报错

  • const声明的常亮不得改变值。这意味着,const一旦声明常亮,就必须立即初始化,不能留到以后赋值。
  • const的作用域与let相同:只在声明所在的块级作用域内有效。
  • const 命令声明的常亮也不会提升,同样存在暂时性死区,只能在声明后使用。
  • const 声明常量也与let一样不可重复声明。

本质

const 实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单的类型的数据(数值,字符串,布尔)而言,值就保存在变量指向的内存地址中,因此等同于常量。但对于复合类型的数据(主要是对象和数组)而言,变量指向的内存地址保存的只是一个指针,const只保证这个指针是固定的,至于它指向的数据结构是不是可变的,这完全不能控制。因此,将一个对象声明为常量时必须非常小心。

javascript 复制代码
const foo = {};
// 为foo添加一个属性,可以成功
foo.prop = 123;
foo.prop //123


//将foo指向另一个对象,就会报错
foo = {};//报错
上面的代码中,常量foo存储的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性,

const a = [];
a.push('Hello');//可执行
a.length = 0;//可执行
a = ['DAVE']//报错
上面的代码中,常量a是一个数组这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错

2.3.3 ES6声明变量的6种方法

  1. var ----ES5
  2. function ----ES5
  3. let
  4. const
  5. import
  6. class
相关推荐
花花鱼2 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k09335 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
神奇夜光杯13 分钟前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
Themberfue15 分钟前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
plmm烟酒僧17 分钟前
Windows下QT调用MinGW编译的OpenCV
开发语言·windows·qt·opencv
EricWang135826 分钟前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning27 分钟前
React.lazy() 懒加载
前端·react.js·前端框架
测试界的酸菜鱼28 分钟前
Python 大数据展示屏实例
大数据·开发语言·python
web行路人36 分钟前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
番茄小酱00138 分钟前
Expo|ReactNative 中实现扫描二维码功能
javascript·react native·react.js