2015年2月22日星期日

阮一峰的网络日志

阮一峰的网络日志


也许,DOM 不是答案

Posted: 21 Feb 2015 08:20 PM PST

有一个词"手机网站"(mobile web),指供手机浏览的网站,但它是不存在的。

人们提到"移动互联网"的时候,其实专指另外一样东西:手机App。

一、Web App vs. Native App

比起手机App,网站有一些明显的优点。

  • 跨平台:所有系统都能运行
  • 免安装:打开浏览器,就能使用
  • 快速部署:升级只需在服务器更新代码
  • 超链接:可以与其他网站互连,可以被搜索引擎检索

但是,现实是怎样呢?

(1)体验差。手机App的操作流畅性,远超网站。

(2)业界不支持。所有公司的移动端开发重点,几乎都是原生app。

(3)用户不在乎。大多数用户都选择使用手机app,而不是网站。

如果将来有一天,Web app会成为主流,一定有一个前提,那就是它的性能可以赶上Native app。

二、为什么Web app有性能瓶颈?

Web app输给Native app的地方,不是界面(UI),而是操作性能。主要是互动(interaction)和动画(animation)这两方面,会出现卡顿(jank),用户会感觉到明显的时滞,有时简直慢得难以忍受。

Web app的性能瓶颈,主要有以下原因。

(1)Web基于DOM,而DOM很慢。浏览器打开网页时,需要解析文档,在内存中生成DOM结构,如果遇到复杂的文档,这个过程是很慢的。可以想象一下,如果网页上有上万个、甚至几十万个形状(不管是图片或CSS),生成DOM需要多久?更不要提与其中某一个形状互动了。

(2)DOM拖慢JavaScript。所有的DOM操作都是同步的,会堵塞浏览器。JavaScript操作DOM时,必须等前一个操作结束,才能执行后一个操作。只要一个操作有卡顿,整个网页就会短暂失去响应。浏览器重绘网页的频率是60FPS(即16毫秒/帧),JavaScript做不到在16毫秒内完成DOM操作,因此产生了跳帧。用户体验上的不流畅、不连贯就源于此。

(3)网页是单线程的。现在的浏览器对于每个网页,只用一个线程处理。所有工作都在这一个线程上完成,包括布局、渲染、JavaScript执行、图像解码等等,怎么可能不慢?

(4)网页没有硬件加速。网页都是由CPU处理的,没用GPU进行图形加速。

上面这些原因,对于PC还不至于造成严重的性能问题,但是手机的硬件资源相对有限,用户互动又相对频繁,结果跟Native app一比,就完全落在了下风。

三、FlipBoard的解决方案

FlipBoard原本是一个手机App,最近开始部署Web版本,结果就遇到了上面的问题:Web版的体验不佳。

上周,他们将解决方案公布在网站上,结果引起了业界轰动,因为这是一个史无前例的解决方案:

---- 他们没有使用DOM,而是将整个网站用canvas输出!

你可以用手机打开flipboard.com,体验一下,看看跟Native app有没有差别。如果你没有帐号,可以直接打开这里这里

这个方案的出发点是这样的:如果将网页变成了一个个canvas,用户就等于在跟图片互动,这样就绕开了DOM,降低了操作时滞。而且,canvas可以被硬件加速,这样就提高了性能。具体的技术细节,可以参考原文。canvas的转化基于React框架实现,FlipBoard 开发了一个专门的库React-canvas,已经开源。

这个方案引发了很多争议(这里这里),主要是canvas只是一个位图,本身没有语义,如果要在它上面实现UI,等于HTML语言已有的东西都要再发明一遍,比如如何实现超链接、如何实现CSS效果等等。一些最简单的东西都变得很麻烦,因为canvas不是自适应的(responsive),文字在哪里断行,都要自己计算,而且用户也无法选中文本。另外,怎么让搜索引擎检索网页,解决起来也不是很容易。

