最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

如何实现JS代码的模块化

来源:动视网 责编:小采 时间:2020-11-27 20:22:26
文档

如何实现JS代码的模块化

如何实现JS代码的模块化: 为什么要使用模块模式 因为在全局作用域中声明的变量和函数都自动成为全局对象Window的属性,这经常会导致命名冲突,还会导致一些非常重要的可维护性难题,全局变量越多,引入错误BUG的概率就越大!所以我们应当尽可能少地使用全局变量,模块化的目的之一就
推荐度:
导读如何实现JS代码的模块化: 为什么要使用模块模式 因为在全局作用域中声明的变量和函数都自动成为全局对象Window的属性,这经常会导致命名冲突,还会导致一些非常重要的可维护性难题,全局变量越多,引入错误BUG的概率就越大!所以我们应当尽可能少地使用全局变量,模块化的目的之一就


  为什么要使用模块模式?

  因为在全局作用域中声明的变量和函数都自动成为全局对象Window的属性,这经常会导致命名冲突,还会导致一些非常重要的可维护性难题,全局变量越多,引入错误BUG的概率就越大!所以我们应当尽可能少地使用全局变量,模块化的目的之一就是为了解决该问题的!

  零全局变量模式

  该模式应用场景较少,通过一个IIFE(立即执行的匿名函数),将所有代码包装起来,这样一来所有的变量、函数都被隐藏在该函数内部,不会污染全局。

  使用情景:

  •   当该代码不会被其它代码所依赖时;

  •   当不需要在运行时不断的扩展或修改该代码时;

  •   当代码较短,且无需和其它代码产生交互时;

  •   单全局变量模式

      基本定义

      单全局变量模式即只创建一个全局变量(或尽可能少地创建全局变量),且该全局变量的名称必须是独一无二的,不会和现在、将来的内置API产生冲突,将所有的功能代码都挂载到这个全局变量上。

      它已经被广泛应用于各种流行的类库中,如:

    1.   YUI定义了唯一的YUI全局对象

    2.   JQuery定义了两个全局对象,$和JQuery

    3.   Dojo定义了一个dojo全局对象

    4.   Closure定义了一个goog全局对象

      例子:

    var Mymodule= {}; 
     
    Mymodule.Book = function(){...}; 
    Mymodule.Book.prototype.getName = function(){....}; 
     
    Mymodule.Car = function(){...}; 
    Mymodule.Car.prototype.getWheels = function(){....};

      一个模块的定义

      模块是一种通用的功能片段,它并没有创建新的全局变量或命名空间,相反,所有的代码都存放于一个单函数中,可以用一个名称来表示这个模块,同样这个模块可以依赖其他模块。

    function CoolModule(){ 
     var something = 'cool'; 
     var another = [1,2,3]; 
     function doSomething(){ 
     console.log( something); 
     } 
     function doAnother(){ 
     console.log(another.join('!')); 
     } 
     return { 
     doSomething: doSomething, 
     doAnother: doAnother 
     }; 
    } 
    var foo = CoolModule(); 
    foo.doSomething(); //cool 
    foo.doAnother(); //1!2!3

      这里的CoolModule 就是一个模块,不过它只是一个函数,这里调用CoolModule函数来创建一个模块的实例foo,此时就形成了闭包(因为CoolModule返回一个对象,其中的一个属性引用了内部函数),模块CoolModule返回的对象就是该模块的公共API(也可以直接返回一个内部函数)

      所以,模块模式需要具备两个必要条件:

    1.   必须有外部的封闭函数,且该函数必须至少被调用一次(每次调用都会创建一个新的模块实例),如CoolModule

    2.   封闭函数必须至少有一个内部函数被返回,这样内部函数才能在私有作用域中形成闭包,并且可以访问或修改私有的状态

      单例模块模式的实现:

    var foo = ( function CoolModule(){ 
     ...//代码同上例 
    })(); 
    foo.doSomething(); 
    foo.doAnother();

      还可以通过在模块内部保留对公共API对象的内部引用,这样就可以在内部对模块实例进行修改,包括添加、删除方法和属性

    function CoolModule(){ 
     var something = 'cool'; 
     var another = [1,2,3]; 
     function change() { 
     pubicAPI.doSomething = doAnother; 
     } 
     function doSomething(){ 
     console.log( something); 
     } 
     function doAnother(){ 
     console.log(another.join('!')); 
     } 
     var pubicAPI = { 
     change: change, 
     doSomething: doSomething 
     }; 
     return pubicAPI; 
    } 
    var foo = CoolModule(); 
    foo.doSomething(); //cool 
    foo.change(); 
    foo.doSomething(); //1!2!3 
    var foo1 = CoolModule(); 
    foo1.doSomething(); //cool

      现代的模块机制

      命名空间是简单的通过在全局变量中添加属性来表示的功能性分组。

      将不同功能按照命名空间进行分组,可以让你的单全局变量变得井然有序,同时可以让团队成员能够知晓新功能应该在哪个部分中定义,或者去哪个部分查找已有功能。

      例如:定义一个全局变量Y,Y.DOM下的所有方法都是和操作DOM相关的,Y.Event下的所有方法都是和事件相关的。

    1.   常见的用法是为每一个单独的JS文件创建一个新的全局变量来声明自己的命名空间;

    2.   每个文件都需要给一个命名空间挂载功能;这时就需要首先保证该命名空间是已经存在的,可以在单全局变量中定义一个方法来处理该任务:该方法在创建新的命名空间时不会对已有的命名空间造成破坏,使用命名空间时也不需要再去判断它是否存在。

    var MyGolbal = { 
     namespace: function (ns) { 
     var parts = ns.split('.'), 
     obj = this, 
     i, len = parts.length; 
     for(i=0;i<len;i++){ 
     if(!obj[parts[i]]){ 
     obj[parts[i]] = {} 
     } 
     obj = obj[parts[i]]; 
     } 
     return obj; 
     } 
    }; 
    MyGolbal.namespace('Book'); //创建Book 
    MyGolbal.Book; //读取 
    MyGolbal.namespace('Car').prototype.getWheel = function(){...}

      大多数模块依赖加载器或管理器,本质上都是将这种模块定义封装进一个友好的API

    var MyModules = (function Manager() { 
     var modules = {}; 
     function define(name, deps, impl) { 
     for(var i=0; i<deps.length; i++){ 
     deps[i] = modules[deps[i]]; 
     } 
     modules[name] = impl.apply(impl,deps); 
     } 
     function get(name) { 
     return modules[name]; 
     } 
     return { 
     define: define, 
     get: get 
     }; 
    })();

      以上代码的核心是modules[name] = impl.apply(impl,deps);,为了模块的定义引入了包装函数(可以传入任何依赖),并且将模块的API存储在一个根据名字来管理的模块列表modules对象中;

      使用模块管理器MyModules来管理模块:

    MyModules.define('bar',[],function () { 
     function hello(who) { 
     return 'let me introduce: '+who; 
     } 
     return{ 
     hello: hello 
     }; 
    }); 
    MyModules.define('foo',['bar'],function (bar) { 
     var hungry = 'hippo'; 
     function awesome() { 
     console.log(bar.hello(hungry).toUpperCase()); 
     } 
     return { 
     awesome: awesome 
     }; 
    }); 
    var foo = MyModules.get('foo'); 
    foo.awesome();//LET ME INTRODUCE: HIPPO

      异步模块定义(AMD):

    define('my-books', ['dependency1','dependency2'], 
     function (dependency1, dependency2) { 
     var Books = {}; 
     Books.author = {author: 'Mr.zakas'}; 
     return Books; //返回公共接口API 
     } 
    );

      通过调用全局函数define(),并给它传入模块名字、依赖列表、一个工厂方法,依赖列表加载完成后执行这个工厂方法。AMD模块模式中,每一个依赖都会对应到的参数传入到工厂方法里,即每个被命名的依赖最后都会创建一个对象被传入到工厂方法内。模块可以是匿名的(即可以省略第一个参数),因为模块加载器可以根据JavaScript文件名来当做模块名字。要使用AMD模块,需要通过使用与AMD模块兼容的模块加载器,如RequireJS、Dojo来加载AMD模块

    requre(['my-books'] , function(books){ 
     books.author; 
     ... 
     } 
    )

      以上所说的模块都是是基于函数的模块,它并不是一个能被稳定识别的模式(编译器无法识别),它们的API语义只是在运行时才会被考虑进来。因此可以在运行时修改一个模块的API

      未来的模块机制

      ES6为模块增加了一级语法支持,每个模块都可以导入其它模块或模块的特定API成员,同样也可以导出自己的API成员;ES6的模块没有‘行内’格式,必须被定义在的文件中(一个文件一个模块)ES6的模块API更加稳定,由于编译器可以识别,在编译时就检查对导入的API成员的引用是否真实存在。若不存在,则编译器会在运行时就抛出‘早期’错误,而不会像往常一样在运行期采用动态的解决方案;

      bar.js

    function hello(who) { 
     return 'let me introduce: '+who; 
    } 
    export hello; //导出API: hello

      foo.js

    //导入bar模块的hello() 
    import hello from 'bar'; 
     
    var hungry = 'hippo'; 
    function awesome() { 
     console.log(hello(hungry).toUpperCase()); 
    } 
    export awesome;//导出API: awesome

      baz.js

    //完整导入foo和bar模块 
    module foo from 'foo'; 
    module bar from 'bar'; 
    foo.awesome();
    1.   import可以将一个模块中的一个或多个API导入到当前作用域中,并分别绑定在一个变量上;

    2.   module会将整个模块的API导入并绑定到一个变量上;

    3.   export会将当前模块的一个标识符(变量、函数)导出为公共API;

    4.   模块文件中的内容会被当做好像包含在作用域闭包中一样来处理,就和函数闭包模块一样;

    文档

    如何实现JS代码的模块化

    如何实现JS代码的模块化: 为什么要使用模块模式 因为在全局作用域中声明的变量和函数都自动成为全局对象Window的属性,这经常会导致命名冲突,还会导致一些非常重要的可维护性难题,全局变量越多,引入错误BUG的概率就越大!所以我们应当尽可能少地使用全局变量,模块化的目的之一就
    推荐度:
    标签: 如何 实现 js
    • 热门焦点

    最新推荐

    猜你喜欢

    热门推荐

    专题
    Top