io.js

来源:互联网 发布:淘宝上丰胸霜可信吗 编辑:程序博客网 时间:2024/05/21 17:44

io.js入门(一)—— 初识io.js

io.js可以说是彻底从NodeJS里分离出来的一条分支,其事情始末可以查看这篇报道,此处便也不赘言。既然是分支,io.js便也基本兼容NodeJS的各种API,连执行指令也依旧兼容Node的 node XXX (新指令是 iojs XXX )。不过io.js采纳了最新版本的V8引擎,并也将会时刻跟进V8的更新,也因此我们可以轻松地在io.js上抢先使用那些JavaScript ECMA-262规范上的新特性。

io.js的首个版本是在13号,也就是昨天发布的,它基于3.31.71.4版本的V8引擎,良好地支持着许多NodeJS将在0.12.x才会开始支持的ES6特性。

io.js的官网是iojs.org ,从上面那行醒目的“Bringing ES6 to the Node Community!”,我们便能知道io.js相比NodeJS,会趋向于添加更多好用的新特性,并以此作为自己的主打和口号。

为何使用io.js

io.js跟NodeJS一样,属于事件驱动I/O的服务端平台,以Event loop机制来无阻塞地、并发处理I/O,关于当初NodeJS的作者为何选用此机制来开发Node项目,可查阅我翻译的这篇《Hands-on Node.js》—— Why? 。总而言之,io.js/NodeJS很适合用于开发I/O密集型应用,我们参照下下方的这张流程图:

图中的event loop中我们假设有A、B、C三个等待执行的命令队列,其中A和B都会在其执行的过程中触发I/O操作(图中右侧红色圆角矩形框,具体I/O操作可举例为“读取数据库数据”)。以A触发自身的I/O操作为例,常规的动态语言可能都会停住整个队列,等待I/O回馈后,才结束中断、继续运行下去。如果遇到I/O很耗时的情况,进程就会白白等待而浪费不少时间。为了解决此问题,NodeJS采用了event loop机制,将所有I/O操作都扔到线程池去处理,从而不再阻塞命令队列的进一步执行操作。因此从上图可以看到,即使A触发了自身的I/O,也不会阻塞队列的下一个命令B的执行。

线程池的理解:通常情况下,Web 服务器会将每个传入的 HTTP 连接与一个线程或一个进程相关联。使用线程模型配置服务器时,通常需要配置一个线程池(设置处理传入连接的最大线程数),如果错误地配置了该值,并且该值过低,那么您将遭遇线程匮乏问题;另一方面,配置过高的线程数可能导致内存不足异常。

安装

io.js的安装非常简单,直接从官网首页下载对应安装包进行安装即可(由于要修改系统变量,故安装过程得让360等杀毒软件放行),安装完毕便能直接在cmd命令行界面,或者从io.js程序下的“io.js command prompt”命令界面直接进行调试,比如我们可以这样查看io.js所使用的V8引擎版本:

   iojs -p process.versions.v8

执行结果:

第一个程序

我们随便在一个地方,比如D盘根目录下新建一个 test.js 文件,里面简单地写上这么两句话就行:

var s = "hello world";console.log(s);

然后我们从命令行上运行这个脚本(注意当前目录路径要转到d盘)

   iojs test.js

执行如下,输出了"hello world":

我们可以试着写点带io.js API的复杂一点的程序,既然兼容NodeJS,那我们就直接拿NodeJS官网首页的示例来试试。我们把test.js改为:

复制代码
var http = require('http');http.createServer(function (req, res) {  res.writeHead(200, {'Content-Type': 'text/plain'});  res.end('Hello World\n');}).listen(1337, '127.0.0.1');console.log('Server running at http://127.0.0.1:1337/');
复制代码

该段代码创建了一个服务端http服务,监听了本地的1337端口,并在命令行输出 “Server running at http://127.0.0.1:1337/” 语句。如果有客户端访问http://127.0.0.1:1337/ ,则会向客户端输出http头部信息以及显示一行 “Hello World” 。

我们照样用

   iojs test.js

来执行脚本:

可以看到该io.js已进入服务端执行状态(监听着本地的1337端口)。这时我们可以打开浏览器访问 http://127.0.0.1:1337/ ,可以看到上述输出的信息:

第一个网页

像我刚接触NodeJS的时候,会蛮在意一个问题,就是如果我要制作一个站点页面的话,总不该要我把一段段标签都以res.write()的形式输出到客户端吧。答案是我们可以这么做,但肯定不会喜欢这么做。

