ES6入门学习_let和const命令

let命令

基本用法

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

{
	let a=10;
	var b=6;
}
console.log(a);// a is not defined
console.log(b);//6

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

for循环的计数器就很适合let

for(let a=0;a<10;a++){
	;
}
console.log(a);// a is not defined

以上代码中的计数器i只在for循环体中有效,在循环体外引用就会报错。

var a=[];
for(var i=0;i<10;i++){
	a[i]=function(){
    	console.log(i);
    };
}
for(let j=0;j<10;j++){
	a[j]();//10
}

在这里插入图片描述

上面代码中,变量i是由var声明的,在全局范围内都有效,所以全局只有一个变量i。每次循环,变量i的值都会改变,而循环内,被赋给数组a的函数内部的console.log(i)中的i指向全局的i。也就是说,所有数组a的成员中的i都指向同一个i,导致运行时所有成员都输出最后一轮的值。

var a=[];
for(let i=0;i<10;i++){
	a[i]=function(){
    	console.log(i);
    };
}
for(let j=0;j<10;j++){
	a[j]();
}

在这里插入图片描述
上面代码中,变量i是由let声明的,当前的i只在本轮中有效。所以每一次循环的i其实都是一个新的变量,于是a数组的每个成员正确的输出了对应的i。

for(let i=0;i<3;i++){
	let i="abc";
    console.log(i);//abc
}

在这里插入图片描述
上面代码中,在循环体中重新声明的i并没有覆盖计数器,并且循环体中正确调用了循环体中声明的i。
这是因为设置循环变量的那部分是一个夫作用域,而循环体内部是一个单独的子作用域。
函数内部的变量i与循环体变量i不在同一个作用域上,而是用各自单独的作用域。
不存在变量提升
var命令会发生“变量提升”现象。即变量可以在声明前使用,值为undefined。按照一般的逻辑变量应该在声明后使用。
let不会有这个问题。

console.log(a);//输出undefined
var a=10;
console.log(b);//报错referenceerror
let b=9;

在这里插入图片描述
在以上代码中,变量a用var声明会发生变量提升,即脚本运行开始时,变量a就已经存在了,但是没有值,所以会输出undefined。变量b用let声明就不会有这个问题,b在声明前是不存在的,这是用到它,就会抛出一个错误。
暂时性死区
只要块级作用域中存在let命令,它所声明的变量就“绑定”(binding)了这个区域,不在受外部的影响。

var tmp=123;
if(true){
	tmp='abc';//referenceerror
    let tmp;
}

在这里插入图片描述
上面的代码中存在全局变量tmp,但是块级作用域中let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6明确规定,如果区块中存在let和const命令,则这个区块对这些命令声明的变量从一开始就形成了封闭作用域。只要这声明之前就是使用这些变量就会报错。
这在语法上称为“暂时性死区”(temporal dead zone,简称TDZ)。

{
	tmp ='abc';
    console.log(tmp);
    
    let tmp;//TDZ
    console.log(tmp);
    
    tmp='123';
    console.log(tmp);
}

"暂时性死区"也意味着typeof不再是一个百分百不报错的操作了。

{
	typeof a;//ReferenceError
    let a;
    
    typeof undefined_var//undefined
}

在这里插入图片描述

变量a在定义前被调用,所以typeof抛出一个错误。
作为比较如果一个变量根本没有被定义,却不会报错。
这样设计是为了让我们养成好的编程习惯。

function bar(x=y,y=2){
	return [x,y];
}
bar();//报错

在这里插入图片描述
上面代码调用函数之所以报错是因为参数x的默认值等于另一个参数y,而此时y没有被声明,属于死区。如果y的默认值是x,就不会报错,因为此时x已声明。

function bar(x=2,y=x){
	return [x,y];
}
console.log(bar());//[2,2]

在这里插入图片描述
另外,下面的代码也报错,与var的行为不同。

var x=x;//不报错

let x=x;//报错

在这里插入图片描述
ES6规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,为了防止声明前就使用变量,从而导致意料之外的行为。
不允许重复声明
let不允许在相同作用域内重复声明同一个变量。

function w(){
	let a=a;
    var a=10;//报错
}

function n(){
	let e=10;
    let e=1;//报错
}

function func1(arg){
	let arg;//报错
}

function fun2(arg){
	{
    	let arg;//不报错
    }
}

块级作用域

为什么需要块级作用域
ES5只有全局作用域和函数作用域,没有块级作用域,这导致很多场景不合理。
1、内部变量可能会覆盖外层变量

