# 闭包

用自执行匿名函数包裹代码,外部无法直接访问,形成闭包

  • 避免合并 js 出现无法正常解析进而报错,在自执行匿名函数前加上 ;
  • jQuery 对象为实参、$ 符号为形参

兼容操作符 $jQuery,避免全局依赖和第三方破坏

;(function ($) {
  // code...
})(jQuery)
1
2
3

# 对象级别组件开发

即挂在 jQuery 原型下的方法,这样通过选择器获取的 jQuery 对象实例也能共享该方法,也称为动态方法

  • $.fn === $.prototype === jQuery.prototype,即 jQuery对象的原型
  • 方法内部 this 指向选择器返回的元素或元素集
  • 使用 return 维持 jQuery 的链式调用
  • each() 方法内的 this 是普通DOM,需转化成 $(this)
;(function ($) {

  $.fn.PageSlide = function() {
    // 这里 this 指向 jQuery 选择器返回的集合
    return this.each(function() {
      // 使用 each 遍历集合中的每个元素
      $(this)
    }); 
  }
  
})(jQuery)
1
2
3
4
5
6
7
8
9
10
11

# 单例模式

如果实例存在则不再重新创建实例

  • 利用 data() 来存放插件对象的实例
$.fn.PageSlide = function() {

  return this.each(function() {
    
    var me = $(this),
      instance = me.data("PageSlide"); // 获取实例对象

    // 若实例不存在则创建,并使用 data 方法存放实例
    if (!instance) {
      me.data("PageSlide", (instance = new PageSlide()));
    }

  })
  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 定义构造函数

  • 将插件所有方法包装到对象上,在构造函数原型上挂载该对象
  • 构造函数推荐为自执行函数表达式,并返回自身维持链式调用
;(function ($) {
  
  // 定义构造函数
  var PageSlide = (function() {
    // 初始执行代码
    this.init()

    return PageSlide;
  })();

  // 在构造函数原型上定义对象方法
  PageSlide.prototype = {
  
    init: function() {
    },
    
    pagesCount: function() {
    }
    
  };
  
  // 在 jQuery 对象原型上 挂载插件
  $.fn.PageSlide = function() {
    return this;
  }
  
})(jQuery)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 配置默认参数

使用 jQuery 提供的 $.extend() 方法来合并默认配置和用户配置

# 将插件的所有方法包装到一个对象上

  • 不影响外部命名空间

# 示例:基于 jQuery 的轮播图插件

HTML

<div data-PageSlide>
  <div class="items">
    <div class="item item1"></div>
    <div class="item item2"></div>
    <div class="item item3"></div>
    <div class="item item4"></div>
  </div>
  <div class="pages"></div>
</div>

<script>
  (function ($) {
    // 实例化插件
    $('[data-PageSlide]').PageSlide({
      index: 0,
      look: true,
      pages: true
    });
  })(jQuery);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

JS

;(function ($) {
  "use strict"; // 严格模式

  // 构造函数
  var PageSlide = (function () {

    function PageSlide (element, options) {
      this.element = element;  // this 指向选择器返回的 DOM 元素集
      this.settings = $.extend({}, $.fn.PageSlide.defaults, options || {}); // 合并配置参数
      this.init(); // 执行初始化方法
    }

    // 将原型上挂载对象,将常用方法包装到对象上
    PageSlide.prototype = {

      // todo: 初始化页面布局及事件绑定
      init: function () {
        var me = this,               // this 多次调用,缓存起来
          userAgent = navigator.userAgent;

        // 获取 DOM 元素
        me.selector = me.settings.selector;
        me.items = me.element.find(me.selector.items);  // 容器
        me.item = me.items.find(me.selector.item);      // 子项
        me.pages = me.element.find(me.selector.pages);  // 分页

        // 获取索引
        me.index = (me.settings.index >= 0 && me.settings.index < me.pageCount()) ? me.settings.index : 0;
        
        // 获取浏览器信息
        if (userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1) {
          me.isIE8 = navigator.userAgent.toLowerCase().match(/msie ([\d.]+)/)[1] == "8.0" ? true : false;
        }

        me._initLayout(); // 页面布局
        me._initEvent();  // 事件绑定
        me._autoPlay();
      },

      // todo: 获取页面总数
      pageCount: function () {
        return this.item.length;
      },

      // todo: 上一页
      prev: function () {
        var me = this;
        me.index--;
        me._switchPage();
      },

      // todo: 下一页
      next: function () {
        var me = this;
        me.index++;
        me._switchPage();

      },

      // todo: 页面布局
      _initLayout: function () {
        var me = this;

        // 分页布局
        if (me.settings.pages) {

          // 生成分页控制点
          var spans = "";
          for (var i = 0; i < me.pageCount(); i++) spans += "<span></span>";
          me.pages.append(spans);

          me.pages.children("span").eq(me.index).addClass("on"); // 高亮当前索引控制点

          // 生成左右切换按钮
          if (me.settings.pagesArrows) {
            me.element.append('<div class = "btn L">&lt;</div><div class = "btn R">&gt;</div>');
          }
        }

        // 子页横向布局
        me.items.css("width", (me.pageCount() + 1) * 100 + "%");
        me.item.push(me.item.eq(0).clone().appendTo(me.items)); // 追加首元素至末尾, 用以css无缝切换
        me.item.each(function () {
          $(this).css("width", 100 / (me.pageCount()) + "%");
        });
      },

      // todo: 事件绑定
      _initEvent: function () {
        var me = this,
        btn = me.element.children(".btn");

        // 控制点移入切换
        if (me.settings.pagesDot) {
          me.pages.find("span").on("mouseover", function () {
            me.index = me._oddIndex($(this).index());
            me._switchPage();
          });
        }

        // 左右切换箭头按钮
        if (me.settings.pagesArrows) {
          btn.eq(0).on("click", function () {
            me.prev();
          });
          btn.eq(1).on("click", function () {
            me.next();
          });
        }

        // 绑定键盘事件
        if (me.settings.keyboard) {
          $(window).keydown(function (e) {
            var keyCode = e.keyCode;
            if (keyCode == 37 || keyCode == 38) {
              me.prev();
            } else if (keyCode == 39 || keyCode == 40) {
              me.next();
            }
          });
        }

        // 绑定鼠标滚轮事件
        if (me.settings.mouseRoll) {
          me.element.on("mousewheel DOMMouseScroll", function (e) {
            e.preventDefault();
            var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;

            if (delta > 0 && (me.index && !me.settings.loop || me.settings.loop)) {
              me.prev();
            } else if (delta < 0 && (me.index < (me.pageCount() - 1) && !me.settings.loop || me.settings.loop)) {
              me.next();
            }
          });
        }
      },

      // todo: 自动播放
      _autoPlay: function () {
        var me = this;

        if (me.settings.loop) {
          // 设置定时器
          var timer = setInterval(function() {
            me.index++;
            me._switchPage();
          }, me.settings.interval);

          // 移入清除定时器
          me.element.hover(function() {
            clearInterval(timer);
          },function() {
            timer = setInterval(function() {
              me.index++;
              me._switchPage();
            }, me.settings.interval);
          });
        }
      },

      // todo: 切换页面
      _switchPage: function () {
        var me = this;

        // 末页切首页
        if (me.index == me.pageCount()) {
          me.items.css("left", "0");
          me.index = 0;
        }

        // 首页切末页
        if (me.index == -1) {
          me.items.css("left", -(me.pageCount() - 2) * 100 + "%");
          me.index = me.pageCount() - 2;
        }

        // 控制点高亮切换
        if (me.index >= me.pageCount() - 1) {
          me.pages.children("span").eq(0).addClass("on").siblings().removeClass("on")
        } else {
          me.pages.children("span").eq(me.index).addClass("on").siblings().removeClass("on");
        }

        // 过渡动画
        me.items.stop().animate({
          left: -(me.index * 100) + "%"
        }, me.settings.speed);
      },

      // todo: IE8控制点索引受PIE生成的元素影响, 这里做一个索引转换
      _oddIndex: function (index) {
        var me = this;
        if (!me.isIE8) return index;

        var arr = [1];
        for (var i = 1; i <= me.pageCount(); i++) arr.push(arr[arr.length - 1] + 2);

        // 让IE8兼容indexOf方法
        if (!Array.prototype.indexOf) {
          Array.prototype.indexOf = function(val){
            var me = this;
            for(var i =0; i < me.length; i++){
              if(me[i] == val) return i;
            }
            return -1;
          };
        }
        
        return arr.indexOf(index);
      }

    };
    return PageSlide;
  })();

  // 注册 jQuery 组件
  $.fn.PageSlide = function (options) {

    // 实现单例模式
    return this.each(function () {
      var me = $(this),
      instance = me.data("PageSlide"); // 获取实例对象

      // 若实例不存在,则创建实例对象
      if (!instance) {
        me.data("PageSlide", (instance = new PageSlide(me, options)));
      }

      // 若实例已存在,直接返回该实例
      if ($.type(options) === "string") return instance[options]();
    });
  };

  // 默认配置参数
  $.fn.PageSlide.defaults = {
    selector: {
      items: ".items", // 容器
      item: ".item",   // 子项
      pages: ".pages"  // 分页
    },

    index: 0,          // 索引

    loop: true,        // 启用循环切换
    interval: 1500,    // 切换间隔
    speed: 800,        // 过渡时间

    pages: true,       // 启用分页
    pagesArrows: true, // 分页箭头控制
    pagesDot: true,    // 分页点控制
    keyboard: false,   // 启用键盘控制
    mouseRoll: false   // 启用鼠标滚轮控制
  }

})(jQuery);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255

参考 https://www.cnblogs.com/Wayou/p/jquery_plugin_tutorial.html