NodeJS提供了不少实用的API,比如文件系统接口,我们可以写一个简单的 index.html 页面,并把它放在D盘根目录上:

复制代码
<!doctype html><html><head><meta charset="utf-8"><title>io.js</title></head><body>这是你的首个站点页面</body></html>
复制代码

然后把我们的 test.js 更改为:

复制代码
var http = require('http'),      fs = require('fs');fs.readFile('./index.html', function(err,html){    if (err) {        throw err;     }           http.createServer(function(req,res) {          res.writeHeader(200,{"Content-Type": "text/html"});        res.end(html);      }).listen(1337);});
复制代码

接着继续执行指令

   iojs test.js

然后在浏览器访问 http://127.0.0.1:1337/  便能成功查阅该页面:

当然上述的方法只是一个粗糙的示例(也未使用文件流),实际上在很多项目上我们还使用了各种实用的开发框架(如Express、Koa...),使用它们来加载页面视图,会是一种蛮有趣的事情,不过这是以后要介绍的东西了。

如果对于上面示例的代码,你并不能完全看懂或掌握,倒是没关系,本章仅仅是开篇,让你知道下io.js是怎么一回事(实际上它也没那么神秘或复杂),如果你是一名前端开发者,那么相信你会很快掌握io.js,因为它基本都由javascript来书写的(当然你得掌握下commonJS的写法)。



io.js入门(二)—— 所支持的ES6(上)

io.js的官网上有专门介绍其所支持的ES6特性的页面(点我查看),上面介绍到,相比nodeJS,io.js已从根本上支持了新版V8引擎上所支持的ES6特性,无需再添加任何运行时标志(如 --harmony )。

有的朋友可能对Node不熟悉,不太知道harmony标志的情况,这里简单介绍下。

在NodeJS中,如果所要执行的脚本(假设为app.js)是基于ES6来编写的,那么得在命令的脚本路径前加上 “--harmony” 运行时标志,也就是这样执行脚本:

node --harmony app.js

“--harmony” 前缀表示让Node支持除了 “typeof” 外的所有标准ES6特性,除此之外还有 “--harmony_typeof”(开启typeof支持)之类的前缀,具体可以参详stackOverflow上的一个问答。

在io.js中,所有的ES6特性被划分为三大类—— 已标准化的(completed)特性、已确定将标准化的(staged)特性、仍处草案待定状态in progress的特性

针对这三大类,io.js做了分别的处理,而不像Node那样都得加harmony标签:

⑴ 已标准化的ES6特性,如我们上文提到的,直接用指令执行即可,无需再加任何运行时标志;

⑵ 已确定将标准化的ES6特性,执行时需要加上运行时标志 “ --es_staging ” ,当然你也可以使用它的同义词 “ --harmony ” ;

⑶ 在草案上但仍未确定将标准化的ES6特性,执行时需要加上它们自己对应的harmony标志,比如你在脚本中使用了 arrow_functions 特性,那么需要加上标志 “--harmony_arrow_functions”。

io.js建议不要使用 ⑵ 和 ⑶ 的不稳定的特性。

下面将较详细地来介绍io.js原生支持的标准ES6特性。

标准ES6特性

该系列特性可直接使用,无需添加运行时标志,但小部分特性要求只能在严格模式("use strict";)下使用。

1. 块级作用域/Block scoping(需要在严格模式下使用)

    ○ let

    ○ const

    ○ 块级作用域中的函数

2. 集合/Collections

    ○ Map

    ○ WeakMap

    ○ Set

    ○ WeakSet

3. Generators

4. 二进制和八进制语法/Binary and Octal literals

5. Promises

6. 新的String方法/New String methods

7. 符号/Symbols

8. 字符串模板/Template strings

let (只能在严格模式下使用)

类似于var,声明一个变量,但只在声明处所处的块级作用域内有效:

复制代码
"use strict";  //使用严格模式{    var a=1;    let b=2;    console.log(a);    console.log(b);}console.log(a);console.log(b);   //undefined
复制代码

上述代码执行如下:

使用 let 可以用于解决变量提升问题:

 View Code

const (只能在严格模式下使用)

类似于var,声明一个“常量” —— 初始赋值后将无法修改其值的变量。

也类似于let,只能在其所声明的块级作用域内来访问到:

复制代码
"use strict";  //使用严格模式{    const i = 1;}console.log(typeof i);  //undefined{    const a = 123;    a = 567;   //报错,a是常量不能修改}
复制代码

