人在江湖漂


  • 首页

  • 归档

由一个题目说到javascript的内存分配

发表于 2012-02-14 | 分类于 javascript

昨天在群里看到一个前端的兄弟发了一个问题,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
(function() {
var A = function() {}
A.prototype = {
//在仅改变这里的代码的情况下,能否使console.log(a2.b.c);结果为1
b : {
c : 1
}
}
var a1 = new A();
var a2 = new A();
a1.b.c = 2;
console.log(a2.b.c);
})();

对于这个问题,我还真没法在仅修改 b : { c : 1 } 的情况下使结果为1。

在未改动代码的情况下,console.log(a2.b.c);的结果是2。

如果不明白,那我们来看看javascript的内存分配吧:

原始值和引用值

在ECMAScript中,变量可以存放原始值和引用值。

原始值,如Number, String, Boolean, Null, Undefined。 指的是代表原始数据类型,即基本数据类型,的值。

引用值,如Object, Function, Array, 以及自定义对象等。指的是复合数据类型的值。

栈和堆

简单的说,原始值放在内存中叫栈的地方,而引用值则放在堆中。

原始值是存储在栈中的简单数据段,也就是说,他们的值直接存储在变量访问的位置。

堆是存放数据的基于 散列算法 的 数据结构,也就是说,存储在变量处的值(即指向对象的变量,存储在栈中)是一个指针,指向存储在堆中的实际对象。

例:var obj = new Object(); obj 存储在栈中,它指向于 new Object()这个对象,而new Object()是存放在堆中的。

下面我们来看一个具体的例子:

1
2
3
4
5
6
7
8
9
10
11
function Person(id,name,age){
this.id = id;
this.name = name;
this.age = age;
}
var num = 10;
var bool = true;
var str = "abc";
var arr = ['a','b','c'];
var obj = new Object();
var person = new Person(001,"spemoon",24);

然后我们来看一下内存分析图:

内存分析

变量num,bol,str为基本数据类型,它们的值,直接存放在栈中,obj,person,arr为复合数据类型,他们的引用变量存储在栈中,指向于存储在堆中的实际对象。

由上图可知,我们无法直接操纵堆中的数据,也就是说我们无法直接操纵对象,但我们可以通过栈中对对象的引用来操作对象。

那为什么引用值要放在堆中,而原始值要放在栈中呢?

堆比栈大,栈比堆的运算速度快,对象是一个复杂的结构,并且可以自由扩展,如:数组可以无限扩充,对象可以自由添加属性。将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。相对于简单数据类型而言,简单数据类型就比较稳定,并且它只占据很小的内存。不将简单数据类型放在堆是因为通过引用到堆中查找实际对象是要花费时间的,而这个综合成本远大于直接从栈中取得实际值的成本。所以简单数据类型的值直接存放在栈中。

现在再让我们回过头来看开始的那个题目:

a1.b.c 这里是指向一个对象,即是堆中的一个引用值,故对其赋值,则会修改堆中的对象的引用,结果会使a2.b.c == 2。

如果上述代码这么写:

1
2
3
4
5
6
7
8
9
10
(function() {
var A = function() {}
A.prototype = {
b : 1
}
var a1 = new A();
var a2 = new A();
a1.b = 2;
console.log(a2.b);
})();

那么a2.b的结果是1,因为对于栈中的原始值,在进行赋值的过程中,其实是为a1新建了一个a1.b,并没有影响到A的原型上的b的值。

再看一个例子:

1
2
3
4
5
6
7
function a(){};   
a.prototype.xx=['a','b','c'];
var a1 = new a();
var a2 = new a();
var a3 = new a();
a1.xx = ['a'];
a3.xx.push(12);

那现在a1.xx,a2.xx和a3.xx分别是多少?

结果是:a1.xx = [‘a’],a2.xx = [‘a’,’b’,’c’,12],a3.xx = [‘a’,’b’,’c’,12]

由于数组是高级javascript对象,因此func a()的实例对象中的xx属性值都是引用到同一个地方的。当a1.xx = [‘a’]时实际上是新建了一个数组然后把引用给了a1.xx属性。

对于a.prototype.xx 是没有影响的。而a3.xx.push(12)才会真正操作了a的prototype上面的xx,因此a2.xx也跟着改变了。

总结:实际上,在开发过程中,不推荐将不想被实例对象共享的属性还是放到prototype中。对于属性还是放在构造器中好些,对于一些公共的方法可以放在原型上。

前端开发的优化问题

