美文网首页
JS模块化 ---从探索到成熟

JS模块化 ---从探索到成熟

作者: 山野乔治 | 来源:发表于2019-04-30 19:49 被阅读0次

    什么是模块?如何结构化管理代码?

    模块是比对象和函数更大的代码单元,使用模块可以将程序进行归类。创建模块的时候,我们应该努力形成一致的抽象和封装。
    自然的优化程序的结构和组织的方式,就是把它们分成下的,耦合相对松散的片段,这些片段就是模块,我们也完成了结构化管理我们的代码。

    代码实现模块化的优点

    使用模块意味着可以在应用程序的不同地方更容易复用模块的功能,甚至可以跨应用来复用模块,极大地提高了应用程序的开发效率。并且模块的作用域的封闭,会形成变量名的保护,有自己的命名空间,合作开发的时候效率更高,会避免很多的麻烦。

    原生实现模块化技术

    利用现有的一些特性,如对象,立即执行函数,闭包等。

    实现模块化前提

    当利用现有技术创造,模块化代码的时候,每个模块系统至少应该能执行以下操作:

    • 定义模块接口,通过接口可以调用模块的功能。
    • 隐藏模块的内部实现,使模块的使用者无需关注模块内部的实现细节。同时,隐藏模块的内部实现,避免有可能产生的副作用和对bug的不必要的修改。

    模块化的了解顺序:

    (1) 如何使用如对象,闭包和立即执行函数来创建模块。

    (2)研究广泛使用的模块化标准AMD(Asynchronous Module Definition) 和CommonJS

    利用原生技术实现模块化

    利用模块模式 实现返回对象接口(对象接口通常包含一组数量和函数)
    使用自执行函数模块模式:

    //创建一个全局模块变量,赋值立即执行的函数  为返回的对象名
    const MouseCounterModule = function(){
        //创建模块私有变量
        let numClicks = 0;
        //创建模块私有函数
        const handleClick = ()=>{
            alert(++numClicks);
        };
        return {
            //返回一个对象,代表模块的接口. 通过闭包可以访问模块的私有变量和方法 但是无法访问模块的内部的实现
            countClicks : ()=>{
                //方法用来注册事件处理器  然后获取次数
                document.addEventListener("click",handleClick);
            }
        };
    }();
    

    立即执行函数内部,定义模块内部的实现细节: 一个函数变量numClicks 一个局部函数handleClick,都只能在模块内部访问。我们创建并返回一个对象作为模块的“公共接口”(MouseCounterModule)。此处由于暴露了接口(没有失去引用)所以可以保持活跃。不停调用countClicks 所以闭包内部的numClicks和handleClick保持活跃。

    这种在JS 中使用立即执行函数,对象和闭包来创建模块的方式我们称之为模块模式

    调用函数的自执行 进行模块扩展:

    //在不修改原有代码的前提下就可以定义更多的功能: 模块扩展
    // 传入参数为模块化
    (function(module){
        //定义新的私有变量和函数
        let numScroll = 0;
        const handleScroll = ()=>{
            alert(++numScrolls);
        }
        module.conuntScrolls =()=>{
            document.addEventListener("wheel",handleScroll);
        }
        return module;
    })(MouseCounterModule);
            
    

    注意 通过独立的立即执行函数扩展模块,无法共享模块私有变量,因为每个函数都分别创建了新的作用域,虽然这是一个缺点,但并不致命,我们仍然可以使用模块模式保持JavaScript应用模块化

    //可以给模块增加新属性
    MouseCounterModule.newMethod = ()=>{}
    //也可以轻松的创建一个子模块
    MouseCounterModule.newSubmodule = ()=>{
        //1 函数自执行 定义作用域  2 返回一个对象接口 一个小的模块
        return {...};
               }()
    

    糟糕的问题是:当我们开始创建模块化应用的时候,模块本身常常依赖其他模块的功能。比如在第二个模块中像用第一个模块的alert的方法计算出点击次数的函数。然而,模块模式无法实现。

    这些依赖关系。我们应该考虑正确的依赖顺序,这样我们的模块化才具有执行时所需的完整的依赖

    在使用 大量 内部模块 依赖的大型应用中则是非常严重的问题。
    总而言之,就是模块模式适应不了一些私有变量共享的问题,所以为了解决这个问题就出现了 AMD 和CommonsJS

    使用AMD和CommonsJS模块化JavaScript应用

    两者主要的区别在于: AMD的设计理念是明确基于浏览器的,而CommonsJS的设计是面向JavaScript环境的(如Node.js服务端)而不局限于浏览器

    AMD

    AMD可以自动解决依赖,异步加载模块,避免阻塞。(因为是浏览器,所以也能想到为什么是异步加载模块)

    AMD可以很容易指定模块及依赖关系。目前,基于AMD规范的是RequireJS依赖加载。

    案例: 利用AMD 进行模块化

    //配置path
    requirejs.config({
        paths: {
            "jquery": './libs/jquery-3.4.0',
            "CounterModule" : './libs/CounterModule'
        }
    })
    
    //定义模块函数
    define('CounterModule',['jquery'],()=>{
        let numClicks =0;
        let $ = jQuery;
        console.log(jQuery);
        const handleClick = ()=>{
            alert(++numClicks);
        };
        return{
            countClicks : ()=>{
                //jquery 引入成功
                console.log($)
                $(document).click(handleClick);
            }
        };
    });
    
    //业务函数 html 调用
    require(['./libs/require.config'],()=>{
        require(['CounterModule'],obj=>obj.countClicks());
    });
    

    注意在项目定义模块函数的时候要进行分情况判断

    1 在AMD 标准下调用 2 在CommonsJS 标准下调用 3 全局对象上调用

    ;(function(global,factory){
        //AMD
        if(type define ==='function' && define.amd){
            define(['jquery'],factory);  
        }else if(type module ==='object' && typeof module.exports ==='object'){
           //commonsJS
            var jquery = require('jquery');
            module.exports = factory('jquery');
        }else{
            //啥都没有
            global.PopUp = factory(jQuery);
        }
    })(window,function($){ 
        //这里的$ 是依赖的jQuery传入  这里定义的$是形参 接受,jQuery这个实参 这个函数PopUp对于        factory来说就是一个实参函数  要注意这里的知识点
        function PopUp(){
            this.init.apply(this,arguments);
        }
        //用后面的对象来覆盖前面的PopUp的原型对象
        $.extend(PopUp.prototype,{
            init(btn_selector,model_selector){
                this.btn = $(btn_selector);
                this.model = $(model_selector);
                this.bindEvent();
            },
            bindEvent(){
              this.btn.om('click',$.proxy(this.toggle,this));
              this.model.on('click',$.proxy(this.toggle,this));
            },
            toggle(){
                this.model.toggle();
            }
        })
        //插件更新了 PopUp的方法 jQuery继承了PopUp的方法
        $.extend({PopUp});
        return PopUp;
    })
    

    ES6模块

    • 于CommonsJS类似,ES6模块语法相对简单,并且基于文件(每个文件就是一个模块)
    • 于AMD类似,ES6模块支持异步模块加载。

    为了提供了这个模块功能 ES6引入了两个关键字:

    export ----- 从模块外部指定标识符

    import -----导入模块标识符

    它的主要思想就是: 必须显示地使用标识符导出模块,才能从外部访问的模式
    其他未标识的标识符,甚至在最顶级作用域中定义的(可以是标准JavaScript中的全局作用域)标识符,只能在模块内使用。这一点是受到CommonsJS启发的。

    引入部分
    • import 核心关键字表示引入内容
    • as 别名关键字
    • from 路径名称关键字
    • * 表示所有模块
    1. 加载 exprot default 返回的模块 (此格式返回的就是你写的对象)
    import obj from "path"
    

    tip : path 是根据当前文件的路径作为起始开始找寻的。

    1. 加载 exprot 返回的模块 (此格式返回的是 {你的对象} 所以导入的时候需要解构)
    import { A , B , C ...} from "path"
    

    tip: 用export返回的对象其实是被Module包裹的一个对象,所以在使用对象中内容的时候必须使用解构赋值

    1. 别名操作及*操作
    import { a as b } from "path"  // 别名
    import * as ModuleName from "path" // 这是加载了整个Module对象,在这个对象之中取值要用解构赋值;
    

    定义模块部分

    • export 定义模块的功能 ,export 直接定义的模块,在Module (ES6内置的一个容器)之中被包裹
    • default 和export连用,表示原样返回定义的模块内容,是什么就返回什么
    • from 和import表示的相同 都是路径
    • const | let | class | .... 所有的声明类关键字都可以用。
    1. 定义es6模块
    export {
          xxx1, 
          xxx2,
          xxx3
    }
    

    定义出来的结果是被Module(对象)包裹的,大概长成这样的对象 :

    Module {
        xxx1 , 
        xxx1 :getter(){}
        xxx1 :setter(){}
    }
    

    tip : 这个对象想要使用请务必使用解构赋值。

    1. 原样返回
    export default xxx //导入的时候直接自己定义变量名就可以
    
    1. 返回各种类型的数据;
    export const FOO = "FOO";
    export let FOO = "FOO";
    ...
    
    1. 返回加载来的数据;
    export { Foo } from "./test2.js";
    

    上述就是模块化的发展历程和使用的方法。

    相关文章

      网友评论

          本文标题:JS模块化 ---从探索到成熟

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