小言_互联网的博客

jQuery easyui源码赏析

313人阅读  评论(0)

引子

jQuery未过时,在一些中小网站中,jQuery还是发挥着瑞士军刀的作用。我们不能为了前后端分离而分离,一些很简单的需求,很简单的页面,freemarker+jQuery+bootstrap就能搞掂,奈何一定要搬动vue和react这些大刀呢?
今天和大家一起欣赏的,是一个不开源,或者说半开源的jQuery界面框架: easyui。

jQuery基础

先做一些铺垫。

jQuery可以把一个dom元素包装成一个jQuery对象

例如:

var obj = $('#div1')

要区分dom对象和jQuery对象。

jQuery插件的开发

jQuery为开发插件提拱了三种方法,分别是:

  • jQuery.extend(object);为扩展jQuery类本身.为类添加新的方法。
  • jQuery.fn.extend(object);给jQuery对象添加方法。
  • 通过$.widget()应用jQuery UI的部件工厂方式创建

jQuery easyui多采用第二种:

$.fn.myPlugin = function() {
   
    //在这里面,this指的是用jQuery选中的元素 example :$('a'),则this=$('a')
    this.css('color', 'red');
}

$.fn其实就是jQuery对象的prototype:

jQuery.fn = jQuery.prototype ={
    
   init: function( selector, context ){
   //....  
   //...... 
};

所以第二种方式就是以扩充jQuery原型的方式添加插件。
注意插件中this的指代:

$.fn.myPlugin = function() {
       
    this.css('color', 'red');   //在这里面,this指的是用jQuery选中的元素
    //对每个元素进行操作
    return this.each(function() {
           
        $(this).append(' ' + $(this).attr('href'));   //此处的this指的是dom元素,需要用$()包装成jQuery对象。
    }))
}

搞清楚上述函数的含义是阅读整个jQuery代码的关键。

  • this.each()函数内部的this和外部的this是不同的,外面的是jQuery对象,内部的是dom对象
  • this.each()的返回就是this,也就是外部的这个jQuery对象
  • each()对外部的jQuery对象进行遍历,遍历原理见下:
jQuery的each实现原理:
// args is for internal usage only
	each: function( obj, callback, args ) {
   
		var value,
			i = 0,
			length = obj.length,
			isArray = isArraylike( obj );

		if ( args ) {
   
			if ( isArray ) {
   
				for ( ; i < length; i++ ) {
   
					value = callback.apply( obj[ i ], args );

					if ( value === false ) {
   
						break;
					}
				}
			} else {
   
				for ( i in obj ) {
   
					value = callback.apply( obj[ i ], args );

					if ( value === false ) {
   
						break;
					}
				}
			}

		// A special, fast, case for the most common use of each
		} else {
   
			if ( isArray ) {
   
				for ( ; i < length; i++ ) {
   
					value = callback.call( obj[ i ], i, obj[ i ] );

					if ( value === false ) {
   
						break;
					}
				}
			} else {
   
				for ( i in obj ) {
   
					value = callback.call( obj[ i ], i, obj[ i ] );

					if ( value === false ) {
   
						break;
					}
				}
			}
		}

		return obj;
	},

jQuery easyui源码赏析

jQuery easyui每个组件为一个jQuery插件。

easyloader.js负责加载所有的插件。

