易天行

积累点滴,汇聚江河


  • 首页

  • 关于

  • 标签

Angular - iframe、DomSanitizer模块、单例

发表于 2017-04-20

Angular开发中的几个小知识点

Angular使用iframe加载外部网站

问题

项目中,需要使用iframe来加载一个外部网页,但src被Angular的默认安全策略所禁止,无法加载网页。

解决

在Angular中,可以使用iframe用来加载一个外部链接地址,但是src需要使用@angular/platform-browser中的DomSanitizer模块中的sanitizer.bypassSecurityTrustResourceUrl(url)方法来特殊处理,因为Angular中有默认的安全规则会阻止链接的加载。

一个例子:

html:

<!--这样无法正常加载网页,被安全策略禁止-->
<iframe [src]="link" width="100%" height="100%" frameborder="0" align="center"></iframe>
<!--需要这样使用-->
<iframe id='MainIframe' [src]="trust(link)" width="100%" height="100%" frameborder="0" align="center"></iframe>
<!--或直接处理-->
<iframe id='MainIframe' [src]="sanitizer.bypassSecurityTrustResourceUrl(link)" width="100%" height="100%" frameborder="0" align="center"></iframe>

ts:


//引入DomSanitizer模块
import { DomSanitizer } from '@angular/platform-browser';

...

link:string = 'http://www.baidu.com';

constructor(private sanitizer: DomSanitizer) {
console.log('Hello IframeFull Component');
}

...

trust(url) {
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}

测试时间:2017-04-26
使用Angular版本: 2.2.1

Angular单例

angular2一个单例的写法

import {Injectable} from '@angular/core';
import {UserInterface} from "../interface/index";

/**
* 这是一个单例模式的config,用于共享全局变量
**/
@Injectable()
export class Config {
public mode = 'dev'; //运行模式 dev or release
// public hostURL = 'https://cnodejs.org/api/v1'; //http请求前缀
public hostURL = 'http://ionichina.com/api/v1'; //http请求前缀
// public hostURL = 'http://localhost:8100/api'; //http请求前缀
public isIonic = true;

public pageLimit = 15; //每页多少
public DRAFTS_URL = '/data/drafts.json'; //草稿本地地址
public token: string = ''; //如果已经登录,存放token,请和localstorage.get('token')同步
public loginUser: UserInterface; //如果已经登录,存放登录用户信息,请和本地存储保持同步
static instance: Config;
static isCreating: Boolean = false;

constructor() {
if (!Config.isCreating) {
throw new Error("You can't call new in Config Singleton instance!");
}
}

static getInstance() {
if (Config.instance == null) {
Config.isCreating = true;
Config.instance = new Config();
Config.isCreating = false;
}
return Config.instance;
}
}

ionic2在scss中访问主题颜色

通过在scss中引用主题颜色,可以增加程序的可维护性,当程序的主题颜色更改时,引用的地方随之自动更改。

使用background-color: map-get($colors, primary)来引用在主题中定义的颜色。

示例:

.progress-inner {
min-width: 15%;
white-space: nowrap;
overflow: hidden;
height: 4px;
border-radius: 10px;
background-color: map-get($colors, primary);
}

Cordova应用中集成Crosswalk

发表于 2017-04-11

在cordova应用中集成crosswalk

Crosswalk是一款开源的Web引擎,其基于 Chromium/Blink 的应用运行环境,对于混合开发的轻量级应用尤为受欢迎。

crosswalk是hybrid应用的运行时环境,它用来代替系统自带的webview,以保证应用行为的一致性(css一致,es6支持等),兼容性(完美支持WebRTC,WebAudio,Flexbox布局等)和提高流畅性(相比老旧安卓设备,因为它们自身的webview比较老旧)。

参考链接:
crosswalk官网
官方npm插件
crosswalk官网 - cordova应用步骤

优点

  • webview不再依赖于安卓版本,因为每个Android版本WebView的表现都有差别,可以最大限度降低Android碎片化的影响,得到一致的,可预测的行为。
  • 兼容性更好,使用最新的Web技术及API,保证WebRTC, WebAudio, Web Components等
  • 性能更好,与旧版本系统的老webview相比

缺点

  • 增大内存占用率,增加大约30MB
  • 增大APK包的大小,大约17MB
  • 增加安装后的磁盘占用空间,大约50MB
  • Crosswalk WebView的本地存储(IndexedDB, LocalStorage, etc)和系统webview的相互独立

安装crosswalk插件

ionic2或者cordova应用中安装:

$ cordova plugin add cordova-plugin-crosswalk-webview --save

构建:

$ cordova build android

Windows下MongoDB的安装配置

发表于 2017-03-29

windows下mongodb的安装配置

2017年3月29日,系统:win10,mongodb version v3.4.2

下载和安装

  1. 下载地址:https://www.mongodb.com/download-center#community
  2. 下载符合你系统的版本,然后安装。默认安装到C:\Program Files\MongoDB,你也可以自定义安装目录。

创建数据目录

MongoDB将数据目录存储在 db 目录下。但是这个数据目录不会主动创建,我们在安装完成后需要创建它。

请注意,数据目录应该放在根目录下((如: C:\ 或者 D:\ 等 )。

这里我们假设创建数据目录在E:\data\db

命令行运行mogondb服务

假设你的mongodb安装在C:\Program Files\MongoDB:

进入安装目录:

cd C:\Program Files\MongoDB\Server\3.4\bin

启动服务:

mongod.exe --dbpath e:\data\db

将MongoDB服务器作为Windows服务运行

在命令行中启动服务之后,执行:

mongod.exe --logpath "e:\data\dbConf\mongodb.log" --logappend --dbpath "e:/data/db" --port 27017 --serviceName "MongoDB" --install

终止命令行中的mongodb服务,打开刚才新建的mongodb服务:

NET START MongoDB

如果出现服务器无法正常启动的问题,是因为mongod.lock这个文件,在服务器异常退出时,该文件会影响下一次启动mongod服务的,我们首先关闭命令行mongodb服务,然后只需要删除该文件就行了:

mongod.exe --config e:\data\db\mongod.lock --remove

windows删除服务命令: sc delete MongoDB

说明:

请注意,你必须有管理权限才能运行下面的命令。执行以下命令将MongoDB服务器作为Windows服务运行:

mongod.exe --bind_ip yourIPadress --logpath "C:\data\dbConf\mongodb.log" --logappend --dbpath "C:\data\db" --port yourPortNumber --serviceName "YourServiceName" --serviceDisplayName "YourServiceName" --install

下表为mongodb启动的参数说明:

参数 描述

–bind_ip 绑定服务IP,若绑定127.0.0.1,则只能本机访问,不指定默认本地所有IP

–logpath 定MongoDB日志文件,注意是指定文件不是目录

–logappend 使用追加的方式写日志

–dbpath 指定数据库路径

–port 指定服务端口号,默认端口27017

–serviceName 指定服务名称

–serviceDisplayName 指定服务名称,有多个mongodb服务时执行。

–install 指定作为一个Windows服务安装。

MongoDB后台管理 Shell

进入安装目录:

cd C:\Program Files\MongoDB\Server\3.4\bin
mongo.exe

db 命令用于查看当前操作的文档(数据库)

插入一些简单的记录并查找它:

> db.runoob.insert({x:10})
WriteResult({ "nInserted" : 1 })
> db.runoob.find()
{ "_id" : ObjectId("5604ff74a274a611b0c990aa"), "x" : 10 }
>

本文章写于,2017年3月29日,测试环境:mongodb版本 v3.4.2,操作系统win10

ES6中5种遍历对象属性的方法

发表于 2017-03-22

ES6中5种遍历对象属性的方法

5种遍历对象属性的方法:

  • for…in
    for...in循环遍历自身的和继承的可枚举属性(不含Symbol属性)

  • Object.keys(obj)
    Object.keys(obj)返回一个数组,包括对象自身的(不含继承)所有可枚举属性(不含Symbol属性)

  • Object.getOwnPropertyNames(obj)
    Object.getOwnPropertyNames(obj)返回一个数组,包括对象自身(不含继承)所有属性(包括不可枚举的属性,不含Symbol属性)

  • Object.getOwnPropertySymbols(obj)
    Object.getOwnPropertySymbols(obj)返回一个数组,包括对象自身所有的Symbol属性

  • Reflect.ownKeys(obj)
    Reflect.ownKeys(obj)返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举

###通过比较,可以得到结论:

  1. 要想获得对象自身和它所继承的属性(可枚举的),必须用for...in,其它只与对象自身有关。
  2. Object.keys(obj)返回自身的可枚举属性。
  3. Object.getOwnPropertyNames(obj)比Object.keys(obj)多包含了对象自身的不可枚举属性。
  4. Reflect.ownKeys(obj)的返回结果是Object.getOwnPropertyNames(obj)和Object.getOwnPropertySymbols(obj)的合集。

babel-cli入门使用步骤

发表于 2017-03-22

babel

babel-cli

项目中遇到了一个es6写的文件代码,在某些老的android设备的webview运行环境中不支持es6,比如class,扩展运算符等,使用这个解决方案:用babel-cli和babel-preset-es2015插件,把代码转为es5的代码。下面是具体步骤:

1.全局安装babel-cli

$ npm install --global babel-cli

babel提供命令行工具,用来在命令行转码,参考:官方文档

基本用法如下:

# 转码结果输出到标准输出
$ 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

2.项目中新建.babelrc 配置文件

.babelrc它是babel的配置文件,存放在项目的根目录下面,用来设置转码规则和插件。基本格式如下:

{
"presets": [
"es2015",
"react",
"stage-2"
],
"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

3.安装es2015插件

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

修改配置文件为:

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

4.在命令行中使用命令转码

$ babel example.js -o compiled.js

关于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本身就不用做任何转码处理。

RxJs常用Operator记录

发表于 2017-03-07

RxJs中的operator

创建操作符

常见的有Observable.forEvent(), Observable.create(), Observable.interval()。

create

Observable.create():自定义创建一个observable

const {Observable} = require('rxjs/Observable');
require('rxjs/add/observable/of');
require('rxjs/add/operator/map');
//create创建操作符
let observable = Observable.create(observer=>{
observer.next(3);
observer.next(2);
});

observable.subscribe(data=>{
console.log(data);
});

interval

interval(time:number):创建一个递增序列,每隔指定时间发送一条数据

let numbers = Rx.Observable.interval(1000);
numbers.subscribe(x => console.log(x)); // 0 1 2 3 4 5 6 ......

of

public static of(values: ...T, scheduler: Scheduler): Observable<T>: 从数组创建一个序列,一次将数据发射出去,然后发射完成命令

var numbers = Rx.Observable.of(10, 20, 30);
var letters = Rx.Observable.of('a', 'b', 'c');
var result = numbers.concat(letters);
result.subscribe(x => console.log(x)); //10 20 30 a b c

变换操作符

对每一次返回数据做一次变换操作,把原本的数据流变成需要的数据流,最常使用的变换操作是observable.map()

比如,数据获取接口经常会有一些规范去包裹数据,比如

{
"err_code":0,
"data":{"name":"Operators"}
}

如果我们只需要data字段,就可以使用map来实现:

observable.map(res=>{
return res.data;
}).subscribe(data=>{
//do sth with data
});

过滤操作符filter

用于过滤掉数据流中一些不需要处理的数据,常用的是observable.filter()
比如,前端获取数据的时候,接口可能会因为各种原因无法返回最终需要的数据,可能由于异常返回的数据为空,或者返回一个错误代码和错误描述告知前端,但是并不需要处理这些错误信息,就需要过滤掉这些错误数据。

//过滤操作符
observable.filter(response=>{
return !!response.data && response.status === 200;
}).map(response=>{
return response.data;
}).subscribe(data=>{
//do sth with data
});

组合操作符

很多业务场景需要依赖两个甚至更多的接口数据,并且在这些接口数据都成功获取后,再进行关联合并。要满足这种场景,就需要把各个数据流汇汇合组成新的数据流,这时就要用到组合操作符,比如Observable.forkJoin(),Observable.prototype.concatMap()

//组合操作符 Observable.forkJoin
let getFirstDataObs = Observable.create(observer => {
observer.next('a1');
observer.next('a2');
observer.complete();
});
let getSecondDataObs = Observable.create(observer => {
observer.next('b1');
observer.next('b2');
observer.complete();
});

let joinedObservable = Observable.forkJoin(
getFirstDataObs, getSecondDataObs
);

joinedObservable.subscribe(datas => {
//data[0] 是getFirstDataObs 的数据
//data[1] 是getSecondDataObs 的数据
//只有在他两个都执行complete()之后,才会返回数据,且返回最后发送的数据
console.log(datas); //[ 'a2', 'b2' ]
});

如果某次请求数据需要依赖前一次请求的结果,也就是说两次请求必须有先后顺序,这时候可以使用Observalbe.prototype.concatMap(),示例:

//concatMap
let getFirstDataObs = Observable.create(observer=>{
setTimeout(res=> { observer.next(1);}, 500);
setTimeout(res=> { observer.next(2);}, 1000);
setTimeout(res=> { observer.next(3);}, 1500);
setTimeout(res=> { observer.complete();}, 2000);
});

let createSecondDataObs = (firstData)=>{
return Observable.create(observer=>{
observer.next(firstData*10+1);
observer.next(firstData*10+2);
observer.next(firstData*10+3);
observer.complete();
});
};

let concatedObs = getFirstDataObs.concatMap(firstData=>{
return createSecondDataObs(firstData);
}).subscribe(secondData=>{
//do sth with second data
console.log(secondData);
});

结果输出:11 12 13 21 22 23 31 32 33

通过Observable.prototype.concatMap()方法,getSecondDataObs()的数据流被紧接在getFirstDataObs()的数据流后,并且最终被数据流被subscribe()所捕获。

工具操作符

在Observable.prototype中有很多有用的工具方法,统称为工具操作符,如delay,timeout,debounce,take,takeUntil,takeLast,takeWhile,skip等等

timeout

timeout:当observable在指定时间内数据没有完全返回时,发出一个错误

//工具操作符timeout
let observable = Observable.create(observer => {
observer.next(3);
setTimeout(function() {
observer.complete();
}, 3000);
});

observable.timeout(2000).subscribe(data=>{
//do sth with data
console.log(data);
},err=>{
handleError(err);
});
//当observable超过2000ms没有返回全部数据流(即返回complete时),便会抛出一个err错误,被handleError捕获。

delay

delay: 返回一个新序列,但是每个序列数据延迟指定时间
delay

//延迟1000ms
observable.delay(1000).subscribe(data=>{
console.log(data);
});

debounceTime

public debounceTime(time: number): 和delay一样,也会延迟发送数据,但仅发送这一段时间内最新的一条消息,如果原序列在这段时间内没有发送消息,就不发送。比如可以用在,前台界面搜索框延迟500ms后自动发起api请求进行查询,或后台数据返回的太过频繁。
debounceTime

Observable.fromEvent(inputSelector,'keyup')
.debounceTime(500)
.switchMap(event=>getValue(event.target.value))
.subscribe(callback);

debounce

public debounce(durationSelector: function(value: T): SubscribableOrPromise): Observable:和debounceTime类似,但它的发送时机不是由延迟时间决定,而是由另一个observable决定,

var clicks = Rx.Observable.fromEvent(document, 'click');
var result = clicks.debounce(() => Rx.Observable.interval(1000));
result.subscribe(x => console.log(x));

take

public take(count:number): 挑选出指定的前几个数据,作为新的序列,并complete
take

var interval = Rx.Observable.interval(1000);
var five = interval.take(5); //取出前5个数据
five.subscribe(x => console.log(x)); //0 1 2 3 4
```

### takeLast

`takeLast(count:number)`:挑选出原序列的最后count个数据,在complete时一起发送出去
![takeLast](http://ol1ftyec4.bkt.clouddn.com/2017-03-07-takeLast.png)

```ts
let interval = Rx.Observable.interval(1000).take(5);
five.takeLast(2).subscribe(x => console.log(x)); //挑选出最后2个,打印:3 4

takeUntil

public takeUntil(notifier: Observable): Observable<T>: 筛选原序列中数据,直到notifier发射出一个数据为止.(Lets values pass until a second Observable, notifier, emits something. Then, it completes.)

var interval = Rx.Observable.interval(1000);
var clicks = Rx.Observable.fromEvent(document, 'click');
var result = interval.takeUntil(clicks); //一直从原序列中取数据,直到点击事件发生为止
result.subscribe(x => console.log(x));

takeWhile

public takeWhile(predicate: function(value: T, index: number): boolean): Observable<T> :满足某个条件时不再从原序列中取数据
takeWhile

skip

public skip(count: Number): Observable:跳过序列中的前count个数据

skip

switchMap

public switchMap(project: function(value: T, ?index: number): ObservableInput, resultSelector: function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any): Observable: 将原序列某个数据转换为另一个observable的若干个序列数据,当元序列有新数据到来时,会截断旧数据

switchMap官方说明

switchMap

结束

JavaScript响应式编程 - RxJs入门

发表于 2017-03-03

在angular2项目的过程中,接触到了Rxjs这个东西,对它进行了一下简单的了解,对它的基本用法进行了学习和总结,介绍了:

  • 基本概念
  • 如何创建一个Observerble可观察序列
  • Observer实例的生命周期
  • 如何对Observer实例进行订阅?以及监听他的next、error、complete事件?
  • rxjs交互图怎么看?
  • map、filter、from等简单的操作符含义

它是什么

官方文档:http://reactivex.io/rxjs/

Rx(ReactiveX, Reactive Extensions),它是微软开发和维护的基于响应式编程(Reactive Programming)范式实现的一套工具库集合,于2012年11月开源,它提供了一系列接口规范来帮助开发者方便的处理异步数据流。Rx系列结合了观察者模式、迭代器模式、函数式编程,它已成为业界响应式编程的优秀实践。

RxJs就是Rx在JavaScript层面上的实现,除此之外还有RxJava、Rx.Net、RxSwift等等。

RxJs全名Reactive Extensions for JavaScript,翻译为Javascript的响应式扩展, 它的思路是把随时间不断变化的数据、状态、事件等等转成可被观察的序列(Observable Sequence),然后订阅序列中那些Observable对象的变化,一旦变化,就会执行事先安排好的各种转换和操作。

几个名词

Observable: 可观察的数据序列.
Observer: 观察者实例,用来决定何时观察指定数据,只有在Rx.Observable.create创建方法可以获取
Subscription: 观察数据序列返回的订阅实例.
Operators: Observable的操作符,包括转换数据序列,过滤等,所有的Operators方法接受的参数是上一次发送的数据变更的值,而方法返回值我们称之为发射新数据变更

Observable可以发射三种类型的事件:next、error、complete
Subscription可以订阅三种类型的事件:onNext onError onComplete
Observable有四个生命周期:创建 、订阅 、 执行 、销毁。

使用create创建一个Observable

const Rx = require('rxjs/Rx');
const {Observable} = require('rxjs/Observable');
require('rxjs/add/observable/of');
require('rxjs/add/operator/map');

//创建序列源
const source = Observable.create(observer=>{
observer.next('foo');
setTimeout(()=> observer.next('bar'), 1000);
});
//订阅
const subscription = source.subscribe(data=>console.log(data));

捕获error和complete

let source = Observable.create(observer=>{
observer.next('foo'); //发射数据
observer.error('my error'); //发射错误
setTimeout(()=> observer.next('bar'), 1000);
setTimeout(()=>observer.complete(),2000); //发射完成
});

let source1 = source.map(e=>`hello ${e}`);

let subscription = source1
.subscribe(
data=>console.log(data),
error=>console.log(error),
()=>console.log('complete')
);

rxjs交互图

  • 每条线表示一个数据序列
  • 每个球表示发送的数据变更,小竖线表示complete
  • 方法下面表示经过map、filter等操作符处理过后产生的新序列

屏幕快照 2017-03-03 14.23.49

//序列1
let source = Observable.create(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
});
//序列2
let source2 = source.map(e=>e=e*10);
//订阅序列1
source.subscribe(data=>console.log(data));
//订阅序列2
source2.subscribe(data=>console.log(data));

操作符

map:对要发送的data作统一处理后再返回
filter:满足条件时才发射出去
toPromise:将observer转为promise
debounce:(防抖动)取一段时间内最新的数据
throttle:(防抖动) 忽略这段时间,发现新值后再发送

更多,如 mapTo, scan, from ,concat, concatAll, merge, mergeAll等

结束

angular2中的HTTP模块,ionic-native中的BLE模块都默认使用了Observable

还需要要研究:

  • 他的使用场景?
  • 与promise相比他的优势劣势是什么?
  • 未来的使用趋势如何?

参考文章

官方文档:http://reactivex.io/rxjs/

rxjs简单入门

使用 RxJS 实现 JavaScript 的 Reactive 编程

Reactive Programming in JavaScript With RxJS

对node事件循环机制中Macrotask和Microtask的理解

发表于 2017-02-27

node事件循环机制中的Microtask和Macrotask?

他是什么?

在Nodejs事件循环机制中,有任务两个队列:Macrotask队列和Microtask队列。在一个事件循环里,这两个队列会分两步执行,第一步会固定地执行一个(且仅一个)Macrotask任务,第二步会执行整个Microtask队列中的所有任务。并且,在执行Microtask队列任务的时候,也允许加入新的Microtask任务,直到所有Microtask任务全部执行完毕,才会结束循环。

Macrotasks一般包括: setTimeout, setInterval, setImmediate, I/O, UI rendering;
Microtasks一般包括: process.nextTick, Promises, Object.observe, MutationObserver。

node事件循环机制详解

从一个事件循环开始,到结束会经历以下步骤:

  1. 检查Macrotask队列,选择其中最早加入(即最老的)的任务X,设置为“目前运行的任务”。如果任务X不存在,那么直接跳到步骤4。
  2. 运行任务X,即运行对应的回调函数。
  3. 设置“目前运行的任务”为null,从Macrotask队列中移除任务X。
  4. 检查Microtask队列:

    1. 选择其中最老的任务a,如果任务a不存在,直接结束Microtask队列。
    2. 设置任务a为“目前运行的任务”,并执行。
    3. 设置“目前运行的任务”为null,从Microtask队列中移除任务a。
    4. 选择下一个最老的任务b,跳到步骤2)。
    5. 直到队列里没有剩余的任务,结束队列。
  5. 跳回步骤1,检查下一个Macrotask任务。