块级作用域中的函数 (只能在严格模式下使用)

严格模式下,函数本身的作用域,在其所处的块级作用域内,可以以此解决函数声明提升问题:

复制代码
"use strict";  //使用严格模式function f() { console.log('outside!'); }(function () {    if(false) {        // 重复声明一次函数f        function f() { console.log('inside!'); }    }    f(); //严格模式(ES6)下输出outside;非严格模式(ES5)输出inside}());
复制代码

Map

新的js集合类型,类似与对象,属于键值对的集合,但其“键”可以为任何类型,而不仅仅局限于字符串:

复制代码
var map = new Map(), //新建一个map对象    o = {"a": 1, "b": 2},    s = "sth";//map.set(key,val) 表示为该map对象添加一个新的键值对map.set(o,"ooo");map.set("a",123);map.set(null,"it`s null");map.set(s,o);//map.delete(key) 表示删除该map对象的某个键值对map.delete(s);//map.has(key) 表示检查该map对象是否存在某键名,返回对应的boolean值console.log(map.has(o));  //trueconsole.log(map.has(s));  //false//map.has(key) 表示获取该map对象中某键名所对应的值console.log(map.get(o));  //"ooo"console.log(map.get("a"));  //123console.log(map.get(null));  //"it`s null"//map.clear() 表示清空map对象中的全部键值对map.clear();console.log(map.get(o));  //undefined
复制代码

Map支持链式写法,故上方的某段代码我们可以这么写:

map.set(o,"ooo")    .set("a",123)    .set(null,"it`s null")    .set(s,o)    .delete(s);

我们可以用 map.size 属性(而不是length)获取Map中键值对的个数:

复制代码
var map = new Map([    [1, 'one'],    [2, 'two'],    [3, 'three']]);console.log(map.size);  //3
复制代码

Map提供了三种遍历器:

  map.keys() //返回键名的遍历器
  map.values() //返回键值的遍历器
  map.entries() //返回所有成员的遍历器

我们可以使用 for...of... 方法来遍历map的遍历器:

复制代码
"use strict";let map = new Map([    ['F', 'no'],    ['T',  'yes']]);for (let key of map.keys()) {    console.log(key);}// "F"// "T"for (let value of map.values()) {    console.log(value);}// "no"// "yes"for (let item of map.entries()) {    console.log(item[0], item[1]);}// "F" "no"// "T" "yes"// 或者let arr = [[],[]];for (arr of map.entries()) {    console.log(arr[0], arr[1]);}// 等同于使用map.entries()for (arr of map) {    console.log(arr[0], arr[1]);}
复制代码

其中map的entries方法等同为Map结构的默认遍历器接口(Symbol.iterator):

console.log(map[Symbol.iterator] === map.entries);// true

另外,我们也可以使用 forEach 方法来遍历Map对象:

复制代码
var map = new Map([    [1, 'one'],    [2, 'two'],    [3, 'three']]);map.forEach(function(value, key){    console.log("Key: %s, Value: %s", key, value);});// Key: 1, Value: one// Key: 2, Value: two// Key: 3, Value: three
复制代码

WeakMap

WeakMap结构与Map结构基本类似,区别是它只接受对象作为键名(null除外),不接受基础类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制。

另外,WeakMap只有 get()、set()、has()、delete() 这么四个方法,而没有 clear() 、size属性和遍历器。

WeakMap示例如下:

var wm = new WeakMap(),    o = {}; // o = document.getElementById("idname");wm.set(o,"DOM");console.log(wm.get(o));  //"DOM"

WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap自动移除对应的键值对。典型应用是,一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除。基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

Set

类似于数组,但是成员的值都是唯一的,没有重复的值。另外Set也支持链式写法:

复制代码
var items = new Set([1,2,null,3,5,5,5,5]);  //重复的值将无效处理console.log(items.size);  //5//set.add(value) 表示添加一个set成员items.add(2).add("aaa");  //“2”已存在,将无效处理console.log(items.size);  //6//set.delete(value) 表示删除一个set成员items.delete(2);//set.has(value) 表示检查是否存在某成员,返回相应boolean值console.log(items.has(2)); //falseitems.clear(); //表示删除全部成员console.log(items.size);  //0
复制代码

跟Map一样,Set也有keys()、values()、entries() 三种遍历器,但遍历到的key名等同与value值。

不过Set的默认遍历器接口是 values() :

console.log(Set[Symbol.iterator] === Set.values);  //ture

