前言:在慕课网上跟着视频《侧边工具栏开发》作了一遍,用到了jquery操做DOM,其中,用requirejs管理模块依赖,而后自定义了两个模块它们都依赖jquery,而且其中一个自定义模块依赖另外一个,因此要暴露出接口。看完视频初步认识了一下requirejs,以及模块化开发的概念,在此作一下总结。感谢慕课网上的老师。javascript
使用模块化开发的好处:css
有效的防止命名冲突html
声明不一样的js文件之间的依赖java
可让咱们写出模块化的代码,便于复用jquery
这个视频《侧边工具栏开发》的需求很简单,就是作一个侧边工具条,windows
固定定位在页面的某个位置,数组
在没有把页面向下滚动时,显示三个按钮,浏览器
当页面向下滚动必定距离以后,第四个按钮出现。微信
点击这个按钮,页面会回到顶部。app
鼠标hover到每一个按钮上都有一些相应的动画(CSS3完成这里不写)
<div class="toolbar"> <!--第一个微信按钮--> <a href="javascript:;" class="toolbar-item toolbar-item-weixin"> <span class="toolbar-layer"></span> </a> <!--第二个意见反馈按钮--> <a href="javascript:;" class="toolbar-item toolbar-feedback"></a> <!--第三个app下载按钮--> <a href="javascript:;" class="toolbar-item toolbar-item-app"> <span class="toolbar-layer"></span> </a> <!--第四个回到顶部按钮--> <a id="backTop" href="javascript:;" class="toolbar-item toolbar-item-top"></a> </div>
一些说明:
为了让页面显示滚动条,须要在这个上述代码下面加不少行<p></p>
标签以便撑开页面显示滚动条。
CSS部分视频中老师讲了三种方法,而且用到了SASS,感兴趣的同窗能够去看一下,这里再也不赘述。
其中jquery-3.1.0.js和require.js是在各自官网下载的资源文件。main.js是自定义的入口文件。
在未引入模块化编写代码之前引入js文件是在<body>
标签以前写多个<script>
标签,根据js文件加载的顺序,添加js文件。如今根据requirejs
的异步加载的特性,能够设定一个主入口文件,只用一个<script>
实现其他js文件的加载。
首先是引入requirejs:在HTML
文件的<body>
标签的以前添加script标签,
而后引入requirejs
文件,而后用data-main
这个属性来引入入口文件。(当用requirejs引用文件时,能够省略js文件的js后缀名,因此此处引入的就是项目目录中的main.js
)
以下:
<script src="js/require.js" data-main="js/main"></script>
requirejs.config
(为模块指定别名,方便模块的引入
),在入口文件中定义
如这个demo要引入屡次jquery-3.1.0.js
,可是这个名字很长因此能够在入口文件main.js
中为它定义一个别名,以下:
//为jquery模块定义别名 requirejs.config({ paths:{ jquery:'jquery-3.1.0.js' } });
requirejs()
方法(将写好的模块进行引入
)
requirejs()接收两个参数,第一个参数是一个数组,写入要引入的模块的名字。第二个参数是一个回调函数,须要传递一个参数,来代替前面所引入的模块。如:引入jquery模块
requirejs(['jquery'],function($){ //写一段代码验证jquery是否被正确引入 //将body背景颜色变为红色 $('body').css('background-color','red'); });
define()
(利用它定义编写模块,而后在相应的地方进行引入。
)define()
接收三个参数,第一个参数是为本模块命名的值,能够不写,第二个参数表示须要引入的模块,第三个参数是各依赖项成功加载后所运行的函数,传入的参数与各个依赖项造成对应的关系。
define( moduleName, //可选,若是此参数不写,则默认使用本模块所在文件的文件名 dependencies, //一个数组,此数组包含着此文件所需的各个依赖项目,这个数组中各项对应的是所依赖文件相对于requirejs库所造成的相对路径文件名。 function(parameters){ //各依赖项成功加载后所运行的函数 //传入的参数与dependencies数组中的各个依赖项造成对应关系 } );
如今先对demo中的基本功能进行实现:
目前的目录结构以下:
首先在HTML中初始化requirejs
:在</body>
标签以前:<script src="js/require.js" data-main="js/main"></script>
在入口文件main.js中实现基本功能
//1.首先为jquery模块定义别名 requirejs.config({ paths: { jquery: 'jquery-3.1.0' } }); //2.而后用requirejs()方法引入jquery模块实现demo中需求 requirejs(['jquery'],function($){ //为id值为backTop的第四个按钮添加点击回到顶部事件,当点击时执行move函数回到顶部 $('#backTop').on('click',move); //监听一下windows对象的滚动事件, //每次滚动都执行函数checkPosition肯定一下位置,是否到达设定的临界点,以显示和隐藏第四个按钮 $(window).on('scroll',function(){ checkPosition($(window).height()); }); //解决bug:在刷新页面时也出现第四个按钮,即页面加载时就检查一下滚动位置 checkPosition($(window).height()); //------------------------------分割线------------------------------------------ //move函数的具体实现,加动画效果 function move(){ $('html, body').animate({ scrollTop:0 },800); } //go函数能够当即移动到顶部 function go(){ $('html, body').scrollTop(0); } //checkPosition函数的具体实现 function checkPosition(pos){ if($(window).scrollTop() > pos){ $('#backTop').fadeIn(); }else{ $('#backTop').fadeOut(); } } });
分割线以上是执行的代码,分割线如下是写的被调用的函数。
上述代码虽然实现了功能,可是存在如下问题:
move和go函数都是到达顶部的功能,实现的功能很类似,做用若是想在其它地方使用这个功能,就要再进行代码的复制,不方便功能的复用。因此应该将功能抽象成模块。
实现功能的功能单一:两个函数都是到达顶部,这样即使抽象成模块也会受到很大的限制,因此能够进一步将问题抽象成移动滚动条到指定位置。
第一步:建立一个新模块,用scrollto.js
表示,目前这个demo的目录结构如图:
第二步:将功能抽象成模块,写入scrollto.js
中
//1.先定义这个模块,由于要用到jquery,因此还要引入jquery define(['jquery'],function($){ //定义构造函数 function ScrollTo(opts){ this.opts = $.extend({},ScrollTo.DEFAULTS,opts); //实现传参覆盖 this.$el = $('html, body'); } //原型添加方法 ScrollTo.prototype.move = function (){ var opts = this.opts; this.$el.animate({ scrollTop:opts.dest },opts.speed); }; ScrollTo.prototype.go = function(){ this.$el.scrollTop(opts.dest); }; //定义默认的参数 ScrollTo.DEFAULTS = { dest:0, speed:800 }; //定义接口 return { ScrollTo:ScrollTo }; });
代码详解:
传递的参数为一个对象,用opts表示
用户没有传递参数时,使用默认的参数,默认参数直接写在ScrollTo构造函数上,至关于造成一个静态属性,而后经过jquery的extend()方法进行原型的扩展
实现用户传递参数用之,不传递参数用默认值。jquery的extend()
方法
在原型上添加move和go方法
第三步:在入口文件main.js中引入这个scrollto.js
的模块
requirejs(['jquery','scrollto'], function($,scrollto){ //为了使用scrollto模块,须要实例化一下 var scroll = new scrollto.ScrollTo({ dest:0, speed:2000 }); //点击回到顶部按钮回到指定位置功能 $('#backTop').on('click', $.proxy(scroll.move, scroll)); });
上述代码中有一点须要注意:
在第6行中,若是添加点击按钮回到指定位置事件时,这么写:$('#backTop').on('click', scroll.move)
;
此时浏览器控制台会报错:Uncaught TypeError: Cannot read property 'ScrollTo' of undefined
分析缘由是由于,在main.js中调用scrollto.js
模块中在ScrollTo.prototype.move
原型方法move时,main.js中this指的是ScrollTo的实例,即scrollto,而在语句$('#backTop').on('click', scroll.move)
;中,这个this指代的是id为backTop的这个按钮。
解决办法:用jquery提供的方法,直接将this指向scroll对象。$('#backTop').on('click', $.proxy(scroll.move, scroll))
第四步:一个bug
这时基本功能虽然实现了,点击底部那个按钮,传入设定的返回位置和返回的速度,页面能够再次返回顶部指定位置,可是目前还存在一个bug:在点击底部按钮回到顶部指定位置时,假如连续屡次点击这个按钮,则页面回到顶部后就没法再次向下滚动页面。
bug分析:
假如执行的函数如上面第三步中代码,速度设置成较慢的速度2000,那么在返回顶部指定位置时能够屡次点击这个按钮,
这样每次点击按钮事件都要调用move方法执行里面的动画,点击多少次,这个动画就要执行多少次。
所以在页面返回顶部后,再次滚动页面向下会当即执行返回顶部动画,因此在执行完点击次数的动画以前,用户都没法向下滚动。(而且很是耗性能)
解决办法,在滚动条正在运动或者已经到达目的地,就不该该执行动画。添加判断。
因此scrollto.js
的代码能够改为以下:
define(['jquery'],function($){ //定义构造函数 function ScrollTo(opts){ this.opts = $.extend({},ScrollTo.DEFAULTS,opts); //实现传参覆盖 this.$el = $('html, body'); } //原型添加方法 ScrollTo.prototype.move = function (){ var opts = this.opts; if ($(window).scrollTop() != opts.dest){ //判断是否到达指定位置 if(!this.$el.is(':animated')){ //判断是否在运动 this.$el.animate({ scrollTop:opts.dest },opts.speed); } } }; ScrollTo.prototype.go = function(){ var dest = this.opts.dest; if($(window).scrollTop() != dest){ this.$el.scrollTop(dest); } }; //定义默认的参数 ScrollTo.DEFAULTS = { dest:0, speed:800 }; //定义接口 return { ScrollTo:ScrollTo }; });
咱们把返回的功能函数move和go都抽象在了scrollto.js
模块中,如今还能够直接把整个返回顶部的功能(包括滚动必定距离后隐藏的按钮出现,和点击按钮以后回到顶部指定位置)
而后在入口文件中只须要引入这个模块(取名叫backtop.js
),这个back.js
须要依赖上面定义的scrollto.js
模块。
因此目前的项目目录以下图:
第一步:如今来写backtop.js
模块
define(['jquery', 'scrollto'], function($, scrollto){ //执行函数部分 function BackTop(el, opts){ this.opts = $.extend({}, BackTop.DEFAULTS, opts); this.$el = $(el); //el是节点 this.scroll = new scrollto.ScrollTo({ dest: 0, speed: this.opts.speed }); this._checkPosition(); //加载时就检查位置,解决bug if(this.opts.mode == 'move'){ //是move才执行move函数,其余执行go this.$el.on('click', $.proxy(this._move, this)); }else{ this.$el.on('click', $.proxy(this._go, this)); } $(window).on('scroll', $.proxy(this._checkPOsition, this)); } //定义默认属性部分 BackTop.DEFAULTS = { mode: 'move', pos: $(window).height(), speed: 800 }; //定义功能函数部分 BackTop.prototype._move = function(){ this.scroll.move(); }; BackTop.prototype._go = function(){ this.scroll.go(); }; BackTop.prototype._checkPosition = function(){ if($(window).scrollTop() > this.opts.pos){ this.$el.fadeIn(); }else{ this.$el.fadeOut(); } }; //暴露模块接口,返回整个对象 return{ BackTop: BackTop }; });
第二步:scrollto.js保持不变
第三步:写main.js入口文件
//定义别名 requirejs.config({ paths: { jquery: 'jquery-3.1.0' } }); //调用backtop.js模块 requirejs(['jquery', 'backtop'], function($, backtop){ //实例化BackTop new backtop.BackTop($('#backtop'),{ mode: 'move', pos:100, speed: 2000 }); });
这个demo中的模块化是这样一种思想:
首先把功能函数放在一个模块中(move和go)
把整个实现功能也抽象成一个模块,依赖上一个功能函数模块
最后只须要在入口文件中实例化一下这个最外层的模块,便可完成一系列功能的调用。
每一个模块都用面向对象的思想,定义模块而且暴露接口
默认值的用法可让调用者拿起就用,能够不用考虑传参数。