Javascript 中的 Lambda 表达式

Lambda 表达式是一种简易的函数定义形式,通常在编程语言用来简化代码的结构。比如以下的 Javascript 函数:

在其它语言中的 Lambda 形式为:

Lambda 表达式之所以流行,是因为它可以方便地嵌在其它语句中,比如 Java 8 使用了 Lambda 表达式大规模简化了数组的处理:

Javascript 也终于迎来的 Lambda 表达式,在 ES6 中的定义是“箭头函数”(Arrow Function),第一个例子中的函数可以被简化为:

语法和 C# 类似,而和 Java 不同。

Lambda 表达式基本上可以看成是一个语法糖,但在 EC6 中,它和 function定义的函数有一点点的不一样。你可能已经想到了,就是 this的定义域。看下面的例子:

上面的函数中,一定要使用自定义的 self来把 this传到 .eachfunction中。而使用 Lambda 表达式的话,就不需要这么做了:

箭头函数在 Chrome 45.0 和 Firefox 22.0 以上版本中支持,具体的信息请参考 Arrow functions

Go 语言 select 语句的优先级

Go 语言中,配合通道“channel”的读写操作,特地提出一个 select语句,用于多个 channel 的并发读写操作。例如:

上述代码的意思是,同时在 a 和 b 中选择,哪个有内容就从哪个读,否则输出“No data”。由于 channel 的读写操作是阻塞操作,使用 select语句可以避免单个 channel 的阻塞。 default的作用是避免两个 channel 同时阻塞。

有一个问题, selectcase顺序是随机的,并不是按代码中写的顺序。于是就产生了优先级的问题。比如我想先从 a 读取,a 没有内容的时候,再从 b 读取,就不能用上述的方式了。这种情况下的逻辑要稍微麻烦一点:

描述起来倒是蛮简单,先从 a 读,没数据的话再从两个一起读。这种方式对于读和写操作都适用, default是否需要取决于应用场景。

Go 语言中奇怪的 if 语句

上篇文章提到过 Go 语言中带有强烈的设计者的主观想法,Go 的 if 语句就是其中一例

常见的 if 语句大约是这个样子的(C++):

这样有一个问题:变量 event 定义在了 if 语句的外面,也就是说,在 if 语句之后,也可以继续使用 event 变量;而如果后续的操作中不需要 event 变量了,它实际上就造成了命名空间的污染。这并不是一个严重的问题,多数情况下不会造成任何问题,而如果一定要解决的话,在 C++ 中可以在 event 的定义之外套一层大括号来限定它的作用域。虽然代码看上去有一些奇怪,但无伤大雅。比如这样:

但是 Go 的设计者不知是出于什么原因,非要从语法上解决这个问题。于是 Go 中的 if 语句可以写成这个样子:

对,你没有看错。虽然 event 是定义在了 if 中,但它在 else 中也是可以用的。也就是说,这种写法实际上是上述的 C++ 写法的语法糖。

并不清楚 Go 的设计者添加这个语法糖的目的是什么。它仅仅是为了解决变量的作用域问题而提出,却牺牲了代码的可读性。if 语句可能会变得过长而不易阅读;在后续重构的过程中,拆分 if 语句也会变得困难。为了解决一个小问题而增加一种有问题的语法,看上去有点得不尝失。

吐槽一下 Go 语言

由于公司一直都在大力推广 Go 语言,我也有幸接触了一下。虽然 Google 内部已有三大语言(C++、Java、Python),Go 在公司内部的政治意义大于它的实际使用,但它的设计却有着很多亮点。比如 Go 自带了代码格式化工具(gofmt)和自动化测试工具(go test),这些标准化的工作在其它语言中一直都存在着多方争议,一直都不能统一,而 Go 从一开始就搞定了这些琐碎问题,让广大程序员可以把重心都放在开发新程序上,而不是争一些有的没的。

当然,这篇文章并不想表扬 Go 语言,而是要吐槽一下它的缺点。

Go 的设计者有着强烈的 Unix 背景,有一些平台依赖的东西都默认甚至是强制成 Unix 的。比如 Go 语言中有一个名为“path”的包,用来操作路径,而它强制路径分隔符为“/”,而无视 Windows 中的“\”。后来估计是开发中遇到一些问题,又做了一个叫“filepath”的包,来兼容 Windows 的使用。又比如 Go 语言中对环境变量的解析,强制环境变量的标识符为“$”,而不视 Windows 中的“%”,甚至整个标准库中都找不到对“%”的处理方法。这些基本的区别都不做处理,很难想象 Go 语言在跨平台设计中有哪些更为隐藏的坑。

这还不是主要的问题。更严重的问题是,Go 语言中有过多的“私有”特性,对使用者不公平。