同样的,我们也可以通过 for...of... 和 forEach 来遍历Map:

复制代码
var items = new Set([1,2,null,3,5,5,5,5]); var arr = [[],[]];for (arr of items.entries()) {    console.log(arr[0], arr[1]);}items.forEach(function(value, key){    console.log("Key: %s, Value: %s", key, value);});
复制代码

Set没有 .get(val) 方法,也不像数组那样可以直接用索引下标取值,常规只是把Set作为数据库索引使用(如为Redis提供索引和查询)。

WeakSet

了解了Map和WeakMap的关系之后,相信你也很容易理解WeakSet跟Set的关系。WeakSet类同于Set,但其成员只能是对象,而且WeakSet无法遍历也没有size属性。

相比Set,WeakSet能使用的方法只有 add(val) 、delete(val)、has(val) :

复制代码
var ws = new WeakSet(),    obj = {},    foo = {};ws.add(foo);ws.add(obj);ws.delete(obj);console.log(ws.has(foo));    // trueconsole.log(ws.has(obj));    // false
复制代码

同WeakMap一样,WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,对象删除时,WeakSet对应引用该对象的成员也跟着删除。

Generators

Generator在ES6中是一个备受瞩目的一个函数类型,它通过 function * funName 的形式来声明一个Generator函数,并以 yield 语句来生成函数内部的遍历器成员。

调用Generator函数时,函数并不执行,而是返回一个遍历器(可以理解成暂停执行)。以后,每次调用这个遍历器的next方法,就从函数体的头部或者上一次停下来的地方开始执行(可以理解成恢复执行),直到遇到下一个yield语句为止:

