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 完成一个页面,包括如下功能:
- 首屏大图为全屏轮播
- 有回到顶部功能
- 图片区使用瀑布流布局(图片高度不一),下部有加载更多按钮,点击加载更多会加载更多数据(数据在后端 mock)
- 使用 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
})