发表于 2011-12-18 | 分类于 fe
  1. 减少http请求次数:css spirit,data uri
  2. JS,CSS源码压缩
  3. 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
  4. 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能
  5. 用setTimeout来避免页面失去响应
  6. 用hash-table来优化查找
  7. 当需要设置的样式很多时设置className而不是直接操作style
  8. 少用全局变量
  9. 缓存DOM节点查找的结果
  10. 避免使用CSS Expression
  11. 图片预载
  12. 避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢

Node.js学习笔记——Crypto 加密模块

发表于 2011-12-15 | 分类于 node.js

使用require(‘crypto’)调用加密模块。

加密模块需要底层系统提供OpenSSL的支持。它提供了一种安全凭证的封装方式,可以用于HTTPS安全网络以及普通HTTP连接。

该模块还提供了一套针对OpenSSL的hash(哈希),hmac(密钥哈希),cipher(编码),decipher(解码),sign(签名)以及verify(验证)等方法的封装。

crypto.createCredentials(details)

创建一个凭证对象,可选参数details为一个带键值的字典:

  • key:为字符串型,PEM编码的私钥。

  • cert:为字符串型,PEM编码的认证证书。

  • ca:字符串形式的PEM编码可信CA证书,或证书列表。

如果没有给出’ca’的详细内容,那么node.js将会使用默认的公开受信任列表,该表位于http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt。

crypto.createHash(algorithm)

创建并返回一个hash对象,它是一个指定算法的加密hash,用于生成hash摘要。

参数algorithm可选择系统上安装的OpenSSL版本所支持的算法。例如:’sha1’, ‘md5’, ‘sha256’, ‘sha512’等。在近期发行的版本中,openssl list-message-digest-algorithms会显示这些可用的摘要算法。

hash.update(data)

更新hash的内容为指定的data。当使用流数据时可能会多次调用该方法。

hash.digest(encoding=’binary’)

计算所有传入数据的hash摘要。参数encoding(编码方式)可以为’hex’, ‘binary’ 或者’base64’。

crypto.createHmac(algorithm, key)

创建并返回一个hmac对象,它是一个指定算法和密钥的加密hmac。

参数algorithm可选择OpenSSL支持的算法 - 参见上文的createHash。参数key为hmac所使用的密钥。

hmac.update(data)

更新hmac的内容为指定的data。当使用流数据时可能会多次调用该方法。

hmac.digest(encoding=’binary’)

计算所有传入数据的hmac摘要。参数encoding(编码方式)可以为’hex’, ‘binary’ 或者’base64’。

crypto.createCipher(algorithm, key)

使用指定的算法和密钥创建并返回一个cipher对象。

参数algorithm可选择OpenSSL支持的算法,例如’aes192’等。在最近的发行版中,openssl list-cipher-algorithms会显示可用的加密的算法。

cipher.update(data, input_encoding=’binary’, output_encoding=’binary’)

使用参数data更新要加密的内容,其编码方式由参数input_encoding指定,可以为 ‘utf8’, ‘ascii’或者’binary’。参数output_encoding指定了已加密内容的输出编码方式,可以为 ‘binary’, ‘base64’或’hex’。

返回已加密的内容,当使用流数据时可能会多次调用该方法。

cipher.final(output_encoding=’binary’)

返回所有剩余的加密内容,output_encoding输出编码为’binary’, ‘ascii’或’utf8’其中之一。

crypto.createDecipher(algorithm, key)

使用给定的算法和密钥创建并返回一个解密对象。该对象为上述加密对象的反向运算。

decipher.update(data, input_encoding=’binary’, output_encoding=’binary’)

使用参数data更新要解密的内容,其编码方式为’binary’,’base64’或’hex’。参数output_encoding指定了已解密的明文内容的输出编码方式,可以为 ‘binary’,’ascii’或’utf8’。

decipher.final(output_encoding=’binary’)

