node.js 源码分析

node.js脚本文件是node.js的js部分的入口。它里面有几个重要的对象:

1.NativeModule:

Nodejs的模块分为两部分,一部分是用C实现的模块,比如:buffer、stdio等,另一部分是用js实现的模块比如net、dns等。对于C的模块,nodejs通过node.h提供的NODE_MODULE方法将模块存储在变量_module里。在srcde_extensions.cc中提供了get_builtin_module(name)接口获取这些模块;而对于js的模块,nodejs在srcde.js中实现了一个NativeModule对象用于管理js模块,它通过调用process.binding(“natives”)把所有内置的js模块放在NativeModule._source上,并提供require接口供调用。
为了提高模块加载的效率,nodejs在binding函数和require函数中都增加了缓存机制,在首次加载模块的时候,模块会copy的缓存中,以后的模块加载实际上只是从缓存中获得模块的接口,这样的机制使得nodejs在模块加载方面十分高效。
Nodejs模块的调用形式如下:
加载C++模块(以stdio为例):
process.binding("stdio")->get_builtin_module("stdio")-> _module -> NODE_MODULE(node_stdio, node::Stdio::Initialize)(定义)
加载js模块(以net为例)
require("net") -> NativeModule.require("net") -> process.binding("natives")["net"] -> DefineJavaScript() -> natives[] -> node_natives.h

function NativeModule(id) {
    this.filename = id + '.js';
    this.id = id;
    this.exports = {};
    this.loaded = false;
  }

  NativeModule._source = process.binding('natives');// 加载内部模块
  NativeModule._cache = {};
  // 主要就是这个方法
  NativeModule.require = function(id) {
    if (id == 'native_module') {
      return NativeModule;
    }

    var cached = NativeModule.getCached(id);
    if (cached) { 
      return cached.exports; // 如果存在缓存,直接返回
    }

    if (!NativeModule.exists(id)) {
      throw new Error('No such native module ' + id); // ID错误
    }

    process.moduleLoadList.push('NativeModule ' + id);

    var nativeModule = new NativeModule(id); // 如果不存在缓存,就创建一个

    nativeModule.cache(); // 缓存起来
    nativeModule.compile(); // 加载

    return nativeModule.exports;
  };

  NativeModule.getCached = function(id) {
    return NativeModule._cache[id];
  }

  NativeModule.exists = function(id) {
    return NativeModule._source.hasOwnProperty(id);
  }

  NativeModule.getSource = function(id) {
    return NativeModule._source[id];
  }

  NativeModule.wrap = function(script) {
    return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  };

  NativeModule.wrapper = [
    '(function (exports, require, module, __filename, __dirname) { ',
    '\n});'
  ];

  NativeModule.prototype.compile = function() {
    var source = NativeModule.getSource(this.id);
    source = NativeModule.wrap(source);

    var fn = runInThisContext(source, this.filename, true);
    fn(this.exports, NativeModule.require, this, this.filename);

    this.loaded = true;
  };

  NativeModule.prototype.cache = function() {
    NativeModule._cache[this.id] = this;
  };

 

 2.startup:

 

(function(process) { 
  this.global = this;
  startup();
});
 整个node.js文件就是一个函数对象的定义,而这个函数只做了两件事,所有到逻辑都在startup里面。

 

 

