Node简介
什么是闭包
闭包就是函数,但是它可以继承并访问它自身被声明的那个作用域里的变量。
var clickCount = 0;
document.getElementById('myButton').onclick = function() {
clickCount += 1;
alert("clicked " + clickCount + " times.");
};
在JS中,函数是第一类对象
全局变量容易和其他代码冲突,最好用函数包装额外创建闭包避免污染全局作用域:
(function() {
var clickCount = 0;
document.getElementById('myButton').onclick = function() {
clickCount += 1;
alert("clicked " + clickCount + " times.");
};
}());
加载模块
导出模块
function Circle(x, y, r) {
function r_squared() {
return Math,pow(r, 2);
}
function area() {
return Math,PI * r_squared()
}
return {
area: area
};
}
module.exports = Circle;
module表示当前模块自身
加载模块
require可以用文件路径也可以用名称,除非是核心模块,否则用名称引用的模块最终都会被映射成路径。
var myModule = require('./myModule');
如果没有找到这个文件,Node会在文件名后加上.js扩展名再次查找路径。
还可以使用文件夹路径:
var myModule = require('./myModuleDir');
Node会假定文件夹是一个包,并试图查找包定义。包定义包含在名为package.json的文件中。
- 如果文件没有package.json,那么包的入口点会假定为默认值index.js。
- 如果有,尝试解析并查找main属性,当做入口点。
Node还会尝试查找node_modules文件夹,从pwd一直查到root。
缓存模块
console.log('initializing...');
module.exports = function() {
console.log('Hi');
};
console.log('initialized.');
var myModuleInstance1 = require('./my_module');
var myModuleInstance2 = require('./my_module');
输出:
initializing...
initialized.
只输出一遍,即只初始化一次
Node取消了JS默认的全局名称空间,而用CommonJS模块系统取代。
缓冲区
创建和设置缓冲区
JS善于处理字符串,但由于它最初是被设计用来处理HTML文档的,因此它不善于处理二进制数据。JavaScript中没有字节类型,也没有结构化类型,甚至没有字节数组类型,只有数值类型和字符串类型。
为了使二进制数据处理任务变得容易些,Node引入了二进制缓冲区实现,以Buffer伪类中的JS API形式暴露给外界。缓冲区长度以字节为计量单位,并且可以随机设置和获取缓冲区数据。
注意:Buffer类的另一个特别之处是数据占用的内存并不是分配在JS VM内存堆中,也就是说这些对象不会被垃圾收集算法处理:它会占据一个不会被修改的永久内存地址,这避免了因缓冲区内容的内存复制所造成的CPU浪费。
var buf = new Buffer('Hello World!');
var buf2 = new Buffer('8b76fde713ce', 'base64');
var buf3 = new Buffer(1024);
可被接受的标识符:
- ascii
- utf8
- base64
注意:如果将缓冲区中某个位置设置为一个大于255的数,则会用256取模;如果设置为小数100.7,则只保留整数部分100。
var buf = new Buffer(100);
console.log(buf.length);
for (var i=0; i < buf.length; i++){
buf[i] = i;
}
切分缓冲区
var buffer = new Buffer('this is the content of my buffer');
var smallerBuffer = buffer.slice(8, 19);
console.log(smallerBuffer.toString());
注意:slice只是对原始数据的引用,修改子缓冲区,父缓冲区也被修改。
在用slice创建子缓冲区时,父缓冲区在操作结束后依然继续被保留,并不会被垃圾收集器回收,容易造成内存泄漏。可以使用copy方法替代slice方法。
复制缓冲区
var buffer1 = new Buffer("this is the content of my buffer");
var buffer2 = new Buffer(11);
var targetStart = 0;
var sourceStart = 8;
var sourceEnd = 19;
buffer1.copy(buffer2, targetStart, sourceStart, sourceEnd);
console.log(buffer2.toString());
缓冲区解码
var b64Str = buf.toString("base64");
事件发射器
标准回调模式
后继传递风格(continuation-passing style,CPS)
var fs = require('fs');
fs.readFile('/etc/passwd', function(err, fileContent) {
if (err) {
throw err;
}
console.log('file content', fileContent.toString());
});
事件发射器模式
var req = http.request(options, function(response)) {
response.on("data", function(data) {
console.log("some data from the response", data);
});
response.on("end", function(){
console.log("response ended");
});
});
req.end();
当需要在请求的操作完成后重新获取控制权时就使用CPS模式,当事件可能发生多次时就使用事件发射器模式。
Node中的大多数事件发射器实现在程序发生错误时都会发射“error”事件。如果不监听该事件,则发生“error”事件时会向上抛出一个未捕获的异常。
事件发生器API
- .addListener和.on
function receiveData(data) {
console.log("got data from file read stream: %j", data);
}
readStream.addListener("data", receiveData);
// = readStream.on("data", receiveData);
可以多绑,但在触发事件时依次调用,若前一绑定抛出异常,则后续绑定不会被调用。
- .once
实现:
var EventEmitter = require("events").EventEmitter;
EventEmitter.prototype.once = function(type, callback) {
var that = this;
this.on(type, function listener() {
that.removeListener(type, listener);
callback.apply(that, arguments);
});
};
注意:function.apply()方法接受一个对象和一个参数数组,并将接受的对象作为一个隐含的this变量。
- .removeListener
- .removeAllListener
创建事件发射器
util = require('util');
var EventEmitter = require('events').EventEmitter;
//Here is the MyClass constructor:
var MyClass = function() {
}
util.inherits(MyClass, EventEmitter);
util.inherits建立了一条原型链,使MyClass类实例能够使用EventEmitter类的原型方法。
发射事件
MyClass.prototype.someMethod = function() {
this.emit("custom event", "argument 1", "argument 2");
};
var myInstance = new MyClass();
myInstance.on('custom event', function (str1, str2) {
console.log('got a custom event with the str1 %s and str2 %s!', str1, str2);
});
var util = require('util'),
EventEmitter = require('events').EventEmitter;
var Ticker = function(){
var self = this;
setInterval(function (){
self.emit('tick');
}, 1000);
};
util.inherits(Ticker, EventEmitter);
var ticker = new Ticker();
ticker.on("tick", function(){
console.log("tick");
});
定时器
process.nextTick
setTimeout使用JS运行时的内部执行队列,而不是事件循环;
用process.nextTick(callback)取代setTimeout(callback, 0),回调函数会在事件队列内的所有事件处理完毕后立刻执行,它要比激活JS的超时队列快得多。
process.nextTick(function() {
my_expensive_computation_function();
});
Node和JavaScript的运行时采用的是单线程事件循环。
事件循环被阻塞:(不要在事件循环内使用CPU敏感的操作)
process.nextTick(function nextTick1() {
var a=0;
while(true) a++;
});
process.nextTick(function nextTick2(){
console.log("next tick");
});
setTimeout (function timeout(){
console.log("timeout");
},1000);
通过使用process.nextTick,可以将一个非关键性的任务推迟到事件循环的下一轮在执行,这样可以释放事件循环,让它可以继续执行其他挂起的事件。
下面的例子,如果需要删除一个之前创建的临时文件,但又不想在客户端做出响应之前进行该操作,就可以延迟删除操作:
stream.on("data", function(data){
stream.end("my response");
process.nextTick(function() {
fs.unlink("/path/to/file");
});
});
setTimeout代替setInterval
var interval = 1000;
setInterval(function() {
my_async_function(function() {
console.log('my_async_function finished!');
});
});
使用setInterval无法保证这些函数不会同时执行。假如my_async_function函数的执行时间比interval变量多1毫秒,它们就会被同时执行,而不是按顺序串行执行。
因此需要指定my_async_function函数执行结束与下个my_async_function函数开始执行之间的时间间隔:
var interval = 1000;
(function schedule() {
setTimeout(function do_it() {
my_async_function(function() {
console.log('async is done!');
schedule();
});
}, interval);
}());
读写文件
三个特殊的文件描述符——1、2和3。它们分别表示标准输入文件、标准输出文件和标准错误文件的描述符。
处理文件路径
规范化路径
var path = require('path');
path.normalize('/foo/bar//baz/asdf/quux/..');
// -> '/foo/bar/baz/asdf'
连接路径
var path = require('path');
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// -> '/foo/bar/baz/asdf'
解析路径
类似挨个进行cd操作,不同在于它只是对路径字符串进行处理,不对路径是否正确进行判断。
var path = require('path');
path.resolve('/foo/bar', './baz');
// -> /foo/bar/baz
path.resolve('/foo/bar', '/tmp/file');
// -> /tmp/file
path.resolve('wwwroot', 'static_files/png/', '../gif/image/gif');
// -> /home/myself/node/ wwwroot/ static_files/gif/image.gif
相对路径
var path = require('path');
path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
// -> ../../impl/bbb
提取路径的组成部分
var path = require('path');
path.dirname('/foo/bar/baz/asdf/quux.txt');
// -> /foo/bar/baz/asdf
path.basename('/foo/bar/baz/asdf/quux.html')
// -> quux.html
path.basename('/foo/bar/baz/asdf/quux.html', '.html')
// -> quux
path.extname('/a/b/index.html');
// -> '.html'
path.extname('/a/b.c/index');
// -> ''
path.extname('/a/b.c/.');
// -> ''
path.extname('/a/b.c/d.');
// -> '.'
确定路径是否存在
var fs = require('fs');
fs.exists('/etc/passwd', function(exists) {
console.log('exits:', exists);
// -> true/false
});
fs模块简介
fs.stat函数查询文件或目录的元信息:
var fs = require('fs');
fs.stat('/etc/passwd', function(err, stats) {
if (err) { throw err; }
console.log(stats);
});
fs.stat()函数返回一个stats类,可以用该类继续调用函数:
- stats.isFile()
- stats.siDirectory()
- stats.isBlockDevice()
- stats.isCharacterDevice()
- stats.isSymbolicLink()
- stats.isFifo()
- stats.isSocket()
打开文件
var fs = require('fs');
fs.open('/path/to/file', 'r', function(err, fd) {
//获取文件描述符fd
});
读取文件
var fs = require('fs');
fs.open('./my_file.txt', 'r', (err, fd) => {
if (err) { throw err; }
var readBuffer = new Buffer(1024),
bufferOffset = 0,
bufferLength = readBuffer.length,
filePosition = 100;
fs.read(fd,
readBuffer,
bufferOffset,
bufferLength,
filePosition,
(err, readBytes) => {
if (err) { throw err; }
console.log('just read ' + readBytes + 'bytes');
if (readBytes > 0) {
console.log(readBuffer.slice(0, readBytes));
}
});
});
写入文件
var fs = require('fs');
fs.open('./my_file.txt', 'a', (err, fd) => {
if (err) { throw err; }
var writeBuffer = new Buffer('writing this string'),
bufferPosition = 0,
bufferLength = writeBuffer.length,
filePosition = null;
fs.write(fd,
writeBuffer,
bufferPosition,
bufferLength,
filePosition,
(err, written) => {
if(err) { throw err;}
console.log('wrote ' + written + ' bytes');
});
});
关闭文件
fs.close(fd [, callback]);
上述都是底层的原语操作来打开、读写和关闭文件。若想并发地写文件,应用WriteStream;读取文件中某个区域,考虑ReadStream。
进程
Node是被设计用来高效处理I/O操作的,但正如你所见,某些类型的程序并不适用于这种模式。比如当用Node处理一个CPU密集型的任务时可能会阻塞事件循环,并会因此降低使用程序的响应能力。替代的方法是,CPU密集型任务应该被分配给另一个进程处理,从而释放事件循环。
执行外部命令
var child_process = require('child_require');
var exec = child_process.exec;
exec(command, callback);
command表示shell命令的字符串表示。
exec('ls', function(err, stdout, stderr) {
//...
});
还可以传递一个包含若干配置选项的可选参数:
var exec = require('child_require').exec;
var options = {
timeout: 10000,
killSignal: 'SIGKILL'
};
exec('cat *.js | wc -l', options, function(err, stdout, stderr) {
//...
});
详细配置选项看官方文档
var env = process.env,
varName,
envCopy = {},
exec = require('child_process').exec;
//将process.env对象的内容复制到envCopy中
for (varName in env) {
envCopy[varName] = env[varName];
}
//分配一些自定义变量
envCopy['CUSTOM ENV VAR'] = 'some value';
envCopy['CUSTOM ENV VAR 2'] = 'some other value';
//结合process_env对象和自定义变量执行命令
exec('ls -la', { env: envCopy }, (err, stdout, stderr) => {
if (err) { throw err; }
console.log('stdout:', stdout);
console.log('stderr:', stderr);
});
环境变量是通过操作系统在进城之间传递的,因此所有环境变量值都是以字符串的形式传入子进程的。如数字123,接收到字符串123。
//parent.js
var exec = require('child_process').exec;
exec('node child.js', {env: {number: 123}}, (err, stdout, stderr) => {
if (err) { throw err; }
console.log('stdout:\n', stdout);
console.log('stderr:\n', stderr);
});
//child.js
var number = process.env.number;
console.log(typeof(number));
number = parseInt(number, 10);
console.log(typeof(number));
生成
test test test