高级4 AMD_CMD_RequireJS

1: 为什么要使用模块化?

  • 解决命名冲突
  • 依赖管理
  • 提高代码可读性
  • 代码解耦,提高复用性

2: AMD、CommonJS、CMD规范分别指什么?有哪些应用

CommonJS 规范:

1、CommonJs规范的出发点:JS没有模块系统、标准库较少、缺乏包管理工具;为了让JS可以在任何地方运行,以达到Java、C#、PHP这些后台语言具备开发大型应用的能力;
2、在CommonJs规范中:

  • 一个文件就是一个模块,拥有单独的作用域;
  • 普通方式定义的变量、函数、对象都属于该模块内;
  • 通过require来加载模块;
  • 通过exports和modul.exports来暴露模块中的内容;

CommonJS是服务器端模块的规范,Node.js采用了这个规范。


AMD规范:

AMD (Asynchronous Module Definition, 异步模块定义) 指定一种机制,在该机制下模块和依赖可以移步加载。这对浏览器端的异步加载尤其适用。

AMD在定义模块的时候需要制定依赖模块,并以形参的方式引入factory中

实现AMD的库(应用)有 RequireJS 、curl 、Dojo 等。


CMD规范:

在 CMD 规范中,一个模块就是一个文件
CMD(Common Module Definition)是 SeaJS推广过程中产生的。

CMD相当于按需加载,定义一个模块的时候不需要立即制定依赖模块,在需要的时候require就可以了

实现了CMD规范的应用就是 SeaJs了。


CommonJS 中 require 模块时是同步的,然后赋值给相应变量后使用。这在服务器端没问题,因为加载模块本质上就是从服务器的硬盘中读取,耗时忽略不计。

但是在浏览器端,加载js脚本,最常见的方法就是document中插入script标签,由于script标签是同步加载的,加载后就会立即执行,这个过程中是会阻止其他内容的加载。假如页面上有100个js脚本需要加载,那么同步加载对用户来说就是噩梦。

这时AMD和CMD就相继出现了,正是为了解决浏览器端需要异步加载大量脚本,以及脚本之间相互依赖的问题。

AMD 和 CMD 规范都是异步加载模块,只是AMD加载完所有模块后,会立即执行,执行完依赖模块后,再进入回调函数,执行主逻辑。

而CMD异步加载完所有模块后,并不会立即执行,仅仅是下载好,然后就会进入主逻辑,遇到require语句的时候,才会执行对应模块,算是按需执行。


3: 使用 requirejs 完成一个页面,包括如下功能:

  1. 首屏大图为全屏轮播
  2. 有回到顶部功能
  3. 图片区使用瀑布流布局(图片高度不一),下部有加载更多按钮,点击加载更多会加载更多数据(数据在后端 mock)
  4. 使用 r.js 打包应用

其实实现了requirejs模块化管理加载之后,使用r.js 只需要分分钟的事,重新配置一个配置文件即可。

requirejs可以不下载到本地,直接在script标签的src引入线上的url,但是r.js这个文件你必须本地要有:

  • 要么放到项目目录里,然后还要安装nodeJS,以便通过命令行来使用这个工具:
> node r.js -o build.js
  • 要么安装nodeJS后,在命令行里使用npm全局安装requirejs,自会赠送你r.js打包工具:
    为了确定是否安装,可以试着查看版本号,显示版本号就对了:
> r.js -v
> r.js: 2.3.5, RequireJS: 2.3.5, UglifyJS: 2.8.29

接下来切换到当前 build.js 所在目录,执行如下命令即可(此时无需添加 node 做前缀即可执行,因为已经全局附带安装了r.js,成为了命令行工具):

> npm install -g requirejs
> r.js -o build.js

因此,项目目录里 r.js 文件不是必须的,看看官方文档就知道了:
http://requirejs.org/docs/optimization.html

效果预览:

  • 在线预览地址(瀑布流中的图片接口可能被墙,你需要fq,才能看到完整功能)

  • github仓库地址


源码如下:

main.js:

requirejs.config({
  baseUrl: './src/js',
  paths: {
    'jquery': 'lib/jquery/jquery.min'
  }
})

//加载入口模块
requirejs(['app/index'])

build.js(用于r.js打包,可选):

({
  baseUrl: '.', //和main.js 指向同样 baseUrl,但build.js中是相对与当前目录,main.js相对于index.html
  paths: {
    'jquery': 'lib/jquery/jquery.min',
  },
  name: 'main',
  out: '../../dist/js/index.merge.min.js'
})

