美文网首页
NODE02-Javascript模块化编程

NODE02-Javascript模块化编程

作者: 杨强AT南京 | 来源:发表于2019-10-23 08:10 被阅读0次

  模块化编程是每个语言都有的,因为Javascript早期的目的与版本的发展,Javascript的模块化编程相对来说比较混乱,尤其早期require/module.exports与后来向Java标准靠拢引入的import与export语法,则ES6以后显得混乱;但总的趋势还是标准化吧,只是这个时间估计还要混乱很久;等标准化后,本主题相关的内容就是过往云烟了。本主题的主要内容:
  1. require与module.exports;
  2. import与export;
  3. Node.js与ES6的语法;


  实际前端技术中的框架都是因这个灾难而起,babel, webpack,poi都与这个有关系(当然更主要的目的是把Javascript的开发技术都统一成一样的编写方式,而不是差异化的)


关于模块化

  • 模块化的概念从有计算机语言与程序依赖,一直都存在,但是我们这里指的模块化是指文件模块化,这是通用的模块化内涵:

    • 函数模块化
    • 类模块化
    • 文件模块化
  • 其他语言的模块化:

    • Java的import与package
    • C/C++语言的动态库与静态库
    • Python的import与目录__init__.py文件
  • 早期从HTML发展到Javascript,本身是提供模块化语法机制的

    • 从HTML引入多个Javascript,采用<script>标签的src属性可以引入多个Javascript文件;
  • 因为Javascript早期是服务与依赖HTML文件,纯粹的Javascript语言从发展开始本身没有提供模块化的语法机制,在ES发展之前,模块化问题导致很多问题:

    • 繁琐;
    • 代码污染;
    • 命名冲突;
    • 加载顺序与依赖关系混乱等;
  • 模块化的发展

    1. 函数与闭包模块化;
    2. 类模块化;
    3. 类模块化框架(早期雅虎研发的YUI);
    4. Node.js最早提出的CommonJS方案;
      • 局限:Node.js是服务器运行环境,这意味着CommonJS方案智能服务器端实现;
    5. AMD和RequireJS
      • AMD:Asynchronous Module Definition:一套抽象的约束定义;
      • RequireJS是AMD的落地实现框架;
    6. CMD和SeaJs
      • CMD:Common Module Definition,国内阿里仿照(据说是启发)AMD与CommonJS实现的一套规范;
      • SeaJs是CMD的实现;
    7. ES6的模块化
      • ES6从语法规范解决了模块化(2015年),问题是目前Node.js运行环境下import支持需要特殊的要求。
      • import作为关键字被引入ES6语法。
  • CommonJS与ES6模块化的主要区别

    1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
    2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

require的使用

  • require函数是实现模块化的应用函数(CommonJS方式,Node.js早期提出的实现);
    • require(id)
      • id: 模块名或者路径;(原生模块使用id名,文件模块使用文件路径)
      • 返回导入的模块内容(深拷贝)

模块的定义

module对象

  • Node.j提供一个内置对象module,表示当前模块的引用;

  • module对象的可阅读字符串输出为:

Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/Users/yangqiang/Documents/09Web/study/nod_demo/n04_module_03call.js',
  loaded: false,
  children: [],
  paths: 
   [ '/Users/yangqiang/Documents/09Web/study/nod_demo/node_modules',
     '/Users/yangqiang/Documents/09Web/study/node_modules',
     '/Users/yangqiang/Documents/09Web/node_modules',
     '/Users/yangqiang/Documents/node_modules',
     '/Users/yangqiang/node_modules',
     '/Users/node_modules',
     '/node_modules' ] }
  • module对象的属性:
    • id属性:模块名或者路径;
    • exports属性:导出的内容;
      • 这个属性是字典类型,单独一个对象也可以直接作为字典;
    • parent/children属性:用来维护模块的嵌套加载;
    • loaded属性:模块的加载状态
    • filename/paths属性:模块文件名与支持的搜索路径;