如何选用Macrotask或Microtask呢?

如果你想让一个任务立即执行,那么就把它设置为Microtask,除此之外都用Macrotask比较好。

因为可以看出,虽然Node是异步非阻塞的,但在一个事件循环中,Microtask的执行方式基本上就是用同步的。

有什么问题?

如果Microtask任务列表太长,或不断加入新的Microtask,就会导致下一个Macrotask任务很久得不到执行,可能导致UI一直无法刷新或IO无法完成。

应该是考虑到了这一点,至少Microtask任务中的process.nextTick任务,是被设置了(在一个事件循环中的)最大调用次数的,叫process.maxTickDepth,默认是1000。一定程度上避免了上述情况。

参考文档:

Node.js事件循环中的:Macrotask 与 Microtask

Node.js事件驱动实现概览

函数闭包、IIFE、JS解释器

发表于 2017-02-27

在掘金上看到了篇文章,里面讲了几个js代码片段,觉得很有意思,其中考察了关于setTimeout、函数闭包、IIFE(立即执行函数表达式)、promise、js解释器等概念的理解

A:下面的程序输出啥?

for(var i=0;i<5;i++){
console.log(i);
}

我:没有坑,别想多了。输出0 1 2 3 4

A:恩,那下面这个呢?

for(var i=0;i<5;i++){
setTimeout(function() {
console.log(i);
}, 1000*i);
}

我:settimeout时间到后,会在下一个tick中执行程序,所以程序打印的时候,i已经变成5了,输出5 5 5 5 5。

A:不错,那我想输出的结果是0 1 2 3 4,应该怎么办呢?

我:用闭包就解决了,如下

for(var i=0;i<5;i++){
(function(i){
setTimeout(function() {
console.log(i);
}, 1000*i);
})(i);
}

因为函数作用域内,保存着对于变量i的引用,故函数执行的时候仍能访问到当初的i。

A:很好。那如果去掉参数i呢?

for(var i=0;i<5;i++){
(function(){
setTimeout(function() {
console.log(i);
}, 1000*i);
})(i);
}

我:这样的话,函数闭包内没有存储对i保持引用,所以输出结果又会变成5 5 5 5 5

A: 对。那我再给你改一下程序,你看看输出啥?

//给setTimeout传递一个立即执行函数 
for(var i=0;i<5;i++){
setTimeout((function(i){
console.log(i);
})(i), 1000*i);
}

我:这里给setTimeout传递的是一个立即执行函数表达式,它会马上运行,执行结果是undefined,所以setTimeout输出0之后,应该会报错吧,因为参数传递不对,应该接受一个函数类型的参数。

上面的代码差不多等于:

//其实等价于
for(var i=0;i<5;i++){
setTimeout(undefined, 1000*i);
}

A:恩!再看下面的这个程序输出什么?

setTimeout(function() {
console.log(1);
}, 0);

new Promise(function executor(resolve){
console.log(2);
for( var i=0;i<10000;i++){
i == 9999 && resolve();
}
console.log(3);
}).then(function(){
console.log(4);
});

console.log(5);

我:思考半天…,应该输出2 3 5 4 1。这个题目是关于js解释器理解的。setTimeout中的函数会在下一个时钟周期运行,肯定比较靠后。2,3在立即执行函数中,所以他会立马打印。4在then中输出,它会放在当前周期的最后执行。最后5会立马打印。所以会这样2 3 5 4 1。

疑问:关于setTimeout和promise.then执行先后的问题,上网找了一下原因,setTimeout属于Macrotask,而promise属于Microtask,Microtask在当前周期队列执行,Macrotask在下一周期执行。

参考自:Excuse me?这个前端面试在搞事!

node版本管理 n和nvm说明

发表于 2017-02-24

项目中我们有时需要使用不同版本 的node来调试或测试程序,最本的方法当然是手动下载不同的版本,并卸载之前的版本,这样可以达到目的,但是无疑是麻烦时间的,在node中有“node版本管理器”来实现类似的功能需要。这里介绍比较流行的两个,“n”和“nvm”.
先说说n。
n是node一个模块,可以用来管理和切换node版本,其作者是TJ Holowaychuk(出名的Express框架作者),使用非常之简单。
安装 方法:

$ sudo npm install -g n

常用命令:

$n //查看已安装版本  
$n latest //安装最新版本并使用
$n latest -d //下载最新版但不使用,-d参数表示为仅下载
$n stable //安装最新稳定版本并使用
$n <version> //安装某个版本并使用,如$n 6.2.2
$n rm <version ...> //删除某些版本
$n ls //查看可用版本
$n --latest //查看最新版本
$n --stable //查看最新稳定版
$n -h //查看帮助信息,更多命令在这里查看
[plain] view plain copy print?在CODE上查看代码片派生到我的代码片
$ n use 0.10.21 some.js //<span style="font-family: Arial, Helvetica, sans-serif;">以指定的版本来执行脚本</span>

下面说说nvm。
问题来了,既然有这么简单好用的n,那么nvm为什么还会大肆流行呢?这个问题一会回答,先说说nvm。
nvm全称Node Version Manager,不同于 n,nvm 不是一个 npm package,而是一个独立软件包。这意味着我们需要单独使用它的安装逻辑:

$ wget -qO- https://raw.github.com/creationix/nvm/v0.4.0/install.sh | sh 
```
独立的安装包,因此当你机器上没有安装node时仍可以使用它。
常用命令:

```bash
$ nvm install 0.10
$ nvm use 0.10 //使用指定的版本
$ nvm ls //查看当前已经安装的版本
.nvm
-> v0.10.24
$ nvm current //查看正在使用的版本
v0.10.24
$ nvm run 0.10.24 myApp.js //以指定版本执行脚本
$ rm -rf ~/.nvm //卸载nvm

对比说明:
我们在使用 n 管理 node 版本前,首先需要一个 node 环境。我们或者用 Homebrew 来安装一个 node,或者从官网下载 pkg 来安装,总之我们得先自己装一个 node —— n 本身是没法给你装的。
然后我们可以使用 n 来安装不同版本的 node。
在安装的时候,n 会先将指定版本的 node 存储下来,然后将其复制到我们熟知的路径 /usr/local/bin,非常简单明了。当然由于 n 会操作到非用户目录,所以需要加 sudo 来执行命令。
两者区别:
安装简易度。nvm 安装起来显然是要麻烦不少;n 这种安装方式更符合 node 的惯性思维。见仁见智吧。
系统支持。注意, nvm 不支持 Windows。
对全局模块的管理。n 对全局模块毫无作为,因此有可能在切换了 node 版本后发生全局模块执行出错的问题;nvm 的全局模块存在于各自版本的沙箱中,切换版本后需要重新安装,不同版本间也不存在任何冲突。
关于 node 路径。n 是万年不变的 /usr/local/bin;nvm 需要手动指定路径。

参考地址:
http://taobaofed.org/blog/2015/11/17/nvm-or-n/ 管理 node 版本,选择 nvm 还是 n?
http://it.taocms.org/03/3079.htm 利用n和nvm管理Node的版本

Angular笔记 - 属性型指令

发表于 2017-02-22

简介

属性型指令用于改变DOM元素的外观或行为。

在Angular中有三种类型的指令:

  1. 组件 — 拥有模板的指令
  2. 属性型指令 — 改变元素外观或行为,例如内置的 NgStyle 指令可以同时修改元素的多个样式
  3. 结构型指令 — 通过添加和移除dom元素改变视图结构,例如,NgFor 和 NgIf。

一个简单的属性型指令

我们这里实现了一个简单的属性型指令,说一下几个重点的内容

功能:当用户鼠标悬浮在元素上时,改变它的背景颜色

代码:

highlight.directive.ts

import { Directive, ElementRef, HostListener, Input, Renderer } from '@angular/core';
@Directive({
selector: '[myHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef, private renderer: Renderer) { }
@HostListener('mouseenter') onMouseEnter() {
this.highlight('yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.renderer.setElementStyle(this.el.nativeElement, 'backgroundColor', color);
}
}

a.html

<h1>My First Attribute Directive</h1>
<p myHighlight>Highlight me!</p>

说明:

  • @Directive装饰器需要一个 CSS 选择器,以便从模板中识别出关联到这个指令的 HTML,这里指令的选择器是[myHighlight],Angular 将会在模板中找到所有带myHighlight属性的元素。
  • 需要把 Angular 的ElementRef和Renderer注入进构造函数。ElementRef是一个服务,它赋予我们通过它的nativeElement属性直接访问 DOM 元素的能力。 Renderer服务允许通过代码设置元素的样式。
  • 需要把这个类添加到 NgModule 元数据的declarations数组中

EXCEPTION: Template parse errors:
Can’t bind to ‘myHighlight’ since it isn’t a known property of ‘p’.

错误原因:你记着设置@NgModule的declarations数组了吗?它很容易被忘掉

  • @HostListener装饰器引用属性型指令的宿主元素,在这个例子中就是<p>

高级点的属性型指令

对上面的那个指令添加功能:

  1. 通过绑定从外部设定这个颜色
  2. 从外部设定一个默认颜色,如果没有传入颜色,则使用这个默认颜色
  3. 这样使用这个指令

    <p [myHighlight]="color" [defaultColor]="'violet'">
    Highlight me too!
    </p>

代码:

highlight.directive.ts

import { Directive, ElementRef, HostListener, Input, Renderer } from '@angular/core';
@Directive({
selector: '[myHighlight]'
})
export class HighlightDirective {
private _defaultColor = 'red';
constructor(private el: ElementRef, private renderer: Renderer) { }
@Input() set defaultColor(colorName: string){
this._defaultColor = colorName || this._defaultColor;
}
@Input('myHighlight') highlightColor: string;
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor || this._defaultColor);
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string) {
this.renderer.setElementStyle(this.el.nativeElement, 'backgroundColor', color);
}
}

a.html

<div>
<input type="radio" name="colors" (click)="color='lightgreen'">Green
<input type="radio" name="colors" (click)="color='yellow'">Yellow
<input type="radio" name="colors" (click)="color='cyan'">Cyan
</div>
<p [myHighlight]="color">Highlight me!</p>
<p [myHighlight]="color" [defaultColor]="'violet'">
Highlight me too!
</p>

说明:

  • 新的highlightColor属性被称为输入属性,因为数据是从绑定表达式流入指令中。 注意,在定义这个属性的时候,我们调用了@Input()装饰器,@Input向类添加元数据,使highlightColor属性能以myHighlight为别名进行绑定
  • 你可以通过重命名属性名到myHighlight来移除这个区别,像这样:@Input() myHighlight: string;
  • defaultColor属性是一个 setter 函数,它代替了硬编码的默认颜色 “red”,它不需要 getter 函数。这里,把字符串字面量'violet'绑定到了defaultColor上。

关于 输入属性的源和目标

Angular 在绑定的源和目标之间的区别:

  • 如果属性出现在了模板表达式等号 (=) 的右侧,它就是一个源。

  • 如果它出现在了方括号 ([ ]) 中,并且出现在等号 (=) 的左侧,它就是一个目标, 就像在绑定到HighlightDirective的myHighlight属性时所做的那样。

例如:

<p [myHighlight]="color">Highlight me!</p>

[myHighlight]="color"中的 'color'是绑定源。 源属性不需要声明。

