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

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

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

代码实现模块化的优点

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

原生实现模块化技术

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

实现模块化前提

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

  • 定义模块接口,通过接口可以调用模块的功能。
  • 隐藏模块的内部实现,使模块的使用者无需关注模块内部的实现细节。同时,隐藏模块的内部实现,避免有可能产生的副作用和对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";

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容