module.exports与require

  • exports指定导出的内容,require返回导出的内容;可以支持的有:
    1. 导出变量;
    2. 导出函数;
    3. 导出类;
    4. 导出对象变量;

导出的例子


    var v_a = "导出的变量";  

    function mod_func(){
        console.log("导出的函数");
    }

    function cls_a(){
        this.m_a = 20;
        this.func = function(){
            console.log("成员函数!");
        }
    }

    var obj_a = new cls_a();

    module.exports = {
        o_1 : v_a,
        o_2 : mod_func,
        o_3 : cls_a,
        o_4 : obj_a
    }

加载导出的例子


    var m = require("./n04_module_01exports");

    console.log(m.o_1);  // 输出1
    m.o_2();   // 输出2

    var m_o = new m.o_3();
    console.log(m_o.m_a);   // 输出3
    m_o.func();

    console.log(m.o_4.m_a);  // 输出4
    m.o_4.func();

exports字典的另外一种方式


    var v_a = "导出的变量";  

    function mod_func(){
        console.log("导出的函数");
    }

    function cls_a(){
        this.m_a = 20;
        this.func = function(){
            console.log("成员函数!");
        }
    }

    var obj_a = new cls_a();
    // 需要引号
    module.exports["o_1"] = v_a;
    module.exports["o_2"] = mod_func;
    module.exports["o_3"] = cls_a;
    module.exports["o_4"] = obj_a;

module.exports中需要注意的情况

  1. exportsda单绑定
    const EventEmitter = require('events');

    module.exports = new EventEmitter();     // 单绑定

    // 处理一些工作,并在一段时间后从模块自身触发 'ready' 事件。
    setTimeout(() => {
      module.exports.emit('ready');          // 直接使用exports调用。
    }, 1000);
  1. 绑定必须在加载的时候,不能再运行的时候
    • 不能在回调函数中绑定
    setTimeout(() => {
      module.exports = { a: 'hello' };
    }, 0);

全局变量exports

  • exports全局变量是module.exports的快捷调用。
    • 其实在模块执行之前,exports变量的内容会被赋值给module.exports属性,从而这两者之间本质是一致的。
    • 下面是一个例子:
    var v_a = "导出的变量";  

    function mod_func(){
        console.log("导出的函数");
    }

    function cls_a(){
        this.m_a = 20;
        this.func = function(){
            console.log("成员函数!");
        }
    }

    var obj_a = new cls_a();
    exports["o_1"] = v_a;
    exports["o_2"] = mod_func;
    exports["o_3"] = cls_a;
    exports["o_4"] = obj_a;

import的使用

  • 由于CommonJS与ES6的模块化不兼容,所以使用import/export与require/module存在差别;
    • 定义给require引用的模块,不能使用import引用;
    • ES6中模块为了解决这种兼容问题,在规范上规定:import引入的模块文件扩展名必须是.jsx

import语句

ImportDeclaration:
    import   ImportClause   FromClause;
    import   ModuleSpecifier;

  • 说明:
    • import支持两种语法风格

      1. import import从句 from从句
        • from从句:
          • from 模块说明符
        • import从句:
          • ImportedDefaultBinding
            • 缺省绑定引入(绑定标识字,yield, await);
          • NameSpaceImport
            • 命名空间引入(* as 绑定标识字,yield, await);
          • NamedImports
            • 命名引入;
              • {}
              • {ImportsList}
              • {ImportsList,}
          • ImportedDefaultBinding, NameSpaceImport
            • 混合引入
          • ImportedDefaultBinding, NamedImports
            • 混合引入
      2. import 模块说明符

