《深入理解ES6》读书笔记(一):作用域问题的解决

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_43624878/article/details/102638443

第一章是关于 块级作用域绑定 的,众所周知,这是个“很沉重”的话题。

var声明及变量提升机制

通过关键字var声明的变量,无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量。

function getValue(condition){
	if(condition){
		var value="blue";
		//其它代码
		return value;
	}else{
		//此处可访问value
		return null;
	}
	//此处亦可访问value
}

事实上,在上面的代码中,无论何种情况,变量value都会被创建。在预编译阶段,js引擎会将上面的代码修改为下面这样:

function getValue(){
	var value;
	if(condition){
		value="blue";
		//其它代码
		return value;
	}else{
		return null;
	}
}

上面的程序总有些不便之处。对“提升”的误解还会导致程序出现bug。
于是,ES6引入了 块级作用域 来强化对变量生命周期的控制。

块级声明

此声明用来声明再指定块的作用域之外无法访问的变量。它存在于:

  1. 函数内部
  2. “{}”之间

这其中,ES6最重要的莫过于 let声明const声明 了。

let
let声明不会被提升。我们通常将let声明语句放在封闭代码块的顶部:

function getValue(condition){
	if(condition){
		let value="blue";
		return value;
	}else{
		//此处不存在value
	}
	//此处不存在value了
}

禁止重声明

let不能声明已经存在过的变量——无论是用var声明过的还是用let声明过的。(但在以前ES5的var中就可以)

var count=30;
let count=40;   //会报错

即,同一作用域中不能用let重复定义已经存在的标识符。但如果当前作用域内嵌另一个作用域,便可以在内嵌作用域中用let声明同名变量:

var count=30;
if(condition){
	let count=40;   //允许
}

除此之外,ES6还提供了const关键字,声明常量,其值不可更改——必须进行初始化!
(常量同样不会被提升至作用域顶部)

临时死区

前面说了,let和const声明的变量都不会被提升至作用域顶部。那么,如果在声明之前访问这些变量,即使是相对安全的typeof操作符也会被触发引用错误!

if(condition){
	console.log(typeof value);   //引用错误!
	let value="blue";
}

并且,由于console.log(typrof value)语句会抛出错误,因此用let定义并初始化变量value的语句不会执行。此时说value还位于所谓的“临时死区”。

let和const的行为很多时候与var一致。然而,他们在循环中的行为却不同。 在for-in和for-of循环中,let和const都会每次迭代时创建新绑定,从而使循环体内创建的函数可以访问到对应迭代的值,而非最后一次迭代后的值。let在for循环中也是如此。

开发者可能最希望实现for循环的块级作用域了,因为可以把随意声明的计数器变量限制在循环内部

循环中的块级作用域绑定

我想起了曾经有道非常有名的面试题:

var funcs=[];
for(var i=0;i<10;i++){
	funcs.push(function(){
		console.log(i);
	});
}
funcs.forEach(function(func){
	func();
})

结果是什么?
输出0到10?

你想多了吧。。。 它会输出10个10!
为什么?
循环里每次的迭代同时共享着变量i,循环内部创建的函数全部保留了对相同变量的引用。在循环结束,i的值为10,此时才会调用console.log输出10个10。

为了解决这个问题,开发者们想到了老朋友——IIFE(立即执行函数)

var funcs=[];
for(var i=0;i<10;i++){
	funcs.push(
		(function(value){
			return function(){
				console.log(value);
			}
		})(i)
	);
}
funcs.forEach(function(func){
	func();
})

这就对了嘛!
在这里插入图片描述
那么,我们用刚学的ES6新语法会不会更好些呢?

var funcs=[];
for(let i=0;i<10;i++){
	funcs.push(function(){
		console.log(i);
	});
}
funcs.forEach(function(func){
	func();
})

同样的结果!
let声明模仿上述事例中IIFE所作的一切来简化循环过程,每次迭代都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。

全局块作用域绑定

我们知道,var在作用域全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器window对象)的属性。(这意味着var很可能会无意中覆盖已经存在的全局属性)

var Reg="Hello!";
console.log(window.Reg);   // "Hello!"

var R="Hi!"
console.log(window.R);   // "Hi!"

他是想说,window自定义属性只能有一个!你后来创建的必定会覆盖之前创建的。

如果你使用了let或const,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。

let Reg="Hello!";
console.log(Reg);   // "Hello!"
console.log(window.Reg===Reg);   // false

const ni="Hi!";
console.log(ni);   // "Hi!"
console.log("ni" in window);   // false

如果希望在全局对象下定义变量,仍然可以使用var。这种情况常见于在浏览器中跨frame或跨window访问代码

猜你喜欢

转载自blog.csdn.net/qq_43624878/article/details/102638443