(function(){
   
	var modules = {
   
		draggable:{
   
			js:'jquery.draggable.js'
		},
		droppable:{
   
			js:'jquery.droppable.js'
		},
		resizable:{
   
			js:'jquery.resizable.js'
		},
		linkbutton:{
   
			js:'jquery.linkbutton.js',
			css:'linkbutton.css'
		},
		progressbar:{
   
			js:'jquery.progressbar.js',
			css:'progressbar.css'
		},
		pagination:{
   
			js:'jquery.pagination.js',
			css:'pagination.css',
			dependencies:['linkbutton']
		},
		datagrid:{
   
			js:'jquery.datagrid.js',
			css:'datagrid.css',
			dependencies:['panel','resizable','linkbutton','pagination']
		},
		treegrid:{
   
			js:'jquery.treegrid.js',
			css:'tree.css',
			dependencies:['datagrid']
		},
		propertygrid:{
   
			js:'jquery.propertygrid.js',
			css:'propertygrid.css',
			dependencies:['datagrid']
		},
		panel: {
   
			js:'jquery.panel.js',
			css:'panel.css'
		},
		window:{
   
			js:'jquery.window.js',
			css:'window.css',
			dependencies:['resizable','draggable','panel']
		},
		dialog:{
   
			js:'jquery.dialog.js',
			css:'dialog.css',
			dependencies:['linkbutton','window']
		},
		messager:{
   
			js:'jquery.messager.js',
			css:'messager.css',
			dependencies:['linkbutton','window','progressbar']
		},
		layout:{
   
			js:'jquery.layout.js',
			css:'layout.css',
			dependencies:['resizable','panel']
		},
		form:{
   
			js:'jquery.form.js'
		},
		menu:{
   
			js:'jquery.menu.js',
			css:'menu.css'
		},
		tabs:{
   
			js:'jquery.tabs.js',
			css:'tabs.css',
			dependencies:['panel','linkbutton']
		},
		splitbutton:{
   
			js:'jquery.splitbutton.js',
			css:'splitbutton.css',
			dependencies:['linkbutton','menu']
		},
		menubutton:{
   
			js:'jquery.menubutton.js',
			css:'menubutton.css',
			dependencies:['linkbutton','menu']
		},
		accordion:{
   
			js:'jquery.accordion.js',
			css:'accordion.css',
			dependencies:['panel']
		},
		calendar:{
   
			js:'jquery.calendar.js',
			css:'calendar.css'
		},
		combo:{
   
			js:'jquery.combo.js',
			css:'combo.css',
			dependencies:['panel','validatebox']
		},
		combobox:{
   
			js:'jquery.combobox.js',
			css:'combobox.css',
			dependencies:['combo']
		},
		combotree:{
   
			js:'jquery.combotree.js',
			dependencies:['combo','tree']
		},
		combogrid:{
   
			js:'jquery.combogrid.js',
			dependencies:['combo','datagrid']
		},
		validatebox:{
   
			js:'jquery.validatebox.js',
			css:'validatebox.css'
		},
		numberbox:{
   
			js:'jquery.numberbox.js',
			dependencies:['validatebox']
		},
		searchbox:{
   
			js:'jquery.searchbox.js',
			css:'searchbox.css',
			dependencies:['menubutton']
		},
		spinner:{
   
			js:'jquery.spinner.js',
			css:'spinner.css',
			dependencies:['validatebox']
		},
		numberspinner:{
   
			js:'jquery.numberspinner.js',
			dependencies:['spinner','numberbox']
		},
		timespinner:{
   
			js:'jquery.timespinner.js',
			dependencies:['spinner']
		},
		tree:{
   
			js:'jquery.tree.js',
			css:'tree.css',
			dependencies:['draggable','droppable']
		},
		datebox:{
   
			js:'jquery.datebox.js',
			css:'datebox.css',
			dependencies:['calendar','combo']
		},
		datetimebox:{
   
			js:'jquery.datetimebox.js',
			dependencies:['datebox','timespinner']
		},
		slider:{
   
			js:'jquery.slider.js',
			dependencies:['draggable']
		},
		parser:{
   
			js:'jquery.parser.js'
		}
	};
	
	var locales = {
   
		'af':'easyui-lang-af.js',
		'bg':'easyui-lang-bg.js',
		'ca':'easyui-lang-ca.js',
		'cs':'easyui-lang-cs.js',
		'cz':'easyui-lang-cz.js',
		'da':'easyui-lang-da.js',
		'de':'easyui-lang-de.js',
		'en':'easyui-lang-en.js',
		'fr':'easyui-lang-fr.js',
		'nl':'easyui-lang-nl.js',
		'zh_CN':'easyui-lang-zh_CN.js',
		'zh_TW':'easyui-lang-zh_TW.js'
	};
	
	var queues = {
   };
	
	function loadJs(url, callback){
   
		var done = false;
		var script = document.createElement('script');
		script.type = 'text/javascript';
		script.language = 'javascript';
		script.src = url;
		script.onload = script.onreadystatechange = function(){
   
			if (!done && (!script.readyState || script.readyState == 'loaded' || script.readyState == 'complete')){
   
				done = true;
				script.onload = script.onreadystatechange = null;
				if (callback){
   
					callback.call(script);
				}
			}
		}
		document.getElementsByTagName("head")[0].appendChild(script);
	}
	
	function runJs(url, callback){
   
		loadJs(url, function(){
   
			document.getElementsByTagName("head")[0].removeChild(this);
			if (callback){
   
				callback();
			}
		});
	}
	
	function loadCss(url, callback){
   
		var link = document.createElement('link');
		link.rel = 'stylesheet';
		link.type = 'text/css';
		link.media = 'screen';
		link.href = url;
		document.getElementsByTagName('head')[0].appendChild(link);
		if (callback){
   
			callback.call(link);
		}
	}
	
	function loadSingle(name, callback){
   
		queues[name] = 'loading';
		
		var module = modules[name];
		var jsStatus = 'loading';
		var cssStatus = (easyloader.css && module['css']) ? 'loading' : 'loaded';
		
		if (easyloader.css && module['css']){
   
			if (/^http/i.test(module['css'])){
   
				var url = module['css'];
			} else {
   
				var url = easyloader.base + 'themes/' + easyloader.theme + '/' + module['css'];
			}
			loadCss(url, function(){
   
				cssStatus = 'loaded';
				if (jsStatus == 'loaded' && cssStatus == 'loaded'){
   
					finish();
				}
			});
		}
		
		if (/^http/i.test(module['js'])){
   
			var url = module['js'];
		} else {
   
			var url = easyloader.base + 'plugins/' + module['js'];
		}
		loadJs(url, function(){
   
			jsStatus = 'loaded';
			if (jsStatus == 'loaded' && cssStatus == 'loaded'){
   
				finish();
			}
		});
		
		function finish(){
   
			queues[name] = 'loaded';
			easyloader.onProgress(name);
			if (callback){
   
				callback();
			}
		}
	}
	
	function loadModule(name, callback){
   
		var mm = [];
		var doLoad = false;
		
		if (typeof name == 'string'){
   
			add(name);
		} else {
   
			for(var i=0; i<name.length; i++){
   
				add(name[i]);
			}
		}
		
		function add(name){
   
			if (!modules[name]) return;
			var d = modules[name]['dependencies'];
			if (d){
   
				for(var i=0; i<d.length; i++){
   
					add(d[i]);
				}
			}
			mm.push(name);
		}
		
		function finish(){
   
			if (callback){
   
				callback();
			}
			easyloader.onLoad(name);
		}
		
		var time = 0;
		function loadMm(){
   
			if (mm.length){
   
				var m = mm[0];	// the first module
				if (!queues[m]){
   
					doLoad = true;
					loadSingle(m, function(){
   
						mm.shift();
						loadMm();
					});
				} else if (queues[m] == 'loaded'){
   
					mm.shift();
					loadMm();
				} else {
   
					if (time < easyloader.timeout){
   
						time += 10;
						setTimeout(arguments.callee, 10);
					}
				}
			} else {
   
				if (easyloader.locale && doLoad == true && locales[easyloader.locale]){
   
					var url = easyloader.base + 'locale/' + locales[easyloader.locale];
					runJs(url, function(){
   
						finish();
					});
				} else {
   
					finish();
				}
			}
		}
		
		loadMm();
	}
	
	easyloader = {
   
		modules:modules,
		locales:locales,
		
		base:'.',
		theme:'default',
		css:true,
		locale:null,
		timeout:2000,
	
		load: function(name, callback){
   
			if (/\.css$/i.test(name)){
   
				if (/^http/i.test(name)){
   
					loadCss(name, callback);
				} else {
   
					loadCss(easyloader.base + name, callback);
				}
			} else if (/\.js$/i.test(name)){
   
				if (/^http/i.test(name)){
   
					loadJs(name, callback);
				} else {
   
					loadJs(easyloader.base + name, callback);
				}
			} else {
   
				loadModule(name, callback);
			}
		},
		
		onProgress: function(name){
   },
		onLoad: function(name){
   }
	};

	var scripts = document.getElementsByTagName('script');
	for(var i=0; i<scripts.length; i++){
   
		var src = scripts[i].src;
		if (!src) continue;
		var m = src.match(/easyloader\.js(\W|$)/i);
		if (m){
   
			easyloader.base = src.substring(0, m.index);
		}
	}

	window.using = easyloader.load;
	
	if (window.jQuery){
   
		jQuery(function(){
   
			easyloader.load('parser', function(){
   
				jQuery.parser.parse();
			});
		});
	}
	
})();