[myHighlight]="color"中的 'myHighlight' 是绑定目标。 必须把它定义为一个输入属性,否则,Angular 就会拒绝绑定,并给出一个明确的错误。

Angular 这样区别对待目标属性有充分的理由。 作为目标的组件或指令需要保护。

TypeScript - 类

发表于 2017-02-22

类

从ECMAScript 2015,也就是ECMAScript 6开始,JavaScript程序员将能够使用基于类的面向对象的方式。 使用TypeScript,我们允许开发者现在就使用这些特性,并且编译后的JavaScript可以在所有主流浏览器和平台上运行,而不需要等到下个JavaScript版本。

创建一个类

class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

let greeter = new Greeter("world");

继承

  • 使用extend关键字来创建子类
  • 子类构造函数中使用super(),执行父类的构造方法
class Animal {
name:string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}

class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}

class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

public、private、protected修饰符

  • 在ts中,成员类型默认为public

  • 当成员被标记成private时,它就不能在声明它的类的外部访问

  • protected修饰符与private修饰符的行为相似,但protected成员在派生类中仍然可以访问

  • 构造函数可以被标记成protected,这是这个类不可以在类外进行实例化,但是能被继承并在子类中实例化。

class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}

// Employee can extend Person
class Employee extends Person {
private department: string;

constructor(name: string, department: string) {
super(name);
this.department = department;
}

public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected

readonly修饰符

  • readnoly修饰符,设置属性为只读的
  • 只读属性必须在声明时或构造函数里被初始化

参数属性

使用参数属性可以方便地在一个地方创建并初始化一个成员,可以简化代码

class Animal {
constructor(private name: string) { }
move(distanceInMeters: number) {
console.log(this.name);
}
}

上面仅在构造函数里使用private name: string参数来创建和初始化name成员,把声明和赋值合并至一处。

等价于

class Animal {
private name:string
constructor(theName: string) {
this.name=theName;
}
move(distanceInMeters: number) {
console.log(this.name);
}
}

存取器 getters/setters

class Person{
private _name:string;
get name():string{
return this._name;
}

set name(name:string){
this._name=name;
}
}

let p = new Person();
p.name='tom'; //调用set
console.log(p.name); //调用get

要注意几点:

  • 使用tsc file.ts --target es5编译,因为它只支持ECMAScript 5或更高
  • 只带有 get不带有set的存取器自动被推断为readonly

用在哪里?
可以用在存取时需要做一些额外操作,而不是直接赋值的地方。

静态属性

  • static可以创建类的静态成员,这些属性存在于类本身,而不是类的实例上
  • 使用类名.静态成员名格式访问类的静态成员

抽象类

  • 抽象类一般不会直接实例化,而是作为派生类的基类。
  • 与接口不同的地方:抽象类可以包含成员的实现细节。
  • abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}

注意:
抽象类中的抽象方法不包含具体实现,而且它必须要在派生类中实现

高级技巧

构造函数

在创建ts的一个类时,声明了几个东西:

  • 类的实例类型
  • 创建了一个叫做构造函数的值

a.ts

class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}

let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

上面的代码使用tsc a.ts 编译后得到的js代码:

let Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

把类当做接口使用

类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。

class Point {
x: number;
y: number;
}

interface Point3d extends Point {
z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

天使时代 - 刘慈欣

发表于 2017-02-21 | 分类于 小说

天使时代

  引子

  对桑比亚国的攻击即将开始。

  执行“第一伦理”行动的三个航空母舰战斗群到达非洲沿海已十多天了,这支舰队以林肯号航母战斗群为核心展开在海面上,如同大西洋上一盘威严的棋局。

  此时天已经暗了下来,舰队的探照灯集中照亮了林肯号的飞行甲板,那里整齐地站列着上千名陆战队员和海军航空兵飞行员。站在队列最前面的是“第一伦理”行动的最高指挥官菲利克斯将军和林肯号的舰长布莱尔将军,前者身材欣长,一派学者风度,后者粗壮强悍,是一名典型的老水兵。在蒸汽弹射器的起点,面对队列站着一位身着黑色教袍的的随军牧师,他手捧《圣经》,诵起了为这次远征而作的祷词:“全能的主,我们来自文明的世界,一路上,我们看到了您是如何主宰大地、天空和海洋,以及这世界上的万物生灵,组成我们的每一个细胞都渗透着您的威严。现在,有魔鬼在这遥远的大陆上出现,企图取代您神圣的至高无上的权威,用它那肮脏的手拨动生命之弦。请赐予我们正义的利剑,扫除恶魔,以维护您的尊严与荣耀,阿门——”

  他的声音在带有非洲大陆土腥味的海风中回荡,令所有的人沉浸在一种比脚下的大海更为深广的庄严与神圣感之中,在上空纷纷飞过的巡航导弹火流星般的光芒中,他们都躬下身来,用发自灵魂的虔诚和道:“阿门——”

  上篇

  自人类基因组测序完成以后,人们就知道飞速发展的分子生物学带来的危机迟早会出现,联合国生物安全理事会就是为了预防这种危机而成立的。生物安理会是与已有的安理会具有同等权威的机构,它审查全世界生物学的所有重大研究课题,以确定这项研究是否合法,并进而投票决定是否终止它。

  今天将召开生物安理会第119次例会,接受桑比亚国的申请,审查该国提交的一项基因工程的成果。按照惯例,申请国在申请时并不提及成果的内容,只在会议开始后才公布。这就带来一个问题:许多由小国提交的成果在会议一开始就发现根本达不到审查的等级。但各成员国的代表们都不敢轻视这个非洲最贫穷的国度提交的东西,因为这项研究是由诺贝尔奖获得者,基因软件工程学的创始人依塔博士做出的。

  依塔博士走了进来,这位年过五十的黑人穿着桑比亚的民族服饰,那实际上就是一大块厚实的披布,他骨瘦如柴的身躯似乎连这块布的重量都经不起,像一根老树枝似的被压弯了。他更深地躬着腰,缓缓向圆桌的各个方向鞠躬,他的眼睛始终看着地面,动作慢地令人难以忍受,使这个过程持续了很长时间。印度代表低声地问旁边的美国代表:“您觉得他像谁?”美国代表说:“一个老佣人。”印度代表摇摇头,美国代表看了看他,又看了看依塔,“你是说……像甘地?哦,是的,真像。”

  本届生物安理会轮值国主席站起来宣布会议开始,他请依塔在身旁就座后说:“依塔博士是我们大家都熟悉的人,虽然近年来深居简出,但科学界仍然没有忘记他。不过按惯例,我们还是对他进行一个简单的介绍。博士是桑比亚人,在三十二年前于麻省理工学院获计算机科学博士学位,而后回到祖国从事软件研究,但在十年后,突然转向分子生物学领域,并取得了众所周知的成就。”他转向依塔问,“博士,我有个问题,纯粹是出于好奇:您离开软件科学转向分子生物学,除了预见到软件工程学与基因工程的奇妙结合外,是不是还有另一层原因:对计算机技术能够给您的祖国带来的利益感到失望?”

  “计算机是穷人的假上帝。”依塔缓缓地说,这是他进来后第一次开口。

  “可以理解,虽然当时桑比亚政府在首都这样的大城市极力推行信息化,但这个国家的大部分地区还没有用上电。”

  当分子生物学对生物大分子的操纵和解析技术达到一定高度时,这门学科就面对着它的终极目标:通过对基因的重新组合改变生物的性状,直到创造新生物。这时,这门科学将发生深刻变化,将由操纵巨量的分子变为操纵巨量的信息,这对于与数学仍有一定距离的传统分子生物学来说是极其困难的。直接操纵四种碱基来对基因进行编码,使其产生预期的生物体,就如同用0和1直接编程产生WINDOWSXP一样不可想象。依塔最早敏锐地意识到这一点,他深刻地揭示出了基因工程和软件工程共同的本质,把基础已经相当雄厚的软件工程学应用到分子生物学中。他首先发明了用于基因编程的宏汇编语言,接着创造了面向过程的基因高级编程语言,被称为“生命BASIC”;当面向对象的基因高级语言“伊甸园++”出现时,人类真的拥有了一双上帝之手。

  这时,人们惊奇地发现,创造生命实际上就是编程序,上帝原来是个程序员。与此同时,程序员也成了上帝,这些原来混迹于硅谷或什么什么技术园区的的人纷纷混进生命科学行业来,他们都是些头发蓬乱衣冠不整的毛头小子,过着睡两天醒三天的日子,其中有许多人连有机物和无机物都分不清,但都是性能良好的编程机器。有一天,项目经理把一个光盘递给一位临时召来的这样的上帝,告诉他光盘中存有两个未编译的基因程序模块,让他给这两个模块编一个接口程序。谈好价钱后上帝拿着光盘回到他那间闷热的小阁楼中,在电脑前开始他那为期一周的创世工作,他干起活来与上帝没有任何共同之处,倒很像一个奴隶。一周后,他摇晃着从电脑前站起来,从驱动器中取出另一块拷好的光盘,趟着淹没小腿的烟蒂和速溶咖啡袋走出去,到那家生命科学公司把那个光盘交给项目经理。项目经理把光盘放入基因编译器中,在一个球形透明容器的中央,肉眼看不见的分子探针精巧地拨弄着几个植物细胞的染色体。然后,这些细胞被放入一个试管的营养液中培养,直至其长成一束小小的植株,后来这个植株被放入无土栽培车间,长成树苗后再被种进一个热带种植园,最后长成了一棵香蕉树。当第一串沉重的果实从树上砍下后,你掰下一个香蕉剥开来,发现里面是一个硕大的橘瓣……

  当然,以上只是一个生动的比喻,实际的基因软件开发都是庞大的工程,绝非个人的力量所能及。例如仅编制一个视网膜感光细胞的基因软件,其代码量与一个最新的视窗操作系统相当。所以完全凭借基因编程创造新的生命还只能是病毒级别,科学家们倾向于从生物的自然基因中分离出各种功能模块和函数,通过引用和组合这些模块和函数来得到具有新的特征的生物,对此,面向对象的基因编程语言“伊甸园++”是一个强有力的工具。

  “依塔博士,在宣布会议议程正式开始之前,我想提醒您:您看上去很虚弱。”会议主席关切地对依塔说。

  一位桑比亚官员起身说:“各位,依塔博士每天吃得很少,你们一定知道,桑比亚国内目前正面临着严重的旱灾,博士自愿同他的人民一同挨饿。”

  法国代表说:“上个月,作为发展计划署考察团的一员,我到过桑比亚和相临的其它两个受灾国家,那里的旱情确实可怕,如果大量的救济不能及时到位,下半年会饿死很多人的。”

  “不过,依塔博士,”美国代表说,“作为一位从事基础研究的科学家,过分的责任心会影响您的研究,结果反而不能够尽到自己的责任。”

  依塔点点头,并半起身冲他微微鞠躬:“您说得很对,唉,小时侯留下来的毛病,很难改了……哦,各位想不想听听我小时侯的事情?”

  这显然离题了,但出于尊敬,大家都没有出声。依塔用低缓的声音讲述起来,仿佛在回忆中自语。

  “那也是一个大旱之年,大地像一个满是裂缝的火炉子,地上被渴死的蛇又被烈日烤干,脚一踏就碎成了末……当时桑比亚正在连年的内战中,就是那场由东方政治集团操纵的推翻布萨诺政权的战争。我们的村子被遗弃了,什么吃的都没有了,雅拉就去吃干草和树叶,哦,雅拉是我的小妹妹,刚懂事,大大的眼睛……她去吃干草和树叶……”依塔的声音平缓而单调,像是早期的语音软件在读一个文本文件,“她吃得浑身浮肿,肠道也堵塞了……那天晚上,她嘴里含了什么东西,碰着牙喀啦啦响,我问她含着什么?她说在吃糖……她以前只吃过一块糖,是一年前一个来村里招募游击队员的苏联顾问给的。我看到一道血从她嘴里流出来,就掰开她的嘴看,雅拉含的不是糖块,是一个箭头,一个涂着响尾蛇的毒液,用来射杀豺狗的箭头。她最后对我说:雅拉难受,雅拉不想再活了,雅拉死后哥哥把雅拉吃了吧,然后哥哥就有劲儿走到城里去,听说那里有吃的…

  …我还记得那天晚上的月亮,从干旱的大地尽头升起来,昏红昏红的……我没吃小妹妹,但那年在村子里,确实发生了人吃人的事,有些老人立下遗嘱,饿死后让孩子们吃……“

  全场陷入长长的沉默。

  主席说:“博士,我们现在理解了你在过去十多年用基因软件技术改良农作物的努力。”

  “一事无成,一事无成啊……”依塔摇头叹息,“想当初桑比亚独立之时,我们曾想在祖先的土地上建起天堂,但后来知道,在这样一块苦难深重的土地上,对生活的期望是不能太高的。我们理想的底线在不断后退,我们不要工业化了,我们不要民主了,我们甚至可能连国家和个人的尊严都不要了,但桑比亚人对生活的要求不可能再后退,我们不能不吃饭。这个国家仍然有三分之二的人在挨饿,我们必须想出办法。”

  依塔的话在会场里引起了很大的反响,代表们纷纷低声议论起来。

  美国代表说:“非洲确实是一个被文明进程抛下的大陆,但,博士,这是一个涉及到社会政治、历史、地理条件等诸多复杂因素的问题,不是科学家们仅凭手中的科学就能够解决的。”

  依塔摇摇头说:“不,科学也许真能解决饥饿问题,关键在于我们要换一个思考方向。”

  代表们茫然地互相对视着,主席首先想到了什么,说:“如果我没理解错,依塔博士已经开始了我们这次会议的议程了。”

  依塔郑重地说:“是的,主席先生,如果您允许,在介绍我们的研究成果前,我想先让各位认识一个孩子,一个能吃饱饭的桑比亚孩子。”

  他挥挥手,一个黑人男孩儿走进会议大厅。他赤裸着上身,肌肉饱满,皮肤光亮,浓密卷鬈发下的一双大眼睛闪闪有神,他用强健而轻快的脚步,把一股旺盛的生命力带进了会议大厅。

  “哇,好一个小奥塞罗!”有人赞叹道。

  依塔介绍说:“这是卡多,十二岁,一个土生土长的桑比亚孩子。当然,在平均寿命只有四十多岁的赞比亚,他这样的年纪通常已经不算是孩子了,但卡多确实是孩子,而且是个小孩子,因为他的寿命肯定要超过我们在座的各位。”

  “这不奇怪,看得出来这孩子的营养状况很好。”代表中的一位医学家说。

  依塔扶着卡多的双肩环视着会场说:“他肯定与各位印象中的桑比亚儿童有很大差别,那些饥饿中的孩子都是细细的脖颈撑着大大的脑袋,四肢像树干般枯瘦,肚子因积水而鼓起,脸上落着苍蝇,身上生着疮……所以大家都看到了。只要吃饱了饭,任何民族的孩子都能变得像天使般高贵。”

  卡多向大家点头致意,大声说了一句谁都听不懂的话。

  “他在向各位问好,”依塔说,“卡多只会讲桑比亚语。”

  “您刚才说,这孩子是在桑比亚土生土长的?”主席问。

  “是的,而且是在桑比亚最贫瘠的地区长大,从未离开那里。在这场旱灾中,他的家乡饿死了不少人。”

  所有人都目不转睛地盯着这个健壮的黑孩子,一时谁也说不出话来。

  依塔第一次露出了淡淡的微笑:“大家的下一个问题自然是:他在那里吃什么?那么下面,我就请大家看卡多吃一顿午餐。”

  他说完又向门的方向挥了一下手,有三个人走进会议大厅,其中两位是参加会议的桑比亚官员,第三个人令大家大吃一惊,他竟是一名纽约警察。他腰上累赘地别着手枪、警棍、对讲机等等,手里提着一个大塑料袋,进门后犹豫地站住了。

  “是我们请这位警官进入会场的。”依塔对主席说,主席示意让那名警察走上前来。

  警察走到圆桌旁,两位代表给他让开了位置,他把大塑料袋中的东西都倾倒在桌面上,首先倒出的是一大捆青草,然后是一堆梧桐树叶,最后是一堆深绿色的松针。警察指指这堆青草和树叶,又指指同他一起进来的那两名赞比亚官员说:“这两位先生在庭院里的草坪上拔草,我去制止他们,他们就把我带到这里来了。”

  依塔起身向警察鞠躬:“尊敬的警官先生,我对我们的粗鲁行为表示歉意,并愿意交纳相应的罚款,我们只是想请你来做个证明,证明这些青草和树叶是真实的。”

  警察瞪大双眼说:“当然是真实的!是我把它们收集到袋子里一直提到这里的。”

  依塔点点头:“好吧,卡多该用他的午餐了。”

