2016年1月26日星期二

阮一峰的网络日志

阮一峰的网络日志


Babel 入门教程

Posted: 25 Jan 2016 05:33 AM PST

(说明:本文选自我的新书《ES6 标准入门(第二版)》的第一章《ECMAScript 6简介》

Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。

这意味着,你可以现在就用ES6编写程序,而不用担心现有环境是否支持。下面是一个例子。

 // 转码前 input.map(item => item + 1);  // 转码后 input.map(function (item) {   return item + 1; }); 

上面的原始代码用了箭头函数,这个特性还没有得到广泛支持,Babel将其转为普通函数,就能在现有的JavaScript环境执行了。

一、配置文件.babelrc

Babel的配置文件是.babelrc,存放在项目的根目录下。使用Babel的第一步,就是配置这个文件。

该文件用来设置转码规则和插件,基本格式如下。

 {   "presets": [],   "plugins": [] } 

presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。

 # ES2015转码规则 $ npm install --save-dev babel-preset-es2015  # react转码规则 $ npm install --save-dev babel-preset-react  # ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个 $ npm install --save-dev babel-preset-stage-0 $ npm install --save-dev babel-preset-stage-1 $ npm install --save-dev babel-preset-stage-2 $ npm install --save-dev babel-preset-stage-3 

然后,将这些规则加入.babelrc

   {     "presets": [       "es2015",       "react",       "stage-2"     ],     "plugins": []   } 

注意,以下所有Babel工具和模块的使用,都必须先写好.babelrc

二、命令行转码babel-cli

Babel提供babel-cli工具,用于命令行转码。

它的安装命令如下。

 $ npm install --global babel-cli 

基本用法如下。

 # 转码结果输出到标准输出 $ babel example.js  # 转码结果写入一个文件 # --out-file 或 -o 参数指定输出文件 $ babel example.js --out-file compiled.js # 或者 $ babel example.js -o compiled.js  # 整个目录转码 # --out-dir 或 -d 参数指定输出目录 $ babel src --out-dir lib # 或者 $ babel src -d lib  # -s 参数生成source map文件 $ babel src -d lib -s 

上面代码是在全局环境下,进行Babel转码。这意味着,如果项目要运行,全局环境必须有Babel,也就是说项目产生了对环境的依赖。另一方面,这样做也无法支持不同项目使用不同版本的Babel。

一个解决办法是将babel-cli安装在项目之中。

 # 安装 $ npm install --save-dev babel-cli 

然后,改写package.json

 {   // ...   "devDependencies": {     "babel-cli": "^6.0.0"   },   "scripts": {     "build": "babel src -d lib"   }, } 

转码的时候,就执行下面的命令。

 $ npm run build 

三、babel-node

babel-cli工具自带一个babel-node命令,提供一个支持ES6的REPL环境。它支持Node的REPL环境的所有功能,而且可以直接运行ES6代码。

它不用单独安装,而是随babel-cli一起安装。然后,执行babel-node就进入PEPL环境。

 $ babel-node > (x => x * 2)(1) 2 

babel-node命令可以直接运行ES6脚本。将上面的代码放入脚本文件es6.js,然后直接运行。

 $ babel-node es6.js 2 

babel-node也可以安装在项目中。

 $ npm install --save-dev babel-cli 

然后,改写package.json

 {   "scripts": {     "script-name": "babel-node script.js"   } } 

上面代码中,使用babel-node替代node,这样script.js本身就不用做任何转码处理。

四、babel-register

babel-register模块改写require命令,为它加上一个钩子。此后,每当使用require加载.js.jsx.es.es6后缀名的文件,就会先用Babel进行转码。

 $ npm install --save-dev babel-register 

使用时,必须首先加载babel-register

 require("babel-register"); require("./index.js"); 

然后,就不需要手动对index.js转码了。

需要注意的是,babel-register只会对require命令加载的文件转码,而不会对当前文件转码。另外,由于它是实时转码,所以只适合在开发环境使用。

五、babel-core

如果某些代码需要调用Babel的API进行转码,就要使用babel-core模块。

安装命令如下。

 $ npm install babel-core --save 

然后,在项目中就可以调用babel-core

 var babel = require('babel-core');  // 字符串转码 babel.transform('code();', options); // => { code, map, ast }  // 文件转码(异步) babel.transformFile('filename.js', options, function(err, result) {   result; // => { code, map, ast } });  // 文件转码(同步) babel.transformFileSync('filename.js', options); // => { code, map, ast }  // Babel AST转码 babel.transformFromAst(ast, code, options); // => { code, map, ast } 

配置对象options,可以参看官方文档http://babeljs.io/docs/usage/options/

下面是一个例子。

 var es5Code = 'let x = n => n + 1'; var es6Code = require('babel-core')   .transform(es5Code, {     presets: ['es2015']   })   .code; // '"use strict";\n\nvar x = function x(n) {\n  return n + 1;\n};' 

上面代码中,transform方法的第一个参数是一个字符串,表示需要转换的ES5代码,第二个参数是转换的配置对象。

六、babel-polyfill

Babel默认只转换新的JavaScript句法(syntax),而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。

举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。

安装命令如下。

 $ npm install --save babel-polyfill 

然后,在脚本头部,加入如下一行代码。

 import 'babel-polyfill'; // 或者 require('babel-polyfill'); 

Babel默认不转码的API非常多,详细清单可以查看babel-plugin-transform-runtime模块的definitions.js文件。

七、浏览器环境

Babel也可以用于浏览器环境。但是,从Babel 6.0开始,不再直接提供浏览器版本,而是要用构建工具构建出来。如果你没有或不想使用构建工具,可以通过安装5.x版本的babel-core模块获取。

 $ npm install babel-core@5 

运行上面的命令以后,就可以在当前目录的node_modules/babel-core/子目录里面,找到babel的浏览器版本browser.js(未精简)和browser.min.js(已精简)。

然后,将下面的代码插入网页。

 <script src="node_modules/babel-core/browser.js"></script> <script type="text/babel"> // Your ES6 code </script> 

上面代码中,browser.js是Babel提供的转换器脚本,可以在浏览器运行。用户的ES6脚本放在script标签之中,但是要注明type="text/babel"

另一种方法是使用babel-standalone模块提供的浏览器版本,将其插入网页。

 <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.4.4/babel.min.js"></script> <script type="text/babel"> // Your ES6 code </script> 

注意,网页中实时将ES6代码转为ES5,对性能会有影响。生产环境需要加载已经转码完成的脚本。

下面是如何将代码打包成浏览器可以使用的脚本,以Babel配合Browserify为例。首先,安装babelify模块。

 $ npm install --save-dev babelify babel-preset-es2015 

然后,再用命令行转换ES6脚本。

 $  browserify script.js -o bundle.js \   -t [ babelify --presets [ es2015 react ] ] 

上面代码将ES6脚本script.js,转为bundle.js,浏览器直接加载后者就可以了。

package.json设置下面的代码,就不用每次命令行都输入参数了。

 {   "browserify": {     "transform": [["babelify", { "presets": ["es2015"] }]]   } } 

八、在线转换

Babel提供一个REPL在线编译器,可以在线将ES6代码转为ES5代码。转换后的代码,可以直接作为ES5代码插入网页运行。

九、与其他工具的配合

许多工具需要Babel进行前置转码,这里举两个例子:ESLint和Mocha。

ESLint 用于静态检查代码的语法和风格,安装命令如下。

 $ npm install --save-dev eslint babel-eslint 

然后,在项目根目录下,新建一个配置文件.eslint,在其中加入parser字段。

 {   "parser": "babel-eslint",   "rules": {     ...   } } 

再在package.json之中,加入相应的scripts脚本。

   {     "name": "my-module",     "scripts": {       "lint": "eslint my-files.js"     },     "devDependencies": {       "babel-eslint": "...",       "eslint": "..."     }   } 

Mocha 则是一个测试框架,如果需要执行使用ES6语法的测试脚本,可以修改package.jsonscripts.test

 "scripts": {   "test": "mocha --ui qunit --compilers js:babel-core/register" } 

上面命令中,--compilers参数指定脚本的转码器,规定后缀名为js的文件,都需要使用babel-core/register先转码。

(完)

文档信息

2016年1月22日星期五

阮一峰的网络日志

阮一峰的网络日志


白领的消亡

Posted: 21 Jan 2016 05:14 PM PST

(说明:本文原载2016年第三期《财新周刊》

新年伊始,咨询公司埃森哲(Accenture)公布了一项调查

他们找到17个行业的1700个白领人士,问了同样的一个问题。

"你觉得计算机对你构成威胁吗?"

结果令人震惊。35%的人回答Yes。他们觉得在未来,机器可以自动完成他们现在的工作,因此职位可能保不住。最焦虑的就是科技行业的白领,回答Yes的人高达50%,其次是银行业,比例是49%。

一位评论家一针见血地指出,技术革命进入新阶段。以前,消失的是依靠体力的职位,现在就连一部分依靠智力的职位也在消失。

"以前,技术革命只是对蓝领工人不利,贸易全球化和自动化技术使得低技能工人,失去工作或者一直拿着低工资。而现在,技术革命开始威胁那些有技能的人了,人工智能、大数据、办公自动化正在快速地消灭办公室职位。"

白领,一般是指那些有技能的人士,比如管理人员、财务人员、金融业者、律师等等,劳动主要以智力投入为主。通常需要坐在办公室里,衣着整洁,穿白衬衫,所以叫做白领。

以前,白领是令人羡慕的工作,家长期待自己的孩子成为白领。可是,信息技术高速发展,机器的判断能力和处理能力,使得很多办公室职位变得不必要了。

美国曾经有一种工作,叫做"税务顾问"(tax consultant)。因为税法非常复杂,普通人根本搞不清楚,所以你会请他帮你报税。这样就不用自己填写复杂的表格,而且他还会告诉你各种节税诀窍。可是,现在有报税网站和软件,你只要在电脑前回答几个问题,电脑就会告诉你应该如何报税,简单、快速又便宜。那些税务顾问发现,自己没法与软件竞争,只能纷纷转业。这个职业在美国已经开始消失了。

我再举一个更常见的例子。银行职员(比如柜台操作员)以前是一份可靠的工作,有稳定的薪水可以养家。现在不是了。如今,你去银行存款或者取款,会找柜台人员吗?不会,大多数时候你直接去ATM机。24小时服务的ATM,正在取代一天工作8小时的柜台人员。

如果你说,柜台操作员不算严格意义的白领,那么信贷员算不算?银行都有一个信贷部门,这是银行利润的关键来源。信贷员负责寻找贷款对象和审核贷款,业绩往往与贷款挂钩,如果做得好,收入非常可观。

可是,现在有了"自动贷款",比如淘宝的"花呗"业务,它会根据消费者的购买历史、信用记录和支付能力,自动计算出每个人不一样的贷款额度。你只要点一下同意按钮,贷款一秒钟就到帐。整个过程完全自动化,根本不需要信贷员参与。将来每个人、每家企业的数据都储存在数据库里,计算机自动评估能不能向你贷款、可以贷多少,那么谁还需要信贷员?

如果仔细考察,你会发现很多银行职位都有消失的危险,比如风险控制、信用记录、外汇交易等等,软件都可以完成。对于现在这些职位上的白领人员来说,这是非常可怕的压力。

未来什么工作才能算白领?说实话,没有人知道。因为很难估计技术会发展到什么地步。厉以宁教授最近说,将来没有白领和蓝领之分。

"以后很多工作会由机器人去做,所以蓝领和白领的界限将来会逐步消失。当人们都在计算机边上的时候,你能说谁是白领、谁是蓝领吗?说不出来的,这个界限在逐渐消失,可能十年、二十年以后就没有了。大家都在运用计算机操纵机器人。"

如果你的职位有可能被计算机取代,一个现实的问题是,你该怎么办?跟机器竞争是不可能的,你没它可靠,没它耐劳,没它便宜。

许多人说,可以接受培训,学习新的技能,实现人生转型。这实际上很难做到。比如,现在很热门的一种职位,叫做"数据科学家"或者"数据工程师"。但是,一个装配线工人,不太可能经过几个月的培训,就转变为一个数据工程师。让我这么说吧,不仅他不太可能,你也不太可能。任何没有专业基础的人转变为数据从业人员的机会,就是四个字:"微乎其微"。

技术革命对人类社会的形态,已经产生了深刻的改变。从上个世纪90年代开始,低技能劳动者的报酬一直无法提高。现在,轮到白领阶层了。他们已经或者即将发现,自己处于挣扎之中,没有职业前景,工作报酬同蓝领工人一样陷入泥潭,无法提高。对于整个社会来说,技术造成的贫富差距将日益严重。这种趋势已经在世界许多国家出现,政府完全束手无策。

那么,有没有计算机不能取代的工作,所需要的技能是计算机无法学会的?答案是有的。吴晓波把难以被机器替代的能力,称为"柔软的能力"。他总结出四种。

(1)个性化服务能力。软件都是统一的算法,个性化服务很难做到。

(2)人格魅力的能力。机器提供的服务,不会有人格魅力,也不会感动人心。

(3)创意的能力。计算机最难以与人类竞争的,就是创造力。

(4)决策领导的能力(即企业家的能力)。机器没有办法团结领导一群人,齐心协力完成一个使命。

作为个人来说,人生规划的时候,应该尽量发展这些能力,才能避免与机器"抢工作"。

(完)

文档信息

npm 模块安装机制简介

Posted: 20 Jan 2016 05:42 PM PST

npm 是 Node 的模块管理器,功能极其强大。它是 Node 获得成功的重要原因之一。

正因为有了npm,我们只要一行命令,就能安装别人写好的模块 。

 $ npm install  

本文介绍 npm 模块安装机制的细节,以及如何解决安装速度慢的问题。

一、从 npm install 说起

npm install 命令用来安装模块到node_modules目录。

 $ npm install <packageName> 

安装之前,npm install会先检查,node_modules目录之中是否已经存在指定模块。如果存在,就不再重新安装了,即使远程仓库已经有了一个新版本,也是如此。

如果你希望,一个模块不管是否安装过,npm 都要强制重新安装,可以使用-f--force参数。

 $ npm install <packageName> --force 

二、npm update

如果想更新已安装模块,就要用到npm update命令。

 $ npm update <packageName> 

它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。

三、registry

npm update命令怎么知道每个模块的最新版本呢?

答案是 npm 模块仓库提供了一个查询服务,叫做 registry 。以 npmjs.org 为例,它的查询服务网址是 https://registry.npmjs.org/

这个网址后面跟上模块名,就会得到一个 JSON 对象,里面是该模块所有版本的信息。比如,访问 https://registry.npmjs.org/react,就会看到 react 模块所有版本的信息。

它跟下面命令的效果是一样的。

 $ npm view react  # npm view 的别名 $ npm info react $ npm show react $ npm v react 

registry 网址的模块名后面,还可以跟上版本号或者标签,用来查询某个具体版本的信息。比如, 访问 https://registry.npmjs.org/react/v0.14.6 ,就可以看到 React 的 0.14.6 版。

返回的 JSON 对象里面,有一个dist.tarball属性,是该版本压缩包的网址。

 dist: {   shasum: '2a57c2cf8747b483759ad8de0fa47fb0c5cf5c6a',   tarball: 'http://registry.npmjs.org/react/-/react-0.14.6.tgz'  }, 

到这个网址下载压缩包,在本地解压,就得到了模块的源码。npm installnpm update命令,都是通过这种方式安装模块的。

四、缓存目录

npm installnpm update命令,从 registry 下载压缩包之后,都存放在本地的缓存目录。

这个缓存目录,在 Linux 或 Mac 默认是用户主目录下的.npm目录,在 Windows 默认是%AppData%/npm-cache。通过配置命令,可以查看这个目录的具体位置。

 $ npm config get cache $HOME/.npm 

你最好浏览一下这个目录。

 $ ls ~/.npm  # 或者 $ npm cache ls 

你会看到里面存放着大量的模块,储存结构是{cache}/{name}/{version}

 $ npm cache ls react ~/.npm/react/react/0.14.6/ ~/.npm/react/react/0.14.6/package.tgz ~/.npm/react/react/0.14.6/package/ ~/.npm/react/react/0.14.6/package/package.json 

每个模块的每个版本,都有一个自己的子目录,里面是代码的压缩包package.tgz文件,以及一个描述文件package/package.json

除此之外,还会生成一个{cache}/{hostname}/{path}/.cache.json文件。比如,从 npm 官方仓库下载 react 模块的时候,就会生成registry.npmjs.org/react/.cache.json文件。

这个文件保存的是,所有版本的信息,以及该模块最近修改的时间和最新一次请求时服务器返回的 ETag 。

 {   "time":{     "modified":"2016-01-06T23:52:45.571Z",     // ...   },   "_etag":"\"7S37I0775YLURCFIO8N85FO0F\"" } 

对于一些不是很关键的操作(比如npm searchnpm view),npm会先查看.cache.json里面的模块最近更新时间,跟当前时间的差距,是不是在可接受的范围之内。如果是的,就不再向远程仓库发出请求,而是直接返回.cache.json的数据。

.npm目录保存着大量文件,清空它的命令如下。

 $ rm -rf ~/.npm/* # 或者 $ npm cache clean 

五、模块的安装过程

总结一下,Node模块的安装过程是这样的。

  1. 发出npm install命令
  2. npm 向 registry 查询模块压缩包的网址
  3. 下载压缩包,存放在~/.npm目录
  4. 解压压缩包到当前项目的node_modules目录

注意,一个模块安装以后,本地其实保存了两份。一份是~/.npm目录下的压缩包,另一份是node_modules目录下解压后的代码。

但是,运行npm install的时候,只会检查node_modules目录,而不会检查~/.npm目录。也就是说,如果一个模块在~/.npm下有压缩包,但是没有安装在node_modules目录中,npm 依然会从远程仓库下载一次新的压缩包。

这种行为固然可以保证总是取得最新的代码,但有时并不是我们想要的。最大的问题是,它会极大地影响安装速度。即使某个模块的压缩包就在缓存目录中,也要去远程仓库下载,这怎么可能不慢呢?

另外,有些场合没有网络(比如飞机上),但是你想安装的模块,明明就在缓存目录之中,这时也无法安装。

六、--cache-min 参数

为了解决这些问题,npm 提供了一个--cache-min参数,用于从缓存目录安装模块。

--cache-min参数指定一个时间(单位为分钟),只有超过这个时间的模块,才会从 registry 下载。

 $ npm install --cache-min 9999999 <package-name> 

上面命令指定,只有超过999999分钟的模块,才从 registry 下载。实际上就是指定,所有模块都从缓存安装,这样就大大加快了下载速度。

它还有另一种写法。

 $ npm install --cache-min Infinity <package-name> 

但是,这并不等于离线模式,这时仍然需要网络连接。因为现在的--cache-min实现有一些问题。

(1)如果指定模块不在缓存目录,那么 npm 会连接 registry,下载最新版本。这没有问题,但是如果指定模块在缓存目录之中,npm 也会连接 registry,发出指定模块的 etag ,服务器返回状态码304,表示不需要重新下载压缩包。

(2)如果某个模块已经在缓存之中,但是版本低于要求,npm会直接报错,而不是去 registry 下载最新版本。

npm 团队知道存在这些问题,正在重写 cache。并且,将来会提供一个--offline参数,使得 npm 可以在离线情况下使用。

不过,这些改进没有日程表。所以,当前使用--cache-min改进安装速度,是有问题的。

七、离线安装的解决方案

社区已经为npm的离线使用,提出了几种解决方案。它们可以大大加快模块安装的速度。

解决方案大致分成三类。

第一类,Registry 代理。

上面三个模块的用法很类似,都是在本机起一个 Registry 服务,所有npm install命令都要通过这个服务代理。

 # npm-proxy-cache $ npm --proxy http://localhost:8080 \   --https-proxy http://localhost:8080 \   --strict-ssl false \   install  # local-npm $ npm set registry http://127.0.0.1:5080  # npm-lazy $ npm --registry http://localhost:8080/ install socket.io 

有了本机的Registry服务,就能完全实现缓存安装,可以实现离线使用。

第二类,npm install替代。

如果能够改变npm install的行为,就能实现缓存安装。npm-cache 工具就是这个思路。凡是使用npm install的地方,都可以使用npm-cache替代。

 $ npm-cache install 

第三类,node_modules作为缓存目录。

这个方案的思路是,不使用.npm缓存,而是使用项目的node_modules目录作为缓存。

上面两个工具,都能将项目的node_modules目录打成一个压缩包,以后安装的时候,就从这个压缩包之中取出文件。

(完)

文档信息

2016年1月18日星期一

阮一峰的网络日志

阮一峰的网络日志


《ES6 标准入门》(第二版)出版了

Posted: 18 Jan 2016 04:41 AM PST

如果你经常看这个博客,就会知道我写了一本书。

现在第二版问世了,书名为《ES6 标准入门》

购买链接如下。全部是现货。现在下单,最快明天就能拿到。

老读者都知道,这本书是开源的。你可以先看看,再决定买不买。出版社有利润压力,但还是支持我开源。希望不会影响销量,如果最后亏钱,就对不起朋友了。

这本书的质量,我很有信心。市场上唯一的ES6出版物,可能也是最好的 JavaScript 进阶教程。所有语法点都讲解了,尽量做到深入浅出。

Github 上已经有了 2000+ 颗星,每一行代码都被无数读者验证过。

2015年,我只做成了一件事,就是写完这本书(第二版)。想想有些伤感,一年的起起伏伏,到头来只是一本书稿。

写书是一件寂寞的事,仿佛一个人坐在电脑前的漫漫长征。现在,我终于可以把它交给你了。你拿在手中的,就是我的这一年,所有的诚意和坚持。

JavaScript 这门语言会有远大未来,但愿我的这本书、以及你,我亲爱的读者,也都是如此。

(完)

文档信息

2016年1月17日星期日

阮一峰的网络日志

阮一峰的网络日志


更多的人死于心碎

Posted: 16 Jan 2016 07:38 PM PST

(说明:本文原载2016年第二期《财新周刊》)

2015年12月28日中午,Debian操作系统的创始人Ian Murdock,在推特上发布了一条简短的消息。

"我将在今晚自杀,请不要打扰,我要抓紧时间说一些事情,不让它们跟我一起走。"

没过多久,他就删除了这条消息,并且关闭了推特账户。大家猜测纷纷,不知道是真是假。第二天,他的死讯被确认。

对于大多数人来说,Debian是一个陌生的名字,但是在程序员之中,它非常有名,国际空间站就使用它。这是一种计算机操作系统,类似于Windows,但是一般只用于服务器。所以,普通用户接触不到,只有企业级用户和专业程序员才会使用。

你可以想象一下,有一样东西你每天使用,突然某天清晨传来消息,它的发明人非正常死亡,你会是什么感受,内心会不会震动?这就是整个IT业界听到这个消息的反应,尤其是死者只有42岁,实在太年轻了。

1973年,Ian Murdock生于西德,后来随父母移民美国。他从小就对计算机感兴趣,9岁开始编程。1993年8月,在普度大学读大二时,他接触到了芬兰大学生林奈斯的Linux项目,顿时产生了兴趣。Linux是操作系统的内核,就好比汽车的发动机,虽然重要,但是本身并不足以使用,必须配合各种零件,装配成一辆汽车以后才能行驶。Ian Murdock想做的就是,将Linux装配成一辆全功能的汽车。

经过几个月的努力,他在1993年底发布了Debian的最初版本。这个名字是女友的名字Deb和他自己名字的结合。当时的计算机社区,急需一个新的操作系统,很多人看到以后,就对这个项目产生了兴趣,纷纷试用和加入。

跟Windows不一样的是,Debian完全免费,可以任意使用。因为Ian Murdock相信免费软件可以帮助到更多需要的人,所以采用了一种叫做GPL的软件授权协议。根据这种协议,只要满足一个条件,你怎么使用都可以,那就是一旦你修改了软件,就必须把你的修改,也同样免费地公开出来。这个条件防止了有人将他人的免费成果占为己有。

随着加入者越来越多,以及GPL协议的保障,Debian项目的影响力越变越大,逐渐成为两种主流的基于Linux的操作系统之一。后来,又得到自由软件基金会的赞助,在全世界获得广泛采用。

1996年,Debian发布0.93版以后,Ian Murdock就不再参与开发了,把领导权交给了其他人。他成立了一家创业公司,担任CTO。2007年,他加入SUN公司担任副总裁,直到2011年。然后,又在不同的硅谷公司任职,直到去世为止。

由于家人要求保护隐私,他的死因没有公布,外界不得而知。现在只知道,他在早先的推特上曾经提到过,与警察发生了冲突,决心抗争到底。旧金山警方则说,有人报警他醉酒后企图闯入民宅,警方赶到制止时,他与警察发生暴力冲突,导致被拘留。警方没有公布,他到底是不是自杀。

Ian Murdock的性格内向,采访和报道都很少。1996年,他与大学时代的女友Debra Lynn(就是Debian这个名字的另一半)结婚,但是在2007年离婚,没有子女。

因为Ian Murdock的死,很多人提到了另一个计算机天才Phil Katz。他是ZIP压缩算法的发明人,2000年4月14日,他被发现孤独一人死在一家小旅馆的桌子旁边,怀里抱着一个空的酒瓶,死因是长期酗酒导致的胰脏严重内出血。此时,他与家人已经长期断绝关系,与自己创立的公司的员工也很久没联系了。他死时只有37岁。

奥地利小说家茨威格的最后一篇小说《象棋的故事》,描述过一个令人心碎的故事。一个普通人关进纳粹监狱,身边只有一本象棋棋谱。漫长的关押和惊恐绝望之中,他只能靠背棋谱,转移一些注意力。由于长期地自己跟自己下棋,他几乎陷入精神分裂和疯狂。等到后来放出监狱,他的棋艺竟然可以战胜世界冠军,但是他对象棋乃至人生,已经毫无兴趣了。茨威格用这篇小说比喻自己的人生,写完后五个月,他就自杀了。

Ian Murdock以及许多计算机天才,就像《象棋的故事》的这位主人公。他们精通计算机,但是个人生活失去了平衡。除了计算机以外,没有人可以对话,生活也失去了意义,最终走上了绝路。

我们这个计算机时代,让人可以不依赖社交,只靠机器就能满足生活的基本需求。生活因此变得方便,但也不可避免地变得更孤独。索尔·贝娄有一部长篇小说,名字叫做《更多的人死于心碎》,这大概就是Ian Murdock真正的死因。

(完)

文档信息

2016年1月15日星期五

阮一峰的网络日志

阮一峰的网络日志


Flux 架构入门教程

Posted: 14 Jan 2016 06:03 PM PST

过去一年中,前端技术大发展,最耀眼的明星就是React

React 本身只涉及UI层,如果搭建大型应用,必须搭配一个前端框架。也就是说,你至少要学两样东西,才能基本满足需要:React + 前端框架。

Facebook官方使用的是 Flux 框架。本文就介绍如何在 React 的基础上,使用 Flux 组织代码和安排内部逻辑,使得你的应用更易于开发和维护。

阅读本文之前,我假设你已经掌握了 React 。如果还没有,可以先看我写的《React入门教程》。与以前一样,本文的目标是使用最简单的语言、最好懂的例子,让你一看就会。

一、Flux 是什么?

简单说,Flux 是一种架构思想,专门解决软件的结构问题。它跟MVC 架构是同一类东西,但是更加简单和清晰

Flux存在多种实现(至少15种),本文采用的是Facebook官方实现

二、安装 Demo

为了便于讲解,我写了一个Demo

请先安装一下。

  $ git clone https://github.com/ruanyf/extremely-simple-flux-demo.git  $ cd extremely-simple-flux-demo && npm install  $ npm start  

然后,访问 http://127.0.0.1:8080 。

你会看到一个按钮。这就是我们的Demo。

三、基本概念

讲解代码之前,你需要知道一些 Flux 的基本概念。

首先,Flux将一个应用分成四个部分。

  • View: 视图层
  • Action(动作):视图层发出的消息(比如mouseClick)
  • Dispatcher(派发器):用来接收Actions、执行回调函数
  • Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面

Flux 的最大特点,就是数据的"单向流动"。

  1. 用户访问 View
  2. View 发出用户的 Action
  3. Dispatcher 收到 Action,要求 Store 进行相应的更新
  4. Store 更新后,发出一个"change"事件
  5. View 收到"change"事件后,更新页面

上面过程中,数据总是"单向流动",任何相邻的部分都不会发生数据的"双向流动"。这保证了流程的清晰。

读到这里,你可能感到一头雾水,OK,这是正常的。接下来,我会详细讲解每一步。

四、View(第一部分)

请打开 Demo 的首页index.jsx ,你会看到只加载了一个组件。

  // index.jsx  var React = require('react');  var ReactDOM = require('react-dom');  var MyButtonController = require('./components/MyButtonController');    ReactDOM.render(    <MyButtonController/>,    document.querySelector('#example')  );  

上面代码中,你可能注意到了,组件的名字不是 MyButton,而是 MyButtonController。这是为什么?

这里,我采用的是 React 的 controller view 模式。"controller view"组件只用来保存状态,然后将其转发给子组件。MyButtonController源码很简单。

  // components/MyButtonController.jsx  var React = require('react');  var ButtonActions = require('../actions/ButtonActions');  var MyButton = require('./MyButton');    var MyButtonController = React.createClass({    createNewItem: function (event) {      ButtonActions.addNewItem('new item');    },      render: function() {      return <MyButton        onClick={this.createNewItem}      />;    }  });    module.exports = MyButtonController;  

上面代码中,MyButtonController将参数传给子组件MyButton。后者的源码甚至更简单。

  // components/MyButton.jsx  var React = require('react');    var MyButton = function(props) {    return <div>      <button onClick={props.onClick}>New Item</button>    </div>;  };    module.exports = MyButton;  

上面代码中,你可以看到MyButton是一个纯组件(即不含有任何状态),从而方便了测试和复用。这就是"controll view"模式的最大优点。

MyButton只有一个逻辑,就是一旦用户点击,就调用this.createNewItem 方法,向Dispatcher发出一个Action。

  // components/MyButtonController.jsx      // ...    createNewItem: function (event) {      ButtonActions.addNewItem('new item');    }  

上面代码中,调用createNewItem方法,会触发名为addNewItem的Action。

五、Action

每个Action都是一个对象,包含一个actionType属性(说明动作的类型)和一些其他属性(用来传递数据)。

在这个Demo里面,ButtonActions 对象用于存放所有的Action。

  // actions/ButtonActions.js  var AppDispatcher = require('../dispatcher/AppDispatcher');    var ButtonActions = {    addNewItem: function (text) {      AppDispatcher.dispatch({        actionType: 'ADD_NEW_ITEM',        text: text      });    },  };  

上面代码中,ButtonActions.addNewItem方法使用AppDispatcher,把动作ADD_NEW_ITEM派发到Store。

六、Dispatcher

Dispatcher 的作用是将 Action 派发到 Store、。你可以把它看作一个路由器,负责在 View 和 Store 之间,建立 Action 的正确传递路线。注意,Dispatcher 只能有一个,而且是全局的。

Facebook官方的 Dispatcher 实现输出一个类,你要写一个AppDispatcher.js,生成 Dispatcher 实例。

  // dispatcher/AppDispatcher.js  var Dispatcher = require('flux').Dispatcher;  module.exports = new Dispatcher();  

AppDispatcher.register()方法用来登记各种Action的回调函数。

  // dispatcher/AppDispatcher.js  var ListStore = require('../stores/ListStore');    AppDispatcher.register(function (action) {    switch(action.actionType) {      case 'ADD_NEW_ITEM':        ListStore.addNewItemHandler(action.text);        ListStore.emitChange();        break;      default:        // no op    }  })  

上面代码中,Dispatcher收到ADD_NEW_ITEM动作,就会执行回调函数,对ListStore进行操作。

记住,Dispatcher 只用来派发 Action,不应该有其他逻辑。

七、Store

Store 保存整个应用的状态。它的角色有点像 MVC 架构之中的Model 。

在我们的 Demo 中,有一个ListStore,所有数据都存放在那里。

  // stores/ListStore.js  var ListStore = {    items: [],      getAll: function() {      return this.items;    },      addNewItemHandler: function (text) {      this.items.push(text);    },      emitChange: function () {      this.emit('change');    }  };    module.exports = ListStore;  

上面代码中,ListStore.items用来保存条目,ListStore.getAll()用来读取所有条目,ListStore.emitChange()用来发出一个"change"事件。

由于 Store 需要在变动后向 View 发送"change"事件,因此它必须实现事件接口。

  // stores/ListStore.js  var EventEmitter = require('events').EventEmitter;  var assign = require('object-assign');    var ListStore = assign({}, EventEmitter.prototype, {    items: [],      getAll: function () {      return this.items;    },      addNewItemHandler: function (text) {      this.items.push(text);    },      emitChange: function () {      this.emit('change');    },      addChangeListener: function(callback) {      this.on('change', callback);    },      removeChangeListener: function(callback) {      this.removeListener('change', callback);    }  });  

上面代码中,ListStore继承了EventEmitter.prototype,因此就能使用ListStore.on()ListStore.emit(),来监听和触发事件了。

Store 更新后(this.addNewItemHandler())发出事件(this.emitChange()),表明状态已经改变。 View 监听到这个事件,就可以查询新的状态,更新页面了。

八、View (第二部分)

现在,我们再回过头来修改 View ,让它监听 Store 的 change 事件。

  // components/MyButtonController.jsx  var React = require('react');  var ListStore = require('../stores/ListStore');  var ButtonActions = require('../actions/ButtonActions');  var MyButton = require('./MyButton');    var MyButtonController = React.createClass({    getInitialState: function () {      return {        items: ListStore.getAll()      };    },      componentDidMount: function() {      ListStore.addChangeListener(this._onChange);    },      componentWillUnmount: function() {      ListStore.removeChangeListener(this._onChange);    },      _onChange: function () {      this.setState({        items: ListStore.getAll()      });    },      createNewItem: function (event) {      ButtonActions.addNewItem('new item');    },      render: function() {      return <MyButton        items={this.state.items}        onClick={this.createNewItem}      />;    }  });  

上面代码中,你可以看到当MyButtonController 发现 Store 发出 change 事件,就会调用 this._onChange 更新组件状态,从而触发重新渲染。

  // components/MyButton.jsx  var React = require('react');    var MyButton = function(props) {    var items = props.items;    var itemHtml = items.map(function (listItem, i) {      return <li key={i}>{listItem}</li>;    });      return <div>      <ul>{itemHtml}</ul>      <button onClick={props.onClick}>New Item</button>    </div>;  };    module.exports = MyButton;  

九、致谢

本文受到了Andrew Ray 的文章《Flux For Stupid People》的启发。

(完)

文档信息