《深入理解ES6》之块级作用域与字符串

块级作用域绑定

使用var声明

原来我们在js中声明变量是使用var关键字。

我们都知道,使用var声明变量,会有变量提升,比如下面的代码:

1
2
console.log(value); //undefined
var value = 0;

通知台会打印undefined,实际JavaScript引擎将代码理解成下面这样:

1
2
3
var value;
console.log(value); //undefined
value = 0;

所有的变量的声明都会被提升至函数的顶部。

另一个使用var声明的特点是,var声明的变量作用域是函数作用域。

块级声明

ES6引入了块级声明,具体有let声明和const声明。

let声明的用法与var相同,区别有如下:

  1. 不会变量提升

用let声明变量,在变量声明之前是不可以使用的,若使用则会报错。

1
2
console.log(value); //ReferenceError: value is not defined
let value = 0;
  1. 块级作用域

用let声明的变量,作用的范围在{}之中,执行流离开{}变量就会销毁。

  1. 禁止重声明

var重复声明则后面的会覆盖前面的,let重复声明则会报错。

const声明的行为类似于let,但const定义的为常量,不可在使用过程中修改。

但是,有一点要注意,js中的常量如果是对象,则对象的值可以修改。这一点与其他语言中的常量有所不同。

循环中的块作用域

js编程中的一个经典问题如下:

1
2
3
4
5
6
7
8
9
10
11
var funcs = [];

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

funcs.forEach(function(func){
func(); //输出10次数字10
});

这个问题的原因是js并不像我们想象的那样运行,我们希望每次的i都是独立的i,每次打印当前循环阶段的i。

而实际情况是,所有的i都是共享的,而在循环结束的时候,i的值为10。传入的回调函数作为闭包可以访问i的值,但当调用的时候,i已经变成10了。所以每次调用打印的都是10。

通常解决这个问题我们都是采用立即调用函数表达式(IIFE)。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var funcs = [];

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

funcs.forEach(function(func){
func(); //输出0,然后是1、2直到9
});

既丑陋又难以理解。

而使用let在循环中声明变量就简单多了:

1
2
3
4
5
6
7
8
9
10
11
var funcs = [];

for(let i = 0; i < 10; i++){
funcs.push(function(){
console.log(i);
});
}

funcs.forEach(function(func){
func(); //输出0,然后是1、2直到9
});

这种结果符合我们理解的期望。每次循环的时候let声明都会创建一个新变量i,并将其初始化为i的当前值。

需要注意的是,let声明在循环中的这种行为是专门定义的,与let的不提升特性无关。

而const声明可以在for-in和for-of中使用,但是若在for循环中,出现i++等修改常量的行为则会报错。

字符串

模板字面量

模板字面量是ES6中一个重要的新增特性,主要有下面这些能力:

  1. 多行字符串

模板字面量可以轻易的创建多行字符串:

1
2
3
4
5
6
let message = `Multiline
string`;

console.log(message); //"Multiline
// string"
console.log(message.length); //16
  1. 字符串占位符

在一个模板字面量中,你可以把任何合法的JavaScript表达式嵌入到占位符中并将其作为字符串的一部分输出到结果中。

1
2
3
4
let name = "Nicholas",
message = `Hello, ${name}.`;

console.log(message); //"Hello,Nicholas."

还可以嵌入如运算式、函数调用等JavaScript表达式

1
2
3
4
5
let count = 10,
price = 0.25,
message = `${count} items cost $${(count * price).toFixed(2)}.`;

console.log(message); //"10 items cost $2.50."

还可以在一个模板字面量里面嵌入另外一个模板字面量

1
2
3
4
5
6
let name = "Nicholas",
message = `Hello, ${
`my name is ${ name }`
}.`;

console.log(message); //"Hello,my name is Nicholas."
  1. 标签模板

模板标签可以执行模板字面量上的转换并返回最终的字符串值。等于可以自定义字符串组合的方式。标签指的是在模板字面量第一个反撇号(`)前方标注的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function passthru(literals,...substitutions){
let result = "";

//根据substitution的数量来确定循环的执行次数
for(let i = 0; i < substitutions.length; i++){

result += literals[i];
result += substitutions[i];

}

//合并最后一个literal
result += literals[literals.length - 1];

return result;

}

let count = 10,
price = 0.25,
message = passthru`${count} items cost $${(count * price).toFixed(2)}.`

console.log(message); //"10 items cost $2.50."