  这个桑比亚孩子抓起一大把青草,卷成粗绳壮的一根,像吃香肠那样咬下一大截,津津有味地嚼了起来,草茎被嚼碎时发出的吱吱声清晰可闻……他吃得很快,转眼把那粗粗的一把草吃光了,又开始大口吃树叶……

  旁观者的反应分为两类:一部分人极力忍住呕吐的欲望,另一部分人则抑制不住开始咽口水,这是在看到别人享用他感觉中的美味时的一种自然条件反射,不管那美味是什么。

  卡多又卷了一把草吃,然后开始吃松针,他咀嚼的声音立刻发生了变化,一道墨绿色的汁液顺着他的嘴角流下来,他含着满嘴的松针和青草,高兴地对依塔说了句什么。

  “卡多说这里的草和树叶比桑比亚的味道好。”依塔解释说,“由于盲目引进高污染的工业,桑比亚已经成了西方的垃圾倾倒场,那里的环境污染比这里要严重得多。”

  在众目睽睽之下,卡多吃光了桌子上所有的青草、梧桐叶和松针,他满意地抹去嘴角的绿色汁液,笑着对依塔点点头,显然是在感谢这顿美味的午餐。

  用后来一位记者的描述,会议大厅陷入“地狱般的寂静”。不知过了多长时间,这寂静才被主席颤抖的声音打破。

  “这么说,依塔博士,这就是您代表桑比亚国提交生物安全理事会审查的研究成果了?”

  依塔镇静地点点头:“是的,这就是我刚才说过的换一个思考方向:我们既然可以用基因工程来改造农作物,为什么不能用它来改造人自身呢?比如说这个桑比亚孩子,他的消化系统经过了重新编程,使他的食物范围大大扩展。对于这样的新人类,农作物完全可以改种一些速生或抗旱的植物,那些以前让我们头疼的疯长的野草对他们来说就是万倾良田。即使是种植传统作物,他们从土地中收获的粮食也要比我们多十倍,比如对于小麦来说,麦秸秆甚至根系他们都能食用。粮食对于他们,将真的如空气和阳光一样随手可得了。”

  各国代表都如石雕般站在大圆桌旁,把阴沉的目光聚焦到依塔身上,依塔坦然地承受着这些目光,平静地说:“尊敬的各位先生,我向联合国转达鲁维加总统的话:桑比亚已准备好为此承受一切。”

  主席首先从呆立的状态中恢复过来,撑着桌沿小心地坐下,好像他已虚弱得站立不稳似的,他两眼平视前方说:“您刚才好像说过,这孩子十二岁?”

  依塔点点头。

  “这么说,你们十二年前就对人类基因重新编程了?”

  “确切地说应该是十五年前,第一批编程是使用基因汇编语言进行的,半年后,编程工具改用面向过程的高级语言‘BASIC’。至于卡多,是用面向对象的‘伊甸园++’编程,这是三年以后的事了。我们从食草动物中提取了大量的消化系统的函数和子模块,去掉了反刍部分,经过优化和组合后植入人类的受精卵的基因编码中,但其中有许多程序,比如胃液的成分、胃壁的强度和肠道蠕动方式等,没有借用任何自然代码,纯粹是我们自行编制开发的。”

  “依塔博士,我们最后想知道,在桑比亚,经过重新编程的人类有多少?”

  “卡多这一批只有不到一百人,因为我们对面向对象的编程方式还没有十分把握。重新编程的桑比亚人只要是十五年前那两批,使用宏汇编语言和‘生命BASIC’编程的受精卵共有两万一千零四十三个,其中两万零八百一十六个成活并正常分娩。”

  哗啦一声,上届诺贝尔生物学奖获得者,法国生物学家弗朗西丝女士晕倒了。她旁边的另一位诺贝尔奖获得者,德国生理学家,本届生物安理会轮值副主席施道芬格博士脸色发紫呼吸急促,正闭着眼从胸前的衣袋中摸索硝化甘油片。只有美国代表很镇静,他指着依塔,转身对那个仍然目瞪口呆的警察说:“逮捕他。”

  他说得很平静,像是朝人借个火儿,看到那个警察茫然不知所措,他平静的薄纱立刻被摧毁了,如火山爆发般咆哮起来:“听到了吗?逮捕他!别管什么辖免权,那是对人的,不是对魔鬼!”

  主席站起身,试图使美国代表平静下来,然后转向依塔,眼里含着悲愤的泪水说:“博士,您和您的国家可以违反联合国生物安全条约的最高禁令,对人类基因进行重新编程,但你们不该如此猖狂,竟到这个神圣的地方来向全人类的脸上泼粪!你们违反了第一伦理,你们抽掉了人类文明的基石!”

  “人类文明的基石是有饭吃,桑比亚人只是想吃饱饭。”依塔向主席鞠了一躬,以他特有的缓慢语调说。

  “好了,我们还是散会吧。”美国代表对主席一挥手说,这时他真的平静下来了,“其实大家早就预料到这事迟早会发生,早些比晚些好。我想各位都知道我们该去做什么了,至少美国知道,我们要赶快去做了!”说完他匆匆而去。

  会议大厅中人们相继走散,最后只剩下依塔和卡多,还有那个警察。依塔搂着卡多的双肩向门口走去,警察阴沉地盯着孩子的背影,一手摸着屁股上的短管左轮低声说:“真该崩了这个小怪物。”

  消息传出,举世震惊。

  第二天,世界各大媒体上都出现了依塔和卡多的图像和照片。依塔用枯枝般的双臂把卡多紧紧搂在他那枯枝般的身躯上,眼睛总是看着地面,而那个黑孩子则强壮剽悍,两眼放光,与依塔形成鲜明对比。两人融为一体,形成了一个不规则的黑色构图,真是活脱脱的一对魔鬼。

  在以后桑比亚代表团逗留美国的两天里,世界各国要求就地逮捕他们的呼声日益高涨,联合国大厦前每天都有人山人海的抗议游行队伍。社会上对桑比亚代表团,特别是依塔和卡多两人的人身威胁层出不穷,但美国政府表现得十分克制,只宣布将代表团驱逐出境。

  这两天,依塔不分昼夜地紧紧搂着小卡多,在公共场合他的眼睛总是看着地面。但正如有记者描述,他有着“魔鬼的灵敏”,周围一有风吹草动,他立刻把孩子护到身后,并抬头凝视着异常出现的方向。他的眼窝很深,整个眼睛都隐没于黑暗中。活脱脱的魔鬼!

  桑比亚政府提出用专机接代表团回国,但美国政府不准桑比亚的飞机入境,别国又不肯租给他们飞机,只好乘欧洲的一架客机。为了安全,桑比亚政府买下了一等舱的全部机票。

  当桑比亚代表团登上飞机,依塔搂着卡多首先走进空荡荡的一等舱时,他长长地松了一口气,紧搂着卡多的手放松了些。在他们登机时,空中小姐表现出遇到魔鬼时理所当然的反应:满脸恐惧地避得远远的,只有一位欧洲空姐勇敢地领着他们进一等舱。这位金发碧眼的姑娘美丽动人,脸上露着真诚的微笑,温暖了桑比亚人那已凉透了的心。在走出机舱前,她双手合十,用不知从哪里学来的东方礼仪向孩子默默祝福,一时让旁边的桑比亚人的眼睛都湿润了。

  然后,她掏出手枪,紧贴孩子的头部开了两枪。

  与后来的传说不同,黛丽丝绝对不是美国政府或其它什么国家派来的杀手,她的谋杀完全是个人行为。事实上,在桑比亚代表团留美期间,美国政府对他们是采取了严密的保护措施的,文明世界要对付的是整个桑比亚国,这之前不想横生枝节,但这最后一击实在是防不胜防。班机上的空姐们都配有反劫机手枪,发射不会破坏机舱的橡木弹头,一般来说被击中后不会致命,但黛丽丝是贴着孩子的两眼开枪的。

  “我没有杀人,哈哈,我没有杀人!哈哈哈!”黛丽丝开枪后挥着沾满鲜血的双手歇斯底里地欢呼着。

  依塔抱着卡多的尸体,眼睛仍看着地面,一直等到黛丽丝安静下来。她把血淋淋的手指咬在嘴里,用疯狂的目光盯着依塔,一时间机舱里死一般寂静,只有孩子头部流出鲜血的汩汩声。

  “姑娘,他是人,他是我的孙子,一个能吃饱饭的孩子。”

  黛丽丝在法庭上被判无罪,很快被媒体炒成捍卫人类尊严的英雄。

  桑比亚代表团回国后的第二天,联合国向桑比亚政府发出最后通牒:交出境内所有生物学家和相应的技术人员,交出所有经过重新编程的个体,销毁所有基因工程设施,该国元首到特别法庭同其他主犯和从犯一起接受审判。

  现在,全世界都小心地把那些基因被重新编程的桑比亚人称为“个体”。

  桑比亚国拒绝了最后通牒,于是,为了维护人类神圣的第一伦理,文明世界向非洲开始了二十一世纪的十字军东征。

  下篇

  “您能不能停一会儿,我看着很累,您这么来回走了有一个多小时了。”布莱尔舰长说。

  菲利克斯将军仍然以军人标准的步伐来回踱着:“在西点,这是教官惩罚学生的办法之一:让他在操场的一角来回走几个小时。久而久之,我喜欢上了这种惩罚,只要在这时我才能很好地思考。”

  “这么说,您在西点是个不讨人喜欢的人。我在安纳波利斯海校却很讨人喜欢,那里也有这种惩罚,我一次也没受过,倒是在高年级时,我常用它来治那些刚进校的毛毛头。”

  “世界任何一所军校都不喜欢爱思考的人,安纳波利斯不喜欢,西点不喜欢,圣西尔和伏龙芝都不喜欢。”

  “是的,思考,特别是像您那样思考,对我是件很累的事。不过,我不认为这场战争有很多可以思考的东西。”

  对桑比亚的“外科手术”已持续了二十多天,每天有上千架次的飞机狂轰滥炸,从舰载机上的激光智能炸弹攻击到从阿森松岛飞来的大型轰炸机的地毯式轰炸,还有巡洋舰和驱逐舰上大口径舰炮日夜不停的轰击,这个非洲穷国实在剩不下什么了。他们那只有二十几架老式米格机的空军和只有几艘俄制巡逻艇的的海军,在二十天前就被首批发射的巡航导弹在半小时内毁灭,而桑比亚陆军的二百多辆老式坦克和装甲车也在随后的两三天内被来自空中的打击消灭干净。

  随后,攻击转向了桑比亚境内所有的车辆、道路和桥梁,而摧毁这些也用不了多长时间。

  现在,桑比亚国已被打回到石器时代。

  参加攻击的三个航母战斗群已撤走了两个,只留下林肯号战斗群完成“第一伦理”行动最后的使命。除了林肯号航母外,战斗群还包括一艘贝尔纳普级巡洋舰、两艘斯普鲁恩斯级驱逐舰、一艘孔兹级驱逐舰、两艘诺克斯级护卫舰、两艘佩里级护卫舰、一艘威奇塔级补给舰,还有三艘看不见的“鲱鱼”级攻击潜艇。

  菲利克斯将军突然从踱步中站住,看着布莱尔舰长,舰长很不舒服地想:这人确实像个学者,而且是神经衰弱的那种。

  “我还是认为我们离海岸太近了。”菲利克斯说。

  “这样我们可以向桑比亚人更有力地显示自己的存在。我不明白您担心什么。”舰长挥着雪茄说。

  舰队,特别是林肯号确实能显示其存在。它是尼米兹级航母的第5艘,于1989年服役,排水量近十万吨,全长三百多米,有二十层楼高,是一座带来死亡的海上钢铁城市。

  菲利克斯又接着踱起步来:“舰长,您清楚我的观点,我对现代化战争中航空母舰在海上的生存能力一直存有疑虑。在我的感觉中,航母总像是一只漂浮在海上的薄壳大鸡蛋,脆弱得很。”

  “您也知道,在参联会和军备听证会上,我是一贯支持您的看法的。但现在,桑比亚军队拥有射程最远的武器可能就是55毫米的迫击炮了,如果有,它也只能藏在地窖里,拉出来十分钟内就会被摧毁……事实上,我也觉得这是一场无聊的战争,军队在精神上正在衰落,主要原因是缺少自己的英雄偶像。二十世纪后期的几场战争,都没有造就出像巴顿、麦克阿瑟、艾森豪威尔的英雄,因为敌手太弱了,这次也一样。”

  这时,一名参谋递给菲利克斯一份电报,他看后喜上眉梢,这几乎是攻击开始后他第一次真正露出笑容。

  “看来这一切都快结束了,桑比亚政府已接受了所有条件,他们将很快交出桑比亚境内所有生物学家和基因工程师,以及所有基因被重新编程的个体,在这一切都完成后,元首将本人将投案自首。”菲利克斯把电报递给布莱尔。

  布莱尔看都没看就把电报扔到海图桌上:“我说过这是一场乏味的战争。”

  两位将军透过他们所在的航母塔岛上的舰长室宽大的玻璃窗看到,一架海军陆战队的直升机从海岸方向飞来,降落到林肯号的甲板上。依塔一行几人从直升机上走下来,并在周围陆战队员的枪口下低头向塔岛走来。依塔走在最前面,他仍穿着那身民族服装,像一根披着一块大布的老树枝。

  过了一会儿,这一行人走进塔岛,进入舰长室。除了依塔仍两眼朝下外,其他人都不由四下打量起来。如果只看四周,这里仿佛就是一间欧洲庄园的豪华餐厅,有着猩红色的地毯,华丽的镶木四壁上刻着浮雕,挂着反映舰长趣味的大幅现代派油画。但抬头一看,就会发现天花板是由错综复杂的管道组成的,这同周围形成了奇特的对比。高大的落地窗外,舰载飞机在不间断地呼啸着起降。

  依塔博士没有抬头,向菲利克斯所在的方向微微弯了一下腰,用虚弱的声音缓缓说:“尊敬的将军,我带来了桑比亚国真诚的敬意,您率领的舰队那天神般的力量令我们胆寒,我们屈服认罪。”

  菲利克斯将军说:“博士,我希望您真的明白你们在做什么。”

  “我们明白,在文明世界的上帝面前我们跪下,我们认罪,但将军,人要是饿得厉害,就顾不得什么廉耻了。”依塔深深地鞠躬说。

  周围一群年轻的参谋都用鄙夷的目光看着面前这根老干柴。“博士?”一直没说话的布莱尔舰长喊了一声,依塔微微抬头,被舰长呸的一口吐在脸上,他仍石雕般一动不动地立着,任白色的唾液顺着他那深纹密布的脸流到纷乱的胡子上。

  菲利克斯惋惜地摇摇头:“您本来可以不挨饿的,留在文明世界,您有可能再获得一次诺贝尔奖,却去为一个连人类最起码的伦理都不顾的极权政府工作。”

  “我为桑比亚人民工作。”依塔又鞠了一躬。

  “你给桑比亚人民带来了灾难。”菲利克斯说。

  “不管这场灾难是谁带来的,将军,鲁维加总统都殷切希望它快些结束。为表达这个和平的心愿,国王还给将军带来了一件小小的礼物。”

  依塔说完,从后面的一个人手中拿过了一个鸟笼大小的木笼子,依塔把笼子放到地毯上,轻轻打开笼门,一个雪白的小动物跑了出来,舰长室中的所有军人发出一阵惊叹声。那是一匹小马!它只有小猫大小,但在地毯上奔跑起来矫健灵活,雪白的鬃毛在飘荡,明亮有神的眼睛惊奇地看着这个世界,然后发出了一声清脆悠扬的嘶鸣。更奇怪的是,小马居然长着一对雪白的翅膀!

  他们仿佛看到了从童话中跑出来的精灵!

  “啊,太美了!我想这是您的基因软件的杰作吧?”菲利克斯惊喜地问。

  依塔又微微鞠了一下身回答:“这是马和鸽子的基因组合体。”

  “它能飞吗?”

  “不能,它的翅膀没那么大力量。”

  菲利克斯说:“博士,我代表贝纳感谢您,哦,贝纳是我的十二岁的小孙女,她为这礼物一定会高兴得发狂的!”

  “祝她幸福美丽,也祝未来的桑比亚孩子有他十分之一的幸运,十分之一就足够了,将军。”

  以后三天,大批的运输直升机频繁往返于桑比亚的内陆和沿海之间,从内地运来大批桑比亚政府交出的经过基因编程的“个体”,他们都是十五岁的黑人,绝大部分是男性。这些人被装上等候在沿海的运输船和登陆艇,每艘船装满后立刻向远海驶去。

  由于收到了中央情报局的一份紧急情报,菲利克斯将军决定再次召见伊塔。伊塔走进舰长室后,立刻目不转睛地看着窗外,在不远的海面上,几架体形庞大的支奴干运输直升机正悬停在一艘运输舰上方,黝黑的“个体”不停地从机舱中爬出,顺着软梯下到戒备森严的甲板上,然后在持枪士兵的推搡下进入舱里。