Dialog的实现

Dialog是最典型的UI组件,使用广泛。

(function($){
   
	/**
	 * wrap dialog and return content panel.
	 */
	function wrapDialog(target){
   
		var t = $(target);
		t.wrapInner('<div class="dialog-content"></div>');
		var contentPanel = t.find('>div.dialog-content');
		
		contentPanel.css('padding', t.css('padding'));
		t.css('padding', 0);
		
		contentPanel.panel({
   
			border:false
		});
		
		return contentPanel;
	}
	
	/**
	 * build the dialog
	 */
	function buildDialog(target){
   
		var opts = $.data(target, 'dialog').options;
		var contentPanel = $.data(target, 'dialog').contentPanel;
		
		$(target).find('div.dialog-toolbar').remove();
		$(target).find('div.dialog-button').remove();
		if (opts.toolbar){
   
			var toolbar = $('<div class="dialog-toolbar"></div>').prependTo(target);
			for(var i=0; i<opts.toolbar.length; i++){
   
				var p = opts.toolbar[i];
				if (p == '-'){
   
					toolbar.append('<div class="dialog-tool-separator"></div>');
				} else {
   
					var tool = $('<a href="javascript:void(0)"></a>').appendTo(toolbar);
					tool.css('float','left').text(p.text);
					if (p.iconCls) tool.attr('icon', p.iconCls);
					if (p.handler) tool[0].onclick = p.handler;
					tool.linkbutton({
   
						plain: true,
						disabled: (p.disabled || false)
					});
				}
			}
			toolbar.append('<div style="clear:both"></div>');
		}
		
		if (opts.buttons){
   
			var buttons = $('<div class="dialog-button"></div>').appendTo(target);
			for(var i=0; i<opts.buttons.length; i++){
   
				var p = opts.buttons[i];
				var button = $('<a href="javascript:void(0)"></a>').appendTo(buttons);
				if (p.handler) button[0].onclick = p.handler;
				button.linkbutton(p);
			}
		}
		
		if (opts.href){
   
			contentPanel.panel({
   
				href: opts.href,
				onLoad: opts.onLoad
			});
			
			opts.href = null;
		}
		
		$(target).window($.extend({
   }, opts, {
   
			onResize:function(width, height){
   
				var wbody = $(target).panel('panel').find('>div.panel-body');
				
				contentPanel.panel('resize', {
   
					width: wbody.width(),
					height: (height=='auto') ? 'auto' :
							wbody.height()
							- wbody.find('>div.dialog-toolbar').outerHeight()
							- wbody.find('>div.dialog-button').outerHeight()
				});
				
				if (opts.onResize) opts.onResize.call(target, width, height);
			}
		}));
	}
	
	function refresh(target){
   
		var contentPanel = $.data(target, 'dialog').contentPanel;
		contentPanel.panel('refresh');
	}
	// 此处定义了一个新的组件:dialog
	$.fn.dialog = function(options, param){
   
		if (typeof options == 'string'){
   
			switch(options){
   
			case 'options':
				return $(this[0]).window('options');
			case 'dialog':
				return $(this[0]).window('window');
			case 'setTitle':
				return this.each(function(){
   
					$(this).window('setTitle', param);
				});
			case 'open':
				return this.each(function(){
   
					$(this).window('open', param);
				});
			case 'close':
				return this.each(function(){
   
					$(this).window('close', param);
				});
			case 'destroy':
				return this.each(function(){
   
					$(this).window('destroy', param);
				});
			case 'refresh':
				return this.each(function(){
   
					refresh(this);
				});
			case 'resize':
				return this.each(function(){
   
					$(this).window('resize', param);
				});
			case 'move':
				return this.each(function(){
   
					$(this).window('move', param);
				});
			case 'maximize':
				return this.each(function(){
   
					$(this).window('maximize');
				});
			case 'minimize':
				return this.each(function(){
   
					$(this).window('minimize');
				});
			case 'restore':
				return this.each(function(){
   
					$(this).window('restore');
				});
			case 'collapse':
				return this.each(function(){
   
					$(this).window('collapse', param);
				});
			case 'expand':
				return this.each(function(){
   
					$(this).window('expand', param);
				});
			}
		}
		
		options = options || {
   };
		// this.each()返回的对象还是this
		return this.each(function(){
   
			var state = $.data(this, 'dialog');
			if (state){
   
				$.extend(state.options, options);
			} else {
   
				var t = $(this);
				var opts = $.extend({
   }, $.fn.dialog.defaults, {
   
					title:(t.attr('title') ? t.attr('title') : undefined),
					href:t.attr('href'),
					collapsible: (t.attr('collapsible') ? t.attr('collapsible') == 'true' : undefined),
					minimizable: (t.attr('minimizable') ? t.attr('minimizable') == 'true' : undefined),
					maximizable: (t.attr('maximizable') ? t.attr('maximizable') == 'true' : undefined),
					resizable: (t.attr('resizable') ? t.attr('resizable') == 'true' : undefined)
				}, options);
				$.data(this, 'dialog', {
   
					options: opts,
					contentPanel: wrapDialog(this)
				});
			}
			buildDialog(this);
		});
	};
	
	$.fn.dialog.defaults = {
   
		title: 'New Dialog',
		href: null,
		collapsible: false,
		minimizable: false,
		maximizable: false,
		resizable: false,
		
		toolbar:null,
		buttons:null
	};
})(jQuery);

解析如下:

  1. (function($){})(jQuery) 是常规套路,调用了一个函数,把jQuery对象作为参数传进去。
  2. $.fn.dialog = function() 定义了一个函数,这个函数返回一个jQuery对象,这个对象是通过执行this.each()方法返回的。在each()里调用buildDialog(this)后,就对这个对象赋予了"生命": 数据和事件处理。所以,当我们执行$(‘#myDialog’).dialog()后,就返回了一个Dialog对象。

其它组件的实现都是和Dialog一个套路。

小结

jQuery easyui的整体架构还是比较清晰的,一个loader加上一堆jQuery插件,每个插件实现一个UI组件。搞清楚了几个关键地方的实现,我们也能自己实现一些简易的UI组件了。


转载:https://blog.csdn.net/jgku/article/details/128151385
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场