var tmp='123';
function f(){
	console.log(tmp);//undefined
	if(true){
    	var tmp = 'hello world';
		console.log(tmp);//'hello world'
	}
}
f();

if代码外部使用外层的tmp变量,内部使用内层的tmp变量,但是函数f执行后,输入结果为undefined,并没有调用外部‘123’,这是因为内层的变量提升导致内层的tmp变量覆盖了外层的tmp变量。

2、用来技术的循环变量泄露为全局变量

var s='hello';

for(var i=0;i<s.length;i++){
	console.log(s[i]);//hello
}

console.log(i);//5

在这里插入图片描述
变量i只用来控制循环,但是循环结束后,它并没有消失,而是泄露成了全局变量。
ES6的块级作用域
let实际上为Javascript新增了块级作用域。

function f1(){
	let n=5;
	if(true){
		let n=10;
	}
	console.log(n);//5
}
f1();

在这里插入图片描述

上面的函数有两个代码块,都声明了变量n,运行后输出5。这表明外层代码块不受内层代码块的影响。如果使用var定义变量n,最后输出的值就是10。
ES6允许块级作用域的任意嵌套

{{{{{let insane = 'Hello World'}}}}};

上面的代码使用了一个5层的会计作用域。外层作用域无法读取内层作用域的变量。

{{{{
	{let insane = 'Hello World';}
	console.log(insane);//报错
}}}}

在这里插入图片描述
内层作用域可以定义外层作用域同名变量

{{{{
	let insane ='Hello World';
	{let insane = 'Hello World';}
}}}}

块级作用域的出现,实际上使得获得广泛应用的执行匿名函数不再必要了。

//IIFE写法
(function(){
	var tmp=……;
	……;
}())

//块级写法
{
	let tmp=……;
	……;
}

块级作用域与函数声明
函数能不能在块级作用域之中声明?
ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

//情况一
if(true){
	function f(){}
}

//情况二
try{
	function f(){}
}catch(e){
	//……
}

上面二种函数声明在ES5中都是非法的。
但是,浏览器没有遵循这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际上都能运行,并不会报错。

ES6引入了块级作用域,明确允许在块级作用域中声明函数。ES6规定,在块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

function f(){console.log('i am outside');}
(function(){
	if(false){
		function f(){
			console.log('i am inside');
		}
	}
	f();
}());

在这里插入图片描述
以上代码在ES5中运行会得到“I am inside”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码。

function f(){console.log('i am outside');}
(function(){
	function f(){
		console.log('i am inside');
	}
	if(false){
	}
	f();
}());

而在ES6中运行就完全不一样了,理论上会得到"i am outside"。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。但是,结果却报错了。
其实,如果改变了块级作用域内声明函数的处理规则,显然会对旧代码产生很大的影响。为了减轻因此产生的不兼容问题,ES6规定,浏览器的实现可以不遵守上面的规定,而有自己的行为方式,具体如下。
1、允许在块级作用域内声明函数
2、函数声明类似于var,即会提升到全局作用域或函数作用域的头部
3、同时,函数声明还会提升到所在的块级作用域的头部。
注:上面三条只对ES6的浏览器实现有效,其他环境的实现不用遵守,仍然将块级作用域的函数声明当作let处理即可。
之所以上面报错是因为ES6实际运行的代码是。

function f(){console.log('i am outside');}
(function(){
	var f = undefined;
	if(false){
		function f(){
			console.log('i am inside');
		}
	}
	f();
}());

考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,应该写成函数表达式的形式,而不是函数声明语句。

//函数声明语句
{
	let a='secret';
	function f(){
		return a;
	}
}
//函数表达式
{
	let a='secret';
	let f=function(){
		return a;
	};
}

另外,还有一个需要注意的地方。ES6的块级作用域允许声明函数的规则只在使用大括号情况下成立,如果没有使用大括号,就会报错。

//不报错
if(true){
	function f(){}
}

//报错
if(true)
	function f(){}

上面代码是在严格遵循ES6环境下,ES6浏览器不会报错原因同上。
do表达式
本质上,块级作用域是一个语句,将多个操作封装在了一起,没有返回值。

{
	let t=f();
	t=t*t+1;
}

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

现在有一个提案,使得块级作用域可以变为表达式,即可返回值,方法就是在块级作用域前加上do,使它变成do表达式。

let x =do{
	let t=f();
	t*t+1;
}

注:至编写博客时,这个提案都没有被浏览器实现,所以使用会报错。

const命令

基本用法
const声明一个只读的常数。一旦声明,常量的值就不能改变。

const PI=3.1415;
console.log(PI);

PI=3;

