viewer.js,其调用方法和使用都很简单。但是开始的时候对于其内部方法的调用,图片更新,插件冲突控制等原理都不是很理解。在这里分析一下viewer.js的框架,和大部分jQuery插件的封装类似,也有很多值得学习的部分。
先省略掉大部分的属性和方法,留下整体框架,再一步步分析结构。
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as anonymous module.
define('viewer', ['jquery'], factory);
} else if (typeof exports === 'object') {
// Node / CommonJS
factory(require('jquery'));
} else {
// Browser globals.
factory(jQuery);
}
})(function ($) {
'use strict';
// Constants
var NAMESPACE = 'viewer';
//globle function
function isUndefined(u) {
return typeof u === 'undefined';
}
function isNumber(n) {
return typeof n === 'number' && !isNaN(n);
}
function isString(s) {
return typeof s === 'string';
}
function toArray(obj, offset) {
var args = [];
if (isNumber(offset)) { // It's necessary for IE8
args.push(offset);
}
return args.slice.apply(obj, args);
}
//省略
//省略
function Viewer(element, options) {
this.$element = $(element);
this.options = $.extend({}, Viewer.DEFAULTS, $.isPlainObject(options) && options);
this.isImg = false;
this.isBuilt = false;
this.isShown = false;
this.isViewed = false;
this.isFulled = false;
this.isPlayed = false;
this.wheeling = false;
this.playing = false;
this.fading = false;
this.tooltiping = false;
this.transitioning = false;
this.action = false;
this.target = false;
this.timeout = false;
this.index = 0;
this.length = 0;
this.init();
}
Viewer.prototype = {
constructor: Viewer,
init: function () {
}
//省略
//省略
};
Viewer.setDefaults = function (options) {
$.extend(Viewer.DEFAULTS, options);
};
Viewer.TEMPLATE = '省略';
//省略
//省略
// Save the other viewer
Viewer.other = $.fn.viewer;
// Register as jQuery plugin
$.fn.viewer = function (options) {
var args = toArray(arguments, 1);
var result;
this.each(function () {
var $this = $(this);
var data = $this.data(NAMESPACE);
var fn;
if (!data) {
if (/destroy|hide|exit|stop|reset/.test(options)) {
return;
}
$this.data(NAMESPACE, (data = new Viewer(this, options)));
}
if (isString(options) && $.isFunction(fn = data[options])) {
result = fn.apply(data, args);
}
});
return isUndefined(result) ? this : result;
};
$.fn.viewer.Constructor = Viewer;
$.fn.viewer.setDefaults = Viewer.setDefaults;
// No conflict
$.fn.viewer.noConflict = function () {
$.fn.viewer = Viewer.other;
return this;
};
});
上面部分只留下了整体结构,看起来就比较清晰明了。注意下面的结构,是一个立即执行的匿名函数。首先定义一个匿名函数function(){},然后用括号括起来,变成(function(){}) 这种形式,然后通过()这个运算符来执行。可以传递参数进去,以供内部函数使用。这里利用了闭包的特性,由于JavaScript是function作用域,这样可以避免内部临时变量影响到全局空间,即外部不能访问到function内部定义的变量。
(function(){
//jQuery插件代码
})();
常见的jQuery插件是这种形式,在插件内部继续使用$作为jQuery的别名。在开头部分加上一个;防止其它函数没有正确结束而报错。
;(function($){
//jQuery插件代码
})(jQuery);
在这个插件中也是这种形式,不过这个立即执行的匿名函数传递的参数是一个function。在执行部分考虑到了多种情况。
(function(factory){
if (typeof define === 'function' && define.amd) {
// AMD. Register as anonymous module.
define('viewer', ['jquery'], factory);
} else if (typeof exports === 'object') {
// Node / CommonJS
factory(require('jquery'));
} else {
// Browser globals.
factory(jQuery);
}
})(function($){
//jQuery插件代码
});
情况一:typeof define === 'function' && define.amd
AMD (异步模块定义)。它吸取了CommonJS的一些优点,但又不照搬它的格式。AMD最开始是CommonJS中模块格式的草案规范,但由于无法与CommonJS开发者达成一致而独立存在。它有独立的wiki 和讨论组 。AMD设计出一个简洁的写模块API:define 。
define(id, dependencies, factory);
其中:
id: 模块标识,可以省略。
dependencies: 所依赖的模块,可以省略。
factory: 模块的实现,或者一个JavaScript对象。
define('viewer', ['jquery'], factory);这里jquery是依赖的模块。也就是 define('viewer', ['jquery'], function($){ //jQuery插件代码}); 依赖jquery作为参数映射到$上。
情况二:typeof exports === 'object'
jquery是我们需要的一个依赖, factory(require('jquery')); 就相当于下面这样,在插件内部继续使用$作为jQuery的别名。
function($){
//jQuery插件代码
}(require('jquery'));
情况三: 一般情况
factory(jQuery);
也就是
(function($){
//jQuery插件代码
})(jQuery);
下面这部分代码,$.fn.viewer = function (options) {};注册了一个名为viewer 的jQuery插件。外部可以用$(selector).viewer(options); 这种方式调用,为了防止类似$(selector).length>1的情况,用jQuery对象的each方法遍历所有元素。 jQuery.data()方法用于向被选元素附加数据,或者从被选元素获取数据。如果data为undefined,说明是第一次初始化。并且创建Viewer的实例对象,然后将该实例对象附加到被选元素上。否则判断Viewer实例上有没有对应方法,如果有就直接在Viewer实例上调用这个方法。这样可以防止每次调用$(selector).viewer(options);的时候创建新的Viewer实例。所以当实例存在的时候,调用$(selector).viewer(options);
传递字符串时可以直接调Viewer.prototype 原型链上的方法,比如说update()更新图片,destroy()解除事件绑定,移除被选元素附加数据等。 最后插件返回一个jQuery对象,以保证插件的可链式调用。
注意 fn.apply(data,args);把调用$.fn.viewer传递的第一个参数之后的参数传递给了对应的实例function
$.fn.viewer = function (options) {
var args = toArray(arguments, 1);
var result;
this.each(function () {
var $this = $(this);
var data = $this.data(NAMESPACE);
var fn;
if (!data) {
if (/destroy|hide|exit|stop|reset/.test(options)) {
return;
}
$this.data(NAMESPACE, (data = new Viewer(this, options)));
}
if (isString(options) && $.isFunction(fn = data[options])) {
result = fn.apply(data, args);
}
});
return isUndefined(result) ? this : result;
};
防止同名插件的冲突
来看一下它是怎么防止冲突的,在定义jQuery插件时,所有的对象方法都应该附加到jQuery.fn对象上。在同一个页面上可能会有同名的插件,这种概率比较小,但如果遇到同名的话要如何选择调用哪一个插件呢?在未注册插件之前,先将同名插件用一个变量保存起来,本质上是增加一个引用,然后再注册插件。如果要调用另一个同名插件就还原该引用。
var NAMESPACE = 'viewer';
Viewer.other = $.fn.viewer;
$.fn.viewer.noConflict = function () {
$.fn.viewer = Viewer.other;
return this;
};
viewer.js原作者github地址:https://github.com/fengyuanchen/viewer