但是不管怎样,这是一个有意义的尝试。

四、未来的路

James Long对FlipBoard的尝试,写了一篇评论《Radical Statements about the Mobile Web》。本文就受到了那篇文章的启发。

在文中,James Long对未来的Web app提出了几点预测,我认为很值得分享。

(1)多线程浏览器。每个网页应该由多个线程进行处理,主线程只负责布局和渲染,而且应该在16毫秒内完成,JavaScript由worker线程执行,这样就不会发生堵塞了。Mozilla正在开发的Servo就是这样一个项目。

(2)DOM的异步操作。JavaScript对DOM的操作不再是同步的,而是触发后,交给Event Loop机制进行监听。

(3)非DOM方案。浏览器不再将网页处理成DOM结构,而是变为其他结构。React的Virtual DOM方案就是这一类的尝试,还有更激进的方案,比如用数据库取代DOM。

(完)

文档信息

2015年2月20日星期五

阮一峰的网络日志

阮一峰的网络日志


Make 命令教程

Posted: 20 Feb 2015 02:03 AM PST

代码变成可执行文件,叫做编译(compile);先编译这个,还是先编译那个(即编译的安排),叫做构建(build)。

Make是最常用的构建工具,诞生于1977年,主要用于C语言的项目。但是实际上 ,任何只要某个文件有变化,就要重新构建的项目,都可以用Make构建。

本文介绍Make命令的用法,从简单的讲起,不需要任何基础,只要会使用命令行,就能看懂。我的参考资料主要是Isaac Schlueter的《Makefile文件教程》《GNU Make手册》

(题图:摄于博兹贾阿达岛,土耳其,2013年7月)

一、Make的概念

Make这个词,英语的意思是"制作"。Make命令直接用了这个意思,就是要做出某个文件。比如,要做出文件a.txt,就可以执行下面的命令。

 $ make a.txt 

但是,如果你真的输入这条命令,它并不会起作用。因为Make命令本身并不知道,如何做出a.txt,需要有人告诉它,如何调用其他命令完成这个目标。

比如,假设文件 a.txt 依赖于 b.txt 和 c.txt ,是后面两个文件连接(cat命令)的产物。那么,make 需要知道下面的规则。

 a.txt: b.txt c.txt     cat b.txt c.txt > a.txt 

也就是说,make a.txt 这条命令的背后,实际上分成两步:第一步,确认 b.txt 和 c.txt 必须已经存在,第二步使用 cat 命令 将这个两个文件合并,输出为新文件。

像这样的规则,都写在一个叫做Makefile的文件中,Make命令依赖这个文件进行构建。Makefile文件也可以写为makefile, 或者用命令行参数指定为其他文件名。

 $ make -f rules.txt # 或者 $ make --file=rules.txt 

上面代码指定make命令依据rules.txt文件中的规则,进行构建。

总之,make只是一个根据指定的Shell命令进行构建的工具。它的规则很简单,你规定要构建哪个文件、它依赖哪些源文件,当那些文件有变动时,如何重新构建它。

二、Makefile文件的格式

构建规则都写在Makefile文件里面,要学会如何Make命令,就必须学会如何编写Makefile文件。

2.1 概述

Makefile文件由一系列规则(rules)构成。每条规则的形式如下。

 <target> : <prerequisites>  [tab]  <commands> 

上面第一行冒号前面的部分,叫做"目标"(target),冒号后面的部分叫做"前置条件"(prerequisites);第二行必须由一个tab键起首,后面跟着"命令"(commands)。

"目标"是必需的,不可省略;"前置条件"和"命令"都是可选的,但是两者之中必须至少存在一个。

每条规则就明确两件事:构建目标的前置条件是什么,以及如何构建。下面就详细讲解,每条规则的这三个组成部分。

2.2 目标(target)