返回全部剩余的已解密的明文,其output_encoding’ 为’binary’, ‘ascii’或’utf8’`其中之一。

crypto.createSign(algorithm)

使用给定的算法创建并返回一个签名器对象。在现有的OpenSSL发行版中,openssl list-public-key-algorithms会显示可用的签名算法,例如:’RSA-SHA256’。

signer.update(data)

使用data参数更新签名器对象。当使用流数据时可能会多次调用该方法。

signer.sign(private_key, output_format=’binary’)

对所有传入签名器的数据计算其签名。private_key为字符串,它包含了PEM编码的用于签名的私钥。

返回签名,其output_format输出可以为’binary’, ‘hex’ 或者’base64’。

crypto.createVerify(algorithm)

使用给定算法创建并返回一个验证器对象。它是上述签名器对象的反向运算。

verifier.update(data)

使用data参数更新验证器对象。当使用流数据时可能会多次调用该方法。

verifier.verify(cert, signature, signature_format=’binary’)

使用参数cert和signature验证已签名的数据,cert为经过PEM编码的公钥字符串,signature为之前已计算的数据的签名,signature_format可以为’binary’,’hex’ 或者’base64’。

根据对数据和公钥进行签名有效性验证的结果,返回true或者false。

Node.js学习笔记——Streams 流

发表于 2011-12-14 | 分类于 node.js

在Node中,Stream(流)是一个由不同对象实现的抽象接口。例如请求HTTP服务器的request是一个流,类似于stdout(标准输出)。流可以是可读的,可写的,或者既可读又可写。所有流都是EventEmitter的实例。

Readable Stream 可读流

一个可读流具有下述的方法、成员、及事件。

Event: ‘data’ 事件:’data’

function (data) { }

‘data’事件的回调函数参数默认情况下是一个Buffer对象。如果使用了setEncoding() 则参数为一个字符串。

Event: ‘end’ 事件:’end’

function () { }

当流中接收到EOF(TCP中为FIN)时此事件被触发,表示流的读取已经结束,不会再发生任何’data’事件。如果流同时也是可写的,那它还可以继续写入。

Event: ‘error’ 事件:’error’

function (exception) { }

接收数据的过程中发生任何错误时,此事件被触发。

Event: ‘close’ 事件:’close’

function () { }

当底层的文件描述符被关闭时触发此事件,并不是所有流都会触发这个事件。(例如,一个连接进入的HTTP request流就不会触发’close’事件。)

Event: ‘fd’ 事件:’fd’

function (fd) { }

当在流中接收到一个文件描述符时触发此事件。只有UNIX流支持这个功能,其他类型的流均不会触发此事件。

stream.readable

这是一个布尔值,默认值为true。当’error’事件或’end’事件发生后,或者destroy()被调用后,这个属性将变为false。

stream.setEncoding(encoding)

调用此方法会影响’data’事件的回调函数参数形式,默认为Buffer对象,调用此方法后为字符串。encoding参数可以是’utf8’、’ascii’、或’base64’。

stream.pause()

暂停’data’事件的触发。

stream.resume()

恢复被pause()调用暂停的’data’事件触发。

stream.destroy()

关闭底层的文件描述符。流上将不会再触发任何事件。

stream.destroySoon()

在写队列清空后(所有写操作完成后),关闭文件描述符。

stream.pipe(destination, [options])

这是Stream.prototype(Stream原型对象)的一个方法,对所有Stream对象有效。

用于将这个可读流和destination目标可写流连接起来,传入这个流中的数据将会写入到destination流中。通过在必要时暂停和恢复流,来源流和目的流得以保持同步。

模拟Unix系统的cat命令:

1
2
process.stdin.resume();
process.stdin.pipe(process.stdout);

默认情况下,当来源流的end事件触发时目的流的end()方法会被调用,此时destination目的流将不再可写入。要在这种情况下为了保持目的流仍然可写入,可将options参数设为{ end: false }。

这使process.stdout保持打开状态,因此”Goodbye”可以在end事件发生后被写入。

1
2
3
4
5
6
7
process.stdin.resume();

process.stdin.pipe(process.stdout, { end: false });

process.stdin.on("end", function() {
process.stdout.write("Goodbye\n");
});

注意:如果来源流不支持pause()和resume()方法,此函数将在来源流对象上增加这两个方法的简单定义,内容为触发’pause’和’resume’事件。

Writable Stream 可写流

一个可写流具有下列方法、成员、和事件。

Event: ‘drain’ 事件:’drain’

function () { }

发生在write()方法被调用并返回false之后。此事件被触发说明内核缓冲区已空,再次写入是安全的。

Event: ‘error’ 事件:’error’

function (exception) { }

发生错误时被触发,回调函数接收一个异常参数exception。

Event: ‘close’ 事件:’close’

function () { }

底层文件描述符被关闭时被触发。

Event: ‘pipe’ 事件:’pipe’

function (src) { }

当此可写流作为参数传给一个可读流的pipe方法时被触发。

stream.writable

一个布尔值,默认值为true。在’error’事件被触发之后,或end() / destroy()方法被调用后此属性被设为false。

stream.write(string, encoding=’utf8’, [fd])

使用指定编码encoding将字符串string写入到流中。如果字符串被成功写入内核缓冲区,此方法返回true。如果内核缓冲区已满,此方法返回false,数据将在以后被送出。当内核缓冲区再次被清空后’drain’事件将被触发。encoding参数默认为’utf8’`。

如果指定了可选参数fd,它将被作为一个文件描述符通过流传送。此功能仅被Unix流所支持,对于其他流此操作将被忽略而没有任何提示。当使用此方法传送一个文件描述符时,如果在流没有清空前关闭此文件描述符,将造成传送一个无效(已关闭)FD的风险。