export语句

    export ExportFromClause FromClause;
    export NamedExports;
    export VariableStatement[~Yield, ~Await]
    export Declaration[~Yield, ~Await]
    export default HoistableDeclaration[~Yield, ~Await, +Default]
    export default ClassDeclaration[~Yield, ~Await, +Default]
  • 说明:

    • ExportFromClause
      • *
      • * as IdentifierName
      • NamedExports
    • NamedExports;
      • {}
      • {ExportsList}
      • {ExportsList,}
  • 上面的语法结构采用比较严谨的定义方式,但是比较晦涩不易于阅读,后面通过例子来理解。

模块的定义

  • 一个模块就是一个独立的文件。该文件内部定义的内容(变量、对象、函数、类等),如果希望外部能够访问模块内部的内容,需要使用export关键字导出希望提供给外部访问的内容。
    var v_a = "导出的变量";  

    function mod_func(){
        console.log("导出的函数");
    }

    function cls_a(){
        this.m_a = 20;
        this.func = function(){
            console.log("成员函数!");
        }
    }

    var obj_a = new cls_a();

    export default {
        mod_func,
        v_a,
        mod_cls:cls_a,
        mod_obj:obj_a
    }

    // 测试环境下不支持
    // export default{
    //     cls_a as mod_cls,
    //     obj_a as mod_obj
    // }

模块的使用

    import mod from "./n05_module_01export"

    console.log(mod.v_a);
    mod.mod_func();
    mod.mod_obj.func();

Node.js对ES6的支持

  • 上面代码在node.js中以测试模式支持,正式运行并不支持,而且测试支持的语法也有一定的限制与改变;

  • 测试运行需要注意两个地方:

    • 扩展名需要mjs
    • node.js执行的时候,需要使用--experimental-modules开启测试模式。
    • Node.js下的测试模式运行

NodeJs兼容ES6的模块化语法

  • 怎样在Node.js中支持ES的import与export语法呢? 利用第三方提供的模块可以实现:
    • 代码翻译:babel-register
    • 运行环境:babel-node

babel-register

babel-register的安装

  • 这么模块需要单独安装:
    • 安装指令:npm install -g babel-register babel-preset-env --save-dev --unsafe-perm
      • 因为Mac OS系统需要权限,后面一个选项是方便处理安装权限。

        - babel-register的安装过程
  • 注意:

    • 上述环境如果安装在全局下无法使用,可以在工程目录下直接本地安装;
    • 全局安装找不到,需要设置系统环境变量;两种选择(需要理解node的模块查找机制):
      • 设置环境NODE_PATH;(下面截图就是配置的系统安装路径)
      • 使用本地安装;
  • 运行的NODE_PATH环境变量

babel-register使用的例子

  • 我们借用全面的例子,完全按照ES6的语法实现模块化,然后再node.js下执行;
    • 注意问文件扩展名是js
  1. 文件1:export
    var v_a = "导出的变量";  

    function mod_func(){
        console.log("导出的函数");
    }

    function cls_a(){
        this.m_a = 20;
        this.func = function(){
            console.log("成员函数!");
        }
    }

    var obj_a = new cls_a();

    export default {
        mod_func,
        v_a,
        mod_cls:cls_a,
        mod_obj:obj_a
    }

    // 测试环境下不支持
    // export default{
    //     cls_a as mod_cls,
    //     obj_a as mod_obj
    // }

  1. 文件2:import
    import mod from "./n06_module_01export"

    console.log(mod.v_a);
    mod.mod_func();
    mod.mod_obj.func();

  1. 文件3:使用label-register封装翻译
    require('babel-register') ({
        presets: [ 'env' ]
    })
    module.exports = require('./n06_module_02import');

babel-node

  • bael-node本质上与上面babel-register一样,只是使用的是命令行模式,这个命令需要安装babel-cli。
    • 好处就是不用编写一个封装js文件;
    • babel-node的帮助
  • 执行命令:
    • babel-node --presets env 需要执行的js文件
    • babel-node的执行

相关文章

网友评论

      本文标题:NODE02-Javascript模块化编程

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