一个目标(target)就构成一条规则。目标通常是文件名,指明Make命令所要构建的对象,比如上文的 a.txt 。目标可以是一个文件名,也可以是多个文件名,之间用空格分隔。

除了文件名,目标还可以是某个操作的名字,这称为"伪目标"(phony target)。

 clean:       rm *.o 

上面代码的目标是clean,它不是文件名,而是一个操作的名字,属于"伪目标 ",作用是删除对象文件。

 $ make  clean 

但是,如果当前目录中,正好有一个文件叫做clean,那么这个命令不会执行。因为Make发现clean文件已经存在,就认为没有必要重新构建了,就不会执行指定的rm命令。

为了避免这种情况,可以明确声明clean是"伪目标",写法如下。

 .PHONY: clean clean:         rm *.o temp 

声明clean是"伪目标"之后,make就不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。像.PHONY这样的内置目标名还有不少,可以查看手册

如果Make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。

 $ make 

上面代码执行Makefile文件的第一个目标。

2.3 前置条件(prerequisites)

前置条件通常是一组文件名,之间用空格分隔。它指定了"目标"是否重新构建的判断标准:只要有一个前置文件不存在,或者有过更新(前置文件的last-modification时间戳比目标的时间戳新),"目标"就需要重新构建。

 result.txt: source.txt     cp source.txt result.txt 

上面代码中,构建 result.txt 的前置条件是 source.txt 。如果当前目录中,source.txt 已经存在,那么make result.txt可以正常运行,否则必须再写一条规则,来生成 source.txt 。

 source.txt:     echo "this is the source" > source.txt 

上面代码中,source.txt后面没有前置条件,就意味着它跟其他文件都无关,只要这个文件还不存在,每次调用make source.txt,它都会生成。

 $ make result.txt $ make result.txt 

上面命令连续执行两次make result.txt。第一次执行会先新建 source.txt,然后再新建 result.txt。第二次执行,Make发现 source.txt 没有变动(时间戳晚于 result.txt),就不会执行任何操作,result.txt 也不会重新生成。

如果需要生成多个文件,往往采用下面的写法。

 source: file1 file2 file3 

上面代码中,source 是一个伪目标,只有三个前置文件,没有任何对应的命令。

 $ make source 

执行make source命令后,就会一次性生成 file1,file2,file3 三个文件。这比下面的写法要方便很多。

 $ make file1 $ make file2 $ make file3 

2.4 命令(commands)

命令(commands)表示如何更新目标文件,由一行或多行的Shell命令组成。它是构建"目标"的具体指令,它的运行结果通常就是生成目标文件。

每行命令之前必须有一个tab键。如果想用其他键,可以用内置变量.RECIPEPREFIX声明。

 .RECIPEPREFIX = > all: > echo Hello, world 

上面代码用.RECIPEPREFIX指定,大于号(>)替代tab键。所以,每一行命令的起首变成了大于号,而不是tab键。

需要注意的是,每行命令在一个单独的shell中执行。这些Shell之间没有继承关系。

 var-lost:     export foo=bar     echo "foo=[$$foo]" 

上面代码执行后(make var-lost),取不到foo的值。因为两行命令在两个不同的进程执行。一个解决办法是将两行命令写在一行,中间用分号分隔。

 var-kept:     export foo=bar; echo "foo=[$$foo]" 

另一个解决办法是在换行符前加反斜杠转义。

 var-kept:     export foo=bar; \     echo "foo=[$$foo]" 

最后一个方法是加上.ONESHELL:命令。

 .ONESHELL: var-kept:     export foo=bar;      echo "foo=[$$foo]" 

三、Makefile文件的语法

3.1 注释