stream.write(buffer)

除了用一个Buffer对象替代字符串之外,其他同上。

stream.end()

使用EOF或FIN结束一个流的输出。

stream.end(string, encoding)

以指定的字符编码encoding传送一个字符串string,然后使用EOF或FIN结束流的输出。这对降低数据包传输量有所帮助。

stream.end(buffer)

除了用一个buffer对象替代字符串之外,其他同上。

stream.destroy()

关闭底层文件描述符。在此流上将不会再触发任何事件。

Node.js学习笔记——Buffers 缓冲器

发表于 2011-12-13 | 分类于 node.js

纯Javascript语言是Unicode友好性的,但是难以处理二进制数据。在处理TCP流和文件系统时经常需要操作字节流。Node提供了一些列机制,用于操作、创建、以及消耗(consuming)字节流。

在实例化的Buffer类中存储了原始数据。Buffer类似于一个整数数组,但Buffer对应了在V8堆(the V8 heap)外的原始存储空间分配。一旦创建了Buffer实例,则无法改变其大小。

另外,Buffer是一个全局对象。

在缓冲器(Buffers)和JavaScript间进行字符串的转换需要调用特定的编码方法。如下列举了不同的编码方法:

  • ‘ascii’ - 仅对应7位的ASCII数据。虽然这种编码方式非常迅速,并且如果设置了最高位,则会将其移去。

  • ‘utf8’ - 对应多字节编码Unicode字符。大量网页和其他文件格式使用这类编码方式。

  • ‘ucs2’ - 2字节的,低字节序编码Unicode字符。只能编码BMP(第零平面,U+0000 - U+FFFF)字符。

  • ‘base64’ - Base64 字符串编码.

  • ‘binary’ - 仅使用每个字符的头8位将原始的二进制信息进行编码。在需使用Buffer的情况下,应该尽量避免使用这个已经过时的编码方式。而且,这个编码方式不会出现在未来版本的Node中。

  • ‘hex’ - 将一个字节编码为两个16进制字符。

new Buffer(size)

使用array的空间创建一个buffer实例。

new Buffer(str, encoding=’utf8’)

创建一个包含给定str的buffer实例。

buffer.write(string, offset=0, encoding=’utf8’)

通过给定的编码方式把string写入到buffer的offset(偏移地址)中,并且返回写入的字节数。如果当前的buffer没有足够存储空间,字符串会部分地保存在buffer中,而不是整串字符。需要注意的是,如果使用’utf8’进行编码,该方法不会对零散的字符进行编写。

例如:将一串utf8格式的字符串写入Buffer,然后输出:

1
2
3
4
buf = new Buffer(256);
len = buf.write('\u00bd + \u00bc = \u00be', 0);
console.log(len + " bytes: " + buf.toString('utf8', 0, len));
// 12 bytes: ½ + ¼ = ¾

buffer.toString(encoding, start=0, end=buffer.length)

对缓冲器中的以encoding方式编码的,以start标识符开始,以end标识符结尾的缓冲数据进行解码,并输出字符串。

buffer[index]

获取或者设置位于index字节的值。由于返回值为单个的字节,因此其范围应该在0x00 到 0xFF(16进制)或者0 and 255(10进制)之间

例如:通过每次仅输入一个字符的方式将整串ASCII字符录入Buffer中:

1
2
3
4
5
6
7
8
9
10
str = "node.js";
buf = new Buffer(str.length);

for (var i = 0; i < str.length ; i++) {
buf[i] = str.charCodeAt(i);
}

console.log(buf);

// node.js

Buffer.isBuffer(obj)

验证obj的类别是否为Buffer类。

Buffer.byteLength(string, encoding=’utf8’)

返回字符串长度的实际值。与String.prototype.length的区别之处在于该方法返回的是字符串中characters的个数。

例如:

1
2
3
4
5
6
str = '\u00bd + \u00bc = \u00be';

console.log(str + ": " + str.length + " characters, " +
Buffer.byteLength(str, 'utf8') + " bytes");

// ½ + ¼ = ¾: 9 characters, 12 bytes

buffer.length

返回Buffer占用的字节数。需要注意的是,length并非其内容占的大小,而是指分配给Buffer实例的存储空间的大小,因此该值不会随Buffer内容的变化而变化。

1
2
3
4
5
6
7
8
buf = new Buffer(1234);

console.log(buf.length);
buf.write("some string", "ascii", 0);
console.log(buf.length);

// 1234
// 1234

buffer.copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)

在两个Buffer之间进行memcpy() 操作。

例如:创建2个Buffer实例,然后将buf1中第16字节到第19字节间的信息复制到buf2中,并使在buf2中新的字符串首字符位于第8字节:

1
2
3
4
5
6
7
8
9
10
11
12
buf1 = new Buffer(26);
buf2 = new Buffer(26);

for (var i = 0 ; i < 26 ; i++) {
buf1[i] = i + 97; // 97 is ASCII a
buf2[i] = 33; // ASCII !
}

buf1.copy(buf2, 8, 16, 20);
console.log(buf2.toString('ascii', 0, 25));

// !!!!!!!!qrst!!!!!!!!!!!!!

buffer.slice(start, end=buffer.length)

返回一个和原Buffer引用相同存储空间的新Buffer,但是新Buffer中的偏移地址截取了原Buffer偏移地址中自start到end的部分。

特别注意:通过修改新的Buffer切片(slice)中的内容同样会修改存储在原Buffer中的信息!

1
2
3
4
5
6
7
8
9
10
11
12
13
var buf1 = new Buffer(26);

for (var i = 0 ; i < 26 ; i++) {
buf1[i] = i + 97; // 97 is ASCII a
}

var buf2 = buf1.slice(0, 3);
console.log(buf2.toString('ascii', 0, buf2.length));
buf1[0] = 33;
console.log(buf2.toString('ascii', 0, buf2.length));

// abc
// !bc

Node.js学习笔记——Events 事件模块

发表于 2011-12-12 | 分类于 node.js

Node引擎中很多对象都会触发事件:例如net.Server会在每一次有客户端连接到它时触发事件,又如fs.readStream会在文件打开时触发事件。所有能够触发事件的对象都是events.EventEmitter的实例。你可以通过require(“events”);访问这个模块。

通常情况下,事件名称采用驼峰式写法,不过目前并没有对事件名称作任何的限制,也就是说任何的字符串都可以被接受。

可以将函数注册给对象,使其在事件触发时执行,此类函数被称作监听器。

events.EventEmitter

通过调用require(‘events’).EventEmitter,我们可以使用事件触发器类。

当EventEmitter事件触发器遇到错误时,典型的处理方式是它将触发一个’error’事件。Error事件的特殊性在于:如果没有函数处理这个事件,它将会输出调用堆栈,并随之退出应用程序。

当新的事件监听器被添加时,所有的事件触发器都将触发名为’newListener’的事件。

emitter.addListener(event, listener)

emitter.on(event, listener)

将一个监听器添加到指定事件的监听器数组的末尾。

1
2
3
server.on('connection', function (stream) {
console.log('someone connected!');
});

emitter.once(event, listener)

为事件添加一次性的监听器。该监听器在事件第一次触发时执行,过后将被移除。

1
2
3
server.once('connection', function (stream) {
console.log('Ah, we have our first user!');
});

emitter.removeListener(event, listener)

将监听器从指定事件的监听器数组中移除出去。 小心:此操作将改变监听器数组的下标。

