Fork me on GitHub

JS学习系列 07 - 标签声明(Label Statement)

label-statement

引言

假设有这么一道题:

1
2
3
4
5
6
7
8
for (var i = 0; i < 10; i++) {
console.log(i);
for (var j = 0; j < 5; j++) {
console.log(j);
}
}

console.log('done');

我想要当 j = 2 的时候就退出所有的for语句,打印最后的 done ,你会怎么做?

可能有的同学会想到这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function foo () {
for (var i = 0; i < 10; i++) {
console.log(i);
for (var j = 0; j < 5; j++) {
console.log(j);
if (j === 2) return;
}
}
}

foo();

console.log('done');

这样可以实现,但是又多写了一个函数,那么有没有别的办法呢?

再看一个例子,你也一定见到过这样的写法:

1
2
3
4
5
// 假设str是你通过ajax接收到的JSON串
var str = '{"name": "liu", "age": 20}';
var obj = eval('(' + str + ')');

console.log(obj);

那么,你有没有想过 eval 里面为什么要加上括号呢?如果不加又是什么情况?(提前剧透,不加括号这里会报错哦)。

接着往下看,当你读完这篇文章的时候,心中的疑惑会完全解开。

Label Statement

学过C语言的同学知道,C的语法中有一个语句叫:goto,同时老师也多次强调不让我们使用goto语句,因为会大大影响程序的可读性可维护性

我们先来看一段C语言的goto代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
void main(){
int a=2, b=3;

if(a>b) {
goto aa;
}

printf("hello");

aa: printf("s");

return 0;
}

当 a < b 的时候,这里会打印字符串 “hello”,然后结束。
当 a > b 的时候,由于goto语句的作用,就会跳过 print(“hello”),直接跳到 aa 标签声明的代码块中,打印字符 “s”,然后结束。

这就是goto语句的作用,通过标签声明一个代码块,然后在任何地方都可以执行 goto ‘labe’ 来进行程序跳转。

显而易见,这样的写法,违背了程序顺序执行的原则,会跳来跳去,最后导致根本无法维护,所以,记住老师的话,不要使用 goto 语句

那么,看完了C语言中的 goto 语句,和我们的 JavaScript 又有什么关系呢?
这就引出了今天的主题:Label Statement,它就是 JS 中的 goto 语句。

用法

首先明确一个原则,在JavaScript中,语句优先
也就是说,如果一段代码既能够以语句的方式解析,也能用语法的方式解析,在JS中,会优先按语句来解析。

1
{ a : 1 }

上面这段代码,在JS中的执行结果是什么呢?
大家思考2分钟….



好,2分钟已过,大家有结果了吗?
千万不要在浏览器的控制台中去写这段代码,虽然结果和你开始想的结果一样,
但是,它是错误的。

这是在console控制台中执行的结果:

label-console图片

这是在watch中的执行结果:

clipboard.png

可以看到两个结果是不一样的。
console是经过处理的这里不能相信,watch是直接JS的运行环境执行后的结果,是正确的。

为什么 { a : 1 } 结果会是 1 呢?

我换一个写法:

1
2
3
{
a : 1
}

相信有的同学已经明白了,在JS中,{}既可以代表代码块,又可以作为Object的语法标志。
那么我们前面说过,JS是语句优先的,当一段代码既可以按照语句解析,又可以按照语法解析的时候,会优先按语句解析。

当把{}当做是代码块的时候,里面的 a : 1,是不是很像C语言goto语句的标签声明呢?
开头我们提出的第一个问题,如果用这种方式来解决,代码如下:

1
2
3
4
5
6
7
8
9
10
11
aa : {
for (var i = 0; i < 10; i++) {
console.log(i);
for (var j = 0; j < 5; j++) {
console.log(j);
if (j === 2) break aa;
}
}
}

console.log('done');

aa是标签声明,包裹一个代码块,break 的作用是跳出当前的循环,本来是无法跳出外面那层for循环的,但是 break aa,这里跳出了整个代码块。

当然,这种写法是完全不提倡的,这里只是用来说明JS中的Label Statement这个特性,大家千万不要这样写代码。

再来看开头提出的第二个问题:

1
2
3
4
5
// 假设str是你通过ajax接收到的JSON串
var str = '{"name": "liu", "age": 20}';
var obj = eval('(' + str + ')');

console.log(obj);

我们知道,eval(str)会把接收到的字符串在当前上下文中执行,如果不加括号:

1
eval('{"name": "liu", "age": 20}}')

这里的执行语句就会变成:

1
2
3
{
"name" : "liu", "age" : 20
}

{}按照语句解析,执行里面的逗号表达式,我们知道逗号表达式要求每一项都必须是表达式,输出最后一项的结果,而这里不满足要求,所以会报错。

label-watch2

但是加上括号就变成了这样:

1
2
3
({
"name" : "liu", "age" : 20
})

小括号可以把里面的内容当做表达式来解析,那么里面的内容就是一个对象了。

label-watch2

这也是立即执行函数的原理:

1
2
3
(function () {
console.log('IIFE');
})()

小括号把函数声明变成了函数表达式,后面再跟一个小括号表示调用。

结束

这里通过几个例子,引出了 JavaScript 的标签声明语句(Label Statement),从而解释了一些我们常用写法的原理。

以后万一有人问你为什么 eval() 解析JSON要加括号呢?
这回知道怎么说了吧。