“灵活”的JavaScript

以前我很喜欢JavaScript,并不仅仅是因为他是我吃饭的工具,更多因为他非常灵活,从学校了写多了C++或者Java,遇到各种诡异的编译链接等等的错误,到JavaScript弱类型动态语言,简直就是满地撒欢。

我可以使用各种奇淫巧技,虽然比不上Python,也足够让我用1行代码实现别人3行代码的功能,当时我以同样的功能下代码字符越少越牛逼为荣。

然而慢慢地,别人开始接手我写的代码,包括自己开始看以前自己写的代码,虽然别人有三行代码,但是一眼就看明白了三行代码所表达的意思,而我的一行代码,看了好几分钟才能看明白所表达的意思,有时候还不一定正确,也许需要跑起来才能知道效果,我开始质疑自己的这种行为,比如以下行为:

  • ~~n, 效果等同于Math.floor( n ),虽然运行更快,但是一眼并不能看出什么意思,而函数Math.floor一眼就能看出什么意思。
  • n === n,效果等同于isNaN( n ),因为NaN并不等于他自己。
  • (![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]甚至包括这种,我还真写过,只是没有这么夸张。

灵活的代价

下面的代码是一个真实的例子,我调用了别人的代码,他的函数:

1
2
3
4
5
6
function test() {
...

// 这里返回是一个Promise
return doSomeAsync();
}

返回的是一个Promise,所以我用await去调用,到这里并没有问题。

而出现问题来源于一次需求的增加,在执行这种函数时,需要判断一些意外情况,如果出现意外,就弹出一个对话框,让用户点击确认后才继续执行,于是他的代码变成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

function test( needConfirm ) {

...

if ( needConfirm ) {
Dialog.show( {
content: '您确定要继续执行么?',
onOk: () => {
doSomeAsync();
}
} );
return;
}

// 这里返回是一个Promise
return doSomeAsync();

}

这么改动乍一看也没什么问题,如果需要确认,就弹出一个对话框,给用户确认,当用户点击确认的时候,在回调函数里面执行原本的操作。

而对于我的代码来说,我依赖于doSomeAsync执行的结束,所以,当我任然使用await test( true )去调用时,我的代码就报错了,因为true === needConfirm的时候返回的是undefinedawait并不会等待,而是继续执行下面的代码,所以导致了错误的出现。

“束缚”的TypeScript

相信以上的代码绝大部分人都写过,我们再仔细看下,其实上面这段代码的问题在于函数的行为不一致,正常情况是返回一个Promise,而分支情况是返回一个undefined,我们看看在TypeScript下怎么写这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function test( needConfirm: boolean ): Promise<void> {

...

if ( true === needConfirm ) {
Dialog.show( {
content: '您确认要继续执行么?',
onOk: () => {
doSomeAsync();
}
} );
return;
}

return doSomeAsync();
}

如果你任然这么写,那么TypeScript的编译阶段就会报错,因为你规定了函数需要返回一个Promise,但是却存在返回undefined的情况,你不得不去修改你的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

functon test( needConfirm: boolean ): Promise<void> {

...

if ( true === needConfirm ) {

let resolveFunc = null;

Dialog.show( {
content: '您确定要继续执行么?',
onOk: async function(): Promise<void> {
const result = await doSomeAsync();
resolveFunc( result );
}
} );

return new Promise( ( resolve, reject ) => {
resolveFunc = resolve;
} );
}
return doSomeAsync();
}

编译通过,函数行为一致,调用者也无需感知里面的逻辑。

最后

强类型写起来限制会更多,然而对于代码的可维护性,可运行性都有很大的提升。

现在前端的复杂性提升地非常快,几年前也许前端仅仅只是更多作为展示型页面存在,但是现在的前端已经承载了太多的东西,用户的交互也发生了很大变化,JavaScript也可以写ReactNative的APP,可以写NodeJs服务,可以用NodeWebkit等工具写跨平台的客户端,使用TypeScript严格按照强类型语言去写你的应用,让你的应用具备更高的可读性和可维护性。

当然事事无绝对,还是得看你的场景,杀鸡焉用牛刀,JavaScript自有存在的意义,至少是目前浏览器官方语言,当你的应用重逻辑时,放手用`TypeScript吧。