美文网首页
《Node.js高级编程》学习笔记

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

作者: VVKoishi | 来源:发表于2018-10-12 21:42 被阅读0次

    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高级编程》学习笔记

          本文链接:https://www.haomeiwen.com/subject/gvzgaftx.html