  菲利克斯来到伊塔身边,同他一起看着海上的情景,“这是最后几船了,三天运走了两万个个体。”

  “他们要被送到哪里?”伊塔问。

  “博士,这不是你我需要关心的事情。”菲利克斯冷冷地说。

  “我们所在的这艘大船叫林肯号是吗?”伊塔突然问,菲利克斯茫然地点点头。“怎么会叫这个名字呢?上上个世纪,非洲的黑奴就是这么被运走的,他们的基因并没有经过重新编程。”

  菲利克斯笑着摇摇头:“这是两回事,博士。我可以许诺,当这些个体还在我的管辖范围内时尽可能得到人道的待遇,就是野生动物也应该受到保护的,但仅此而已,他们以后的命运与我无关,与您也没有关系了。”

  看到伊塔沉默无语,菲利克斯接着说:“那么,我们谈正事吧。博士,我知道那些个体比正常人要健康得多,但他们有时也会得一些正常人不会得的病,比如前不久,在个体中传染一种皮肤病,虽不会致命,但患者十分痛苦。为了制止这种病的传染,你们研制了一种接种疫苗,委托欧洲的一家制药公司生产,据我所知,已交货的疫苗总量够四万个个体用的。”

  菲利克斯注意到伊塔掩着披布的一只手难以觉察地抖动了一下,但说话的声调仍是那么沉缓:“只有两万余名个体,将军。”

  菲利克斯点点头:“我愿意相信,博士,只是有一个小小的要求:能把那剩下的两万份疫苗让我们看一下吗?只是看一下,我们不带走,它们对正常人没用。”

  伊塔不说话。

  “您是想说,它们在轰炸中毁了吗?”

  伊塔缓缓地摇摇头:“不,那些疫苗都用完了。将军,我清楚您已经什么都知道了。”

  “是的博士,您撒了谎:十五年前重新编程的受精卵不是两万个个体!立刻把他们交出来。”

  伊塔把枯瘦的身体转向菲利克斯,眼睛仍然看着下方,这使人觉得他像一个人盲人,他说:“将军,在我的感觉中,您是一个明白人。”

  菲利克斯双眉一挑问:“哦,在哪些方面?”

  “很多方面,比如,您真是以一个十字军骑士的激情来领导这场战争吗?”

  菲利克斯摇摇头:“不,我是以很理性的态度来对待自己的使命的,对于国际社会在这件事情上的大惊小怪,我觉得多少是一种矫情。”

  伊塔无动于衷,倒是旁边的布莱尔舰长把目光从伊塔移到菲利克斯身上,吃惊地盯着他:“将军……”

  “随着本世纪头二十年基因工程突飞猛进的发展,人类社会的宗教情绪也与日俱增,表面看来这是对生命伦理的崇敬和维护,其实是人类在使其茫然的技术社会中试图找到一种精神依托的表现。”

  布莱尔大叫起来:“怎么能这样说将军?您应该知道,对人类基因的重新编程等于把人类置于与他自己可以随意制造的机器一样的地位,这将摧毁现代文明的整个法制和伦理体系基础!”

  “您把电视上的话背得很熟,”菲利克斯不以为然地笑笑说,“但您所说的信仰和伦理体系是以西方基督教文化为基础的,而别的文化并不一定认同这种体系。在伊塔博士的非洲文化中,创世主的概念是很模糊的,比如马萨伊曾说:”当神着手准备开创世界时,他发现那里有了一只多洛勃(狩猎的部落),一头象和一条蛇。‘就是说人类和其它生命是先在的,是一种自发的创造物。对人为干预生命的进化,并没有西方基督教文化这么多的忌讳。就以西方文化本身来说,它的法制和伦理也不会因为对人类基因的重新编程而崩溃,事实上,为了更小的理由,我们早就在违反第一伦理,比如本世纪出现的克隆人,上世纪的试管婴儿,更早一些的时候,我们那些高贵的女士为了少一些麻烦和责任,并没有太多的犹豫就去流产和堕胎了。在这些事实面前,我们的法制和伦理体系好像也很现实地适应了,并没有丝毫崩溃的迹象。至于西方世界对在非洲发生的这件事这么大惊小怪,不过是因为我们不需要以野草和树叶充饥罢了。“

  布莱尔目瞪口呆了好一会儿,迷惑地摇摇头。

  菲利克斯对伊塔笑笑说:“别在意,博士,布莱尔舰长显然平时很少思考这类问题。”

  “我的任务不是思考。”舰长气鼓鼓地说。

  “菲利克斯将军是个明白人。”伊塔真诚地说。

  “我已经足够坦率,那么请问博士,您是如何一眼把我看透的呢?”

  “不是一眼,我们十多年前见过面,那是在麻省理工的一次鸡尾酒会上,你当时还是一名准将,在南卡罗来纳州的新兵训练营负责新兵训练工作。您说在现在的美国青年中,可以招到像科学家的士兵,像工程师的士兵,像艺术家的士兵,但像士兵的士兵却越来越难找了。接着你就说,基因工程有可能为美国创造出合格的士兵,这是军方人士第一次在这样的生物学家会上说这种话,因此我记住了您。”

  “这真是一个好主意。”布莱尔舰长赞许地点点头。

  “所以,舰长,只要有需要,伦理终究是第二位的。”菲利克斯对布莱尔说,极力掩盖自己的轻蔑。

  “那么,将军,您一定理解我的恳求,求你们放过那两万个桑比亚人吧。”伊塔对“第一伦理”行动的指挥官连连鞠躬,看上去真像一个老乞丐。

  菲利克斯坚定地摇摇头:“博士,我是军人,在执行使命,这与我对基因工程的看法没有关系。再说一遍:把那两万个个体交出来,即使您认为他们是桑比亚的未来。”

  “将军,他们是全人类的未来。”

  “这没有意义,我们不但确切地知道那两万个体的存在,甚至能猜到他们的隐藏之处,如果你们拒绝交出,我们只能轰炸那些丛林。”菲利克斯把手向下一劈说。

  “知道怎样轰炸吗?”布莱尔把脸凑近伊塔说,“不是用林肯号上的飞机,它们太小了,是从阿松森基地飞来的巨型轰炸机,它们装满了燃烧弹,在那些丛林地带沿X形的对角线投弹,这样不管风向如何,都能形成一片完美的火场,其中的温度可以烧化桥梁,连细菌都活不下去。”

  菲利克斯接着说:“怎么样博士,即使为了那些个体着想,也应该把它们交出来。”

  伊塔用当地的土语哀叹了一句什么,整个身体像失去支撑似的摇摇欲坠。“给我电话,我向政府转达你们的意思。”

  “很好,还要说明,不能用上次的移交方式,从内陆用直升机运送两万人太困难,在降落地点和途中还不时遭到游击队的袭击。我们要求你们把那两万个个体运到海岸来,就在这片沿海平地上,在舰队的火力控制范围内。以上的事完全由你们来做,然后我们用登陆艇一次性接收。”

  “我转达。”伊塔无力地点点头。

  当伊塔随着押解的陆战队员走到舰长室门口时,他突然转过身来,美国人惊奇地发现他的腰不驼了,现在站得挺直,这才可以看到他原来是那么高大的一个人。他那双隐没于眼窝中黑影中的眼睛,自那仿佛看不见底的黑潭中射出两道冷光,令在场所有人打了个寒战。

  “离开非洲。”伊塔说。

  “您说什么?”布莱尔舰长问。

  伊塔没有理会,转身迈着大步走出去,那步伐之强健有力也与以前判若两人。

  “他说什么?”布莱尔又转身问其他人。

  “他让我们离开非洲。”菲利克斯说,双眼沉思地盯着伊塔离去的方向。

  “他……哈……他真幽默!”布莱尔大笑起来。

  入夜,在舰长室里,菲利克斯将军入神地看着桑比亚人送他的那匹小马,它正站在宽大的海图桌上,津津有味地吃着勤务兵刚送来的卷心菜。然后,他起身来到外面的舰桥上,凝视着远方的非洲海岸,一股热风吹到脸上,风中夹着烟味,远方的陆地笼罩在一片红光之中,那是桑比亚的城市在燃烧。火光映红了半边夜空,并在海水中反射,构成了一个虚假的黎明。

  “将军,看得出您很忧虑。”布莱尔舰长也悄声来到舰桥上,在菲利克斯后面问。

  “我们面对的,是一个被逼到墙角的民族。”菲利克斯看着燃烧的大陆说。

  “那又怎么样?在这个世界上,鸡蛋就是鸡蛋,石头就是石头,我相信一切都会很顺利的。”

  “但愿如此吧。四十多年前的那一天我记得很清楚,我和几名陆战队员一起守在西贡大使馆的楼顶,直升机正在运走最后一批人。文进勇将军指挥的北越军队离那儿只有几百米了,而美国在越南的势力范围,只剩大使馆楼顶这几十平方米了。一颗炮弹飞来,一名陆战队员被齐胸炸成两半,我还记得他的名字,他是最后一个死于越南的美国军人……那一时刻铭心刻骨,从此我明白了战争是一个很深奥的东西,谁都难以真正看透它。”

  当菲利克斯被一名中校参谋叫醒时,天刚蒙蒙亮。参谋告诉他,指定的海岸地段已经集结了两万多桑比亚人,好像就是桑比亚政府交出的那两万个个体。

  “不可能这么快的!”菲利克斯盯着参谋喊道,“他们靠什么集结?桑比亚大部分的公路和铁路都难以通行,就是有畅通的道路和足够的车辆也不可能这么快集结两万人!”

  菲利克斯起身抓起一个望远镜,冲到舰桥上,清晨的海风让他打了一个寒战,舰桥上已站满了举着望远镜观察海岸的海军军官,布莱尔舰长也在其中。

  向岸上望去,望远镜中出现的是从海岸伸延出去的广阔平原,燃烧的城市升起的烟雾如同平原后面一张巨大的黑灰色幕布。菲利克斯看到平原的地平线上有几个黑点,这些黑点渐渐变成了一条条黑线,很快,这些黑线连接起来,给地平线镶上了一道黑边。菲利克斯将军立刻看出了这不是那两万个等待接收的“个体”,而是一支准备发起攻击的陆军部队。他们队形整齐地推进着,菲利克斯放下望远镜,用肉眼也能看到桑比亚军队像黑色的地毯一样渐渐覆盖了平原。

  他再次举起望远镜,看到阵线在加快速度,很快整个方阵都飞奔起来,黑人士兵们高举着冲锋枪怒吼着,像潮水一样扑向大海。

  “桑比亚人要投海自杀?”舰队中所有目睹这一壮观景象的人都迷惑不解。在林肯号上,菲利克斯将军首先发现了什么,脸一下变得煞白,他扔下望远镜,声嘶力竭地大叫起来。

  “战斗警报!舰炮射击!所有攻击机起飞!快!”

  战斗警报尖厉地响起。已冲到海边的桑比亚步兵阵线中突然出现了一大片白色的东西,那一片白色急剧抖动着,激起了高高的尘埃,舰队的人们一时无法相信自己的眼睛。

  所有的桑比亚士兵都长着一对白色的翅膀,这是两万多名会飞的人!

  在一片尘埃之上,飞人升到空中,飞行的阵线黑压压一片,遮住了初升的太阳,这空中军队越海向舰队扑来。

  这时,舰队的宙斯盾系统已对来袭的飞人做出了反应,首批舰对空导弹从林肯号周围的巡洋舰射向飞人,约五十条白色的烟迹扎入了飞人群中。这首批导弹都击中了目标,清脆的爆炸声从空中传来,在一阵闪光后飞人群中出现了一团团黑烟,被击中的飞人血肉横飞,翅膀的白色羽毛如一片片细微的雪花在天空飘散。航母上观战的人们发出一阵欢呼声,但凭理智仔细观察攻击效果的菲利克斯将军和布莱尔舰长心凉了半截,一道简单但严酷算术题摆在他们面前。

  从现在的情况看,每枚航空导弹在击中目标时,弹头爆炸的杀伤力可击落周围2到3个人飞人。舰队的舰空导弹的弹头是为击毁空中战机这样的点状目标而设计的,爆炸时只产生很少的高速弹片,因而面积杀伤力不大,而飞人受到导弹攻击后正以很快的速度散开,所以,一枚舰空导弹很快只能击落一个飞人了。具有较强面积杀伤力的舰对舰导弹和巡航导弹对这样方向和距离的目标毫无用处。

  这里还有一个致命的弱点:舰队的舰空导弹中只有不到一半采用传统的红外、雷达和激光制导方式,这大多是上世纪就已准备的“海标枪”、“海麻雀”和“标枪”型舰空导弹。

  近年来,被这只强大舰队真正引以为骄傲的是采用像素制导的舰空导弹,像素制导是上世纪的导弹设计师们追求已久的梦想,在这种制导方式下,导弹感受到的目标不再是传统制导方式下的点状,而是一个三维图像,通过高超的模式匹配技术对目标进行识别,正如给导弹装上了一双智慧的眼睛,这就使得导弹可以打击目标最致命的部位,因而像素制导导弹的战斗部较传统导弹大为减小。但现在在这双智慧之眼中,那些飞人怎么看也不像是需要打击的空中目标,更像是大些的飞鸟,所以这些聪明的导弹都做出了理智的选择:绕开他们。人工智能再一次变成了人工愚蠢,更换每个导弹的模式数据库是无论如何也来不及了。

  整个舰队携带的舰对空导弹约为3000枚,这比正常情况已超载一倍了。这样数量的导弹在“宙斯盾”系统的引导下,足以对付一个大国的全部空军力量对舰队发动的攻击,进行这种攻击的敌机可能有两千架左右。而现在,舰队面对着十倍数量的飞人,每个飞人对舰只的攻击能力当然无法同战机相比,但要击落它,也要耗费一枚导弹。用航母上的战斗机对付飞人,道理也一样,况且战斗机可能来不及起飞。于是,两位将军,他们统率着这个星球上最强大的舰队,现在不得不承认这样的现实:对于飞人,航母战斗群的主要武器不再具有优势,质量代替不了数量。

  林肯号的周围,舰空导弹一批接着一批地发射,导弹的尾迹在空中组成一团巨大的乱麻。

  舰队没有人欢呼了,现在即使普通水兵也解开了那道算术题,以往他们最引以为自豪的东西现在也靠不上了。

  当所有的舰空导弹全部用光后,只击中了不到两千个飞人,而现在,从海岸方向向舰队冲来的飞人阵线前锋,已掠过了战斗群外围的巡洋舰和驱逐舰,直向林肯号航母扑来。

  现在,舰队只能依靠舰炮和机枪火力了,几乎所有的舰炮都全力射击。打击飞人最为有效的武器是密集阵火炮系统,它原是用于击落1500米范围内突破舰队防御系统的漏网反舰导弹的,它由6管20毫米火炮组成,具有每分钟3000发的高射速。密集阵火炮的每一次扫射,都在空中划出一条死亡的曲线,都有一排飞人被它那密集的弹流击落。但密集阵火炮无法长时间连续射击,它的高射速和快初速使炮管很快发热老化,必须频繁地更换,加上数量有限,它们最终也无法对来袭的大批飞人形成有力的阻击。其它的大口径舰炮射速太慢,同时,飞人的飞行轨迹是一条不断波动的正弦线,用普通舰炮对它们射击就像用步枪打蝴蝶一样,命中率很低。所以现在惟一能依靠的武器就是机枪了。

  这时,菲利克斯的脑海中浮现出古代中国关于冷兵器战争的一句话“临敌不过三发”,意思是说在敌人的骑兵冲到阵地前这段时间里,弓箭手只能射出三支箭,这绝妙地反映了目前林肯号的处境。

  现在,飞人开始对林肯号冲击了,飞人从各个高度接近航母,最高的飞人飞到上千米,最低的紧贴海面掠过。近两万名飞人使林肯号笼罩在一团死亡的阴云中,航母上的人听到从各个方向传来的飞人的呼喊声,这些声音使他们他们头皮发炸,抬头看着那密密麻麻的遮住阳光的飞人群在头顶盘旋,他们仿佛身处噩梦之中,同时也意识到一个严酷的现实:在高技术的温床中沉浸了几十年后,他们终于获得了一个成为真正战士的机会——要同敌人面对面肉搏了。

  意识到这点,菲利克斯反而冷静了许多,他拿起扩音器,沉着地发出命令:“立刻向舰上人员分发所有轻武器,重点防守塔岛、升降机口、弹药库、航空油库和核反应堆。这是最高指挥官在说话,全舰人员,准备接敌近战!”

  布莱尔舰长茫然地看着菲利克斯将军,好半天才理解了他的话的含义。他默默地走到海图桌面前,从一个抽屉里拿出自己的手枪,他看着枪,无言地沉思着。突然,他听到了一声悠扬的嘶鸣,是那匹小飞马发出的。舰长抬枪对着小马射出三发子弹,那个美丽的小精灵倒在血泊中。