function startup() { 
    var EventEmitter = NativeModule.require('events').EventEmitter;

    process.__proto__ = Object.create(EventEmitter.prototype, {
      constructor: {
        value: process.constructor
      }
    });
    EventEmitter.call(process);

    process.EventEmitter = EventEmitter; // process.EventEmitter is deprecated 已经过时

    // do this good and early, since it handles errors.
    startup.processFatal(); // 异常处理模块

    startup.globalVariables(); // 全局属性初始化
    startup.globalTimeouts(); // 定义全局时间方法
    startup.globalConsole(); // 加载控制台模块

    startup.processAssert(); // 定义断言对象
    startup.processConfig(); // 加载process.config里面的配置内容,保存做process.config中
    startup.processNextTick(); // 定义process.nextTick方法,这个方法是让一个行为在下一次事件轮循的时候执行。
    startup.processStdio(); // 标准流
    startup.processKillAndExit(); // 定义进程到kill和exit方法
    startup.processSignalHandlers(); // 定义为进程添加信号的方法,这里的信号是指事件

    startup.processChannel(); // 这个暂时不清楚

    startup.resolveArgv0();

    // There are various modes that Node can run in. The most common two
    // are running from a script and running the REPL - but there are a few
    // others like the debugger or running --eval arguments. Here we decide
    // which mode we run in.

    if (NativeModule.exists('_third_party_main')) {
      // To allow people to extend Node in different ways, this hook allows
      // one to drop a file lib/_third_party_main.js into the build
      // directory which will be executed instead of Node's normal loading.
      process.nextTick(function() {
        NativeModule.require('_third_party_main');
      });

    } else if (process.argv[1] == 'debug') {
      // Start the debugger agent
      var d = NativeModule.require('_debugger');
      d.start();

    } else if (process._eval != null) {
      // User passed '-e' or '--eval' arguments to Node.
      evalScript('[eval]');
    } else if (process.argv[1]) { // 有第一个参数到时候
      // make process.argv[1] into a full path
      var path = NativeModule.require('path');
      process.argv[1] = path.resolve(process.argv[1]);

      // If this is a worker in cluster mode, start up the communiction
      // channel.
      if (process.env.NODE_UNIQUE_ID) {
        var cluster = NativeModule.require('cluster');
        cluster._setupWorker();

        // Make sure it's not accidentally inherited by child processes.
        delete process.env.NODE_UNIQUE_ID;
      }

      var Module = NativeModule.require('module');

      if (global.v8debug &&
          process.execArgv.some(function(arg) {
            return arg.match(/^--debug-brk(=[0-9]*)?$/);
          })) {

        // XXX Fix this terrible hack!
        //
        // Give the client program a few ticks to connect.
        // Otherwise, there's a race condition where `node debug foo.js`
        // will not be able to connect in time to catch the first
        // breakpoint message on line 1.
        //
        // A better fix would be to somehow get a message from the
        // global.v8debug object about a connection, and runMain when
        // that occurs.  --isaacs

        var debugTimeout = +process.env.NODE_DEBUG_TIMEOUT || 50;
        setTimeout(Module.runMain, debugTimeout);

      } else {
        // Main entry point into most programs:
        Module.runMain();
      }

    } else { // 没有参数的时候
      var Module = NativeModule.require('module');

      // If -i or --interactive were passed, or stdin is a TTY.
      if (process._forceRepl || NativeModule.require('tty').isatty(0)) {
        // REPL
        var opts = {
          useGlobal: true,
          ignoreUndefined: false
        };
        if (parseInt(process.env['NODE_NO_READLINE'], 10)) {
          opts.terminal = false;
        }
        if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) {
          opts.useColors = false;
        }
        var repl = Module.requireRepl().start(opts);
        repl.on('exit', function() {
          process.exit();
        });

      } else {
        // Read all of stdin - execute it.
        process.stdin.setEncoding('utf8');

        var code = '';
        process.stdin.on('data', function(d) {
          code += d;
        });

        process.stdin.on('end', function() {
          process._eval = code;
          evalScript('[stdin]');
        });
      }
    }

  startup.globalVariables = function() { // 这个方法很蛋疼啊,定义这么多重复变量吃啊
    global.process = process;
    global.global = global;
    global.GLOBAL = global;
    global.root = global;
    global.Buffer = NativeModule.require('buffer').Buffer;
    process.binding('buffer').setFastBufferConstructor(global.Buffer);
    process.domain = null;
    process._exiting = false;
  };

startup.globalTimeouts = function() {
    global.setTimeout = function() {
      var t = NativeModule.require('timers');
      return t.setTimeout.apply(this, arguments);
    };

    global.setInterval = function() {
      var t = NativeModule.require('timers');
      return t.setInterval.apply(this, arguments);
    };

    global.clearTimeout = function() {
      var t = NativeModule.require('timers');
      return t.clearTimeout.apply(this, arguments);
    };

    global.clearInterval = function() {
      var t = NativeModule.require('timers');
      return t.clearInterval.apply(this, arguments);
    };

    global.setImmediate = function() {
      var t = NativeModule.require('timers');
      return t.setImmediate.apply(this, arguments);
    };

    global.clearImmediate = function() {
      var t = NativeModule.require('timers');
      return t.clearImmediate.apply(this, arguments);
    };
  };
  }

 

 执行脚本部分代码:

function evalScript(name) {
    var Module = NativeModule.require('module');
    var path = NativeModule.require('path');
    var cwd = process.cwd();

    var module = new Module(name);
    module.filename = path.join(cwd, name);
    module.paths = Module._nodeModulePaths(cwd);
    var script = process._eval;
    if (!Module._contextLoad) {
      var body = script;
      script = 'global.__filename = ' + JSON.stringify(name) + ';\n' +
               'global.exports = exports;\n' +
               'global.module = module;\n' +
               'global.__dirname = __dirname;\n' +
               'global.require = require;\n' +
               'return require("vm").runInThisContext(' +
               JSON.stringify(body) + ', ' +
               JSON.stringify(name) + ', true);\n';
    }
    var result = module._compile(script, name + '-wrapper');
    if (process._print_eval) console.log(result);
  }

 nodejs执行脚本的方式有两种,一种是process进程对象的._compile方法,上面已经给出了。另外一种是evals模块的Script对象。module._compile(script, name + '-wrapper');。Script = process.binding('evals').Script,Script.runInThisContext(),Script.runInNewContext()。他们的本质是一样的,都是把内容传给V8解析执行。

var NativeModule = require('native_module');
var Script = process.binding('evals').NodeScript;
var runInThisContext = Script.runInThisContext;
var runInNewContext = Script.runInNewContext;

 

你可能感兴趣的:(node.js)