在这里插入图片描述
上面的代码表明改变常量的值会报错。
const一旦声明常量,就必须立即初始化,不能留到以后赋值

const foo;

在这里插入图片描述
const的作用域与let命令相同:只在生命所在的块级作用域内有效。

if(true){
	const Max=10;
}
console.log(Max);

在这里插入图片描述
const命令声明的常量也不会提升,同样存在暂时性死区,只能在声明后使用。

if(true){
	Max=1;
	const Max=10;
}

在这里插入图片描述
上面的代码在常量MAX之前就被调用,报错。
const声明变量与let一样,不可重复声明。

var message="Hello!";
let age=25;
//以下两行都会报错
const message="Goodbye!";
const age=30;

在这里插入图片描述
本质
const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据而言,值就是保存在变量指向的内存地址中,因此等同于常量。但对于复合类型的数据而言,变量指向的内存地址保存的只有一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,他不能控制。因此将一个对象声明为常量时必须非常小心。

const foo = {};

//为foo添加一个属性,可以成功
foo.prop=123;
console.log(foo.prop);//123
//将foo指向另一个对象,就会报错
foo={}//报错

在这里插入图片描述
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
再看另一个例子

const a=[];
a.push('Hello');
a.length=0;
a=['Dave'];//报错

在这里插入图片描述
上面的代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。

如果真的想冻结对象,应该使用Object.freeze方法

const foo = Object.freeze({});

//常规规模时,下面一行不起作用
//严格模式下,报错
foo.prop=123;

上面代码中,常量foo指向一个冻结的对象,所以添加新属性时不起作用,严格模式时还会报错。
除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

var constantize = (obj) => {
	Object.freeze(obj);
	Object.keys(obj).forEach((key,i) => {
		if(typeof obj[key] === 'object'){
			constantize(obj[key]);
		}
	})
}
var obj = {a:{a1:1},b:2};
constantize(obj);
obj.a.a1=2;
console.log(obj.a.a1);//1

在这里插入图片描述
可以看到上面作为obj属性的子对象也冻结了。
ES6声明变量的6中方法
ES5只有两种声明变量的方法:使用var命令和function命令。ES6除了添加了let和const命令,后面的章节还会学习import和class。

顶层对象的属性

顶层对象在浏览器环境中指的是window对象,在Node环境中指的是global对象。在ES5中,顶层对象的属性与全部变量是等价的。

window.a=1;
console.log(a);

a=2;
console.log(window.a)//2

在这里插入图片描述
上面的代码中,顶层对象的属性与全局变量的赋值是同一件事。
顶层对象的属性与全局变量相关,被认为是JavaScript语言中最大的设计败笔之一。
这样会带来几个很大的问题:
1、无法在编译时就提示变量为声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的)。
2、程序员很容易不知不觉地就创造全局变量(比如打错字)。
3、顶层对象地属性是到处都可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,这样也是不合适的。

ES6为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量不属于顶层对象的属性。也就是说从ES6开始,全局变量将逐步与顶层对象的属性隔离。

var a=1;
//如果在Node的REPL环境中,可以写成global.a
//或者采用通用方法,写成this.a
window.a//1

let b=1;
window.b //undefined

在这里插入图片描述

上面的代码中,全局变量a由var命令声明,所以它是顶层对象的属性;全局变量b由let命令声明,所以它不是顶层对象的属性,返回undefined。

global对象

ES5的顶层对象本身也是一个问题,因为他在各种实现中是不统一的。
1、在浏览器中,顶层对象是window,但Node和Web Worker没有window。
2、在浏览器和Web Worker中,self也指向顶层对象,但是Node没有self。
3、在Node中,顶层对象是global,但其他环境都不支持。
同一段代码为了能够在各种环境中都取到对象,目前一般是使用this变量,但是也有局限性。
1、在全局环境中,this会返回顶层对象。但是,在Node模块和ES6模块中,this返回的是当前模块。
2、对于函数中的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,this会返回undefined。
3、不管是严格模式还是普通模式,new Function(‘return this’)()总会返回全局对象。但是,如果浏览器用了CSP,那么eval、new Function 这些方法都可能无法使用。

综上,很难找到一种方法可以在所有情况下找到顶层对象。一下是书中给出两种勉强可以使用的方法。

console.log(typeof window !== 'undefined'
?window
:(typeof process === 'object' &&
	typeof require === 'function' &&
	typeof global === 'object')
?global
:this
);

在这里插入图片描述

console.log(typeof window !== 'undefined'
?window
:(typeof process === 'object' &&
	typeof require === 'function' &&
	typeof global === 'object')
?global
:this
);

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值