zoukankan      html  css  js  c++  java
  • 你知道require是什么吗?

    引题     

         用过node的同学应该都知道require是用来加载模块的,那你是否存在如下的疑问呢? 

    1. require(path)是如何依据path找到对应module呢? 

    2. 为何在模块定义中,一定要通过module.exports暴漏出接口?module.exports与require存在什么关系

         对上述问题进行概括可以抽象出如下两个问题:

    1. module的路径分析
    2. 文件加载

    切入

       首先来直观地看看require是什么? 

    // node环境下执行:
    console.log(require.toString)
    
    //输入结果为:
    'function require(path) {
        return self.require(path);
      }'
    

      上述代码说明require函数仅仅是module.require的封装,这样就需要查看node中的module源代码了。

    加载模块的方式

           首先来直观来认识一下node的模块加载方式有哪些方式:

    case 1:

    // 'path'为node的核心模块
    var path = require('path')

    case2:

    // a.js,路径为: basePath/a.js
    var myModule = require('./my-module')
    // my-module的路径为basePath/node_modules/myModule.js

    case 3:

    // a.js, 路径: basePath/a.js
    var main = require('./')
    // basePath下还包括package.json, index.js

    路径解析

         在node的官方API中,我们可以找到这段描述:

    To get the exact filename that will be loaded when require() is called, use the require.resolve() function.

    Putting together all of the above, here is the high-level algorithm in pseudocode of what require.resolve does:

     ......

         试试在node环境下用用require.resolve这个API:

    require.resolve('./a.js')
    // 这样就得到a.js的绝对路径
    

         为了探索缘由,就从node核心代码中的mdoule.js找答案吧: 

    require.resolve = function(request) {
      return Module._resolveFilename(request, self);
    }
    	
    Module._resolveFilename = function(request, parent) {
      // 判断是否为node的核心模块
      if (NativeModule.exists(request)) {
         return request;
       }
       // 得到查询路径,格式为数组:[id, [paths]]
       var resolvedModule =  Module._resolveLookupPaths(request, parent);	  	                 
    
       var paths = resolvedModule[1];
      // 根据path、fileName得到绝对路径
      var filename = Module._findPath(request, paths);
       return filename;
    }    
    

         那Module._resolveLookupPaths是如何得到所有查询路径的呢?

    1. 为node的核心模块,stop
    2. 以./或../开头,本地查找, stop
    3. 沿着文件树,得到node_module的所有路径,直到/node_modules,在node_module中查找,stop
    4. path为目录,则检查package.json文件是否存在main属性,否则默认为index.js
    5. 最后返回new Error('Cannot find module"' + request + '"');

    模块加载

          先看require的源代码:

    // 我们经常使用的require函数
    function require(path) {
        return self.require(path);
    }
    // 调用_load函数,加载所需的模块
    Module.prototype.require = function(path) {
      return Module._load(path, this);
    }
    

         这样模块函数的调用连接到了Module._load函数:

    Module.cache = {};
    Module._load = function() {
      // 检测模块是否已经加载过
      var cachedModule = Module._cache[filename];
       if (cachedModule) {
         return cachedModule.exports;
       }
       // 模块还未加载,则为模块创建module实例
       var module = new Module(filename, parent);
      // 新创建的实例存储于cache中
       Module._cache[filename] = module;
       // 开始获取模块的内容
       module.load(filename);
       // 对外提供接口
       return module.exports;
    }
    

      接下来问题的关键就变成了module.load,该方法用于获取module的内容,然后进行解析:

    Module.prototype.load = function(filename) {
      // 解析出文件的后缀, 存在['.js', '.json', 'node']三种后缀
      var extension = path.extname(filename) || '.js';
      // 根据后缀,获取相关的模块
      Module._extensions[extension](this, filename);
    }
    

      node会匹配按照.js、.json、.node三种格式进行模块匹配,根据文件类型的不同采取不同的加载策略,但是以实际开发中以加载.js最多,该种策略最后需要调用Module.prototype._compile进行编译处理:

    Module._extensions['.js'] = function(module, filename) {
      var content = fs.readFileSync(filename, 'utf8');
      module._compile(stripBOM(content), filename);
    };
    
    Module.prototype._compile = function(content, filename) {
      //将内容放入到(function() { content }),形成闭包,创建私有作用域
      var wrapper = Module.wrap(content);
      // bind新的执行上下文
      var compiledWrapper = runInThisContext(wrapper, { filename: filename });
      // 向外暴漏接口:module.exports, require, module,__filename,  __dirname, 
      var args = [self.exports, require, self, filename, dirname];
       return compiledWrapper.apply(self.exports, args);
    }
    

      这样,我们就可以在require来获取相应地module。

    结论
          node现在这么火,各种优势铺天盖地涌来,会让刚刚入行的人觉得深不可测,因而往往会让人望而却步。但是只要我们敢于突破第一步,深入下来仔细分析,就会发现其实没有那么晦涩难懂,踏出第一步真的很关键!

    参考资料

    http://thenodeway.io/posts/get-fancy/how-require-actually-works/

    https://github.com/joyent/node/blob/master/lib/module.js

    http://nodejs.org/api/modules.html

    https://github.com/substack/node-resolve

  • 相关阅读:
    apicloud教程
    apicloud教程3 (转载)
    apicloud教程2 (转载)
    apicloud教程1 (转载)
    API CLOUD 快捷键
    JS IIFE写法
    php事件驱动
    JQuery实践--Why JQuery
    Jquery实践--精读开篇
    python 实践--新闻聚合
  • 原文地址:https://www.cnblogs.com/bigbrother1984/p/4174663.html
Copyright © 2011-2022 走看看