index.js:

define(['jquery', 'com/gotop', 'com/carousel', 'com/waterfall'], function($, GoTop, Carousel, WaterFall){
  new GoTop()
  Carousel.init($('.carousel'))
  new WaterFall()

  $(window).on('scroll', function(){
    if ($(this).scrollTop() > 900){
      $('nav').css('display', 'none')   
    }
    else{
      $('nav').css('display', 'block')
    }
  })

})
以下是具体的模块:

carousel.js(轮播):

/* 定义carousel轮播模块 */

define(['jquery'], function($){

  var Carousel = (function(){
    function _Carousel($ct){
      this.$ct = $ct
      this.init()
      this.bind()
      this.autoPlay()
    }
  
    _Carousel.prototype.init = function(){
      var $imgCt = this.$imgCt = this.$ct.find('.img-ct')
      var $imgs = this.$imgs = this.$ct.find('.img-ct > li') //这里缓存的对象,不会改变
      var $preBtn = this.$preBtn = this.$ct.find('.pre')
      var $nextBtn = this.$nextBtn = this.$ct.find('.next')
      var $bullets = this.$bullets = this.$ct.find('.bullet li')
  
      var imgCount = this.imgCount = $imgs.length //img数量
      var imgWidth = this.imgWidth = $imgs.width() //img宽度
  
      this.pageIndex = 0 //需要个变量来保存当前页数
      this.isAnimate = false  //定义一个锁,防止动画还没完成的重复点击,如果当前动画没完成,return 不理
  
      $imgCt.append($imgs.first().clone())
      $imgCt.prepend($imgs.last().clone())
      $imgCt.width((imgCount + 2) * imgWidth) //真实容器的宽度,算上了clone的两张,有了宽度,后面才可以通过css或者animate实现定位
      $imgCt.css({left: -imgWidth}) //初始到图片1的位置,由于新增了img,所以目前展示的是新增的,而计划中的img在第二位,故需调整一格
    }
  
    _Carousel.prototype.bind = function(){
      var _this = this
      this.$nextBtn.click(function(e){
        _this.playNext(1)//滚动一格,抽象一下,为了清晰明了
      })
      this.$preBtn.click(function(e){
        _this.playPre(1)
      })
      this.$bullets.click(function(){
        var index = $(this).index() //当前被点击的li所处顺序
        if(index > _this.pageIndex){
          _this.playNext(index - _this.pageIndex) //当前pageIndex 是0 ,点击了index是3, 所以3 - 0 = 3,执行三次
        }else if(index < _this.pageIndex){
          _this.playPre(_this.pageIndex - index)
        }
      })
    }
  
    _Carousel.prototype.playNext = function(len){
      var _this = this
      if(this.isAnimate) return;
      this.isAnimate = true //声明动画已经开始,结束前,不接受任何点击,会被直接return
      this.$imgCt.animate({  //原先 left: '-=' + imgWidth
        left: '-=' + len*this.imgWidth  //left 已经在上面变成了 -320px, 这里每次点击,必须累加才行 left = left - imgWidth
      }, function(){  //每次动画结束后,需要立刻更新pageIndex,然后判断这次动画是否属于到了末尾,又被点击的情况
        _this.pageIndex += len  //更新pageIndex
        if(_this.pageIndex === _this.imgCount){
          _this.pageIndex = 0 //如果pageIndex到达了4(图片数量),则立即pageIndex变为0
          _this.$imgCt.css({left: -_this.imgWidth}) //同时回到初始化的时候第一张图片,即回到图片1的位置,这里使用的可是css,不是animate,所以从clone img 转化为亲生的img,肉眼看不出来
        }
        _this.setBullet() //设置指示灯
        _this.isAnimate = false //声明动画已经结束,可以接受下次点击
      })
    }
  
    _Carousel.prototype.playPre = function(len){
      var _this = this
      if(this.isAnimate) return;
      this.isAnimate = true
      this.$imgCt.animate({
        left: '+=' + len*this.imgWidth
      }, function(){
        _this.pageIndex -= len
        if(_this.pageIndex === -1){ //或者判断 pageIndex < 0,  如果本次点击动画属于第一张再往前,则此时立即跳到最后一张原始图片,即 imgCount - 1
          _this.pageIndex = _this.imgCount - 1  //pageIndex 指向原始图片最后一张,但目前展示的图片实际上却是clone的最后一张
          _this.$imgCt.css({left: -_this.imgCount*_this.imgWidth})  //因此继续用css,悄无声息地定位到真正的最后一张图片
        }
        _this.setBullet()
        _this.isAnimate = false
      })
    }
    _Carousel.prototype.setBullet = function(){
      this.$bullets  //先去除所有的class,然后给与pageIndex对应的元素添加 active
          .removeClass('active')
          .eq(this.pageIndex)
          .addClass('active')
    }
    _Carousel.prototype.autoPlay =  function(){
      var _this = this
      setInterval(function(){
        _this.playNext(1)
      }, 3000)
    }

  
    return {  //声明的变量 Carousel,最后赋值的是立即执行函数return回来的东西,立即执行函数中其他的东西,都和Carousel无关
      init: function($ct){
        $ct.each(function(index, node){
          //new Carousel($(this))
          new _Carousel($(node))
        })
      }
    }
  })()
  
  //// Carousel.init($('.carousel')) //一次性启用所有DOM中的轮播 (如果jQuery选择器匹配到了多个结果)

  return Carousel
})