井号(#)在Makefile中表示注释。

 # 这是注释 result.txt: source.txt     # 这是注释     cp source.txt result.txt # 这也是注释 

3.2 回声(echoing)

正常情况下,make会打印每条命令,然后再执行,这就叫做回声(echoing)。

 test:     # 这是测试 

执行上面的规则,会得到下面的结果。

 $ make test # 这是测试 

在命令的前面加上@,就可以关闭回声。

 test:     @# 这是测试 

现在再执行make test,就不会有任何输出。

由于在构建过程中,需要了解当前在执行哪条命令,所以通常只在注释和纯显示的echo命令前面加上@。

 test:     @# 这是测试     @echo TODO 

3.3 通配符

通配符(wildcard)用来指定一组符合条件的文件名。Makefile 的通配符与 Bash 一致,主要有星号(*)、问号(?)和 [...] 。比如, *.o 表示所有后缀名为o的文件。

 clean:         rm -f *.o 

3.4 模式匹配

Make命令允许对文件名,进行类似正则运算的匹配,主要用到的匹配符是%。比如,假定当前目录下有 f1.c 和 f2.c 两个源码文件,需要将它们编译为对应的对象文件。

 %.o: %.c 

等同于下面的写法。

 f1.o: f1.c f2.o: f2.c 

使用匹配符%,可以将大量同类型的文件,只用一条规则就完成构建。

3.5 变量和赋值符

Makefile 允许使用等号自定义变量。

 txt = Hello World test:     @echo $(txt) 

上面代码中,变量 txt 等于 Hello World。调用时,变量需要放在 $( ) 之中。

调用Shell变量,需要在美元符号前,再加一个美元符号,这是因为Make命令会对美元符号转义。

 test:     @echo $$HOME 

有时,变量的值可能指向另一个变量。

 v1 = $(v2) 

上面代码中,变量 v1 的值是另一个变量 v2。这时会产生一个问题,v1 的值到底在定义时扩展(静态扩展),还是在运行时扩展(动态扩展)?如果 v2 的值是动态的,这两种扩展方式的结果可能会差异很大。

为了解决类似问题,Makefile一共提供了四个赋值运算符 (=、:=、?=、+=),它们的区别请看StackOverflow

 VARIABLE = value # 在执行时扩展,允许递归扩展。  VARIABLE := value # 在定义时扩展。  VARIABLE ?= value # 只有在该变量为空时才设置值。  VARIABLE += value # 将值追加到变量的尾端。  

3.6 内置变量(Implicit Variables)

Make命令提供一系列内置变量,比如,$(CC) 指向当前使用的编译器,$(MAKE) 指向当前使用的Make工具。这主要是为了跨平台的兼容性,详细的内置变量清单见手册

 output:     $(CC) -o output input.c 

3.7 自动变量(Automatic Variables)

Make命令还提供一些自动变量,它们的值与当前规则有关。主要有以下几个。

(1)$@

$@指代当前目标,就是Make命令当前构建的那个目标。比如,make foo的 $@ 就指代foo。

 a.txt b.txt:      touch $@ 

等同于下面的写法。

 a.txt:     touch a.txt b.txt:     touch b.txt 

(2)$<

$< 指代第一个前置条件。比如,规则为 t: p1 p2,那么$< 就指代p1。

 a.txt: b.txt c.txt     cp $< $@  

等同于下面的写法。

 a.txt: b.txt c.txt     cp b.txt a.txt  

(3)$?

$? 指代比目标更新的所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,其中 p2 的时间戳比 t 新,$?就指代p2。

(4)$^

$^ 指代所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,那么 $^ 就指代 p1 p2 。

(5)$*

$* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1。

(6)$(@D) 和 $(@F)

$(@D) 和 $(@F) 分别指向 $@ 的目录名和文件名。比如,$@是 src/input.c,那么$(@D) 的值为 src ,$(@F) 的值为 input.c。

(7)$(<D) 和 $(<F)

$(<D) 和 $(<F) 分别指向 $< 的目录名和文件名。

所有的自动变量清单,请看手册。下面是自动变量的一个例子。

 dest/%.txt: src/%.txt     @[ -d dest ] || mkdir dest     cp $< $@ 

上面代码将 src 目录下的 txt 文件,拷贝到 dest 目录下。首先判断 dest 目录是否存在,如果不存在就新建,然后,$< 指代前置文件(src/%.txt), $@ 指代目标文件(dest/%.txt)。

3.8 判断和循环

Makefile使用 Bash 语法,完成判断和循环。

 ifeq ($(CC),gcc)   libs=$(libs_for_gcc) else   libs=$(normal_libs) endif 

上面代码判断当前编译器是否 gcc ,然后指定不同的库文件。

 LIST = one two three all:     for i in $(LIST); do \         echo $$i; \     done  # 等同于  all:     for i in one two three; do \         echo $i; \     done  

上面代码的运行结果。

 one two three 

3.9 函数

Makefile 还可以使用函数,格式如下。

 $(function arguments) # 或者 ${function arguments} 

Makefile提供了许多内置函数,可供调用。下面是几个常用的内置函数。

(1)shell 函数

shell 函数用来执行 shell 命令

 srcfiles := $(shell echo src/{00..99}.txt) 

(2)wildcard 函数

wildcard 函数用来在 Makefile 中,替换 Bash 的通配符。

 srcfiles := $(wildcard src/*.txt) 

(3)替换函数

替换函数的写法是:变量名 + 冒号 + 替换规则。

 min: $(OUTPUT:.js=.min.js) 

上面代码的意思是,将变量OUTPUT中的 .js 全部替换成 .min.js 。

四、Makefile 的实例

(1)执行多个目标

 .PHONY: cleanall cleanobj cleandiff  cleanall : cleanobj cleandiff         rm program  cleanobj :         rm *.o  cleandiff :         rm *.diff 

上面代码可以调用不同目标,删除不同后缀名的文件,也可以调用一个目标(cleanall),删除所有指定类型的文件。

(2)编译C语言项目

 edit : main.o kbd.o command.o display.o      cc -o edit main.o kbd.o command.o display.o  main.o : main.c defs.h     cc -c main.c kbd.o : kbd.c defs.h command.h     cc -c kbd.c command.o : command.c defs.h command.h     cc -c command.c display.o : display.c defs.h     cc -c display.c  clean :      rm edit main.o kbd.o command.o display.o  .PHONY: edit clean 

今天,Make命令的介绍就到这里。下一篇文章我会介绍,如何用 Make 来构建 Node.js 项目。

(完)

文档信息

2015年2月10日星期二

阮一峰的网络日志

阮一峰的网络日志


JavaScript 有多灵活?

Posted: 09 Feb 2015 10:53 PM PST

JavaScript 是一种灵活的语言,表达力极强,我来举一个例子,保证让很多人大吃一惊。

本文受到了 Kyle Simpson 的文章《Iterating ES6 Numbers》的启发。

首先,在 Number.prototype 对象上,部署一个 add 方法。

  Number.prototype.add = function (x) {    return this + x;  };  

上面代码为 Number 的实例定义了一个 add 方法。(如果你对这种写法不熟悉,建议先阅读我写的《JavaScript 面向对象编程》。)

由于 Number 的实例就是数值,在数值上调用某个方法,数值会自动转为实例对象,所以就得到了下面的结果。

  8['add'](2)  // 10  

上面代码中,调用方法之所以写成8['add'],而不是8.add,是因为数值后面的点,会被解释为小数点,而不是点运算符。

将数值放在圆括号中,就可以使用点运算符调用方法了。

  (8).add(2)  // 10  

其实,还有另一种写法。

  8..add(2)  // 10  

上面代码的第一个点解释为小数点,第二个点解释为点运算符。为了语义清晰起见,下面我统一采用圆括号的写法。

由于add方法返回的还是数值,所以可以链式运算。

  Number.prototype.subtract = function (x) {    return this - x;  };    (8).add(2).subtract(4)  // 6  

上面代码在Number对象的实例上部署了subtract方法,它可以与add方法链式调用。

如果使用方括号调用属性,写法会很古怪。

  8["add"](2)["subtract"](4)  // 6  

我们还可以部署更复杂的方法。

  Number.prototype.iterate = function () {    var result = [];    for (var i = 0; i <= this; i++) {      result.push(i);    }    return result;  };    (8).iterate()  // [0, 1, 2, 3, 4, 5, 6, 7, 8]  

上面代码在 Number 对象的原型上部署了 iterate 方法,可以将一个数值自动扩展为一个数组。

总之,现在我们可以在数值上直接调用方法了,但是后面一对圆括号看着有点碍眼,有没有可能去掉圆括号呢?也就是说,能不能将下面的表达式

  (8).double().square()  

写成另一种样子?

  (8).double.suqare  

这是可以做到的。

ES5规定,每个对象的属性都有一个取值方法get,用来自定义该属性的读取操作。

  Number.prototype = Object.defineProperty(    Number.prototype, "double", {      get: function (){return (this + this)}     }  );    Number.prototype =  Object.defineProperty(    Number.prototype, "square", {      get: function (){return (this * this)}     }  );  

上面代码在 Number.prototype 上定义了两个属性 double 和 square ,以及它们的取值方法 get 。

因此,在任一数值上,读取这两个属性,就可以写成下面的样子。

  (8).double.square  // 256  

也可以改用方括号运算符。

  8["double"]["square"]  // 256  

(完)

文档信息

2015年2月8日星期日

阮一峰的网络日志

阮一峰的网络日志


强类型 JavaScript 的解决方案

Posted: 07 Feb 2015 09:10 PM PST

JavaScript 是一种弱类型(或称动态类型)语言,即变量的类型是不确定的。

  x = 5; // 5  x = x + 'A'; // '5A'  

上面代码中,变量x起先是一个数值,后来是一个字符串,类型完全由当前的值决定,这就叫弱类型。

弱类型的好处是十分灵活,可以写出非常简洁的代码。但是,对于大型项目来说,强类型更有利,可以降低系统的复杂度,在编译时就发现类型错误,减轻程序员的负担。

一直有人尝试,让 JavaScript 变成强类型语言。在官方最终支持强类型之前,本文介绍三种现在就可用的解决方案。

(题图:摄于花莲,台湾,2012年6月)

一、TypeScript

TypeScript 是微软2012年推出的一种编程语言,属于 JavaScript 的超集,可以编译为 JavaScript 执行。 它的最大特点就是支持强类型和 ES6 Class

首先,安装TypeScript。

  $ npm install -g typescript  

然后,为变量指定类型。

  // greet.ts  function greet(person: string) {    console.log("Hello, " + person);  }    greet([0, 1, 2]);  

上面是文件 greet.ts 的代码,后缀名 ts 表明这是 TypeScript 的代码。函数 greet 的参数,声明类型为字符串,但在调用时,传入了一个数组。

使用 tsc 命令将 ts 文件编译为 js 文件,就会抛出类型不匹配的错误。

  $ tsc greeter.ts  greet.ts(5,9): error TS2345: Argument of type 'number[]'     is not assignable to parameter of type 'string'.  

二、Flowcheck

Flowcheck 是一个轻量级的类型断言库,可以在运行时(runtime)检查变量类型是否正确。

首先,安装Flowcheck。

  $ npm install -g flowcheck  

然后,编写一个声明了变量类型的脚本。

  function sum(a: number, b: number) {    return a + b;  }    sum('hello','world')  

接着,使用下面的命令,将脚本转换为正常的 JavaScript 文件。

  $ browserify -t flowcheck -t [reactify --strip-types] \  input.js -o output.js  

转换后的文件如下。

  var _f = require("flowcheck/assert");    function sum(a, b) {      _f.check(arguments, _f.arguments([_f.number, _f.number]));    return a + b;  }  

可以看到,代码中插入一个断言库。每次运行函数之前,会先执行断言,如果类型不符就报错。

  $ node output.js  // throw new TypeError(message);              ^  TypeError:     Expected an instance of number got "hello",     context: arguments / [number, number] / 0    Expected an instance of number got "world",    context: arguments / [number, number] / 1  

三、Flow

Flow 是 Facebook 在2014年发布的一个类型检查工具,用来检查 React 的源码。

安装命令如下。

  $ npm install --global flow-bin  

如果安装不成功(我就是如此),就需要自己从源码编译了。

Flow 的用法很多,我只举几个例子。前文介绍的两种工具,只能检查声明了类型的变量,而 Flow 可以推断变量类型。

  // hello.js  /* @flow */  function foo(x) {    return x*10;  }  foo("Hello, world!");  

上面是文件 hello.js ,该文件的第一行是注释,表明需要使用 Flow 检查变量类型。

  $ flow check  hello.js:7:5,19: string  This type is incompatible with  /hello.js:4:10,13: number  

运行 flow check 命令,得到报错信息:预期函数 foo 的参数是一个数值,但是实际为一个字符串。

Flow 也支持变量的类型声明。

  /* @flow */  function foo(x: string, y: number): string {    return x.length * y;  }  foo("Hello", 42);  

另一个有趣的功能是,Flow 可以将类型注释(annotation),转为类型声明。

  // annotation.js  /**    @param {number} x    @return {number}   */  function square(x) {    return x * x;  }  square(5);  

运行 flow port 命令,会得到下面的结果。

  $ flow port annotation.js  function square(x: number) : number {     return x * x;   }  

Flow 的更多介绍,可以阅读《Exploring Flow, Facebook's Type Checker for JavaScript》

本文的原始幻灯片点击这里(里面有更多内容)。

(完)

文档信息

2015年2月3日星期二

阮一峰的网络日志

阮一峰的网络日志


图灵访谈

Posted: 02 Feb 2015 07:47 PM PST

上周,我接受了图灵教育访谈,谈了自己的人生经历。

我的目的是卖书,如果你还没买《如何变得有思想》,请考虑一下吧。只有出版社赚到钱,我才有机会出下一本。

下面是书面访谈的原文。

================================================

Q - 图灵,A - 阮一峰

Q: 请介绍一下自己。

A: 我是70后,在上海出生和长大,大学糊里糊涂读了经济学。工作了几年,又去读了世界经济的研究生,毕业后,在上海一所本地高校当了老师,教财经类的课程。最近,去了支付宝的前端团队,在玉伯负责的"体验技术部"工作,目前主要从事JavaScript和Node.js的开发。

我翻译了《软件随想录》《黑客与画家》,出版了技术专著《ECMAScript 6入门》和博客文集《如何变得有思想》

Q: 你是怎么接触到互联网的?

A: 2000年前后,我第一次上网,一下子就着迷了。那时候,只有图书馆的机房能上网,除了吃饭,我从早上8点开馆一直待到晚上10点闭馆,只做三件事:在化云坊fanso.com听歌,在chinaren.com下棋,在水木清华BBS灌水。

Q: 你是怎么开始学习计算机的?

A: 我对计算机一直有兴趣,但从没想过去编程。因为我是文科生,学校里只教Word和Excel,对计算机没概念。大学快毕业了,为了打游戏和上网,才买了第一台个人电脑,CPU是奔腾133MHz。研究生时,我想考计算机二级C语言,买了指定教材,但发现看不懂。

那个时候是互联网泡沫,很容易申请免费的个人主页空间。我在网易(yeah.net)和亿唐(etang.com)申请了个人主页,从此开始接触到网页制作。中国青年出版社引进的台湾"旗标计算机丛书",对我帮助很大。那套书图文并茂,通俗易懂,我从那里学会了最简单的HTML语言。

Q: 你后来怎么学会编程?

A: 学了HTML以后,发现网页要做得漂亮,必须懂CSS。学了CSS以后,发现制作动态网页,必须懂一门计算机语言,就去学了asp。很快发现,asp不如php功能强,就去学了php和数据库MySQL。用了开源数据库,就离不开了Linux系统了。这个时候,又重新去学C语言,这一次学会了。

Q: 你是怎么开始写博客的?

A: 2003年以前,我制作网页都是使用微软的FrontPage ,需要一张张地制作,非常麻烦。后来听说博客软件可以动态生成网页、更换样式、站内搜索,就很动心。

我最早使用的博客软件是asp语言写的,忘了叫什么名字。2003年底,我看到了王建硕的博客,非常震惊,原来博客可以玩得这么精彩。他用的软件是MovableType,我照他的样子搭建了一个,一直用到现在。遗憾的是,王建硕已经换成WordPress了,MovableType也停止更新了,国内还用这个软件的人屈指可数。互联网时代就是这样,一切变得太快了,我觉得自己是个怀旧的人。

Q: 你的博客是怎么出名的?

A: 我其实不知道,一开始我只把博客当作私人园地,并没有想到有人会来看。但是,不知道是否错觉,Google对于MovableType生成的网页,会给很高的权重。我的很多文章排在Google的第一页,很多人这样发现了我的博客。

我的文风可能也有点帮助。我喜欢用口语,喜欢用短句,别人比较容易看懂。另外,比我水平更高、写得更好的人,很多都放弃写博客了,只有我还在写。所以,我的体会是,如果你认认真真写,认认真真排版,保证别人可以看懂,还时不时插入一些漂亮的插图,或者一段趣闻轶事,你坚持这么做,就可以出名。

Q: 你的博客早期都是谈文学的,怎么会转变为一个技术博客?

A: 大学里,我喜欢读小说和传记,曾经以为社会科学是自己一辈子的专业方向。后来发现,在中国搞社会科学没前途,除非愿意给政策背书。那时,我还喜欢写社会和政治评论,每次都有一大堆互相争吵、人身攻击的留言,无法得到任何共识。我还接到过有关部门的电话,要求删除文章

最终,我觉得文学、政治学、经济学、社会学之类的学科,都没多大用处,就渐渐不想走这条路了。它们也不是真的没用,就是在中国不行,改变不了现实,只会让自己走入绝境。在我看来,走技术这条路至少有一个好处。我曾经在一篇文章里写过:"(在国内,)如果你想不撒谎、不干坏事、并且被公正地对待,那么可能你只能去编程了。"

Q: 很多人批评你的技术文章,错误非常多,你怎么看?

A: 我一直是外行,从来不敢说自己是专家。对我来说,博客首先是一种知识管理工具,其次才是传播工具。我的技术文章,主要用来整理我还不懂的知识。我只写那些我还没有完全掌握的东西,那些我精通的东西,往往没有动力写。炫耀从来不是我的动机,好奇才是。

当然,我肯定只写那些我觉得对的东西,尽量对读者负责。但是没法保证,我觉得对的东西就是对的。网站流量越来越大,对我的争议也越来越多,好几次我都在文章前加上说明,这是初学者的笔记。我还会以这种方式写下去。我希望自己永远都能保持,那种不怕丢面子,敢于当众说蠢话的勇气。

Q: 你近期在忙什么?

A: 最近一两年,我的关注重点都将是Node(io.js)。我觉得,Node很适合作为前后端之间的中间层。我希望用Node、PostgreSQL、MongoDB、Nignx实现一个网站,会是一个开源项目。

如果有时间,我很希望能学一下Go语言,用它来写后端实现。我也很想学函数式编程,它带来了全新的思考方式,而且非常优雅。

[说明] 摄于艾西拉,摩洛哥,2014年8月

(完)

文档信息