复制代码
function * gFun() {    yield "第一个遍历器成员,第一次调用next()会停在这里"; //生成遍历器成员    var i = 0;    while (true){        console.log("第"+i+"次循环");        yield i++; //生成遍历器成员        console.log("继续第"+i+"次循环");    }}var g = gFun();  //没有.next()语句的调用,Generator函数不会执行,故不会造成while无限循环console.log(g.next().value);//"第一个遍历器成员,第一次调用next()会停在这里"   console.log(g.next().value);//第0次循环 //0console.log(g.next().value);//继续第1次循环//第1次循环//1
复制代码

.next().value返回了遍历器成员的值,常规为 “yield” 或 "return" 语句所生成/返回的值。

.next() 方法除了 value 属性外,还有判断遍历是否结束的 done 属性,它返回一个表示当前遍历是否结束的boolean值:

复制代码
function * gFun() {    yield "第一个遍历器成员";    var i = 0;    yield ++i;}var g = gFun();  //没有.next()语句的调用,Generator函数不会执行,故不会造成while无限循环console.log(g.next());//{ value: '第一个遍历器成员', done: false }console.log(g.next());//{ value: 1, done: false }console.log(g.next());//{ value: undefined, done: true }
复制代码

Generator函数里比较有意思的地方是,可以给 .next() 加一个参数,该参数数值就会覆盖掉上一个yield语句的返回值,我们可以利用该特性来进行一些有趣的运算:

复制代码
function* foo(x) {    var y = 2 * (yield (x + 1));    var z = yield (y / 3);    return (x + y + z);}var g = foo(5);console.log(g.next());//{ value:6, done:false }console.log(g.next(12));  // (y  /3)=(2*12  /3)=8//{ value:8, done:false }console.log(g.next(13)); // (x  +  y  +  z)=(5  +  2*12  +  13)=42//{ value:42, done:true }
复制代码

二进制和八进制语法

原先的JS对二进制/八进制的转换处理不太友好,我们得动用parseInt()或toString()方法,并使用对应的进制位参数:

复制代码
var num = 11;console.log(parseInt(num,2)); //把num作为二进制转为十进制//3console.log(parseInt(num,8)); //把num作为八进制转为十进制//9console.log(num.toString(2)); //把num作为十进制转为二进制//1011console.log(num.toString(8)); //把num作为十进制转为八进制//13
复制代码

在ES6中有些许改善,比如以“0b”开头表示二进制,以“0”开头表示八进制,以“0x”开头表示十六进制:

复制代码
console.log(0b1101); //13console.log(0B11); //3console.log(071); //57console.log(081); //81  (因为“8”超过了八进制可用数值,这里被当作十进制来转换)console.log(0x11); //17console.log(0X2A); //42
复制代码

Promise

如果你熟悉jQuery的deferred对象,那么你会很轻松地理解ES6的Promise特性 —— 用于延迟、异步状态处理。

一般Promise会有三种状态:

  待定(pending):初始状态,执行、等待中,没有被履行或拒绝。
  完成(fulfilled/resolved):操作成功
  拒绝(rejected):操作失败。

Promise是一个构造函数,用来生成Promise实例。它接受一个函数作为参数,该函数又有两个参数——resolve方法和reject方法。如果异步操作成功,则用resolve方法将Promise实例的状态变为“成功”;如果异步操作失败,则用reject方法将状态变为“失败”。

promise实例生成以后,可以用then方法分别指定resolve方法和reject方法的回调函数:

复制代码
var promise = new Promise(function (resolve, reject) {    if (/* 异步操作成功 */) {        resolve(value); //返回resolved状态并触发resolve回调    } else {        reject(error); //返回rejected状态并触发reject回调    }});promise.then(function (value) {    // resolve,即成功的回调}, function (error) {    // reject,即失败的回调});
复制代码

这种机制对Node/io.js来说是非常有用的,因为我们知道,Node/io.js走的无阻塞异步I/O,js部分在V8执行,I/O部分在线程池做异步处理,如果我们希望在I/O操作成功或失败后执行相应的回调函数,以常规的做法不得不在事件的回调中继续嵌套I/O状态的回调。但Promise的出现,很好地解决了该问题。

拿Ajax举个例子:

复制代码
var getJSON = function(url) {    var promise = new Promise(function(resolve, reject){        var client = new XMLHttpRequest();        client.open("GET", url);        client.onreadystatechange = handler;        client.responseType = "json";        client.setRequestHeader("Accept", "application/json");        client.send();        function handler() {            if (this.status === 200) {                resolve(this.response);  //成功则触发resolve回调            } else {                reject(new Error(this.statusText));  //失败则触发reject回调            }        }    });    return promise;};getJSON("/posts.json").then(function(json) { //resolve的回调    console.log('Contents: ' + json);}, function(error) {  //reject的回调    console.error('出错了', error);});
复制代码

.then() 方法返回的是一个新的Promise对象,它支持链式写法,可以让代码逻辑更清晰。如上述 getJSON() 执行的代码段可写为:

复制代码
getJSON("/posts.json").then(function (json) {    console.log('Contents: ' + json);}, function (error) {    console.error('出错了', error);}).then(function () { //在上个then执行完之后才会执行    console.log("已经执行完并输出信息了")});
复制代码

Promise对象还有 .all() 和 .trace() 方法,Promise.all 方法接受一个数组作为参数,数组对象为不同的Promise对象:

复制代码
//接之前的代码段var promises = [getJSON("/post/a.json"), getJSON("/post/b.json"),getJSON("/post/c.json")];Promise.all(promises).then(function (json) {    console.log('Contents: ' + json);}, function (error) {    console.error('出错了', error);});
复制代码

当数组对象的状态都变为resolved时,Promise.all(promises)的状态也跟着变为resolved。如果其中一个数组对象的状态为rejected,那Promise.all(promises)则变为rejected状态。

Promise.trace 方法跟 Promise.all 方法类似,不过只要Promise.trace的参数中的某一个对象率先改变了状态,那么 Promise.trace(promises) 的状态也会变为该状态。

如上文提到的,作为trace 方法跟 all 方法的参数,要求其必须为Promise实例组成的数组,如果有非Promise实例的对象想加入到数组参数中,我们可以先通过 Promise.resolve 方法将其转为Promise实例:

var ES6Promise = Promise.resolve($.ajax('/whatever.json')); //将一个jQuery deferred对象转化为ES6的Promise实例

另外,我们可以使用 .catch() 来捕捉Promise对象的错误信息,要知道的是,Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获:

复制代码
getJSON("/post/1.json").then(function(post) {  return getJSON(post.commentURL);}).then(function(comments) {  // some code}).catch(function(error) {  // 处理前两个回调函数的错误});
复制代码
 



io.js入门(三)—— 所支持的ES6(下)


标准ES6特性

6. 新的String方法/New String methods

7. 符号/Symbols

8. 字符串模板/Template strings

新的String方法/New String methods

codePointAt() 和  String.fromCodePoint()
JS内部是以UTF-16的形式(每个字符长度都固定为16位元、2个字节)来存储字符编码的。就汉字来说,大部分的汉字均以2字节的形式来存储即可,却也有部分汉字需要有4个字节的长度来存储(其codePoint/码点大于0xFFFF),比如这个生僻字“

0 0
原创粉丝点击