《Node.js高级编程》学习笔记

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

你可能感兴趣的:(《Node.js高级编程》学习笔记)