gotop.js(回到顶部):

/* 定义gotop回到顶部模块 */

define(function(){

  function GoTop(){
    this.createNode()
    this.bindEvent()
  }

  GoTop.prototype.bindEvent = function(){
    var self = this
    this.target.style.display = 'none' //先隐藏
    this.target.onclick = function(){
      window.scrollTo(0, 0)         //当点击按钮时,横纵滚动条全部复位
    }
    window.onscroll = function(){
      if(this.scrollY > 500){       //当滚动的时候,距离大于500px了,再显示gotop按钮
        self.target.style.display = 'block'
      }else{
        self.target.style.display = 'none'
      }
    }
  }

  GoTop.prototype.createNode = function(){
    var target = document.createElement('div')
    target.innerText = '回到顶部'
    target.classList.add('goTop')
    document.querySelector('body').appendChild(target)
    this.target = target
  }

  //new GoTop()
  
  return GoTop
})

waterfall.js(瀑布流)(这里用到图片api如果不能正常加载的话,或许你需要fq,或者你换个api):

/* 定义waterfall瀑布流新闻模块 */

define(['jquery'], function($){

  function WaterFall(){
    this.init()
  }

  WaterFall.prototype = {
    init: function(){
      this.nodeWidth = $('.waterfall>.item').outerWidth(true)
      this.count = 40
      this.colSumHeight = []
      this.imgWidth = $('.waterfall>.item').width()
      this.getResult(this.count)
      this.bind()
    },

    bind: function(){
      var self = this
      $('.wrap>#load').on('click', function(){
        if(this.clock){
          clearTimeout(this.clock)
        }
        this.clock = setTimeout(function(){
            self.getResult(self.count) //count本例中是固定20张
        }, 500)
      })
    },

    getResult: function(num){
      var arrItem = this.getItem(num)
      var self = this
      $.each(arrItem, function(idx, item){ //遍历数组每一项(这里的数组不是后端返回的,而是拼装DOM时,顺便放进了数组里)
        $(item).find('img').on('load', function(){
          $('.waterfall').append($(item)) //append单个item
          self.waterFallPlace() //waterfall单个item
        })
      })
    },

    getItem: function(num){
      var arrItem = []
      for(var i = 0; i < num; i++){
        var randomQuery = Math.random()
        var imgHeight = this.imgWidth/4 + 50*Math.floor(1 + Math.random()*5)
        var item = `
            
  • ![](https://unsplash.it/${this.imgWidth}/${imgHeight}?random&${randomQuery})
  • ` arrItem.push(item) } return arrItem }, waterFallPlace: function(){ var self = this var colNum = 5 //指定5列 for(var i = 0; i < colNum; i++){ //初始化高度数组 this.colSumHeight[i] = 0 } $('.waterfall>.item').each(function(){ var minSumHeight = Math.min.apply(null, self.colSumHeight) //使用apply才能传入数组,否则参数是单个列写的 var idx = self.colSumHeight.indexOf(minSumHeight) //找到最小高度列的索引值 $(this).css({ top: minSumHeight, left: self.nodeWidth*idx, opacity: 1 }) self.colSumHeight[idx] += $(this).outerHeight(true) $('.waterfall').height(Math.max.apply(null, self.colSumHeight)) //更新一下waterfall的高度(取决于高度数组最大的高度) }) } } // new WaterFall() return WaterFall })

    你可能感兴趣的:(高级4 AMD_CMD_RequireJS)