  又一个措手不及的尴尬场面出现了:在早期航母中,轻武器是由各战位分散保管的,但由于自二战以来舰上人员从未有使用轻武器的机会,所以不知从什么时候起,现代航母上的轻武器都在一个专用仓库中集中保管。林肯号上有近六千人,除了岗位不能离开的人外,有近四千人拥向位于航母中层的军火库中去领枪,一时把狭窄的通道堵塞了。军火库门口更是乱做一团,负责发放武器的军官只能把枪向人群中扔,领到枪的人也挤不出去,只能把枪向后传,看上去很像近代某个城市暴动的场面。这时林肯号广阔的飞行甲板只能由舰上数量不多的海军陆战队守卫了。

  第一个飞人在林肯号的飞行甲板上着陆了,他那雪白的双翅轻盈地抖动,双脚接触到甲板时没发出一点声音。这时谁也不会认为他是魔鬼,这是希腊神话中才有的人物,是神灵的化身,它来自远古的梦幻,如同一个美丽的幻影降落到人类这粗陋的钢铁世界中。甲板上的陆战队员被他那惊人的美震撼了,很多人呆呆地站着,忘了开枪。但这个飞人战士还是很快被来自各个方向的弹雨击倒了,飞人倒在甲板上,双翅上雪白的羽毛被他自己的鲜血染红了。紧接着又有三个飞人着舰,其中一名幸存下来,躲到飞行甲板左舷的一个光学引导装置后面同陆战队员们对射起来。

  又有几个飞人降落被击毙后,飞人战士们意识到这时着舰代价太大,就开始从空中向航母投掷手榴弹。航母上的人们也尝到了被轰炸的滋味,当一大群飞人呼啸着从飞行甲板上空掠过后,手榴弹如冰雹般劈哩啪啦地落下,然后在一片爆炸声中,那些仍停在甲板上的昂贵的“雄猫”和“大黄蜂”一架架被炸成碎片。

  来自空中的手榴弹成功地遏制了航母上的轻武器火力,飞人的第二次强行降落取得了成功,很快就有上百名飞人战士登上了林肯号,他们依托着左右舷的下陷结构和甲板上飞机的残骸同舰上的陆战队和水兵枪战,掩护更多的飞人着舰。

  现在,令林肯号的守卫者们最尴尬的局面出现了:首先,他们在人员素质上处于劣势。经过基因优化,又在非洲丛林中成长的飞人是天生的战士,在这传统的近战中,他们骁勇敏捷,所向无敌。而林肯号上的人,除了为数不多的海军陆战队员外,其他人与其说是军人还不如说是工程师和技师,受过的陆战训练不多,在这残酷的近战中根本不是飞人战士的对手。最可怜的要数那些飞行员了,这些曾令多少敌人闻风丧胆的空中杀手,航母战斗群的刀锋,现在什么都不是了。布莱尔悲哀地从舰长室的窗中看到一名中校飞行员,缩在F14的座舱中,伸出手枪乱打一气,弹夹打光了还在不停扣扳机,直到一名脸上涂着红黑相间条纹的飞人爬上飞机,用一把猎刀砍下他的脑袋为止……

  更令“第一伦理”行动的执行者们无法忍受的是,他们现在在武器上也处于劣势!在这样的近战中,他们的M16步枪并不比桑比亚飞人手中古老的AK47好多少。而且,林肯号上轻武器库中的步枪只有不到两千支,这样,舰上大部分人只能用手枪作战了。林肯号上的6000官兵不过是被堵在钢铁中的一堆肉而已。

  在三个足球场大小的飞行甲板上,飞人仍在以很快的速度降落,现在,他们在舰上的人数已过千人。林肯号虽然在人数上仍占优势,但大部分人都被刚才飞人从空中的手榴弹轰炸堵在舱内,飞行甲板渐渐被飞人战士控制。现在,他们重点攻击的目标有两个:一个是飞机升降机口,这是进入舰体内最宽敞的通道;另一个是塔岛,这是航母的神经中枢。

  一群飞人从舰长室外掠过,可以听到手榴弹乒乒乓乓地砸在舱壁上,有一枚破窗而入,落到海图桌上。看着那个冒着青烟旋转的东西,菲利克斯将军仿佛走进了时间隧道,又闪回到他的青年时代。那是在热带暴雨中的越南丛林中,18岁的他也看到一枚手榴弹在眼前冒着青烟旋转,甚至外形也同眼前这颗一样,是前华约国制式武器,弹体和弹柄都是绿色的……对历史和现实的感触都凝缩在这生死一瞬,将军出神地盯着那个东西,多亏一名参谋把他扑倒在地。

  又过了十几分钟,着舰的飞人已超过两千,他们完全控制了飞行甲板,也成功地阻击了周围的巡洋舰和驱逐舰上的增援。现在从外面看,林肯号上已全是飞人的身影,AK冲锋枪嘶哑粗放的射击声盖住了一切,M16步枪纤细的啪啪声只能零星听到。

  突然,布莱尔舰长听到了一声爆炸,从升降机方向传来。同到处响起的手榴弹爆炸声相比,它很沉闷,只是隐隐约约能听到。他的心顿时沉到了底,作为一名经验丰富的军人他不会听错的,这是飞人战士在用塑性炸药炸开舰体内部的水密门,他们已进入了林肯号。菲利克斯也意识到了这点,他知道,现代巨型航空母舰的内部结构是极其复杂的,即使舰上人员,在没有地图的情况下也会迷路。但对于飞人战士,这可能不是个太大的障碍,因为他们要找的地方都是体积庞大的方位明确的。林肯号有三个致命处:弹药库、航空油库(存放着供舰上作战飞机使用的8000吨航空燃油)和为全舰提供动力的两座压水核反应堆,飞人战士找到这三样东西中的任何一样,林肯号就死定了。同时,核动力航母是一个极其复杂的系统,在内部随意的破坏也可能带来致命的后果。

  那不详的爆炸声又响了起来,一声比一声更沉闷,如同一只巨兽的脚步,一步步走向林肯号的深处走去……

  现在,结局只是时间问题。

  着舰的飞人已过五千,甲板上的战斗基本停止了,而指挥塔岛同全舰和外界的联系几乎中断,虽然塔岛还未完全失守,林肯号已失去了大脑。

  在以后的一个多小时内,林肯号几乎沉静下来,只有舰体内的爆炸声能隐约听到,而且向不同的方向扩散。飞人战士像进入林肯号这只巨兽体内的无数只蚂蚁,正在吞食着它的内脏。同时,飞人加强了对塔岛的攻击,在从下面攻打的同时,他们从空中直接跳到塔岛的上层建筑上。

  突然,林肯号微微振动了一下,布莱尔冲到窗边,看到大团的白色蒸气从舰体两侧升起,并听到一阵隆隆声,那是舰体下面的海水沸腾的声音。舰长知道,飞人战士找到了林肯号三个致命处的一个:核反应堆。虽然反应堆在舰体的最下部,但它们的方位是最明确的。

  飞人战士显然已炸毁了反应堆的冷却系统,布莱尔可以想像,堆中的反应物质如火山岩浆般流了出来,但它比岩浆灼热许多倍,它流到航母的舰底,就如同把烧红的火炭放到硬纸板上一样,很快就把舰底烧穿了。

  又一阵冰雹般的手榴弹扔到舰长室周围,震耳欲聋的爆炸后,AK冲锋枪密集地在外面响了起来,好像是一阵突然爆发的狂笑。保卫舰长室的陆战队员们在舱门和窗口相继倒毙,一群飞人战士撞开门冲了进来,他们的翅膀合在身后,像是披着白色的斗篷。布莱尔舰长伸手去拿放在海图上的手枪,立刻同几名年轻参谋一起被眼疾手快的飞人战士乱枪打死。菲利克斯将军手里握着枪,但没举起来,飞人战士盯着他肩上的四颗星,没有再开枪,他们就这样对峙着。

  飞人们突然向两边分开,伊塔博士走了进来。他们仍披着那块披布,同周围戎装的飞人战士形成鲜明的对比,一个飞人用生疏的英语让菲利克斯放下武器。

  菲利克斯仍紧握着手枪,用另一只手整理了一下军服:“开枪吧,黑鬼。”

  伊塔博士抬起头来,菲利克斯又一次看到了他那深邃的双眼。

  “将军,我们的血也是红的。”

  “你们可以击沉林肯号,但最后一个也跑不掉的!”

  伊塔笑了一下,这是菲利克斯第一次看到他笑。“他们当然能跑掉,他们可以任意飞越国境,雷达系统不能把他们同飞鸟区别开来,他们到处都能得到食物,即使是现代社会,要消灭这样一批人也是不容易的。更重要的是,他们很快就会成为合法的人,将享有作为一个人的一切权利。”

  “这我不明白。”

  “您是个聪明人,正如您所说,即使在所谓的文明世界,只要有需要,伦理是第二位的。

  那里的人们当然不需要吃野草和树叶,但他们肯定需要飞翔,这是人类最古老的梦幻,没人能抵挡它的诱惑。您将会看到,想像中的魔鬼并不存在,天使时代即将到来,在那个美好的时代里,人类在城市和原野上空飞翔,蓝天和白云是他们散步的花园,人类还将像鱼一样潜游在海底,并且以上千岁的寿命来享受这一切。将军,您已经看到了这个时代的曙光。“

  伊塔博士说完,转身走了出去,同时用桑比亚语说了句什么,接着所有的飞人战士都转身走了,没有一个人再看菲利克斯一眼。

  林肯号航空母舰直到黄昏时才完全沉没,当舰上的塔岛最后沉入水中时,被压出的空气发出巨大的嘶鸣,像非洲海岸凄厉的号角,菲利克斯将军站在一艘巡洋舰的舰桥上,用困惑的目光望着远方古老的土地。

  在那块百万年前诞生人类的土地上,飞人群正在夕阳中盘旋。

es6 扩展运算符 三个点(...)

发表于 2017-02-17

它是什么

es6中引入扩展运算符(…),它用于把一个数组转化为用逗号分隔的参数序列,它常用在不定参数个数时的函数调用,数组合并等情形。因为typeScript是es6的超集,所以typeScript也支持扩展运算符。

用在哪儿

可变参数个数的函数调用

function push(array, ...items) {  
array.push(...items);
}

function add(...vals){
let sum=0;
for(let i=0;i<vals.length;i++){
sum+=vals[i];
}
return sum;
}

let arr = [1,2,3,4,5,6];
let sum = add(...arr);
console.log(sum); //21

更便捷的数组合并

let arr1 = [1,2];
let arr2 = [5,6];
let newArr = [20];
//es5 旧写法
newArr = newArr.concat(arr1).concat(arr2); //[20,1,2,5,6]
console.log(newArr);
//es6 使用扩展运算符
newArr = [20,...arr1,...arr2]; //[20,1,2,5,6]
console.log(newArr);

替代es5的apply方法

// ES5 的写法  
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f(...args);

求最大值Math.max()

// ES5 的写法  
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);

通过push函数,将一个数组添加到另一个数组的尾部

// ES5 的写法  
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6 的写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1.push(...arr2);

新建Date类型

// ES5  
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);

与解构赋值结合,生成新数组

// ES5  
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
下面是另外一些例子。
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []:
const [first, ...rest] = ["foo"];
first // "foo"
rest // []

将字符串转为真正的数组

[...'hello']  
// [ "h", "e", "l", "l", "o" ]

将实现了 Iterator 接口的对象转为数组

var nodeList = document.querySelectorAll('div');  
var array = [...nodeList];

end 2017-02-17 14:46:14

ionic2页面page的生命周期钩子函数

发表于 2017-02-08

ionic2页面page的生命周期钩子函数

  • Page Event Returns Description

  • ionViewDidLoad void Runs when the page has loaded. This event only happens once per page being created. If a page leaves but is cached, then this event will not fire again on a subsequent viewing. The ionViewDidLoad event is good place to put your setup code for the page.

  • ionViewWillEnter void Runs when the page is about to enter and become the active page.
  • ionViewDidEnter void Runs when the page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page.
  • ionViewWillLeave void Runs when the page is about to leave and no longer be the active page.
  • ionViewDidLeave void Runs when the page has finished leaving and is no longer the active page.
  • ionViewWillUnload void Runs when the page is about to be destroyed and have its elements removed.
  • ionViewCanEnter boolean/Promise Runs before the view can enter. This can be used as a sort of “guard” in authenticated views where you need to check permissions before the view can enter
  • ionViewCanLeave boolean/Promise Runs before the view can leave. This can be used as a sort of “guard” in authenticated views where you need to check permissions before the view can leave

end 2017-02-08 11:44:47

参考文档:http://ionicframework.com/docs/v2/api/navigation/NavController/

ionic2使用根导航(rootNav)跳转

发表于 2017-02-08

ionic2使用根导航(rootNav)跳转

在一个使用tab构建的ionic2应用程序中,如果我们直接使用NavController进行导航跳转,会将新的page压入当前tab的导航栈中。但login、signin、setting等page在业务上不应该属于当前tab,而应该从根组件进行导航,那么此时该如何做呢?
看下面的例子即可:

在一个组件中跳转到根导航中

import { Component } from '@angular/core';
import { App, ViewController } from 'ionic-angular';

@Component({
template: `
<ion-content>
<h1>My PopoverPage</h1>
<button ion-button (click)="pushPage()">Call pushPage</button>
</ion-content>
`
})
class PopoverPage {
constructor(
public viewCtrl: ViewController
public appCtrl: App
) {}

pushPage() {
this.viewCtrl.dismiss();
this.appCtrl.getRootNav().push(SecondPage);
}
}

在主组件中跳转到一个根导航

import { Component, ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
template: '<ion-nav #myNav [root]="rootPage"></ion-nav>'
})
export class MyApp {
@ViewChild('myNav') nav: NavController
public rootPage = TabsPage;

// Wait for the components in MyApp's template to be initialized
// In this case, we are waiting for the Nav with reference variable of "#myNav"
ngOnInit() {
// Let's navigate from TabsPage to Page1
this.nav.push(Page1);
}
}

end 2017-02-08 11:32:08
参考文档: http://ionicframework.com/docs/v2/api/navigation/NavController/

Angular组件生命周期和钩子函数

发表于 2017-02-08

Angular组件生命周期和钩子函数

概述

angular2中每个组件都有其生命周期,包括创建组件、渲染组件、创建渲染子组件、绑定的属性发生变化时检查它、从dom中移除。
angular2提供了声明周期钩子函数,把这些关键的周期暴露给我们,以便我们做一些自定义的操作。

屏幕快照 2017-02-08 10.49.03

声明周期顺序解释

  • ngOnChanges 当angular绑定的输入属性发生变化时响应,首次调用一定会发生在ngOnInit之前
  • ngOnInit 初始化指令/组件,在第一轮ngOnChanges完成之后调用,只调用一次
  • ngDoCheck在每个angular变更检测周期中调用,在ngOnChanges和ngOnInit之后调用
  • ngAfterContentInit 把内容渲染到组件之后调用,第一次ngDoCheck之后调用,只适用于组件,只调用一次
  • ngAfterContentChecked 每次完成内容到组件的渲染的变更检测后调用,ngAfterContentInit和每次NgDoCheck之后调用,只适用于组件
  • ngAfterViewInit 组件视图及其子视图初始化完成之后调用,第一次ngAfterContentChecked之后调用,只调用一次,只适合组件
  • ngAfterViewChecked 每次做完组件视图和子视图的变更检测之后调用,ngAfterViewInit和每次ngAfterContentChecked之后调用,
    只适合组件
  • ngOnDestroy 在Angular销毁指令/组件之前调用。

钩子函数执行顺序的例子

屏幕快照 2017-02-08 11.17.24

当输入属性发生变化时,就会看到OnChanges和至少两组DoCheck、AfterContentChecked和AfterViewChecked钩子执行。这四个钩子会执行很多次,应该让它们的逻辑尽可能精简,否则可能会影响用户体验。

再详细解释几个钩子函数

ngOnInit

  • ngOnInit是组件获取初始数据的好地方
  • 在构造函数中只执行简单的局部变量初始化工作,其他什么都不要做
  • 在component中,构造函数完成之前,那些绑定的输入属性还都没有值;在ngOnInit执行的时候,这些属性都已经被正确赋值过了

我们访问这些属性的第一次机会,实际上是ngOnChanges方法,Angular会在ngOnInit之前调用它。 但是在那之后,Angular还会调用ngOnChanges很多次。而ngOnInit只会被调用一次

ngDoCheck

  • 使用DoCheck钩子来检测那些Angular自身无法捕获的变更并采取行动
  • 这个钩子被调用的十分频繁,应该使其实现尽可能的精简

AfterView

AfterViewInit和AfterViewChecked钩子,Angular会在每次创建了组件的子视图后调用它们。

