【瞎扯】谈谈为什么我喜欢JavaScript

brambles 发布于 2016年02月26日
tinyfool 清醒疯子 等2人欣赏。

原文链接

JavaScript 有哪些地方被喷?

javaScript 是一门被喷得非常之多的语言,然而大部分人都只不过是跟风喷。限于这些喷子的水平不足,他们并不能说出个靠谱的喷点来。被这些跟风喷子喷的最多的就是 Callback和异步 这个问题。接下来分别来讲讲这两个问题。

Callback

其实在那些喷子眼里,Callback 和 异步是同一个问题,因为他们眼里没有高阶函数这个概念。他们觉得只要把函数当成参数传入另一个函数,就是异步。这种想法显然是错误的。所以本小节的主题应该是高阶函数,而不是Callback。

什么是高阶函数(Higher-order function)?从定义上来讲,高阶函数是一种函数,它接受一个函数作为参数并且返回另一个函数。举个例子:

``` JavaScript
var double = function(f){ // 接受一个函数做参数
    return function(x){ // 返回另一个函数
        return  f(x) * 2; // 函数的结果 *2
    };
};

var f = function(x){
    return x + 2;
};

var g =  double(f); // 把 f 作为参数传入 double 并返回新函数 g

console.log(g(1)); // => 输出 6
```

例子举完了了,有一点计算机基础的应该都能看得懂,高阶函数就干了这点破事而已。但是问题还没结束,看到这里如果对函数编程不了解的同学可能就要问了: 你说的我都懂啊,但是你举的这个例子有什么卵用呢?真写代码的时候谁会真么写啊?

有这样的疑问一点都不奇怪,以为上面那个例子只是告诉你高阶函数是啥,并没有告诉你高阶函数有卵用。高阶函数的卵用有且仅有一点——过程抽象。不过很多编程书都告诉你写代码中抽象很重要吗?现在看到高大上的抽象两个字有没有觉得很激动啊?接下来我用最最最简单的例子 map 来讲。

``` JavaScript
var l = [1,2,3,4,5,6,7];
var double_l = l.map(function(i){return i*2;});
console.log(double_l); // => 输出 [2,4,6,8,10,12,14]
```

嗯……讲完了。map 就是那么简单的东西。接受一个函数,然后应用到数组的每一项上,最后返回一个新数组。所以map是啥?通俗地讲,map 是一个抽象过程。map 会对一个数组上的每一项做一样事,并把做完这样事后产生的结果组成一个新的数组,具体做什么会返回什么样的结果,则由你传入的函数决定。

所以说了那么多,高阶函数就是一种对过程的抽象。而 Callback 不过就是给一个高阶函数穿进去的参数而已。

异步

喷JavaScript的另一点在异步上,而且喷点多在:反直觉和Callback Hell

反直觉

首先喷反直觉的我就不想说什么,这说明计算机理论知识堪忧啊,操作系统课没上过吗?我们现在的多任务操作系统是怎么来的?给你们补一补计算机历史内容吧。

原来我们的操作系统还是单任务,一次只能运行一个程序的时候。一个勇敢的骚年突发奇想,在电脑上写了两个程序。A程序不断地输出A字符,B程序不断地输出B字符。这个勇敢的骚年写了一个调度程序,用来调度切换AB两程序。最后此骚年惊喜看到了电脑屏幕上A和B交叉着输出,于是我们现在的多任务操作系统雏形就产生了。

我来问问,当我们的CPU还是单核单线程的时候,怎么切换两个程序啊?我来自问自答:对CPU发出中断,然后利用中断处理程序来切换正在运行的程序。哎?你听着有没有点耳熟啊?对CPU发送的中断,这不跟 JavaScript 里面事件很像啊?中断处理程序有没有像 JavaScript 里面传入的 Callback 啊?

