块儿作用域の使用价值

写在前面

在开始这篇文章之前,你需要提前了解一些概念:JavaScript的词法作用域、常见的嵌套作用域类比于气泡模型、产生一个气泡有函数和块儿两种方式。

这篇文章介绍什么时候需要使用块儿作用域,即使用块儿作用域的价值。其中创建块儿作用域的方法主要是letconst,本文使用let创建块儿作用域。

价值

首先,我们来看一个代码片段:

var foo = true;

if (foo) {
	var bar = foo * 2;
	bar = something( bar );
	console.log( bar );
}
复制代码

我们可以理解这个代码片段的意图,当footrue的情况,去声明变量bar,再对bar做数据操作。但是不幸的是,实际上无论footrue还是false,变量bar都会被声明,因为提升(Hoisting)。 这就造成变量bar去污染整个作用域(因为是多余的)。

这是块儿作用域可以做的第一个价值——治污染,即去除多余的变量声明。仅仅需要将var barvar更改为let

var foo = true;

if (foo) {
	let bar = foo * 2;
	bar = something( bar );
	console.log( bar );
}
复制代码

bar将仅存在于if(..){...}块儿作用域内。


我们再来看第二个代码片段:

function process(data) {
	// 做些有趣的事
}

var someReallyBigData = { .. };

process( someReallyBigData );

var btn = document.getElementById( "my_button" );

btn.addEventListener( "click", function click(evt){
	console.log("button clicked");
}, /*capturingPhase=*/false );
复制代码

前半部分是拿process函数处理someReallyBigData这个数据,后半部分是按钮的事件绑定。但是前半部分和后半部分是毫无关联的,所以从性能上考虑,我们是希望在process函数执行之后,someReallyBigData这个消耗巨大内存的数据结构可以被垃圾回收。然后JS引擎很可能仍会保持这个结构一段时间,因为click函数在整个作用域上拥有一个闭包。

这是块儿作用域可以做的第二个价值——垃圾回收。适当修改一下前面这个代码片段:

function process(data) {
	// 做些有趣的事
}

// 运行过后,任何定义在这个块中的东西都可以消失了
{
	let someReallyBigData = { .. };

	process( someReallyBigData );
}

var btn = document.getElementById( "my_button" );

btn.addEventListener( "click", function click(evt){
	console.log("button clicked");
}, /*capturingPhase=*/false );
复制代码

我们再来看第三个代码片段,一个常见的面试题目:

for (var i=1; i<=5; i++) {
	setTimeout( function timer(){
		console.log( i );
	}, i*1000 );
}
复制代码

我们可以理解这个代码片段想要输出的是1,2,3,4,5,但其实输出的是6,6,6,6,6。因为在5次timer函数执行的时候,由于闭包找到的相同的作用域内i的值已经变成了6。

想要输出1,2,3,4,5,有个方便的方法是为每次的timer绑定一自个儿的块儿作用域,这是块儿作用域的第三个价值——循环中的块儿作用域。代码修改为 :

for (let i=1; i<=5; i++) {
	setTimeout( function timer(){
		console.log( i );
	}, i*1000 );
}
复制代码

也可以是:

{
  let j;
  for (j=1; j<=5; j++) {
      let i = j;
	  setTimeout( function timer(){
		  console.log( i );
	  }, i*1000 );
  }
}
复制代码

工具与更好的使用

在前面我们使用let创建块儿作用域,但是let/const都是ES6引入的,如果在ES6之前,想要使用块儿作用域,就使用try...catch的catch块儿

Google 维护着一个称为“Traceur”[^note-traceur]的项目,它的任务正是为了广泛使用 ES6 特性而将它转译为前 ES6(大多数是 ES5,但不是全部!)代码。TC39 协会依赖这个工具(和其他的工具)来测试他们所规定的特性的语义。

比方这样的代码片段:

{
	let a = 2;
	console.log( a ); // 2
}

console.log( a ); // ReferenceError
复制代码

Traceur会转换成:

{
	try {
		throw undefined;
	} catch (a) {
		a = 2;
		console.log( a );
	}
}

console.log( a );
复制代码

另一个是在使用let创建块儿作用域的时候,创建更加明确的块儿(考虑到代码移动、代码复用、可读性等)。比如我们第一个治污染的例子:

var foo = true;

if (foo) {
	/*let*/{ 
		let bar = foo * 2;
		bar = something( bar );
		console.log( bar );
	}
}

console.log( bar ); // ReferenceError
复制代码

YDKJS的作者Kyle Simpson做了个名为let-er的工具,let-er 是一个编译期代码转译器,它唯一的任务就是找到 let 语句形式并转译它们。它允许这么写let语句:

var foo = true;


if (foo) {
   let(bar = foo*2){
     	bar = something( bar );
		console.log( bar )
   }
}


console.log( bar ); // ReferenceErr
复制代码

总结与参考链接

let还是var是一个考虑是否使用块儿作用域的问题。如果决定使用块儿作用域,创建更加明确的块儿是推荐的。另外摘了冴羽 对于letconst的最佳实践:

在我们开发的时候,可能认为应该默认使用 let 而不是 var ,这种情况下,对于需要写保护的变量要使用 const。然而另一种做法日益普及:默认使用 const,只有当确实需要改变变量的值的时候才使用 let。这是因为大部分的变量的值在初始化后不应再改变,而预料之外的变量值的改变是很多 bug 的源头。

参考链接:

YDKJS-SCOPE&CLOSURES-apB

YDKJS-SCOPDE&CLOSURES-ch3

掘金-冴羽-ES6系列之let和const

转载于:https://juejin.im/post/5c5c4b676fb9a04a006f76ce

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值