最后说明

这是angular2组件自带的生命周期钩子函数,每个基于angular2的框架还可能会有自己的钩子函数,用以方便程序的编程。比如ionic2框架的view就有自己的生命周期钩子函数。


end 2017-02-08 11:32:00

git tag 标签使用说明

发表于 2017-01-22

git tag 标签使用说明

git tag命令可以对某一时间点做标记,常用于版本发布。

查看标签

$ git tag           # 查看所有标签
$ git tag -l 'v1.' # 搜索符合模式的标签
$ git show v1.0 # 查看某个标签详情

添加标签

附注标签(推荐)

附注标签则是仓库中的一个独立对象,建议使用附注标签:

$ git tag -a v1.1 -m "1.1版本"

参数a即annotated的缩写,指定标签类型,后面是标签名,参数m指定标签说明,说明信息会保存在标签对象中

轻量级标签

轻量标签是指向提交对象的引用:

$ git tag v0.1 -light

为某次commit打标签

上面的命令默认将标签打在head上,如果需要我们也可以为之前的提交版本打标签:

$ git log --oneline        # 显示版本提交历史
$ git tag -a v0.1 c8cbe39 # 为c8cbe39添加标签

切换标签

与切换分支命令相同:

$ git tag                 # 显示当前分支下的标签
$ git checkout [tagname] # 此时指向标签指向的代码状态(此时处于空分支)
$ cat a.txt # 查看某个文件
$ git checkout master # 切换回master

删除标签

$ git tag -d v0.1 # 删除标签v0.1

注意:误打或需要修改标签时,需要先将标签删除,再打新标签。

标签发布

git push不会将标签信息提交到远程服务器,需要显示更新标签:

$ git push origin v0.1   #将v0.1标签提交到git服务器
$ git push origin –tags #将本地所有标签一次性提交到git服务器

end 2017-01-22 15:38:33

为本地git仓库新增github远程仓库

发表于 2017-01-19

为本地git仓库新增github远程仓库

人已新建过几个github仓库,但每次用的方法不一样,这里整理一个简单的方法流程,供以后参考。

  1. 登录github,新建一个空repository
  2. 为本地项目添加远程地址

    $ git remote add origin git@github.com:hao5743/gittest.git
  3. 推送到远程

    $ git push -u origin master

    第一次推送的时候,需要加上-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,以后就可以简化命令

    之后使用git push,git pull命令就行了


end. 2017-01-19 17:55:57

关于gitosis

发表于 2016-12-30

关于gitosis

在工作中,我们有自己的git服务器,但是随着git项目的增多和项目成员的增多,我们开始使用了Gitosis软件。使用它可以方便的管理与git相关的如成员公钥、权限、项目成员等问题,这里我只对我项目中用到一些简单配置做了整理和记录,并非全面的介绍gitosis。这里提供一份更为详尽介绍的文档。

Git是非常著名的分布式版本控制系统。

Gitosis则是方便通过Git与ssh架设中央服务器的软件。

安装

已经配置安装好,故没有从头介绍,如需要看这个安装步骤

说明

gitosis安装好之后的几个目录说明:

/home/git目录: gitosis默认把这里作为存储所有 Git 仓库的根目录

/home/git/repositories目录: 这里是存放需要管理的git项目(无需直接操作这个目录,要用gitosis来管理)

管理

远程克隆gitosis到本地,方便管理

# 在你的本地计算机上
git@gitserver:gitosis-admin.git

如果你无法克隆,说明你没有权限,也就是gitosis.conf中[group gitosis-admin]没有存储你的公钥。你需要联系管理员给你管理权限。

创建者或管理员新增一个管理员权限

# 在创建者或管理员的机器上(如有略过)
$ git clone git@gitserver:gitosis-admin.git
$ cd gitosis-admin.git
# 被添加者上传自己公钥(如有略过)
$ scp ~/.ssh/id_rsa.pub 用户名@主机:/tmp
# 管理员拷贝公钥到keydir,并重命名(如有略过)
$ cp /tmp/id_rsa.pub keydir/tom.pub

# 开始配置
# 编辑gitosis.conf,在[group gitosis-admin]下members中添加成员tom
$ vim gitosis.conf
# ...编辑操作...wq...
# [group gitosis-admin]
# members = admin tom
# writable = gitosis-admin

# 提交更改(有可能需要先git add)
$ git commit
$ git push

# OK!tom用户现在已经有gitosis的管理权限了,
# 它可以克隆gitosis-admin.git它的个人电脑上,并执行管理操作

目录说明:

gitosis.conf文件: 此处用于项目配置,包括gitosis管理员、git项目读写权限

keydir目录: 此处存放着相关用户的公钥

git status可能的情况

发表于 2016-12-29

git status可能的情况

工作中我们使用git作为版本管理工具,它的确是一个强大的工具。易于学习和使用,但是要学习的面面俱到也很难,就git status就可能会出现n多种的情况。本文记录了git status可能出现的不同状态信息、状态说明、处理办法。注意,列举的情况并不全面,只是列举了工作中遇到的一些常见情况,同时遇到新状态,还会随时增加。

git仓库中文件的状态
git仓库中所有文件可分为两种状态已跟踪和未跟踪。已跟踪的文件被git纳入了版本控制,git会记录它的每次更新状态。
状态生命周期有四种:untracked, unmodified, modified, staged

git status可能的情况:

1. Your branch is ahead of ‘origin/master’ by 1 commit.

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working directory clean
cochona@cochona:~/gitosis-admin/keydir$

此时说明当前目录中所有的已跟踪文件,都已经和远程保持同步,并且没有修改。刚从远程克隆的git仓库,都是这个状态。

2. nothing added to commit but untracked files present

$ git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
a.text
nothing added to commit but untracked files present (use "git add" to track)

表示此时目录下,有新添加的未被跟踪的文件,如果我们需要将此文件提交到仓库,可以执行git add

3. Changes to be committed: new file

$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README

在“Changes to be committed”这行下面的都是已暂存的状态,此时可以执行git commit

4. Changes to be committed: modified

cochona@cochona:~/gitosis-admin$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: gitosis.conf

有修改的文件,此时可以选择直接git commit -m "info"

5. Changes not staged for commit: modified

cochona@cochona:~/gitosis-admin$ git commit -m "add shaoc@pub to admin-group"
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
modified: gitosis.conf

no changes added to commit

此时如果要保存修改,需要先git add gitosis.conf,然后git commit -m "",然后git push

ionic state命令将要废弃

发表于 2016-12-29

ionic state命令将要废弃

参考自官方网站

Discuss: Deprecate/Remove state command #904

cordova说明

The state command was created to manage Cordova plugins before it was supported in Cordova. Cordova now manages plugins and platforms in config.xml: https://cordova.apache.org/docs/en/latest/platform_plugin_versioning_ref/index.html so there is a decent amount of confusion between the Ionic and Cordova behavior. As Cordova platforms and plugins are not an Ionic concern, let’s deprecate the state command and document the existing Cordova command workflow, ie plugin add –save.

state命令是之前用来管理cordova插件的。现在Cordova自身已经开始通过config.xml来支持插件和平台管理(官方说明),这会让我们对ionic和cordova的不同的管理方式感到困惑。因为cordova的平台和插件是ionic所不关心的,所以建议停止使用state命令,而使用cordova的管理方式,比如plugin add --save

对于插件我们应该这样管理

重装所有插件

rm -rf plugins
# 更新插件,执行时会自动安装在config.xml中的插件
cordova prepare

从工程中彻底删除一个插件,–save命令会将删除或更改影响到config.xml,与package.json无关

cordova plugin reomve ionic-plugin-keyboard --save
cordova plugin add ionic-plugin-keyboard --save

platform add的时候会自动检测config.xml,并安装记录的插件

cordova platform add android

Mac配置oh-my-zsh

发表于 2016-12-28

Mac配置oh-my-zsh

mac环境变量保存的地方

没有zsh的时候,mac中的环境变量保存在:

1./etc/profile (建议不修改这个文件 )

全局(公有)配置,不管是哪个用户,登录时都会读取该文件。

2./etc/bashrc (一般在这个文件中添加系统级环境变量)

全局(公有)配置,bash shell执行时,不管是何种方式,都会读取此文件。

3.~/.bash_profile (一般在这个文件中添加用户级环境变量)

每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行

如果要修改环境变量,一般修改~/.bash_profile就行了。

查看环境变量

$ printenv

查看当前使用的shell

$ echo $SHELL
/bin/zsh

zsh安装后

安装zsh后,默认情况下就不会自动读取~/.bash_profile了。
在用户目录下应该有.oh-my-zsh目录,和.zshrc配置文件

  1. .oh-my-zsh目录: 它是zsh的安装文件夹,可以自己更改

  2. .zshrc: 里面是zsh默认配置,可以用于设置环境变量(export),alias命令别名,设置主题等

    但是zsh不建议直接操作这个默认配置,如果用户需要自定义配置,推荐去这里./oh-my-zsh/custom/custom.zsh修改。注意:custom文件夹里的所有配置都会被zsh自动读取并配置。

  3. ./oh-my-zsh/custom/my_custom.zsh:用户设置自定义系统变量、自定义命令等等

    一般情况下,我们在./oh-my-zsh/custom/my_custom.zsh中配置一个快捷键

    alias zshconfig="subl ~/.oh-my-zsh/custom/my_custom.zsh"

综上,如果要配置环境变量的步骤

  1. 执行zshconfig,会自动使用你定义的命令,打开配置文件
  2. 在该文件中添加你想要添加的环境变量,比如export ANDROID_HOME=/Development/android-sdk/
  3. 重启cmd,生效!OK。配置完成

备份一个我自己的配置文件

./oh-my-zsh/custom/my_custom.zsh如下:

alias zshconfig="subl ~/.oh-my-zsh/custom/my_custom.zsh"
alias code=\''/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code'\'
alias subl="'/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl'"
alias nano="subl"
alias sl="subl"
alias py="python"
alias nw="/Applications/nwjs.app/Contents/MacOS/nwjs"
export ANDROID_HOME=/Development/android-sdk/

vscode中beautifyrc插件配置文件翻译和设置

发表于 2016-12-26

vscode中beautifyrc插件配置文件翻译和设置

2016-12-26 23:08:09

在 VisualStudio Code 中有beautify插件,用来格式化代码,下面是研究它的一些配置说明,一些英文缩写还是很难一眼看懂的,下面记录下来。

顺便给出一个英文的设置说明:https://github.com/HookyQR/VSCodeBeautify/blob/master/Settings.md.英文好的话,直接去看就行啦。

配置说明:


{
"indent_size": 4, //缩进大小,默认4
"indent_char": " ", //缩进字符,默认" "
"eol": "\n", //end of line,行结尾字符"\n"
"indent_level": 0, //初始缩进级别
"indent_with_tabs": false, //使用tab缩进,将会覆盖“indent_size”和“indent_char”设置,默认false
"preserve_newlines": true, //保留空行,默认“true”
"max_preserve_newlines": 10, //一次最多保留多少行的空行,默认10
"jslint_happy": false, //开启jslint-stricter的严格模式(强制开启“space_after_anon_function”选项),默认false
"space_after_anon_function": false, //在匿名函数前自动加一个空格,比如`function (){}`,默认false
"brace_style": "collapse", //括号风格,"collapse-preserve-inline", "collapse", "expand", "end-expand", or "none" ,默认“collapse”
"keep_array_indentation": false, //保持数组缩进,默认false
"keep_function_indentation": false, //保持函数缩进,默认false
"space_before_conditional": true, //在条件语句之前保留一个空格,默认true
"break_chained_methods": false, //中断多行间的链式方法调用,默认true
"eval_code": false,
"unescape_strings": false, //解码用xNN编码的可打印字符,默认false
"wrap_line_length": 0, //Wrap lines at next opportunity after N characters. (Set zero to ignore wrapping),默认0,下次在n个字符后换行
"wrap_attributes": "auto", //将html属性标签放在新行“auto”,“force”,默认auto
"wrap_attributes_indent_size": 4, //html属性标签新行缩进字符数,默认为"indent_size"4
"end_with_newline": false //在文件结尾保证有换行,默认false
}

indentation 缩进

ReactNative环境安装

发表于 2016-12-17

ReactNative环境安装

2016年12月17日13:04:16

在安装rn,初始化rn应用,以及npm install rn工程的时候,要尽量注意做到使用npm,不要使用cnpm代替,可能会出现未知的错误。

1.安装node、配置镜像加速

node安装过程略。

建议一定要设置镜像来加速,不然速度会很慢:

npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global

2.安装rn和yarn

npm install -g yarn react-native-cli

Yarn是Facebook提供的替代npm的工具,可以加速node模块的下载。React Native的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。

如果出现错误,考虑修改权限

sudo chown -R `whoami` /usr/local

3.选择安装

brew install watchman

Watchman是由Facebook提供的监视文件系统变更的工具,安装此工具,packager可以快速捕捉文件的变化从而实现实时刷新,提高效率。

brew install flow

Flow是一个静态的JS类型检查工具,它只是Facebook自家的代码规范。新手可以选择跳过。

4.新建测试项目

react-native init AwesomeProject
cd AwesomeProject
react-native run-ios

渐进式Web应用程序在现代移动环境中的优势

发表于 2016-12-08 | 分类于 翻译

渐进式Web应用程序在现代移动环境中的优势
The Advantages of Progressive Web Apps in the Modern Mobile Environment

文章翻译自:http://blog.ionic.io/the-advantages-of-pwas/
2016/12/1,作者:Justin,译者:shaochong

我们一直在谈论Progressive Web Apps(下文简称PWA)- 它是用现代化的web API如service workers,web manifest和web push来构建的web应用程序,让人们在浏览器中就可以体验到和本地程序一样棒的使用体验。

我来解释几个概念(译者注)
service workers是啥玩意?你可以这么理解,它运行于你当前页面主线程外的另一个线程中,用来加速你的APP,以它独特的技术来建立高效的离线体验,拦截网络请求,并且会根据当前的网络是否可用、服务器的内容是否更新来采取合适的策略。他们还允许通知推送和后台同步API。更多请参见MDN Service Workers。
web manifest又是啥玩意?它提供了一个文本文件的应用程序(如名称,作者,图标和描述)的信息,其目的是安装Web应用程序到设备的主屏幕,提供更快速、更丰富的用户体验。更多参见MDN Web App Manifest。
web push是什么鬼?Web Push API让web应用有了可以接受来自服务端推送来的消息的能力,无论web应用是否在前台运行,甚至是否加载完成。这让开发者可以实现异步通知和更新,及时的数据更新会让用户有更好的参与感。更多请参见Push API

全新的移动环境

在我们谈论PWA的优势之前,有一件很重要的事情,那就是要搞清楚现在的移动环境和我们过去的开发环境有什么不同的地方。随着发展中国家越来越多的人能够通过他们的移动设备访问互联网,我们也要准备好为他们构建app,就像为在旧金山使用iphone7的人一样。
一,这些用户主要是通过2G或这3G来上网,它们网速很糟糕。
二,他们的设备通常有1-2G的RAM,慢速四核CPU,4GB的磁盘存储空间,运行android5.0或更高。
三,移动数据上网费用还是很昂贵的。到2016年,印度1GB移动数据平均成本为3.81美元,而最低工资为每小时31美分。这是一个很具有挑战性的应用场合,但是通过PWA我们仍然可以构建很棒、用户体验很好的应用程序。

为什么PWAs在现代移动环境中有着无与伦比的优势

和本地原生应用相比,PWA有三个关键的特点,让它能够构建功能更完善、用户体验更好的应用程序。

  • 高效的使用磁盘空间
  • 高效的使用网络
  • 不需要一次性安装大型的安装包

Service workers可以帮我们完成这三个工作。
举个例子来说,service workers 能够让我们动态的缓存资源到本地设备,所以即使是在慢速、糟糕的网络环境中,我们仍能够提供很好的使用体验。这里的关键点是动态缓存。这意味着在慢速糟糕的网络环境中,我们可以优先缓存那些对程序来说最重要的资源,而无需去下载一个大的APK文件。比如,对于一个电商app,你可能不希望将app中的每一个产品页都缓存到本地。我们仅仅需要将那些之前浏览过的产品页和app启动所必须的资源文件缓存到本地。
此外,由于是网站的工作方式,用户不必一次性的下载大的app文件。他们只需要下载他们所访问的PWA页面,并且通过使用service worker的动态缓存,我们可以选择仅缓存我们PWA应用的重要页面,因此这些页面只需要下载一次即可。我们可以通过Flipkart PWA来看看实际效果。在我的Nexus6上,他占用了大约351KB的存储空间。而在同一台设备上,Twitter的应用程序占用了69.36MB。如你所见,差距很大,在那些只有4G存储空间的设备上,差距将更加明显。

Ionic对于PWA的计划