所以啊,我说你们都 too naive。别整天瞎跟着那些瞎忽悠的“技术前沿”转悠,先把老祖宗们留下的精粹吃透了。学习老祖宗们留下来的基本方法和基本思想还是很有指导意义的,以后上手那些整天瞎逼逼吹牛的新技术也会很轻松的。

处理并行有两个非常经典的模型:多线程模型 和 事件驱动模型。说起异步,我们自然而然地对号入座,明确我们要讨论的是事件驱动模型。JavaScript 为什么要选用 事件驱动模型 来作为它的异步处理方案呢?这个问题的答案是非常明显的,JavaScript 生出来是为了给浏览器做交互用的,而 事件驱动模型 跟 UI 是最合得来的。其实 JavaScript 在处理页面 UI 交互方面是最符合直觉的,有人觉得反直觉我就没懂了。

Callback Hell

这是一个不是槽点的槽点。Callback Hell 的问题不在 JavaScript 这门语言上面,而在于使用者本身。JavaScript 这种函数定义就是表达式的特点比那些单行 Lambda 高到不知道哪里去了。然而由于使用者的滥用,导致 JavaScript Callback Hell 这个槽点的产生。异步问题其实就是一个 Continuation 的问题,而现在的类似 Promise 这种东西,也不过就是一个 Continuation Monad 而已。我都不觉得有什么好说的了,贴一段 本博客程序 LoveAria.Me 处理异步的代码给你们看看,虽然是 PureScript ,但是本质上还是编译到 JavaScript 执行的,你们看看哪里来的 Callback Hell?

``` Haskell
getCategoryTree :: forall eff. Int -> ModelAff eff CategoryTree -- 这是一个异步函数,返回一个 CategoryTree 类型
getCategoryTree id = do
  category <- findCategoryById id -- 这里是异步查询数据库
  children <- findallCategory ("parent_id" .== category.id .&& "id" .!= 0) (Desc "id") -- 这里是也是异步查询数据库
  children_tree <- sequence $ map (c -> getCategoryTree c.id) $ children -- 这里是异步并且还递归查询数据库,getCategoryTree 就是本函数
  return $ CategoryTree category children_tree -- 这里是返回 CategoryTree

```

为什么我喜欢 JavaScript ?

好了,前面扯了那么多蛋了,现在回到正题,为啥我喜欢 JavaScript?

JavaScript 该有的都有了, 虽然都堪堪可用

JavaScript是一种基于原型的、多范式的动态脚本语言,并且支持面向对象、命令式编程风格和函数式编程风格。——摘自MDN

JavaScript设计上虽然有很多缺陷和不完善,但毫无疑问的是,JavaScript该有的都有了(真的是大杂烩),虽然每样都只是堪堪够用。在语言层面上,JavaScript 覆盖了面对对象、函数式、命令式多个编程语言范式。所以JavaScript也可以非常轻易的作为非常多其他语言所翻译的目标语言,无论是命令式、面对对象或者是函数式语言,都能非常轻松得翻译成JavaScript进行执行。放在其他语言身上,那可能就需要拖着沉重的 Runtime 了。

JavaScript 在目前主流语言中拥有最顺手的函数式特性

毕竟JavaScript是first-class-functions,而且函数本身也是表达式,所以可以非常轻松愉快得把函数放到任何地方使用。而且,JavaScript 是一门面对对象的语言。在 JavaScript 中,函数也是对象,可以像对象一样操作和传递,甚至还可以用函数对象存储变量。用函数存储变量应该算是一个在 JavaScript 中很 Hack 的技巧了吧 ╮(╯▽╰)╭

就目前来看,我觉得JavaScript的函数式特性是目前主流编程语言中让我用得最顺手的,没有之一。其他非主流语言我就不说了。因为譬如本博客程序 LoveAria.Me 所使用的 PureScript 是纯函数式语言,当然跟 JavaScript 这种业余的不可同日而语。不过 PureScript 实在是太小众了,小众得没朋友了都。