1
2
3
4
5
6
var callback = function(stream) {
console.log('someone connected!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);

emitter.removeAllListeners(event)

将指定事件的所有监听器从监听器数组中移除。

emitter.setMaxListeners(n)

默认情况下当事件触发器注册了超过10个以上的监听器时系统会打印警告信息,这个默认配置将有助于你查找内存泄露问题。很显然并不是所有的事件触发器都需要进行10个监听器的限制,此函数允许你手动设置该数量值,如果值为0意味值没有限制。

emitter.listeners(event)

返回指定事件的监听器数组对象,你可以对该数组进行操作,比如说删除监听器等。

1
2
3
4
server.on('connection', function (stream) {
console.log('someone connected!');
});
console.log(util.inspect(server.listeners('connection')); // [ [Function] ]

emitter.emit(event, [arg1], [arg2], […])

以提供的参数作为监听器函数的参数,顺序执行监听器列表中的每个监听器函数。

Event: ‘newListener’

function (event, listener) { }

任何时候只要新的监听器被添加时该事件就会触发。

Node.js学习笔记——util 工具模块

发表于 2011-12-11 | 分类于 node.js

下列函数属于’util’(工具)模块,可使用require(‘util’)访问它们。

util.debug(string)

这是一个同步输出函数,将string参数的内容实时输出到stderr标准错误。调用此函数时将阻塞当前进程直到输出完成。

1
require('util').debug('message on stderr');

util.log(string)

将string参数的内容加上当前时间戳,输出到stdout标准输出。

1
require('util').log('Timestmaped message.');

util.inspect(object, showHidden=false, depth=2)

以字符串形式返回object对象的结构信息,这对程序调试非常有帮助。

如果showHidden参数设置为true,则此对象的不可枚举属性也会被显示。

可使用depth参数指定inspect函数在格式化对象信息时的递归次数。这对分析复杂对象的内部结构非常有帮助。

默认情况下递归两次,如果想要无限递归可将depth参数设为null。

显示util对象所有属性的例子如下:

1
2
var util = require('util');
console.log(util.inspect(util, true, null));

util.pump(readableStream, writableStream, [callback])

实验性的

从readableStream参数所指定的可读流中读取数据,并将其写入到writableStream参数所指定的可写流中。当writeableStream.write(data)函数调用返回为false时,readableStream流将被暂停,直到在writableStream流上发生drain事件。当writableStream流被关闭或发生一个错误时,callback回调函数被调用。此回调函数只接受一个参数用以指明所发生的错误。

util.inherits(constructor, superConstructor)

将一个构造函数的原型方法继承到另一个构造函数中。constructor构造函数的原型将被设置为使用superConstructor构造函数所创建的一个新对象。

此方法带来的额外的好处是,可以通过constructor.super_属性来访问superConstructor构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var util = require("util");
var events = require("events");

function MyStream() {
events.EventEmitter.call(this);
}

util.inherits(MyStream, events.EventEmitter);

MyStream.prototype.write = function(data) {
this.emit("data", data);
}

var stream = new MyStream();

console.log(stream instanceof events.EventEmitter); // true
console.log(MyStream.super_ === events.EventEmitter); // true

stream.on("data", function(data) {
console.log('Received data: "' + data + '"');
})
stream.write("It works!"); // Received data: "It works!"

Node.js学习笔记——process 进程

发表于 2011-12-10 | 分类于 node.js

process对象是一个全局对象,可以在任何地方访问它。

它是EventEmitter事件触发器类型的一个实例。

Event: ‘exit’ 事件:’exit’

当进程对象要退出时会触发此方法,这是检查模块状态(比如单元测试)的好时机。当’exit’被调用完成后主事件循环将终止,所以计时器将不会按计划执行。

监听exit行为的示例:

1
2
3
4
5
6
process.on('exit', function () {
process.nextTick(function () {
console.log('This will not run');
});
console.log('About to exit.');
});

Event: ‘uncaughtException’ 事件:’uncaughtException’

function (err) { }

当一个异常信息一路冒出到事件循环时,该方法被触发。如果该异常有一个监听器,那么默认的行为(即打印一个堆栈轨迹并退出)将不会发生。

监听uncaughtException事件的示例:

1
2
3
4
5
6
7
8
9
10
11
process.on('uncaughtException', function (err) {
console.log('Caught exception: ' + err);
});

setTimeout(function () {
console.log('This will still run.');
}, 500);

// Intentionally cause an exception, but don't catch it.
nonexistentFunc();
console.log('This will not run.');

注意:就异常处理来说,uncaughtException是一个很粗糙的机制。在程序中使用try/catch可以更好好控制程序流程。而在服务器编程中,因为要持续运行,uncaughtException还是一个很有用的安全机制。

Signal Events 信号事件

该事件会在进程接收到一个信号时被触发。可参见sigaction(2)中的标准POSIX信号名称列表,比如SIGINT,SIGUSR1等等。

监听 SIGINT的示例:

1
2
3
4
5
6
// Start reading from stdin so we don't exit.
process.stdin.resume();

process.on('SIGINT', function () {
console.log('Got SIGINT. Press Control-D to exit.');
});

在大多数终端程序中,一个简易发送SIGINT信号的方法是在使用Control-C命令操作。

process.stdout

一个指向标准输出stdout的Writable Stream可写流。

示例:console.log的定义。

1
2
3
console.log = function (d) {
process.stdout.write(d + '\n');
};

process.stderr

一个指向错误的可写流,在这个流上的写操作是阻塞式的。

process.stdin

一个到标准输入的可读流Readable Stream。默认情况下标准输入流是暂停的,要从中读取内容需要调用方法process.stdin.resume()。

示例:打开标准输入与监听两个事件:

1
2
3
4
5
6
7
8
9
10
process.stdin.resume();
process.stdin.setEncoding('utf8');

process.stdin.on('data', function (chunk) {
process.stdout.write('data: ' + chunk);
});

process.stdin.on('end', function () {
process.stdout.write('end');
});

process.argv

一个包含命令行参数的数组。第一个元素是’node’,第二个元素是JavaScript文件的文件名。接下来的元素则是附加的命令行参数。

1
2
3
4
// print process.argv
process.argv.forEach(function (val, index, array) {
console.log(index + ': ' + val);
});

这产生如下的信息:

1
2
3
4
5
6
$ node process-2.js one two=three four
0: node
1: /Users/mjr/work/node/process-2.js
2: one
3: two=three
4: four

process.execPath

这是一个启动该进程的可执行程序的绝对路径名。

例如:

1
/usr/local/bin/node

process.chdir(directory)

改变进程的当前工作目录,如果操作失败则抛出异常。

1
2
3
4
5
6
7
8
console.log('Starting directory: ' + process.cwd());
try {
process.chdir('/tmp');
console.log('New directory: ' + process.cwd());
}
catch (err) {
console.log('chdir: ' + err);
}

process.cwd()

返回进程的当前工作目录。

1
console.log('Current directory: ' + process.cwd());

process.env

一个包括用户环境的对象。可参见environ(7)。

process.exit(code=0)

用指定的code代码结束进程。如果不指定,退出时将使用’success’(成功)代码 0。

以’failure’(失败)代码退出的示例:

1
process.exit(1);

执行node的shell会把退出代码视为1。

process.getgid()

获取进程的群组标识(详见getgid(2))。这是一个数字的群组ID,不是群组名称。

1
console.log('Current gid: ' + process.getgid());

process.setgid(id)

设置进程的群组标识(详见getgid(2))。参数可以是一个数字ID或者群组名字符串。如果指定了一个群组名,这个方法会阻塞等待将群组名解析为数字ID。

1
2
3
4
5
6
7
8
console.log('Current gid: ' + process.getgid());
try {
process.setgid(501);
console.log('New gid: ' + process.getgid());
}
catch (err) {
console.log('Failed to set gid: ' + err);
}

process.getuid()

获取进程的用户ID(详见getgid(2))。这是一个数字用户ID,不是用户名。

1
console.log('Current uid: ' + process.getuid());

process.setuid(id)

设置进程的用户ID(详见getgid(2))。参数可以使一个数字ID或者用户名字符串。如果指定了一个用户名,那么该方法会阻塞等待将用户名解析为数字ID。

1
2
3
4
5
6
7
8
console.log('Current uid: ' + process.getuid());
try {
process.setuid(501);
console.log('New uid: ' + process.getuid());
}
catch (err) {
console.log('Failed to set uid: ' + err);
}

process.version

一个编译内置的属性,用于显示NODE_VERSION(Node版本)。

1
console.log('Version: ' + process.version);

process.installPrefix

一个编译内置的属性,用于显示NODE_PREFIX(Node安装路径前缀)。

1
console.log('Prefix: ' + process.installPrefix);

process.kill(pid, signal=’SIGTERM’)

发送一个信号到进程。pid是进程的ID,参数signal是欲发送信号的字符串描述。信号名称是像’SIGINT’或者’SIGUSR1’这样的字符串。如果参数signal忽略,则信号为’SIGTERM’。详见kill(2)。

注意该函数名为process.kill,实际上也就像kill系统调用一样仅仅是一个信号发送器。发送的信号可能是要终止目标进程,也可能是实现其他不同的目的。

一个给自己发送信号的示例:

1
2
3
4
5
6
7
8
9
10
process.on('SIGHUP', function () {
console.log('Got SIGHUP signal.');
});

setTimeout(function () {
console.log('Exiting.');
process.exit(0);
}, 100);

process.kill(process.pid, 'SIGHUP');

process.pid

进程的PID。

1
console.log('This process is pid ' + process.pid);

process.title

获取或设置在’ps’命令中显示的进程的标题。

process.platform

运行Node的平台信息,如’linux2’,’darwin’`等等。

1
console.log('This platform is ' + process.platform);

process.memoryUsage()

返回一个描述Node进程内存使用情况的对象。

1
2
var util = require('util');
console.log(util.inspect(process.memoryUsage()));

这会生成如下信息:

1
2
3
4
{ rss: 4935680,
vsize: 41893888,
heapTotal: 1826816,
heapUsed: 650472 }

heapTotal与heapUsed指V8的内存使用情况。

process.nextTick(callback)

在事件循环的下一次循环中调用callback回调函数。这不是setTimeout(fn, 0)的一个别名,因为它有效率多了。

1
2
3
process.nextTick(function () {
console.log('nextTick callback');
});

process.umask([mask])

设置或者读取进程的文件模式创建掩码。子进程从父进程中继承这个掩码。如果设定了参数mask那么返回旧的掩码,否则返回当前的掩码。

1
2
3
4
var oldmask, newmask = 0644;

oldmask = process.umask(newmask);
console.log('Changed umask from: ' + oldmask.toString(8) + ' to ' + newmask.toString(8));

Node.js学习笔记——Modules 模块

发表于 2011-12-09 | 分类于 javascript

Node使用CommonJS模块系统。Node有一个简单的模块装载系统,在Node中,文件和模块是一一对应的。下面的例子展示了foo.js文件如何在相同的目录中加载circle.js模块。

foo.js的内容为:

1
2
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is ' + circle.area(4));

circle.js的内容为:

1
2
3
4
5
6
7
8
9
var PI = Math.PI;

exports.area = function (r) {
return PI * r * r;
};

exports.circumference = function (r) {
return 2 * PI * r;
};

circle.js模块输出了area()和circumference()两个函数,为了以对象的形式输出,需将要输出的函数加入到一个特殊的exports对像中。

模块的本地变量是私有的。在上面的例子中,变量PI就是circle.js私有的。

###Core Modules 核心模块###

Node有一些编译成二进制的模块。

核心模块在node源代码中的lib文件夹下。

核心模块总是被优先加载,如果它们的标识符被require()调用。例如,require(‘http’)将总是返回内建的HTTP模块,即便又一个同名文件存在。

###File Modules 文件模块###

如果没有找到确切的文件名,node将尝试以追加扩展名.js后的文件名读取文件,如果还是没有找到则尝试追加扩展名.node。.js文件被解释为JavaScript格式的纯文本文件,.node文件被解释为编译后的addon(插件)模块,并使用dlopen来加载。

以’/‘为前缀的模块是一个指向文件的绝对路径,例如require(‘/home/marco/foo.js’)将加载文件/home/marco/foo.js。

如果标明一个文件时没有 ‘/‘ 或 ‘./‘前缀,该模块或是”核心模块”,或者位于 node_modules目录中。

###Loading from ‘node_modules’ Folders 从 ‘node_modules’ 目录中加载###

如果传递到 require()的模块标识符不是一个核心模块,并且不是以’/‘,’../‘或’./‘开头,node将从当前模块的父目录开始,在其/node_modules子目录中加载该模块。

如果在那里没有找到,就转移到上一级目录,依此类推,直到找到该模块或到达目录树的根结点。

例如,如果在文件 ‘/home/ry/projects/foo.js’中调用 `require(‘bar.js’),node将会依次查找以下位置:

  • /home/ry/projects/node_modules/bar.js
  • /home/ry/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js

这允许程序本地化他们的依赖关系,避免发生冲突。

###Optimizations to the ‘node_modules’ Lookup Process 优化 ‘node_modules’ 的查找过程###

如果有很多级的嵌套信赖,文件树会变得相当的长,下面是对这一过程的一些优化。

首先, /node_modules不要添加到以 /node_modules结尾的目录上。

其次,如果调用require()的文件已经位于一个node_modules层次中,最上级的node_modules目录将被作为搜索的根。

例如,如果文件’/home/ry/projects/foo/node_modules/bar/node_modules/baz/quux.js’调用require(‘asdf.js’),node会在下面的位置进行搜索:

  • /home/ry/projects/foo/node_modules/bar/node_modules/baz/node_modules/asdf.js
  • /home/ry/projects/foo/node_modules/bar/node_modules/asdf.js
  • /home/ry/projects/foo/node_modules/asdf.js

###Folders as Modules 目录作为模块###

很方便将程序或库组织成自包含的目录,并提供一个单独的入口指向那个库。有三种方式可以将一个子目录作为参数传递给 require()

第一种方法是在目录的根下创建一个名为package.json的文件,它指定了一个main 模块。一个package.jso文件的例子如下面所示:

1
2
{ "name" : "some-library",
"main" : "./lib/some-library.js" }

如果此文件位于./some-library目录中,require(‘./some-library’)将试图加载文件./some-library/lib/some-library.js。

这是Node感知package.json文件的范围。

如果在目录中没有package.json文件,node将试图在该目录中加载index.js 或 index.node文件。例如,在上面的例子中没有 package.json文件,require(‘./some-library’)将试图加载:

  • ./some-library/index.js
  • ./some-library/index.node

###Caching 缓存###

模块在第一次加载后将被缓存。这意味着(类似其他缓存)每次调用require(‘foo’)如果解析到相同的文件,那么将返回同一个对象。

Node.js学习笔记——Timers 定时器

发表于 2011-12-08 | 分类于 javascript

本节内容与原生js类似。

setTimeout(callback, delay, [arg], […])

设定一个delay毫秒后执行callback回调函数的计划。返回值timeoutId可被用于clearTimeout()。可以设定要传递给回调函数的参数。

clearTimeout(timeoutId)

清除定时器,阻止指定的timeout(超时)定时器被触发。

setInterval(callback, delay, [arg], […])

设定一个每delay毫秒重复执行callback回调函数的计划。返回值intervalId可被用于clearInterval()。可以设定要传递给回调函数的参数。

clearInterval(intervalId)

清除定时器,阻止指定的interval(间隔)定时器被触发。

1234

SpeMoon

32 日志
59 分类
49 标签
© 2018 SpeMoon
由 Hexo 强力驱动
|
主题 — NexT.Muse v6.0.0