先来举一个 C++ 的例子,C++ 中有一些运算符,比如加减乘除,这些运算符可以使用于 C++ 的基础类型(如 int)上。基础类型是 C++ 语言本身提供的,如果运算符只能作用于基础类型,那么 C++ 也可被视为“自私”的。然而 C++ 的设计者并没有这么做,这些运算符,不仅可以用于基础类型,也可以在自定义类型中,通过重载的方式来使用,使得 C++ 中不仅两个 int 可以相加,两个 class 的实例也可以相加。这样一来,运算符对于使用者就是公平的。

而 Go 语言中,则有着很多反例。比如它的泛型:Go 的官方说法是“我们不支持泛型”,具体原因就不深入讨论了。但是,Go 语言的自有类型 map 和 channel 却是支持泛型的。这就成了一个典型的“自私”的例子。类似的例子还有 Go 语言中的 internal 包有着特殊的作用域;Go 的官方库有着极短的包名,而第三方库通常是以“github”开头的很长一串文本。

这些“私有”特性,说明 Go 语言的设计者并没有为开发人员考虑过,自己设计得爽了,却徒增了学习曲线,不利于 Go 语言的推广。如果能把这些问题解决好,Go 还是可以成为一门不错的语言的。

在 Markdown 中设置链接由新窗口打开

尽管我对 Markdown 不是那么感冒,但工作和生活中总是难免会使用到一些,比如在向某些开源项目发 PR 的时候。于是对 Markdown 的一些小技巧也有所了解。

Markdown 中的链接语法是这样的:[文本](链接)。它相当于 HTML 中的

但众所周知,HTML 的 a 标签有一个实用的属性 target,当它的值为“_blank”的时候,浏览器会默认为这个链接打开一个新窗口,而不是在当前页面转向新页面。而 Markdown 中没有这个“_blank”选项,于是所有链接都只能在当前页面打开,有些情况会非常不方便。

在多数 Markdown 解释器中(如 GitHub Flavored Markdown),这个情况无解。如果一个 Markdown 解释器同时支持 HTML 语法,则这种情况可以通过直接写 HTML 解决。

于是我又多了一个不喜欢 Markdown 的理由。

HHVM 中的 RepoAuthoritative 模式

HHVM 3.8 中提出了一个 RepoAuthoritative 模式,用于提升 PHP 代码的效率。

PHP 代码是解释型的,也就是每次执行的时候,PHP 引擎都会读取源代码,编译成机器代码之后再执行。这也就是为什么解释型语言比较慢的原因之一,它不能一次编译完成,而是一定要运行到哪里编译到哪里。编译的过程拖慢了整个运行的效率。而这样每次“编译”的过程显得有些多余,因为代码是基本不会变的。于是 PHP-FPM 是提出了 OpCache 来缓存编译的结果,如果代码已经被编译过,且源代码没有改动,则继续使用缓存。

而 HHVM 则更进了一步,在 RepoAuthoritative 开启之后,指定的文件夹会被编译成一个二进制文件,并同时进行优化。官方说法是,这样的优化可以在 HHVM 本身的基础上,再提升 20% 的运行效率。

开启的方法如下,其中 /var/www 是想要编译的文件夹。

关闭的方法如下:

编译的过程使用了 /var/run 做为临时文件夹,实测 WordPress 编译完成之后,要占用 175MB 的空间,如果 /var/run 太小会提示“disk is full”错误。

当然, RepoAuthoritative 也有一定的限制,比如不能使用 eval() 和 create_function() 等动态改变代码的语句。不过 WordPress 中几乎没有用到,说“几乎”是因为实际上有两处用到了,一个是在 wp-includes/pomo/po.php 中,这个文件貌似根本不会被执行到;另一处是在 wp-includes/atomlib.php 中,只要不使用 atom 作为 RSS feed 即可。另外就是编译完成之后,源代码的改动就会“失效”,要再一次编译才会使改动起作用。

然而,由于 HHVM 自身已经相当地高效了,开启 RepoAuthoritative 模式之后没有明显的变化。本站实测下来,TTFB 时间大概减少了 50ms,对于原本只有 700ms 左右的速度来说,看不出效果。我试用了一下之后就把它关闭了。不过至少说明在 WordPress 上使用这个功能是没有问题的。如果你的 WordPress 中有非常慢的功能,不妨试一下 RepoAuthoritative 模式。

在 PHP 中实现 String 的 StartsWith 和 EndsWith

和 Javascript 一样,PHP 中也没有对 String 实现 StartsWith 和 EndsWith 这两个方法。由于这两个方法很常用,我们只好自己来实现一下。

在 PHP 4 中,常见的做法和 Javascript 类似,如下:

上面的方法会使用更多的内存,并且对字符串扫描了两遍,效率不高。这种方法在 PHP 5 中已经过时了,因为 PHP 5 给出了一个 substr_compare 方法,可以直接比较子字符串,如果代码就可以简化成这样:

上述实现的效率至少快了一倍。由于历史遗留的问题,很多现存代码都没有更新,比如 WordPress 的源码中就存在了老版本的实现。如果你已经在使用 PHP 5 或者 HHVM,不妨试试第二种实现。