精华内容
下载资源
问答
  • 模块化编程是一种非常常见Javascript编程模式。它一般来说可以使得代码更易于理解,但是有许多优秀的实践还没有广为人知。 基础 我们首先简单地概述一下,自从三年前Eric Miraglia(YUI的开发者)第一次发表博客...
  • RequireJS模块化编程详解 转自:https://www.cnblogs.com/moqiutao/p/6864248.html 文章目录 1.模块的写法 原始方法 对象写法 立即执行函数写法 放大模式 宽放大模式(Loose augmentation) 输入全局...

    RequireJS模块化编程详解

    转自:https://www.cnblogs.com/moqiutao/p/6864248.html

    文章目录

    1.模块的写法

    模块化编程一般都有这么几个过渡过程,如下描述。

    原始方法

    function m1(){
      //...
    }
    function m2(){
      //...
    }

    上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就行了。

    这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

    对象写法

    为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。

    var module1 = new Object({
        _count : 0,
        m1 : function (){
          //...
        },
        m2 : function (){
          //...
        }
      });

    上面的函数m1()和m2(),都封装在module1对象里。使用的时候,就是调用这个对象的属性。

    module1.m1();

    但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。

    module1._count = 5;

    立即执行函数写法

    使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。

    var module1 = (function(){
        var _count = 0;
        var m1 = function(){
          //...
        };
        var m2 = function(){
          //...
        };
        return {
          m1 : m1,
          m2 : m2
        };
      })();

    使用上面的写法,外部代码无法读取内部的_count变量。

    console.info(module1._count); //undefined

    module1就是Javascript模块的基本写法。下面,再对这种写法进行加工。

    放大模式

    如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式"(augmentation)。

    var module1 = (function (mod){
        mod.m3 = function () {
          //...
        };
        return mod;
      })(module1);

    上面的代码为module1模块添加了一个新方法m3(),然后返回新的module1模块。

    宽放大模式(Loose augmentation)

    在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"。

    var module1 = ( function (mod){
        //...
        return mod;
      })(window.module1 || {});

    与"放大模式"相比,"宽放大模式"就是"立即执行函数"的参数可以是空对象。

    输入全局变量

    独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。

    为了在模块内部调用全局变量,必须显式地将其他变量输入模块。

    var module1 = (function ($, YAHOO) {
        //...
      })(jQuery, YAHOO);

    上面的module1模块需要使用jQuery库和YUI库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

    2.AMD规范

    2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。

    这标志"Javascript模块化编程"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。

    node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载。

    var math = require('math');

    然后,就可以调用模块提供的方法:

    var math = require('math');
      math.add(2,3); // 5

    因为这个系列主要针对浏览器编程,不涉及node.js,所以对CommonJS就不多做介绍了。我们在这里只要知道,require()用于加载模块就行了。

    有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。

    但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。还是上一节的代码,如果在浏览器中运行,会有一个很大的问题,你能看出来吗?

    var math = require('math');
      math.add(2, 3);

    第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。

    这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。

    因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。

    AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

    AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:

    require([module], callback);

    第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:

    require(['math'], function (math) {
        math.add(2, 3);
      });

    math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。

    3.require.js的加载

    <script src="js/require.js"></script>

    有人可能会想到,加载这个文件,也可能造成网页失去响应。解决办法有两个,一个是把它放在网页底部加载,另一个是写成下面这样:

    <script src="js/require.js" defer async="true" ></script>

    async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上。

    加载require.js以后,下一步就要加载我们自己的代码了。假定我们自己的代码文件是main.js,也放在js目录下面。那么,只需要写成下面这样就行了:

    <script src="js/require.js" data-main="js/main"></script>

    data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。

    require.config()的配置

    使用require.config()方法,我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。

    require.config({
        baseUrl: "js/lib",
        paths: {
          "jquery": "jquery.min",
          "underscore": "underscore.min",
          "backbone": "backbone.min"
        }
      });

    AMD模块的写法

    模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

    假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:

    // math.js
      define(function (){
        var add = function (x,y){
          return x+y;
        };
        return {
          add: add
        };
      });

    加载方法如下:

    // main.js
      require(['math'], function (math){
        alert(math.add(1,1));
      });

    如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

    define(['myLib'], function(myLib){
        function foo(){
          myLib.doSomething();
        }
        return {
          foo : foo
        };
      });

    当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。

    define()的完整定义:

    define('sample3' ,['sample','sample1'],function (sample,sample1) {
        var sample4 = require('sample4');
        return function(){
            alert(sample.name+':'+sample.sayhell());
        }
    });

    关于define函数的name和require函数的依赖名称之间的关系

    1)define(name,[] , callback); 这个name可以省掉,默认是文件名称;当然也可以自定义,一旦我们定义了name,根据源代码我们可以发现define函数内部其实就是把这个name以及依赖模块、回调函数作为一个对象存储在全局的数组当中,也就是 defQueue.push([name,deps,callback]);那么这个name就是这个组件注册的的ID!

    2)require([name , name2],callback); 系统首先会在全文检索path中是否对应的路径,如果没有自然把他作为路径拼接在baseUrl上去异步加载这个js文件,加载时从源代码中可以看到 ,var data = getScriptData(evt);返回的 data.id 其实就是name,然后执行contex.completeLoad(node.id),其内部就很清楚了,把define中注册的name和这里得到的name进行比较如果相等就执行,所以道理就是:require 和 define 的 name 必须保证一致!

    标签加载完成之后,获取标签的唯一标识name

    加载非规范的模块

    举例来说,underscore和backbone这两个库,都没有采用AMD规范编写。如果要加载它们的话,必须先定义它们的特征。

    require.config({
        shim: {
    
          'underscore':{
            exports: '_'
          },
          'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
          }
        }
      });

    require.config()接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义:

    (1)exports值(输出的变量名),表明这个模块外部调用时的名称;

    (2)deps数组,表明该模块的依赖性。

    比如,jQuery的插件可以这样定义:

    shim: {
        'jquery.scroll': {
          deps: ['jquery'],
          exports: 'jQuery.fn.scroll'
        }
      }

    require.js插件

    require.js还提供一系列插件,实现一些特定的功能。

    domready插件,可以让回调函数在页面DOM结构加载完成后再运行。

    require(['domready!'], function (doc){
        // called once the DOM is ready
      });

    text和image插件,则是允许require.js加载文本和图片文件。

    define([
        'text!review.txt',
        'image!cat.jpg'
        ],
    
        function(review,cat){
          console.log(review);
          document.body.appendChild(cat);
        }
      );

    类似的插件还有json和mdown,用于加载json文件和markdown文件。

    参考地址:

    Javascript模块化编程(一):模块的写法

    Javascript模块化编程(二):AMD规范

    Javascript模块化编程(三):require.js的用法

    requirejs 源码分析 http://schifred.iteye.com/blog/2330214

    展开全文
  • keil5 的模块化编程详解

    千次阅读 2018-06-23 21:18:00
    模块化编程是什么?就是可以吧你学的程序分成一个个模块,方便修改和移植。从事单片机和嵌入式这方面,编程具有强大移植性可以说是重中之重,如果你写一些LED数码管之类的小程序, 用模块化编程有些小题大做。但是...

     

    模块化编程是什么?就是可以吧你学的程序分成一个个模块,方便修改和移植。从事单片机和嵌入式这方面,编程具有强大移植性可以说是重中之重,如果你写一些LED数码管之类的小程序,

    用模块化编程有些小题大做。但是在团队做项目中,需要不同的人负责不同的一段程序,这时候就需要模块化编程了,在个人编大程序时也要用,因为好修改,想想你的代码几十行还好,如果

    几百几千行呢?所以说到这里,可见模块化编程几乎是所有大程序要做的,不然怎么修改?尤其是单片机这种灵活的语言。(建议有C语言基础的同学看)

          先提前说说与模块化编程有关的关键字加上英文意思; 

              static(静止的)

            extern(外部的)

       typedef(类型定义)

            #define(定义)

    **********************************

    #ifndef __**_H_  //如果有**.h文件,就。。。

    #define __**_H_  //如果没有就寻找**.h文件

     

    #endif  //否则就结束

             呵呵,先看看热热身 ,我们知道C语言有.c文件和.h文件,.c文件就是我们写的程序。.h文件呢是库文件,在写C语言程序的时候, #include“***.h”或include<***.h>,就是寻找库文件路径的意思,只是开始寻找的起点不同,

    比如printf输出函数在stdio库里就有这个函数,我们只不过是拿来用而已,我想表达的是,库文件里面存了方便我们去写程序的函数(也是程序,不过以函数的形式打包来让我们用)

    顺便说说code个关键字吧,比如 int code arr[]={};意思是把这个 i 变量的所占用的空间放在ROM中,也就是只读存储器,这样动态数据就可以拥有更多的空间速度也快了CPU也轻松了。单片机的内存是很宝贵的,RAM的容量运比ROM的小,而RAM又与CPU挂钩,其实我们手机的性能好不好与RAM有很大关系,我们说的运行内存就是RAM,太专业的小编也不会,我们知道code可以节省单片机内存就知道了。

    关键字这些与内存挂钩,C语言难也是难在内存上,我们掌握基本的用法来编C51就行不是吗,我这里是浅谈,

           想深入了解static的点这个链接            https://blog.csdn.net/keyeagle/article/details/6708077

    static 在程序中的作用是,当你在变量或函数面前加static后,表示该函数或变量就只能在这个C文件里用了,相当于一把锁来锁住它们。

               想深入了解extern的点这个链接           https://www.cnblogs.com/Lunais/p/5991135.html

    typedef和#define在两个宏定义很常用,以后还会专门为typedef搞个库方便调用,

       比如现在要定义数据类型

         可以这样  typedef  int   P;(加分号)

                        #define  P  int(不允许加任何符号)

    在后面要用到int来定义变量时可以直接,P  i=0(int  i=0);  我们用宏定义给这个数据可惜起了一个新名字P;C51的大程序里用的很多;

      typedef unsigned int  uInt8;      typedef unsigned char uChar16; 表示用uInt8新名字来代替unsigned int的类型

    接下来开始模块化编程,想按照步骤试试一遍,首先建立一个文件夹 

     

     

    在里面

    建立四个文件夹,c文件夹放.c文件,h文件夹放.h文件,main文件夹放main.c主函数文件,project文件夹放一些项目文件;

    这样可能有点晕是吧,没事下面就懂了;打开keil软件新建项目

     放在project文件夹里;取名pro,当然名字随便取的

    之后开始建立才C和H文件,一个C文件对应一个H文件,

     

     

     分别命名为delay.c     delay.h    led.h  led.c  main.c  存到之前建立的c和h main文件夹中。

     

     main.c主C文件单独放在main文件夹中

    全部保存效果是这样的

    一个C文件对应一个h文件,名字一样,这样好找。现在可以先在C文件里编程,也可以先提前设置好一些东西,这个前后顺序是没关系的,我先设置吧

    点这个;弹出

    可以吧标号1看做是大的以个文件夹,标号2是子文件,编号3是子文件夹对应的C文件;名字随便取,这里我取的是这些名字,接下来是到入C文件

    蓝底宽表示选中的目录下 导入,

    接下来是同样的步骤导入对应的C文件,完成后点击OK

    完成之后就是这样的以个布局效果;

    问题来了,这么多的C文件,我们要把编译的路径告诉给软件,不然找不到H也就是库文件,之前不是模块化编程我们不用理路径,是应为软件自带了#include"reg52.h"这些了,而现在我们是要自己

    造出#include“”这些,所以要给路径,

    第三是设置晶振频率,和编写的程序的晶振频率一样,第四是勾选生存HEX文件

    第五是搞路径的如图

    在这个框点6,

    点7建立   ×是删除

    点7后有框出来点8添加文件夹上的H文件夹就可以了;然后一直OK;

    编译后有两个警告是因为两个H文件夹没用,接下来编程;

     

    还有就是特别注意,#include" **.h"这里的名字一定是和H文件夹的名字一样。

    是的delayh文件书写格式是这样的;把函数名搞都里面;其实还要在void前面加extern的,但是keil5软件是默认前面已经加了,所以可加可不加;还有就是#ifndef后面加的这些__DELAY_H_  红色部分是可以以随便起不与其它重复的名称的,但是不管起什么名都得和下面#define一样,

    LED的C程序,引用了Delay_Ms函数;

    所以接下来

    在LED的H中包delay的库包含进来那么现在#include“led.h中有几个库呢?有52的耶有delay的,

    那写个主程序main编译后就直观了。

    零错误警告

    看到了吗,箭头的地方是那些h文件,是不是层层叠起来的,所以现在有点明白了这些格式的含义和模块化编程的强大了吗,

    都练习几次,加深理解,还有学到这里读者可以去网上看看别的模块化程序,还有很多技巧笔者就不多说了,有了基础可以慢慢理解别人的的知识,然后

    会做,最后形成自己的编程风格。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    转载于:https://www.cnblogs.com/hongjuedong/p/9214594.html

    展开全文
  • 前端技术发展日新月异,随着Ajax技术的广泛引用,jQuery库的层出不穷,前端代码日益膨胀,javascript的发展领域越来越广泛,就会应用使用模块化编程去规范管理。本文从模块化概念,为什么要模块化,各种模块化方式的...

    前言

    前端技术发展日新月异,随着Ajax技术的广泛引用,jQuery库的层出不穷,前端代码日益膨胀,javascript的发展领域越来越广泛,就会应用使用模块化编程去规范管理。本文从模块化概念,为什么要模块化,各种模块化方式的优缺点。以及并且都一一通过实例模拟演练,介绍模块化的发展进程,模块化规范,。能让读者更好的理解模块化编程的理念。

    模块化基本概念

    • 模块是什么?
      模块就是独立存储实现特定功能的一组方法的程序结构。其实就是一个实现特定功能的js文件。
    • 什么是模块化编程?
      模块化编程就是,程序中都是用模块先分类组织方法,再按需引入并使用模块中的方法。其实就是按一定规范将一个大文件拆分成相互依赖的小文件,再进行统一的拼装和加载。
    • 为什么要使用模块化编程?
      随着前端网站和应用的功能逐渐发展,引入的Javascript代码越来越庞大,就迫切需要按业务逻辑划分和维护程序结构,然后由团队分工协作才能完成。模块化编程就是为了便于按业务逻辑划分程序结构,并且便于团队分工协作。比如一个十万行代码的项目,如果都放在一个文件里,然后一群人围着一个文件办公,你可以想像一下那是个什么情况
    • 模块化编程优点
      1.避免变量全局污染,命名冲突
      2.提高代码复用率
      3.提高代码的可维护性
      4.多人协作互不干扰

    js文件实现模块化编程(原始方式)

    其实就是将多个相关的函数,集中定义在一个js文件中。

    • 全局function模式 : 将不同的功能封装成不同的全局函数。比如,我们可以创建一个users.js文件,包含登录和注册的方法,然后使用时引入users.js文件,就可以调用js文件中的方法了。
    //user.js文件
    function signin(){
      console.log("登录...");
    }
    function signup(){
      console.log("注册...");
    }
    
    <!-- 引入user.js文件 -->
    <script src="users.js"></script>
    <script>
      signin();
      signup();
    </script>
    

    问题:模块中所有内容都是全局变量和函数,造成全局污染,也极易发生冲突。

    比如我们修改users.js文件,添加getById函数,表示按id查找一个用户。再新建products.js文件,也添加getById函数,表示按id查找一个商品。

    //user.js文件
    function signin(){
      console.log("登录...");
    }
    function signup(){
      console.log("注册...");
    }
    function getById(){
      console.log("按id查询一个用户...");
    
    // product.js文件
    function getById(){
      console.log("按id查询一个用户...");
    }
    
    <!-- 引入user.js和product.js文件 -->
    <script src="users.js"></script>
    <script src="product.js"></script>
    <script>
      getById()
      //结果只会输出一个,后引入的getById()会把之前引入的覆盖掉
    </script>
    
    • namespace模式 : 简单对象封装。定义功能时,将一个js文件中一组相关方法,存储在一个对象结构中,,一定程度上减少了全局污染。
      使用时,同样引入js文件。
      但是,不能直接调用函数,而是通过对象.方法()的方式,调用对象的方法。但这样做也有问题,会将整个对象,暴露在其他程序中,易被篡改。
    //user.js文件
    var users={
      count: 10,
      signin(){
        console.log("登录...");
      },
      signup(){
        console.log("注册...");
      },
      getById(){
        console.log("按id查询一个用户...");
      },
      getCount(){
        console.log("在线人数: "+this.count);
      }
    }
    
    // product.js文件
    var products={
      getById(){
        console.log("按id查询一个商品...");
      }
    }
    
    <!-- 引入user.js和product.js文件 -->
    <script src="users.js"></script>
    <script src="product.js"></script>
    <script>
      users.signin();
      users.signup();
      users.getById();
      products.getById();//两个id查找函数都可以调用
      users.getCount();
      users.count=0;//轻松篡改对象中的属性
      users.getCount();
    </script>
    
    • IIFE模式:匿名函数自调用(闭包),可以防止对象中的属性被篡改。
      使用匿名函数自调,包裹内层函数定义。匿名函数返回生成的包含内层函数的模块对象。这样做的优点是:外部程序,只能获得返回的对象中的函数,而未包含在返回对象中的变量或函数,则被封装在形成的闭包中,不会被篡改。
    //user.js文件
    var users=(function(){
      var count=10;
      function signin(){}//里面代码和上面一样,省略了
      function signup(){}
      function getById(){}
      function getCount(){
        // 不是对象中的方法了,而是普通的函数了,所以不用加this
        console.log("在线人数: "+count);
      }
      return {signin, signup, getCount}
    })();
    
    <!-- 引入user.js和product.js文件 -->
    <script src="users.js"></script>
    <script src="product.js"></script>
    <script>
      users.signin();
      users.signup();
      users.getCount();
      // 尝试修改未抛出的count属性
      users.count=0; //访问不到闭包中的count,只是强行给users又添加了一个count属性
      users.getCount();//输入为10,内层函数只能访问闭包中保护的变量count,而不会访问对象上强行添加的属性
      users.getById();//试图访问未返回的方法,也访问不了!
      //只能调用return抛出的,未抛出,就无法访问
    </script>
    

    模块化规范

    • 虽然前边的三种方法都能一定程度实现模块化,但毕竟都很随意,没有形成标准,这就对不同功能模块之间的兼容性,造成了障碍。 为了消除这种模块定义方式上的不一致带来的障碍,行业内制定了一系列规范,来统一模块的定义方式。
    • 这种为了让程序互相之间能顺畅的无缝的加载各种模块,而制定的模块定义和使用的标准,就称为规范。定义模块和使用模块化编程,都必须遵守相应的规范。
    • 目前主流的模块化规范有四种:分别是CommonJS规范,AMD规范,CMD规范和ES6规范。接下来,我们就逐个聊聊每种规范的要求,以及使用场景。

    1. Commonjs规范

    • CommonJS规范是为了便于划分nodejs中各种服务器端功能,比如文件读写,网络通信,HTTP支持等,而专门制定的。
    • CommonJS规定:
      1.一个单独的js文件就是一个模块。js文件中,module对象,代表当前模块本身。
      2.加载模块使用require方法。require方法读取一个js文件并执行,最后返回文件内部的module对象的exports对象属性的内容
      3.require方法使用单例模式创建该模块。首次加载后,缓存起来,再次require时,反复使用同一个模块对象,不再重复创建。

    创建users文件,定义users模块,其中,专门封装用户相关的一组方法。所有方法都封装在一个对象中,最后,将对象整体赋值给module.exports属性。

    //user.js文件
    module.exports={
      signin(){
        console.log("登录...");
      },
      signout(){
        console.log("注销...");
      },
      signup(){
        console.log("注册...");
      }
    }
    

    添加use_users.js, 引入users模块,并解构其中的部分方法,为我所用

    //use_user.js文件
    var users=require("./users");
    var {signin, signup}=users;
    signin();
    signup();
    

    强调:

    • 使用require方法引入自定义模块,必须加路径前缀,比如./
    • 使用require方法引入系统内置模块,可直接用模块名。

    我们可以尝试使用exports别名来定义模块。
    强调: 虽然有简写的exports,但只是别名而已,不能完全代替module.exports,如果用’.'添加成员,则等效于module.exports。
    如果返回整个替换exports对象,就必须用module.exports属性。
    修改: users.js中module.exports为别名exports

    //user.js文件
    exports={ //module.exports={
      signin(){
        console.log("登录...");
      },
      signout(){
        console.log("注销...");
      },
      signup(){
        console.log("注册...");
      }
    }
    //运行,结果,报错
    
    //但修改为以下方式运行正确
    exports.signin=function(){
      console.log("登录...");
    },
    exports.signout=function(){
      console.log("注销...");
    },
    exports.signup=function(){
      console.log("注册...");
    }
    // 结果:正确
    

    问题: Commonjs规范中模块是同步加载:
    1.前一个模块加载完,才能加载后续模块,才能执行后续代码。
    2.如果运行在服务端,加载本地js文件模块,则不用担心加载速度问题
    3.如果运行在客户端,要加载远程服务器上的模块,这样就会造成延迟。因为: script默认是同步加载的。前一个script加载不完,后一个script不能开始。
    4.那么如何解决呢? 就是在客户端采用异步加载模块的方式。下面看异步模块加载AMD规范。

    2.AMD规范

    • AMD,全称:Asynchronous Module Definition,意为,采用异步方式加载模块。也就是说:前面模块的加载不影响它后面模块加载或语句的运行。
    • 如果模块之间确实有先后的依赖关系,那么,所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。
      这样的优点有两个:
      1.能异步加载多个js文件,减少网页加载的等待。
      2.能设置某两个js文件前后顺序加载,管理模块间的依赖性。
    • 如何使用AMD规范
      AMD规范不是JavaScript原生支持,需要用到对应的库函数:RequireJS库

    第一步:定义子模块,模块必须用特定的define()函数来定义,define()中回调函数的返回值return,决定了模块对外抛出的内容。
    第二步:在主js文件中引入子模块:
    强调: 无论是define()还是require()中[ ]里的模块js文件路径,都是相对于主js文件的。
    最后一步:HTML中,先引入require.js,并引入主js文件。

    引入主js文件,有两种方式: 普通引入和异步加载
    1.普通引入,是以同步方式加载require.js文件本身

    <script src="require.js" data-main="demo_js/main"></script>
    

    2.异步加载,使require.js文件也以异步方式加载

    <script src="require.js" defer async="true" data-main="demo_js/main"></script>
    

    新建文件夹demo_js,其中,新建modules文件夹,其中添加demo_js/modules/users.js,封装用户相关业务功能的模块

    // 用户模块,包含三个方法
    // AMD中define定义模块
    define(function(){
      alert("加载users.js模块");
      function signin(){
        console.log("登录...");
      }
      function signup(){
        console.log("注册...");
      }
      function signout(){
        console.log("注销...");
      }
      //AMD中要求用return抛出
      return {signin,signup,signout}
    });
    

    添加demo_js/modules/utils.js,封装通用工具功能的库

    alert("加载工具库utils.js...");
    

    添加demo_js/modules/products.js,封装商品相关业务功能的模块,依赖于utils模块

    define(['modules/utils'], function() { 
      alert("加载products.js模块...");
      function getById(){
        console.log("按id查询一个商品...");
      }
      return { getById }
    });
    

    在demo_js文件夹下,modules文件夹平级,添加demo_js/main.js,主js文件,引入其它子模块

    // 主模块
    alert("加载main.js");
    // 定义统一路径,要在require引入之前
    require.config({
      baseUrl: "demo_js/modules/"
      // paths:{
      //   "user":"module/user",
      //   "product":"module/product",
      //   "utils":"module/utils"
      // }
    })
    // 主模块中引入用户模块和商品模块,用require
    // function中user获得数组中第一个user模块返回的对象
    // product获得数组中第二个product模块返回的对象
    require(["users","products"],function(users,products){
      // 解构,不想用解构也可以用user.signin()
      const {signin,signup,signout} = users;
      signin();
      signup();
      signout();
      products.getById();
    });
    

    添加amd.html,用script引入require.js文件和主js文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
      <!-- data-main指定主模块  defer async="true"使require.js文件也以异步方式加载-->
      <script src="require.js"  defer async="true" data-main="demo_js/main"></script>
    </body>
    </html>
    

    运行,并观察network中和elements中head元素的变化
    发现是按照依赖顺序动态添加的script元素加载的其它模块的js文件
    在这里插入图片描述

    存在的问题

    • 1: 引入模块时,都要重复指定路径名
      修改主模块main.js,统一定义路径,2种方法解决

    方法一: 提前配置所有子模块以及更子一级模块的路径,并起别名。

    // 主模块demo_js/main.js中添加配置,要在require引入之前
    require.config({
      paths: {
        "users":"modules/users",
        "products":"modules/products",
        "utils":"modules/utils"
      }
    });
    // 然后修改所有require()和define()中引入的子模块路径,删除文件夹名,只保留文件名即可
    // 运行,发现结果正常。
    

    方法二: 配置基础目录:

    // 主模块demo_js/main.js中,要在require引入之前
    // paths是相对于主模块的路径,baseUrl是相对于根目录
    require.config({
      baseUrl:"demo_js/modules/"
    });
    // 运行,结果正常
    
    • 2: 打开network发现实际上,还是多个script请求,降低了页面加载的效率
      解决方法:使用requireJS工具,将多个js,按照依赖关系,打包为一个js文件
      从requireJS官网,下载r.js工具文件
      在r.js所在文件夹运行node命令, 注意: 不支持Es6语法,要把之前的Es6语法都改回来
    node r.js –o baseUrl=./demo_js/modules name=../main out=demo_js/main-built.js
    

    -o后,跟参数列表:

    • baseUrl=./demo_js/modules 定义包含所有子模块文件夹的
    • name=…/main 相对于baseUrl,指定main.js主文件的位置
    • out=demo_js/main-built.js 定义最终生成的一个js文件所在的路径

    结果在demo_js目录下生成了main-built.js
    修改amd.html页面中的data-main属性值为demo_js/main-built.js
    运行HTML,观察network和elements中head元素的变化
    结果network中发现除require.js外,只有一个main-built.js请求

    总结:

    • AMD的主要思想是,提前执行依赖的模块,再执行后续模块
      优点: 如果前一个依赖模块发生错误,后一个模块不再请求,节约带宽
    • 缺点: 无法按需加载模块
      比如: 如果模块的功能,只有在if判断满足条件时,才被使用。那么,提前执行,会导致即使条件不满足,也必须加载依赖的模块js
    • 复现AMD的缺点
    // 修改demo_js/main.js,使用随机数决定调用或不调用products.getById()方法
    if(Math.random()<0.5){
      products.getById();
    }else{
      alert("没用到products模块...");
    }
    

    运行: 结果: 即使没用到products模块的功能,也必须提前加载了products模块,在程序中按需加载js,这就要用到下一个标准CMD

    CMD规范

    • CMD 全称: Common Module Definition,像CommenJS和AMD的结合
    • 优点: 按需加载模块对象,因为也不是原生,必须用SeaJS库来实现CMD规范。
    • 大体步骤和AMD类似: 首先,定义子模块
    define(function(require, exports, module) {
      require()//用法同CommenJS中的require()
      exports别名//用法同CommenJS中的exports别名用法
      module对象//用法同CommenJS中的module对象用法
    });
    //require()用于在需要时引入子模块
    
    • 如果SeaJS配置了base基础路径,则不必每次都加./
    • 定义主模块,在主模块中,按需加载子模块使用
      其中:
    seajs.config({ })//用于配置基础路径 
    seajs.use(["主模块名",...],function(main,...){
        //用于引入主模块并执行
        //主模块路径相对于sea.js所在目录
    })
    

    seajs.use() vs require()

    • seajs.use() 主要用于载入入口js文件
    • require() 用于模块中引入其它子模块
    • 最后,在自定义脚本中引入主模块

    复制demo_js目录,重名名为demo_js1
    修改demo_js1/modules/users.js

    define(function(require, exports, module) {
      alert("加载users.js模块...");
      function signin(){
        console.log("登录...");
      }
      function signout(){
        console.log("注销...");
      }
      function signup(){
        console.log("注册...");
      }
      module.exports={
        signin:signin, signout:signout, signup:signup
      }
    });
    

    修改demo_js1/modules/utils.js

    define(function(){
      alert("加载工具库utils.js...");
    });
    

    修改demo_js1/modules/products.js

    define(function(require, exports, module) {
      alert("加载products.js模块...");
      require("utils");
      function getById(){
        console.log("按id查询一个商品...");
      }
      exports.getById=getById;
    });
    

    修改demo_js1/main.js

    define(function(require, exports, module){
      alert("加载main.js...");
      // 主模块中引入用户模块和商品模块,都有一半的几率,主要是看如果没有引入会不会提前加载
      if(Math.random()<0.5){
        var users=require("users");
        users.signin();
        users.signout();
        users.signup();
      }else{
        alert("没用到users模块...");
      }
      if(Math.random()<0.5){
        var products=require("products");
        products.getById();
      }else{
        alert("没用到products模块...");
      }
    });
    seajs.config({
      base:"./demo_js1/modules"
    });
    seajs.use(["./demo_js1/main"],function(main){})
    

    项目根目录:定义cmd.html,引入sea.js和主模块。

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Document</title>
    </head>
    <body>
      <script src="sea.js"></script>
      <script src="demo_js1/main.js"></script>
    </body>
    </html>
    

    在这里插入图片描述
    运行: 结果: 按需执行
    但是,观察element和network,依然有问题,只是按需执行,其实,还是在开始时,就加载了全部js文件,没有起到优化的目的

    • 解决: 用require.async()代替require()
    require.async("子模块",function(模块对象){
       ... 后续要执行的代码 ...
    })
    

    那么,require() vs require.async(),有什么不同?

    • 加载方式不同:
      1.require() 提前加载完,但暂不执行,等待按需执行
      2.require.async() 异步加载,后续代码放在回调函数中按需执行
    • 加载阶段不同
      1.require() 在代码分析阶段就加载所有模块js,没起到优化带宽的作用
      2.require.async() 在执行阶段,真正按需加载,按需执行

    下面我们在练习中体会一下:
    修改demo_js1/main.js,用require.async()代替require()

    define(function(require, exports, module){
      alert("加载main.js...");
      if(Math.random()<0.5){
        require.async("users",function(users){
          users.signin();
          users.signout();
          users.signup();
        })
      }else{
        alert("没用到users模块...");
      }
      if(Math.random()<0.5){
        require.async("products",function(products){
          products.getById();
        });
      }else{
        alert("没用到products模块...");
      }
    });
    

    在这里插入图片描述

    运行,结果一样。但是观察network和element,不再一开始加载所有,而是按需加载。

    • 那么,CMD规范的模块如何合并打包呢?
      使用的是Grunt工具:专门压缩,合并,打包的工具。但是,使用Grunt有两个前提:
      1.如果合并,就不能用require.async,动态引入模块,必须用require
      因为合并是将所有js文件合并为一个js文件,要下载一定是整个文件都下载。原来单独的js文件已经找不到了,自然无法有选择的下载。
      2.require中的路径不能偷懒: 同目录必须加./, 下级目录中的js必须: 子目录名/模块名
    • 如何使用呢?
      主要有四步:
      1.全局安装grunt-cli
      2.添加package.json
      3.定义Gruntfile.js
      4.调用命令合并,压缩模块代码

    东西挺多的,先写到这吧,希望看了之后可以对模块化编程有自己的理解。有问题欢迎交流,个人博客

    展开全文
  • 30.第十二讲.专题一之头文件与模块化编程详解中--力天手把手教你学单片机之附录篇
  • 31.第十二讲.专题一之头文件与模块化编程详解下--力天手把手教你学单片机之附录篇
  • 29.第十二讲.专题一之头文件与模块化编程详解上--力天手把手教你学单片机之附录篇
  • 模块化编程一般都有这么几个过渡过程,如下描述。 原始方法 function m1(){  //... } function m2(){  //... } 上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就行了。 这种做法的缺点很明显:"污染...
        

    1.模块的写法

    模块化编程一般都有这么几个过渡过程,如下描述。

    原始方法

    function m1(){
      //...
    }
    function m2(){
      //...
    }

    上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就行了。

    这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

    对象写法

    为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。

    var module1 = new Object({
        _count : 0,
        m1 : function (){
          //...
        },
        m2 : function (){
          //...
        }
      });
    

    上面的函数m1()和m2(),都封装在module1对象里。使用的时候,就是调用这个对象的属性。

    module1.m1();

    但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。

    module1._count = 5;
    

    立即执行函数写法

    使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。

    var module1 = (function(){
        var _count = 0;
        var m1 = function(){
          //...
        };
        var m2 = function(){
          //...
        };
        return {
          m1 : m1,
          m2 : m2
        };
      })();
    

    使用上面的写法,外部代码无法读取内部的_count变量。

    console.info(module1._count); //undefined
    

    module1就是Javascript模块的基本写法。下面,再对这种写法进行加工。

    放大模式

    如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式"(augmentation)。

    var module1 = (function (mod){
        mod.m3 = function () {
          //...
        };
        return mod;
      })(module1);
    

    上面的代码为module1模块添加了一个新方法m3(),然后返回新的module1模块。

    宽放大模式(Loose augmentation)

    在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"。

    var module1 = ( function (mod){
        //...
        return mod;
      })(window.module1 || {});

    与"放大模式"相比,"宽放大模式"就是"立即执行函数"的参数可以是空对象。

    输入全局变量

    独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。

    为了在模块内部调用全局变量,必须显式地将其他变量输入模块。

    var module1 = (function ($, YAHOO) {
        //...
      })(jQuery, YAHOO);
    

    上面的module1模块需要使用jQuery库和YUI库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

    2.AMD规范

    2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。

    这标志"Javascript模块化编程"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。

    node.js的模块系统,就是参照CommonJS规范实现的。在CommonJS中,有一个全局性方法require(),用于加载模块。假定有一个数学模块math.js,就可以像下面这样加载。

    var math = require('math');

    然后,就可以调用模块提供的方法:

    var math = require('math');
      math.add(2,3); // 5
    

    因为这个系列主要针对浏览器编程,不涉及node.js,所以对CommonJS就不多做介绍了。我们在这里只要知道,require()用于加载模块就行了。

    有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。

    但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。还是上一节的代码,如果在浏览器中运行,会有一个很大的问题,你能看出来吗?

    var math = require('math');
      math.add(2, 3);

    第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。

    这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。

    因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。

    AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

    AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:

    require([module], callback);
    

    第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码改写成AMD形式,就是下面这样:

    require(['math'], function (math) {
        math.add(2, 3);
      });
    

    math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。

    3.require.js的加载

    <script src="js/require.js"></script>

    有人可能会想到,加载这个文件,也可能造成网页失去响应。解决办法有两个,一个是把它放在网页底部加载,另一个是写成下面这样:

    <script src="js/require.js" defer async="true" ></script>
    

    async属性表明这个文件需要异步加载,避免网页失去响应。IE不支持这个属性,只支持defer,所以把defer也写上。

    加载require.js以后,下一步就要加载我们自己的代码了。假定我们自己的代码文件是main.js,也放在js目录下面。那么,只需要写成下面这样就行了:

    <script src="js/require.js" data-main="js/main"></script>
    

    data-main属性的作用是,指定网页程序的主模块。在上例中,就是js目录下面的main.js,这个文件会第一个被require.js加载。由于require.js默认的文件后缀名是js,所以可以把main.js简写成main。

    require.config()的配置

    使用require.config()方法,我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径。

    require.config({
        baseUrl: "js/lib",
        paths: {
          "jquery": "jquery.min",
          "underscore": "underscore.min",
          "backbone": "backbone.min"
        }
      });
    

    AMD模块的写法

    模块必须采用特定的define()函数来定义。如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

    假定现在有一个math.js文件,它定义了一个math模块。那么,math.js就要这样写:

    // math.js
      define(function (){
        var add = function (x,y){
          return x+y;
        };
        return {
          add: add
        };
      });
    

    加载方法如下:

    // main.js
      require(['math'], function (math){
        alert(math.add(1,1));
      });
    

    如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

    define(['myLib'], function(myLib){
        function foo(){
          myLib.doSomething();
        }
        return {
          foo : foo
        };
      });

    当require()函数加载上面这个模块的时候,就会先加载myLib.js文件。

    define()的完整定义:

    define('sample3' ,['sample','sample1'],function (sample,sample1) {
        var sample4 = require('sample4');
        return function(){
            alert(sample.name+':'+sample.sayhell());
        }
    });
    

    关于define函数的name和require函数的依赖名称之间的关系
    1)define(name,[] , callback); 这个name可以省掉,默认是文件名称;当然也可以自定义,一旦我们定义了name,根据源代码我们可以发现define函数内部其实就是把这个name以及依赖模块、回调函数作为一个对象存储在全局的数组当中,也就是 defQueue.push([name,deps,callback]);那么这个name就是这个组件注册的的ID!

    2)require([name , name2],callback); 系统首先会在全文检索path中是否对应的路径,如果没有自然把他作为路径拼接在baseUrl上去异步加载这个js文件,加载时从源代码中可以看到 ,var data = getScriptData(evt);返回的 data.id 其实就是name,然后执行contex.completeLoad(node.id),其内部就很清楚了,把define中注册的name和这里得到的name进行比较如果相等就执行,所以道理就是:require 和 define 的 name 必须保证一致!

    标签加载完成之后,获取标签的唯一标识name

    加载非规范的模块

    举例来说,underscore和backbone这两个库,都没有采用AMD规范编写。如果要加载它们的话,必须先定义它们的特征。

    require.config({
        shim: {
    
          'underscore':{
            exports: '_'
          },
          'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
          }
        }
      });
    

    require.config()接受一个配置对象,这个对象除了有前面说过的paths属性之外,还有一个shim属性,专门用来配置不兼容的模块。具体来说,每个模块要定义(1)exports值(输出的变量名),表明这个模块外部调用时的名称;(2)deps数组,表明该模块的依赖性。

    比如,jQuery的插件可以这样定义:

    shim: {
        'jquery.scroll': {
          deps: ['jquery'],
          exports: 'jQuery.fn.scroll'
        }
      }
    

    require.js插件

    require.js还提供一系列插件,实现一些特定的功能。

    domready插件,可以让回调函数在页面DOM结构加载完成后再运行。

    require(['domready!'], function (doc){
        // called once the DOM is ready
      });

    text和image插件,则是允许require.js加载文本和图片文件。

    define([
        'text!review.txt',
        'image!cat.jpg'
        ],
    
        function(review,cat){
          console.log(review);
          document.body.appendChild(cat);
        }
      );

    类似的插件还有json和mdown,用于加载json文件和markdown文件。

    参考地址:
    Javascript模块化编程(一):模块的写法
    Javascript模块化编程(二):AMD规范
    Javascript模块化编程(三):require.js的用法
    RequireJS define 详细介绍

    展开全文
  • JavaSript模块化 在了解AMD,CMD规范前,还是需要先来简单地了解下什么是模块化模块化开发? 模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问 题进行系统性的分解以之处理。...
  • 实例详解模块化编程

    千次阅读 2013-06-25 21:48:55
    当你在一个项目小组做一个相对较复杂的工程时,意味着你不再独自单干。你需要和你的小组成员分工合作,一起完成项目,这就要求小组成员各自负责一部分工程。比如你可能只是负责通讯或者...模块化的好处是很多的,不仅
  • Javascript模块化编程(二):模块化编程实战,require.js详解  上一篇文章“JavaScript模块化编程(一):模块原型和理论概念详解”,介绍了Javascript模块原型和理论概念,今天介绍如何将它们用于实战。  我...
  •  前段时间转载了阮一峰老师的两篇讲解Javascript模块化编程的文章:“JavaScript模块化编程(一):模块原型和理论概念详解”,介绍了Javascript模块原型和理论概念;Javascript模块化编程(二)...
  • 模块化 这个系列的文章,容我后续码字,敬请期待! 学习前端的过程中,我整理了很多资料,也希望能共享出来帮助到更多刚接触或者接触前端不久的同学。不过也为了把控微信群的质量,入群的一定要是前端的小伙伴...
  • JavaScript模块化编程(一):模块原型和理论概念详解 随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂。  网页越来越像桌面程序,需要一个团队分工协作、进度管理...
  • 新手发帖,很多方面都是刚入门,有错误的地方请大家见谅,欢迎批评指正  看了阮一峰教师的关于JavaScript模块化的文章后,解答了我思考良久的问题,然突有...“JavaScript模块化编程(一):模块原型和论理观点详解
  • 模块化编程

    2019-09-25 18:38:08
    AMD: 是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块模块的加载不影响它后面...AMD也采用require()语句加载模块详解参照http://www.ruanyifeng.com/blog/2012/10...
  • ID:嵌入式云IOT技术圈作者:杨源鑫传送门:【架构篇】嵌入式编程中如何给代码的结构分层废话不多说,理论讲太多没啥感觉,这些条条框框本质就是基于面对对象的设计模式相关的一些理论,设计模式...
  •  第一个参数是一个数组,表示所依赖的模块,上例就是['moduleA', 'moduleB', 'moduleC'],即主模块依赖这三个模块;  第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。 加载的模块会以...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 626
精华内容 250
关键字:

模块化编程详解