目前在ionic中支持对PWA的开箱即用,而且在不久的将来我们将会使它的支持更加强大。我们现在默认提供一个service worker和web manifest,让你的ionic应用具有PWA应用的能力,比如离线缓存,浏览器提示添加到主屏幕等。
此外,由于ionic是使用纯web技术来构建的,所以它可以很好的在浏览器中工作。展望未来,我们已经致力于通过代码拆分(code splitting),树形目录(treeshaking)和属性重命名(property renaming)来使我们的捆绑包更小,这可以让你的ionic2应用运行的更快。我们知道,在移动网络上,加载时间是很重要的,相信通过这些改变,我们可以更好的满足这一点。
使用Progressive Web Apps和现代化网络的强大功能,即使在低端,便宜的设备上,我们也能提供出色的用户体验。 PWA为移动应用程序提供了全新的范例,这对新的移动环境至关重要。 下次当你有一个极好的构思和想法,而且想要把它构建为移动app的时候,考虑将其构建为一个PWA应用,这可以让你的app触及到下一个十亿级的用户群体。

译者:shaochong

E-mail:hao5743@163.com

博客:http://hao5743.github.io/

在angular1中自定义directive时数据显示不同步问题的分析和解决

发表于 2016-12-05 | 分类于 Angular

在angular1中自定义directive时数据显示不同步问题的分析和解决

2016-12-05 16:33:38

在angular1项目中,有的时候我们需要写自定义directive来完成某些应用逻辑,但是如果你有过一些的angular1项目的开发经验,肯定会因为一些莫名其妙数据双向绑定,回调函数无法调用执行等问题,困扰过。下面是一个简单的案例。

问题引出

这里我们写一个简单的directive来说明问题。

directive.js


angular.module('app.directive')
.directive('ayTest',function(){
return {
restrict: 'EA',
scope: {
name:'='
},
template:`
<div class="padding mystyle">
{{vm.name}}
</div>
`,
controller:function($scope){
var vm = $scope.vm = {};
vm.name = $scope.name;

}
}

});

A.controller.js

var vm = $scope.vm = {};
vm.name = 'Tom1';
$timeout(function(){
vm.name = 'Tom2';
},1000);
$timeout(function(){
vm.name = 'Tom3';
},2000);

A.html

<ay-test name="vm.name"></ay-test>

这个directive可以实现以我们自己的格式展示‘name’的功能,我们可以通过在template中填写自己独有的style或者嵌套div来完善name展示模块。

在这我们希望name能够实现双向绑定,即当在A页面中name值发生变化的时候,directive也自动获得更改。

但是,在这里并不能实现这个功能,当name显示为Tom1之后,就不会再变化了。

###为什么会这样呢?

我们都知道自定义directive时,有3个绑定符号,分别是

  • @ 单向绑定属性值
  • = 双向绑定属性
  • & 绑定用户函数

这里为了实现信息的双向绑定,已经使用了=,为什么还无法完成双向绑定呢?对于大神们,当然能一眼看出问题的所在,但小白可能会摸索比较长的时间~~~~

问题出在vm.name = $scope.name;这句代码上。因为name是一个基本字符串,赋值时,相当于对vm.name创建了一个新值,并赋值为$scope.name的值。这样,虽然controller中的$scope.name和页面上的仍存在双向绑定,但是和我们的vm.name却无任何关系了。

我们应该还知道下面的这个问题:

//controller.js
$socpe.username = 'Mary';
//B.html
<input ng-model='username' />

如果你按照上面的方式来编码,十有八九会出现意外情况。

出现这样错误的根本原因,其实还是对JavaScript变量的值访问、引用访问,开发中的值传递、引用传递没有理解清楚。下面说一些这方面的问题。

Js基本类型、值传递

下面是笔者摘自《JavaScript高级程序设计》中的几段话,读者可以参照理解。这里并不是笔者自己懒,而书中的表达更准确,而且也很容易理解~~(如果你说不知道这本书,推荐快去读一下,如果你做Js相关开发,会让你受益匪浅,我能给你电子版,联系hao5743@163.com )

5种基本数据类型:Undefined、Null、Boolean、Number和String,5种基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。

引用类型的值是保存在内存中的对象。于其他语言不同,JavaScript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。引用类型的值是按引用访问的。

–参见《JavaScript高级程序设计》P69 4.1 基本类型和引用类型的值

如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。

–参见《JavaScript高级程序设计》P69 4.1.2 复制变量值

ECMAScript中所有函数的参数都是按值传递的。也就是说,把函数外部的值赋值给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,二引用类型值的传递,则如同引用类型变量的复制一样。有不少开发人员在这一点可能会感到困惑,因为访问变量有按值和按引用两种方式,而参数只能按值传递。

–参见《JavaScript高级程序设计》P70 4.1.3 传递参数

解决方案

理解了上面的话,那么你就能轻易用不同的方案解决这个问题了。

方案1 直接使用$scope.name展示数据

directive.js


angular.module('app.directive')
.directive('ayTest',function(){
return {
restrict: 'EA',
scope: {
name:'='
},
//这里直接使用name,不再使用vm.name
template:`
<div class="padding mystyle">
{{name}}
</div>
`,
controller:function($scope){
var vm = $scope.vm = {};

}
}

});

不再使用vm的方式,而是直接绑定到页面,可以解决问题。

方案2 使用引用类型打包基本类型再传递

如果,你在angular1开发中,一直遵循了好的实践,那么有可能vm.name你可能已经使用习惯了。也许你会问,如果我还想使用vm.name的方式,我想遵循官方给出的最佳实践,怎么解决这个问题呢?

(什么是最佳实践?如果你是angular开发者,而还不知道的话,那么一定要去读读,原版在这里,英文差的看这里)

当然有方法啦!看下面
controller.js

var vm = $scope.vm = {};
vm.name = {};
//将数据封装到到内部属性data上
vm.name.data = 'Tom1';
$timeout(function(){
vm.name.data = 'Tom2';
},1000);
$timeout(function(){
vm.name.data = 'Tom3';
},2000);

A.html

<ay-test name="vm.name"></ay-test>

directive.js

.directive('ayTest',function(){
return {
restrict: 'EA',
scope: {
name:'='
},
template:`
<div class="padding">
{{vm.name.data}}
</div>
`,
controller:function($scope){
var vm = $scope.vm = {};
//注意,虽然这里重新赋值了name,但是我们的vm.name.data还是同一份,所以仍能实现绑定
vm.name = $scope.name;

}
}

});

方案3 使用$scope.$watch

如果你知道$scope.$watch,并且了解何时使用它,那么你应该知道这里也可能使用它来解决。看代码。

controller.js

var vm = $scope.vm = {};
//并没有进行数据封装
vm.name = 'Tom1';
$timeout(function(){
vm.name = 'Tom2';
},1000);
$timeout(function(){
vm.name = 'Tom3';
},2000);

A.html

<ay-test name="vm.name"></ay-test>

directive.js

angular.module('app.directive')
.directive('ayTest',function(){
return {
restrict: 'EA',
scope: {
name:'='
},
template:`
<div class="padding">
{{vm.name}}
</div>
`,
controller:function($scope){
var vm = $scope.vm = {};
//添加数据监测
$scope.$watch('name',function(newValue,oldValue){
vm.name = $scope.name;
});

}
}
});

这个方法其实相当于多加了一层的监测,vm.name和页面上的绑定,通过$scope.$watch,我们让vm.name和$scope.name两个变量的值保持一致性。

哪个方法好呢

个人推荐第一种方法,因为他所做的工作最少,效率相对较高(虽然高不了很多)。

最后的话

本文首先提出了在编写自定义directive时有时候会出现的数据无法同步的问题。随后分析了问题,并给出了几种解决方案。

其实,问题的根本首先在于对JavaScript变量值访问、引用访问和值传递等概念的理解,其次在于对angular1scope层级、双向绑定、脏值检测等概念的理解。

有时,还能需要处理其他情况,比如默认值处理,分条件展示,错误值处理、数据回显、回调函数处理等具体问题,可以参考上面3种思路来具体实现。

很简单一个问题,个人只是做了一下分析,有错误的地方请大神及时指出,感激不尽~~

Author:shaochong

Email:hao5743@163.com

文章地址:https://hao5743.github.io/2016/12/05/Data-bind-problem-in-Angular-when-define-your-own-directives/

博客:http://hao5743.github.io/

彻底解决Ionic项目中的跨域问题

发表于 2016-12-03 | 分类于 ionic

在ionic项目中,如果你使用ionic serve或者ionic run,并且开启了动态加载(live reload),且访问了远端服务器的API,那么你就可能会遇到 跨域资源共享(CORS) 问题。出现类似下面的错误提示:

XMLHttpRequest cannot load http://api.ionic.com/endpoint.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:8100' is therefore not allowed access.

那么,什么是CORS? 为什么在这里会发生这个错误呢?

什么是CORS?

CORS = Cross origin resource sharing 跨域资源共享
origin是你当前所在页面的主机地址。
如果你在浏览http://ionicframework.com/blog/handling-cors-issues-in-ionic页面,它的origin就是ionicframework.com。在此页面上,如果你要向http://cors.api.com/api发送一个AJAX请求,您的主机源将由Origin标头指定,该标头会自动包含在浏览器的CORS请求中。因为ionicframework.com和主机api.com并不匹配,我们从ionicframework.com的请求必须要经过服务器的批准,才能够访问该资源,以Http Options请求头的形式。

如果我们遇到了上面的错误,说明服务器没有给我们的地址ionicframework.com赋予访问该资源的权限。

下面来看一下,当通过ionic serve,ionic run,ionic run -l三种不同方式运行app时,你的origin分别是什么?

在浏览器中运行时

在执行ionic serve时,发生了什么?

  • 启动了一个本地web服务器
  • 你的浏览器自动打开了指向本地服务器地址的一个页面

这时你可以看到你的app加载到了你的浏览器上,地址是http://localhost:8100(如果你选择了localhost)。

这时,你的origin是localhost:8100。

任何发送到主机而不是localhost:8100的AJAX请求,都将以localhost:8100作为其origin,因此需要经过CORS预检请求,来查看是否有权限访问该资源。

在设备上运行时

当执行ionic run时,又发生了些什么呢?

  • 应用程序文件被复制到设备(或模拟器)。
  • app开始运行,然后启动一个设备(或模拟器)上的浏览器来打开这些文件,类似file://some/path/www/index.html

这样,你的origin将不存在了,因为你正在运行于一个文件(file://)URI。理所当然,你的任何向外请求将不需要通过CORS预检。

在设备上通过自动加载(livereload)运行时

执行ionic run -l时,又将发生什么呢?

  • 首先启动一个本地web服务器
  • 设备(或模拟器)上app开始运行,启动一个浏览器来开地址http://192.168.1.1:8100(你的本地IP地址)上的文件。

这时,你的origin是192.168.1.1:8100。

任何发送到主机而不是192.168.1.1:8100的AJAX请求,都将以192.168.1.1:8100作为其origin,因此需要经过CORS预检请求,来查看是否有权限访问该资源。

在Ionic中处理CORS问题

CORS问题仅仅发生在,当我们以ionic serve或ionic run -l来运行或测试app的时候。

有2种方法来解决这个问题.

第一种,比较容易的方法,是设置远端服务器,让它可以接受所有源的请求。第二种,有时候我们没有权限去修改远端服务器,此时就需要一个不指定源的请求。

我们可以使用代理服务器来实现这个方案。ionic cli给我们提供了一个很方便的配置代理服务器的方法,下面我们看一下如何来实现。

Ionic CLI代理服务器

下面是关于代理(proxy)的一个定义:

In computer networks, a proxy server is a server (a computer system or an application) that acts as an intermediary for requests from clients seeking resources from other servers.

在计算机网络中,代理服务器是一个客户端请求的中介服务器(计算机系统或应用),它用于帮助客户端寻找位于其他服务器上的资源。

要解决我们在ionic中遇到的CORS问题,我们需要设置一个代理服务器,它接受我们的请求,并向API端点发出新的请求,然后接受响应,并将其转发回我们的应用程序。

由于代理服务器需要向目标发送新的请求,因此请求中将不会有origin(源),也就不再需要CORS了。 需要非常注意一点是,origin是浏览器自动帮我们添加在请求头中的。

Ionic CLI具有为客户端请求设置代理服务器的能力,用来帮助您解决CORS问题,下面来看一下Ionic CLI设置代理的方法。

设置代理服务器

再次说明一下,仅仅在ionic serve或ionic run -l的时候需要设置代理。

首先,我们需要在ionic.project(注意,ionic2 cli新版本已经将此文件重命名为ionic.config.json)配置文件中设置代理。 这将告诉Ionic本地服务器监听这些路径并将这些请求转发到目标网址。

在app中,我们需要替换包含endpoint的URLS,并设置为我们的代理服务器地址。(In our app, we will need to replace our endpoint URLS to be set to the proxy server address for when we are running serve or run -l.)

什么是endpoint?

在项目中 endpoint有时也写作baseUrl,比如这个http://cors.api.com/api/book/2/detail的endpoint是http://cors.api.com/api,再比如http://localhost:8101/api/user/123的endpoint是http://localhost:8101/api。

笔者没有想到好的中文来替代这个英文单词,所以没有进行翻译,希望小伙伴们能够理解它的意思就好啦~

我们可以通过设置一些gulp任务,用gulp中的替换模块(replace)来替换出这些URLS,来简化这一步骤。

比较推荐的方法是设置一个Angular Constant来同一设置的endpoint,方便统一进行修改和维护。

下面是操作步骤。我们需要设置一个Angular Service ApiEndpoint来获取数据。

配置代理地址

配置代理有两个需要注意的地方。

  • path:你在本地Ionic服务器上访问它们的路径
  • proxyUrl:你最终希望通过API调用达到的proxyUrl

修改ionic.config.json(旧版本Ionic CLI是ionic.project):

{
"name": "proxy-example",
"app_id": "",
"proxies": [
{
"path": "/api",
"proxyUrl": "http://cors.api.com/api"
}
]
}

如上所述,当您请求访问http://localhost:8100/api的ionic服务器时,它会替你代理请求到http://cors.api.com/api上。这样,就不需要CORS了。

设置Angular Constant

很容易将你的ApiEndpoint设置为Angular Constant。下面,我们已经将ApiEndpoint指定为我们的代理url。然后,我们就可以使用这个url作为一个常数。

angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])
.constant('ApiEndpoint', {
url: 'http://localhost:8100/api'
})

//For the real endpoint, we'd use this
// constant('ApiEndpoint', {
// url: 'http://cors.api.com/api'
// })

设置Angular Service

angular.module('starter.services', [])

//NOTE: We are including the constant `ApiEndpoint` to be used here.
.factory('Api', function($http, ApiEndpoint) {
console.log('ApiEndpoint', ApiEndpoint)

var getApiData = function() {
return $http.get(ApiEndpoint.url + '/tasks')
.then(function(data) {
console.log('Got some data: ', data);
return data;
});
};

return {
getApiData: getApiData
};
})

使用Gulp实现URL自动切换

首先,需要安装replace module

npm install --save replace

然后,我们需要修改gulpfile.js,并添加2个任务,来添加代理地址、移出代理地址。

// `npm install --save replace`
var replace = require('replace');
//注意下面的文件地址,它是包含你endpoint或baseurl的文件
var replaceFiles = ['./www/js/app.js'];

gulp.task('add-proxy', function() {
return replace({
regex: "http://cors.api.com/api",
replacement: "http://localhost:8100/api",
paths: replaceFiles,
recursive: false,
silent: false,
});
})

gulp.task('remove-proxy', function() {
return replace({
regex: "http://localhost:8100/api",
replacement: "http://cors.api.com/api",
paths: replaceFiles,
recursive: false,
silent: false,
});
})

结束语

这个教程展示给你了在运行ionic serve或ionic run -l的时候,一个处理CORS问题的方法。

我们知道,如果按这个方法处理,当我们需要在ionic serve和ionic run -l切换的时候,将ApiEndpoint替换出来可能会比较麻烦。因此我们推荐使用gulp在程序启动时自动完成这个过程。

其实,最简单的处理CORS问题的方法,是要求你的api提供服务器来允许所有的origin。但是,有时候我们无法这么做。

使用Angular Constant和replace module会给我们一个愉快的解决方法来处理CORS问题。

如果你需要一个具体的例子,可以看一下这个示例工程。

上面说的就是所有的当你访问一个API服务器时,关于处理CORS问题的全部。

如果你有疑问或想法,请在下面给我们评论,或者通过twitter or github.


文章翻译自:Handling CORS issues in Ionic,By Josh on February 24, 2015

译者:shaochong

说明:如果有翻译的不合适的地方,您可以联系(hao5743@163.com)我修改~

12
Ethan Hunt

Ethan Hunt

Make more time

58 日志
4 分类
20 标签
© 2019 Ethan Hunt
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.3