前我见到有人提问,为什么JavaScript中没有lambda表达式。我倒是觉得JavaScript中根本不需要lambda表达式,因为JavaScript中的函数本身就是表达式,比其他很多语言中蹩脚的lambda表达式好用不知道多少。

其他动态语言,诸如 Python / Ruby 等虽然有部分函数式特性,但是用起来都不是很顺手。除了 Python 的装饰器写起来还算赏心悦目。

哪里都能跑

目前为止,JavaScript应该是最接近Java所提出的目标的——一处编写,到处运行。JavaScript可以运行在非常多得平台上包括不限于浏览器、服务器、桌面、移动终端甚至是嵌入式设备上。而且核心不涉及调用宿主API的部分可以轻松分离封装可以不用修改一行代码而直接运行在任何JavaScript环境下的模块。

我自己还给自己封装了一个工具,用来模块化 JavaScript 。让模块不改一行代码就可以用 script 标签、CMD、AMD 以及 node 上的 CommonJS 加载执行。

其他

以npm为核心的生态环境是JavaScript的另一个亮点,上面包括了各种种类繁多的各种库,虽然有部分质量堪忧,但是光是“有”就已经超越非常多其他语言了。很多语言还是没有靠谱的包管理,和生态环境呢,还需要靠着大量的复制粘贴来活着。

元编程是动态语言的一大利器,JavaScript作为一门动态语言,也能靠元编程来进行一些意想不到的Hack。当然啦,元编程作为一门黑魔法,遭到过很多人的抵制并且说什么性能差啊、不可控啊、安全隐患啊……但是我觉得使用和滥用是两码事,并且自由也必然会带来这些副作用。

总结

如果让我给 JavaScript 打分,我估计每项都只能给他 60 分,但他是个不偏科的好孩子啊,只是不像其他语言优点那么明显而已(除了可以跑在浏览器上这一点,我真找不出他有啥明显优势)。JavaScript 该有的都有了,虽然只是堪堪可用。而他的优点就是,啥都有,做啥都行,虽然都不一定做的很好。

哎?怎么感觉我整篇文章都是在黑 JavaScript 的啊?逃~

共10条回复
adad184 回复于 2016年02月26日

广告 差评

brambles 回复于 2016年02月26日

1楼 @adad184 哪里来的广告……这是我博客上的第一篇博文……

adad184 回复于 2016年02月27日

2楼 @brambles 哈哈 就是给你的博客打广告

brambles 回复于 2016年02月27日

3楼 @adad184

我博客的实现比较有意思

happyming 回复于 2016年02月28日

javascript看起来好像也不是很难啊

hrong 回复于 2016年02月28日

ES6新增的箭头函数我是不是可以认为就是lambda表达式的变种??。。。。。。。

brambles 回复于 2016年02月28日

6楼 @hrong

箭头函数就js的lambda表达式…

我不喜 es6 就是因为他几乎都是在加语法糖,而没有实际改善一些设计缺陷…

brambles 回复于 2016年02月28日

5楼 @happyming

光看是没意义的,不实际写写就不知道有些什么坑…

编程语言本身都不会有什么多难的东西,难点都在于怎么用好

happyming 回复于 2016年02月28日

8楼 @brambles js现在发展的这么好 应该已经有了最佳实践的写法了吧 比如那本 js精粹 的书

brambles 回复于 2016年02月28日

9楼 @happyming

我是非常讨厌所谓最佳实践这种东西…这种最佳实践往往是用来防傻逼的,他的最终目的就是哪怕让傻逼按照这个方法写代码写,也不容易引入比较大的坑。

这种规范当然有,但是现在一个问题是市场上主流的那部分写前端的质量不够高,并且在很多网站里面,前端挂了并不会对业务有什么很大的影响…所以对这方面的重视度也不高…

最终导致,市面上写js的平均水平非常低,而且也很少经过一些规范的训练…多是那种上脚本之家抄代码的水平…

登录 或者 注册