引言
Werner Vogels在主题演讲上简要叙述了Lambda设计思路的由来。
Vogels首先抛出一个问题:什么是云计算的根本(primitives)?
答案:云计算是一个执行环境。
Vogels再抛出第二个问题:什么是应用的根本?
答案:函数(functions,即业务逻辑的载体)+数据(data,即跟业务相关的输入与输出),以及这两者之间的交互——即事件(events。常见的事件如增加、变更、删除等)。
换言之,对于一个应用来说,除了functions、data、events这三个东西是根本之外,其他无论什么代码和框架,无非都是胶水或者UI罢了。
既然如此,理想的情况是用最少的时间写胶水,将做多的时间投入到应用的核心当中。
而最常见的胶水代码,就是触发器(trigger):当发生一个事件(event)时,执行某个函数(function),输出新的数据(data)。Vogels以Excel表单为例:在一个表单当中,一个单元格数值的变更,触发总和列对应单元格数值的变更,就是一个事件触发函数将新的变量纳入计算得出新的数值的过程。这个过程是自动的,还可以是并发的,即一处变更同时触发多个函数。
从编程语言角度审视Vogels的观点
在编程语言中,函数(functions)+数据(data)+事件(events)的有机组合就是类(class)。在ES6中、类(class)的主体只能包含方法,不能包含数据属性。从一般的编程理论中也明确阐述,类变量或称为数据(data)应该用访问方法(如getter和setter)进行封装,以保证数据的完整性和一致性。
Vogels以Excel表单举例,说明当数据(data)变化后,触发函数执行;更多人会用react的stats举例,当数据(data)变化后,触发DomTree刷新。这是受数据驱动编程的影响?我只能理解为流派之争。怎么着你都得写个listener吧?listener也是函数!
回到正题,既然应用的本质都是一个类(class),当然最好的API接口也是一个类(class)。当类变量或称数据(data)只能通过类函数访问时,正好符合ES6中的类(class)定义。ES6中的类(class)只包含类函数(functions),其中一部分函数(functions)被其它程序直接调用,另一个部分函数(functions)被事件激活调用。
API类(class)是单例对象(singleton object)
你new与不new,API单例对象都在那里,不增也不减。
这里借用scala语言中对单例对象的叙述:“类和单例对象间的一个差别是,单例对象不带参数,而类可以。因为你不能用new关键字实例化一个单例对象,你没机会传递给它参数。每个单例对象都被作为由一个静态变量指向的虚构类:synthetic class的一个实例来实现,因此它们与Java静态类有着相同的初始化语法。Scala程序特别要指出的是,单例对象会在第一次被访问的时候初始化。”
与Java语言一样,在ES6中也没有单例对象(singleton object)的定义方法。而要记住的是对每一个调用接口的程序而言,实现的接口(API)类是个单例对象。
API类(class)三个基本问题(直接上结论)
跨域访问 - 支持
浏览器中调用 - 支持
nodejs中调用 - 支持
编写第一个接口API类 - HelloWorld.es6
class HelloWorld {
constructor() {
this.greeting = 'Hello World!';
}
welcome(callback) {
callback(null, this.greeting);
}
}
export default HelloWorld;
使用babel转成ES5
$ babel HelloWorld.es6 -o HelloWorld.js
现在要把你写好的class发布出去了!
# npm install nodeway -g
# nodeway --class HelloWorld.js --host 0.0.0.0 --port 8080 --docs . &
这句的意思是启动一个Web Server,把HelloWorld.js发布出去。好了,现在剩下的就是测试了。
编写测试程序 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HelloWorld</title>
<script type="text/javascript" src="/HelloWorld.js"></script>
</head>
<body>
<script>
var cb = function(err, greeting) {
document.write(greeting+'<br/>');
};
var api = new HelloWorld.default;
api.welcome(cb);
</script>
</body>
</html>
用浏览器访问你写的这个index.html文件,就可以看到你发布成功了。
在HelloWorld.es6实现事件(events),需要增加两个函数emit和on。(这段只是原理性展示,实际代码不要这样写)
class HelloWorld {
constructor() {
this.greeting = 'Hello World!';
// 以下是新添加代码,6秒发一个'again'事件
this.events = {};
setInterval(() => {
this.emit && this.emit('again', null, this.greeting);
}, 6000);
// 以上是新添加代码
}
// 以下是新添加代码
on(eventName, fn) {
this.events[eventName] = fn;
}
emit() {
let args = Array.from(arguments),
fn = this.events[args.shift()];
fn && fn.apply(this, args);
}
// 以上是新添加代码
welcome(callback) {
callback(null, this.greeting);
}
}
export default HelloWorld;
一点基础知识 - ES6原型链
为了不暴露emit代码,将其放到函数on中(参考ES6原型链)。
class HelloWorld {
constructor() {
this.greeting = 'Hello World!';
// 以下是新添加代码,6秒发一个'again'事件
this.events = {};
setInterval(() => {
this.emit && this.emit('again', null, this.greeting);
}, 6000);
// 以上是新添加代码
}
// 以下是新添加代码
on(eventName, fn) {
this.events[eventName] = fn;
this.constructor.prototype.emit ||
(this.constructor.prototype.emit = function() {
let args = Array.from(arguments),
fn = this.events[args.shift()];
fn && fn.apply(this, args);
});
}
// 以上是新添加代码
welcome(callback) {
callback(null, this.greeting);
}
}
export default HelloWorld;
修改测试程序 index.html,实现事件(events)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>HelloWorld</title>
<script type="text/javascript" src="/HelloWorld.js"></script>
</head>
<body>
<script>
var cb = function(err, greeting) {
document.write(greeting+'<br/>');
};
var api = new HelloWorld.default;
api.welcome(cb);
// 以下是新添加代码
api.on('again', cb);
// 以上是新添加代码
</script>
</body>
</html>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。