代码要写成别人看不懂的样子(二十六)

本篇文章参考书籍《JavaScript设计模式》–张容铭

前言

  嗨!各位乡绅,上一节的字符串模板,有没有很崩溃,模板引擎写起来是有点费劲,但是思路比较简单,慢慢梳理就好。

  本节我们来个硬货,如果各位经常浏览招聘信息,那么很多公司都会在岗位要求上面写上,熟悉 MVC 框架者优先。今天我们就来搞搞,到底什么是传说中 MVC
在这里插入图片描述

MVC 模式

  MVC 即模型(model)—视图(view)—控制器(controller),用一种将业务逻辑、数据、视图分离的方式组织架构代码

  现在我们抛出一个问题,在开发的时候,如果把页面分为三个部分,应该怎么划分?

  思维活跃的同学一定第一时间反应到,前端开发老三样, HTML,CSS,JS

  这样划分没错哈,那如果问题再具体一点,按功能把页面划分成三个部分,应该怎么分?

  有同学可能还会说,还是老三样, HTML 是页面骨架, CSS 负责美容, JS 执行逻辑。这样划分也对也不对,问题就出在 JS 身上,这玩意功能太强大,不光能处理逻辑,也能构建 HTML ,控制 CSS ,简直无所不能了有点。

  所以为了划分好 JS 的功能,我们需要把页面分成一下三个层次:

   - 视图层:创建视图

   - 数据层:处理数据

   - 业务逻辑层:处理交互逻辑

  开发中我们需要分清者三个层次,管理好各个层次的内容,页面才会繁而不乱。我们今天学习的 MVC 就是专门处理这类问题的。

实现传说

  对于 MVC 大家应该都有所耳闻,但是要去实现这么个东西,我估计大家可能就没什么思路了,其实依照 MVC 的思想一步一步来,就可以了。

  首先第一步,那当然是把页面分成三个层面,分别是数据层,视图层,控制器层。

  其次我们知道,视图层可以调用数据层创建视图,控制器层可以调用数据层数据与视图层内的视图创建页面增添逻辑。

  因此,我们可以简化页面为三个部分:

//为了简化页面操作,这里引用链模式中实现的 A 框架,具体方法在本文最后附录
//页面加载后创建 MVC 对象
$(function() {
    
    
	//初始化 MVC 对象
	var MVC = MVC || {
    
    };
	//初始化 MVC 数据模型层
	MVC.model = function() {
    
    } ();
	//初始化 MVC 视图层
	MVC.view = function() {
    
    } ();
	//初始化 MVC 控制器层
	MVC.ctrl = function() {
    
    } ();
});

  上面 MVC 的雏形还是很好理解的,三个层次分别书写在 3 个对象中。有一点需要注意的是,每个对象是一个自动执行函数,这是因为三个层次对象需要可被调用,而声明的函数在执行前是不能被调用的,比如模型对象要被视图和控制器调用,所以执行一遍是为其他对象调用提供接口方法。

数据模型层

  接下来我们开始实现数据模型层,这一层的服务器端数据和组件配置数据要能被视图层调用,所以我们需要让数据模型对象返回操作这两类数据的接口方法。

//MVC 数据模型层
MVC.model = function() {
    
    
	//内部数据对象
	var M = {
    
    };
	//服务器端获取数据,通过Ajax获取并储存,后面为简化实现,直接作为同步数据写在页面
	M.data = {
    
    };
	//配置数据,页面加载时即提供
	M.config = {
    
    };
	//返回数据模型层对象操作方法
	return {
    
    
		//获取服务器端数据
		getData: function(m) {
    
    
			//根据数据字段获取数据
			return M.data[m];
		},
		//设置服务器端数据
		setData: function(m, v) {
    
    
			//设置数据字段 m 对应的数据 v
			M.data[m] = v;
			return this;
		},
		//设置配置数据
		setConf: function(c, v) {
    
    
			//设置配置数据字段 c 对应的配置数据 v
			M.conf[c] = v;
			return this;
		}
	}
}();

  通过 model 接口对象返回的 4 个操作方法即可对 model 内部的服务器端数据与配置数据做增删改查。这样我们可以在视图对象以及控制器对象内部轻松操作模型数据对象内部的数据了。

视图层

  根据模型数据层对象的设计思想我们可以创建出视图对象,当然视图对象保存着对应组件内部的视图,为了创建这些视图我们还要在视图对象内部引用模型数据对象以操作模型数据对象内部的数据,最后为了让控制器可以操作视图层内的视图,我们则需要返回一些操作接口方法。

//MVC 视图层
MVC.view = function() {
    
    
	//模型数据层对象操作方法引用
	var M = MVC.model;
	//内部视图创建方法对象
	var V = {
    
    };
	//获取视图接口方法
	return function(v) {
    
    
		//根据视图名称返回视图
		//(由于获取的是一个方法,这里需要将该方法执行一遍以获取相应视图)
		V[v]();
	}
}();

  视图层其实要比想象中简单的多吧,只有三个部分,一是操作模型数据对象方法的引用( M ),二是内部视图创建方法对象,三是外部获取视图创建方法接口。

  所以 MVC 只是在以讹传讹的过程中被神化了,其实实现起来并不难。

控制器层

  最后一个控制器对象,该对象要操作视图层对象与模型数据层对象,参考数据模型层与视图层的实现思想,将控制器层创建出来。

//MVC 控制器层
MVC.ctrl = function() {
    
    
	//模型数据层对象操作方法引用
	var M = MVC.model;
	//视图数据层对象操作方法引用
	var V = MVC.view;
	//控制器创建方法对象
	var C = {
    
    };
} ();

测试完去干饭

  我们用 MVC 简单实现一个小组件,比如这里有一个导航栏,当鼠标移动到导航栏上时,显示该模块浮层,离开后消失,导航栏下方有按钮可以隐藏整个导航区域。

  对于这个需求我们可以拆分三个模块对象层次,第一显示这些导航模块数据(图片和文字)是可变的,需要从服务器获取数据,属于模型数据层内部数据,由于在导航显隐过程中需要屏蔽箭头按钮的操作功能,因此需要在页面中配置一个记录数据,而该数据应属于模型数据层内的配置数据。

MVC.model = function() {
    
    
	var M = {
    
    };
	M.data = {
    
    
		//左侧侧边栏导航服务器端请求得到的响应数据
		slideBar: [
			{
    
    
				text: '二次元',
				icon: 'left_quadratic.png',
				title: '喵耳萝莉千本樱',
				content: '自古萝莉有三好~',
				href: 'http://www.hao123.com'
			},
			{
    
    
				text: '萌妹子',
				icon: 'left_meng.png',
				title: '石原里美哦哈呦',
				content: '高山流水遇知音~',
				href: 'http://www.hao123.com'
			},
			{
    
    
				text: '动漫',
				icon: 'left_cartoon.png',
				title: '少女战士水冰月',
				content: '无边落木萧萧下~',
				href: 'http://www.hao123.com'
			},
			{
    
    
				text: '电影',
				icon: 'left_movie.png',
				title: '倩女幽魂王祖贤',
				content: '直道相思了无益~',
				href: 'http://www.hao123.com'
			},
			{
    
    
				text: '电视剧',
				icon: 'left_quadratic.png',
				title: '神雕侠侣李若彤',
				content: '曾经沧海难为水~',
				href: 'http://www.hao123.com'
			}
		]
	}
	M.conf = {
    
    
		//侧边导航动画配置数据
		slideBarCloseAnimate: false
	}
	return {
    
    /*接口方法*/}
} ();

  对于每个导航图标模块结构都是类似的,因此他们有相同的视图模板,将侧边导航模块视图模块分为两部分:一部分是模块容器模板,另一部分是导航图标模块模板。为了得到侧边导航视图,我们需要用该模块对应的数据,通过模板渲染引擎 formateString 来渲染整个模块视图。

  这里需要用到上一节介绍的简单模板模式中定义的 formateString 方法。

MVC.view = function() {
    
    
	var M = MVC.model;
	var V = {
    
    
		//创建侧边导航模块视图
		createSlideBar: function() {
    
    
				//导航图标内容
			var html = '',
				//视图渲染数据
				data = M.getData('slideBar');
			//屏蔽无效数据
			if(!data || !data.length) {
    
    
				return;
			}
			//创建视图容器(参考附录中 A 框架中创建元素方法 create)
			var dom = $.create('div', {
    
    
				'class': 'slidebar',
				'id': 'slidebar'
			});
				//视图容器模板
			var tpl = {
    
    
				container: [
					'<div class="slidebar_inner"><ul>{#content#}</ul></div>',
					'<a hidefocus href="#" class="slidebar_close" title="收起"/>'
				].join(''),
				//导航图标模块模板
				item: [
					'<li>',
						'<a class="icon" href="{#href#}">',
							'<img src="common/img/{#icon#}">',
							'<span>{#text#}</span>',
						'</a>',
						'<div class="box">',
							'<div>',
								'<a class="title" href="{#href#}">{#title#}</a>',
								'<a href="{#href#}">{#content#}</a>',
							'</div>',
							'<a class="image" href="{#href#}"><img src="common/img/{#img#}" /></a>',
						'</div>',
					'</li>',
				].join('')
			};
			//渲染全部导航图片模块
			for(var i = 0, len = data.length; i < len; i++) {
    
    
				html += $.formateString(tpl.item, data[i]);
			}
			//在页面中创建侧边导航视图
			dom
				//向侧边导航模块容器中插入侧边导航视图
				.html(
					//渲染导航视图
					$.formateString(tpl.container, {
    
    content: html})
				)
				//将侧边导航模块容器插入页面中
				.appendTo('body');
		}
	}
	return function(v) {
    
    
		V[v]();
	}
}();

  通过调用视图层对象中某组件的视图方法,我们即可在页面中创建侧边导航栏视图。接下来就是为视图添加交互方法,这一部应当放在控制器对象中,获取视图元素,并为试图中的元素绑定事件交互,以及添加动画特效。

MVC.ctrl = function() {
    
    
	var V = MVC.view;
	var M = MVC.model;
	var C = {
    
    
		//侧边导航栏模块
		initSLideBar: function() {
    
    
			//渲染导航栏模块视图
			V('createSliderBar');
			//为每一个导航图标添加鼠标光标划过与离开交互事件(参考附录)
			$.('li', 'slidebar')
			//鼠标移入导航 icon 显示导航浮层
			.on('mouseover', function(e) {
    
    
				$(this).addClass('show');
			})
			//鼠标移出导航 icon 隐藏导航浮层
			.on('mouseout', function(e) {
    
    
				$(this).removeClass('show');
			});
			//箭头 icon 动画交互
			$('.slidebar-close', 'slidebar')
			//点击箭头 icon 时
			.on('click', function(e) {
    
    
				//如果正在执行动画
				if(M.getConf('slideBarCloseAnimate')) {
    
    
					//终止操作
					return false;
				}
				//设置侧边导航模块动画配置数据开关为打开状态
				M.setConf('slideBarCloseAnimate', true);
				//获取当前元素(箭头 icon)
				var $this = $(this);
				//如果箭头 icon 是关闭状态(含有 is-close 类)
				if($this.hasClass('is-close')) {
    
    
					//为侧边导航模块添加显示动画
					$.('.slider-inner', 'slidebar')
					.animate({
    
    
						//动画时间
						duration: 800,
						//动画类型
						type: 'easeOutQuart',
						//动画主函数
						main: function(dom) {
    
    
							//每一帧改变导航模块容器  left 值
							dom.css('left', -50 + this.tween * 50 + 'px');
						},
						//动画结束时回调函数
						end: function() {
    
    
							//设置箭头 icon 为打开状态(删除 is-close)类
							$this.removeClass('is-close');
							//设置侧边导航模块动画配置数据开关为关闭状态(此时可以继续进行模块显隐动画交互)
							M.setConf('slideBarCloseAnimate', false);
						}
					});
				//如果箭头 icon 是打开状态(不含 is-close)类
				} else {
    
    
					//为侧边导航模块添加显示动画
					$('.slidebar-inner', 'slidebar')
					.animate({
    
    
						//动画时间
						duration: 800,
						//动画类型
						type: 'easyOutQuart',
						//动画主函数
						main: function(dom) {
    
    
							//每一帧改变导航模块容器 left 值
							dom.css('left', this.tween * -50 + 'px');
						},
						//动画结束时回调函数
						end: function() {
    
    
							//设置箭头 icon 为打开状态(删除 is-close)类
							$this.addClass('is-close');
							//设置侧边导航模块动画配置数据开关为关闭状态(此时可继续进行模块显隐动画交互)
							M.setConf('slideBarCloseAnimate', false);
						}
					});
				}
			})
		}
	};
	//为侧边导航模块添加交互与动画特效
	C.initSlideBar();
} ();

  接下来轮到执行控制器了,控制器要做的事很简单,第一创建视图页面,第二添加交互与动画特效。而在 MVC 的控制器中,由于创建视图的主要逻辑在视图层对象中,因此也就弱化了控制器中创建对象的功能。

  我们只需要一行就能搞定(直接调用视图层对象接口方法渲染),而控制器对象也将自己的主要的精力放在交互与特效上。

  最后为了实现控制器中的功能,我们显性的调用了 C.initSlideBar() 方法,但是如果模块很多,那么一次一次的调用会造成控制器内部混乱,有以下两种解决办法。第一种,可以将对象中的方法改为创建即执行:

var C = {
    
    
	//侧边导航栏模块
	initSlideBar: function() {
    
    } ()
};
//第二种,可以在对象末尾处遍历内部对象 C 中的每一个方法并执行
for(var i in C) {
    
    
	//如果模块方法存在则执行
	C[i] && C[i]();
}

  这样设计程序的好处在于可以实结构层次清晰,用数据时可以专心处理数据,创建视图时也可责无旁贷,将更多的精力放在交互与特效上。

  比如我们再增加一个新闻模块,只需要为三个对象添加代码即可。

MVC.model = function() {
    
    
	var M = {
    
    };
	M.data = {
    
    
		//左侧侧边栏导航服务器端请求得到响应数据
		slideBar: {
    
    
			//...
		},
		/*新增模块追加代码*/
		newMod: {
    
    
			//...
		}
	}
	M.conf = {
    
    
		//侧边导航动画配置数据
		slideBarCloseAnimateL: falase
		/*新增模块追加代码*/
		// newModConf...
	}
	return {
    
    
		// ...
	}
}();
MVC.view = function() {
    
    
	var M = MVC,model;
	var V = {
    
    
		//创建侧边导航模块视图
		createSlideBar: function() {
    
    
			//...
		},
		/*新增模块追加代码*/
		createNewMod: function() {
    
    
			//...
		}
	}
	return function(v) {
    
    
		V[v]();
	}
}();
MVC.ctrl = function() {
    
    
	var V = MVC.view;
	var M = MVC.model;
	var C = {
    
    
		//侧边导航栏模块
		initSlideBar: function() {
    
    
			//...
		}
		/*新增模块追加代码*/
		initNewMod: function() {
    
    
			//...
		}
	};
	//为侧边导航模块添加交互与动画特效
	for(var i in C) {
    
    
		C[i] && C[i]();
	}
}();

总个小结

  只通过一个小例子大家可能感觉不到 MVC 框架的好处,大家可以了解下现在比较火的框架, Vue,React ,用了框架后,大部分人都不再想回到那个获取页面元素,处理页面逻辑,渲染页面数据的年代了,所有的无聊操作,框架帮你实现,你只需要关注数据就可以了。

附录

  下面是 A 框架的代码。有点长,大家不方便使用的话,可以去我上传的资源里面去下载。

/**
* A Library v1.0.0
* Author  Louhongxing
* Date:   2021-1-5
*/
~(function(window) {
    
    
	/**
	* @name   框架单体对象 A
	* @param  selector  选择器或页面加载回调函数
	* @param  context   查找元素上下文
	*/
	var A = function(selector, context) {
    
    
		//如果 selector 为放大则为窗口添加页面加载完成事件监听
		if(typeof selector == 'function') {
    
    
			A(window).on('load', selector);
		} else {
    
    
			//创建 A 对象
			return new A.fn.init(selector, context);
		}
	}
	//原型方法
	A.fn = A.prototype= {
    
    
		//强化构造函数
		constructor: A,
		//构造函数
		init: function(selector, context) {
    
    
			//modify 选择器为元素
			if(typeof selector === 'object') {
    
    
				this[0] = selector;
				this.length = 1;
				return this;
			};
			//设置获取到的元素长度属性
			this.length = 0,
			//矫正上下文
			context = document.getELementById(context) || document;
			//如果是 id 选择器
			if(~selector.indexOf('#')) {
    
    
				this[0] = document.getElementById(selector.slice(1));
				this.length = 1;
			//如果是类选择器
			} else if(~selector.indexOf('.')) {
    
    
				var doms = [],
					className = selector.slice(1);
				//支持通过类获取元素方法
				if(context.getElementsByClassName) {
    
    
					doms = context.getElementsByClassName(className);
				} else {
    
    
					doms = context.getELementsByTagName('*');
				}
				//设置获取到的元素
				for(var i = 0, len = doms.length; i < len; i++) {
    
    
					if(doms[i].className && !!~doms[i].className.indexOf(className)) {
    
    
						this[this.length] = doms[i];
						//矫正长度
						this.length++;
					}
				}
			//否则为元素名选择器
			} else {
    
    
				var doms = context.getElementsByTagName(selector),
					i = 0,
					len = doms.length;
				for(; i < len; i++) {
    
    
					this[i] = doms[i];
				}
				this.length = len;
			}
			//设置当前对象的选择上下文
			this.context = context;
			//设置当前对象的选择器
			this.selector = selector;
			return this;
		},
		//元素长度
		length: 0,
		//曾强数组
		push: [].push,
		splice: [].splice
	}
	//设置构造函数原型
	A.fn.init.prorotype = A.fn;
	/**
	* @name  对象拓展
	* @param[0]       目标对象
	* @param [1, ...] 拓展对象
	*/
	A.extend = A.fn.extend = function() {
    
    
		var i = 1,
			len = arguments.length,
			target = arguments[0],
			j;
		//如果一个参数,则为当前对象拓展方法
		if(i == len) {
    
    
			target = this;
			i--;
		}
		//遍历拓展对象
		for(; i < len; i++) {
    
    
			//遍历拓展对象中方法与属性
			for(j in arguments[i]) {
    
    
				//浅复制
				target[j] = arguments[i][j];
			}
		}
		//返回目标对象
		return target;
	};
	//单体对象 A 方法拓展
	A.extend({
    
    
		/*
		* @name  将横线式命名字符串转化为驼峰式
		* eg:  'test-demo' -> 'testDemo'
		*/
		cameCase: function(str) {
    
    
			return str.replace(/\-(\w)/g, function(match, letter) {
    
    
				return letter.toUpperCase();
			});
		},
		/*
		* @name  去除字符串两端空白
		* eg:  '  t e s t  ' -> 't e s t'
		*/
		trim: function(str) {
    
    
			return str.replace(/^\s+\\s+$/g, '')
		},
		/*
		* @name 创建一个元素并包装成 A 对象
		* @param type    元素类型
		* @param value   元素属性对象
		*/
		create: function(type, value) {
    
    
			var dom = document.createElement(type);
			return A(dom).attr(value);
		},
		/*
		* @name   格式化模板
		* @param  str   模板字符串
		* @param  data  渲染数据
		* eg: '<div>{#value#}</div>' + {value: 'test'} => '<div>test</div>'
		*/
		formateString: function(str, data) {
    
    
			var html = '';
			//如果渲染数据是数组,则遍历数组并渲染
			if(data instanceof Array) {
    
    
				for(var i = 0, len = data.length; i < len; i++) {
    
    
					html += arguments.callee(str, data[i]);
				}
				return html;
			} else {
    
    
				//搜索{#key#}格式字符串,并在 data 中查找对应的 key 属性替换
				return str.replace(/\{#(\w+)#\}/g, function(match, key) {
    
    
					return typeof data === 'string' ? data : (typeof data[key] === 'undefined' ? '' : data[key])
				});
			}
		}
	});
	//事件绑定方法
	var _on = (function() {
    
    
		//如果标准浏览器
		if(document.addEventListener) {
    
    
			return function(dom, type, fn, data) {
    
    
				dom.addEventListener(type, function(e) {
    
    
					fn.call(dom, e, data);
				}, false);
			}
		//如果 IE
		} else if(document.attachEvent) {
    
    
			return function(dom, type, fn, data) {
    
    
				dom.c('on' + type, function(e) {
    
    
					fn.call(dom, e, data);
				});
			}
		//如果是老版本浏览器
		} else {
    
    
			return function(dom, type, fn, data) {
    
    
				dom['on' + type] = function(e) {
    
    
					fn.call(dom, e, data);
				};
			}
		}
	})();
	A.fn.extend({
    
    
		//添加事件
		on: function(type, fn, data) {
    
    
			var i = this.length;
			for(; --i >= 0;) {
    
    
				//通过闭包实现对 i 变量保存
				_on(this[i], type, fn, data);
			}
			return this;
		},
		//设置或者获取元素样式
		css: function() {
    
    
			var arg = arguments,
				len = arg.length;
			//如果无获取到的元素则返回
			if(this.length < 1) {
    
    
				return this;
			}
			//如果是一个参数
			if(len === 1) {
    
    
				//如果参数是字符串则返回获取到的第一个元素的样式
				if(typeof arg[0] === 'string') {
    
    
					//ie 浏览器
					if(this[0].currentStyle) {
    
    
						return this[0].currentStyle[name];
					} else {
    
    
						return getComputedStyle(this[0], false)[name];
					}
				//如果参数为对象,则为获取到的所有元素设置样式
				} else if(typeof arg[0] === 'object') {
    
    
					for(var i in arg[0]) {
    
    
						for(var j = this.length - 1; j >= 0; j--) {
    
    
							this[j].style[A.camelCase(i)] = arg[0][i];
						}
					}
				}
			//如果两个参数
			} else if(len === 2) {
    
    
				//为获取到的所有元素设置样式
				for(var j = this.length - 1; j >= 0; j--) {
    
    
					this[j].style[A.camelCase(arg[0])] = arg[1];
				}
			}
			return this;
		}
		//设置或者获取元素属性
		,attr: function() {
    
    
			var arg = arguments,
				len = arg.length;
			//如果无获取到的元素则返回
			if(this.length < 1) {
    
    
				return this;
			}
			//如果是一个参数
			if(len === 1) {
    
    
				//如果参数是字符串则返回获取到的第一个元素的属性值
				if(typeof arg[0] === 'string') {
    
    
					return this[0].getAttribute(arg[0]);
				//如果参数为对象则为获取到的所有元素设置属性
				} else if (typeof arg[0] === 'object') {
    
    
					for(var i in arg[0]) {
    
    
						for(var j = this.length - 1; j >= 0; j--) {
    
    
							this[j].setAttribute(i, arg[0][i]);
						}
					}
				}
			//如果是两个参数
			} else if(len === 2) {
    
    
				//为获取到的所有元素设置属性
				for(var j = this.length - 1; j >= 0; j--) {
    
    
					this[j].setAttribute(arg[0], arg[1]);
				}
			}
			return this;
		}
		//获取或者设置元素内容
		,html: function() {
    
    
			var arg = arguments,
				len = arg.length;
			//如果无获取到的元素则返回
			if(this.length < 1) {
    
    
				return this;
			}
			//如果无参数则返回获取到的第一个元素内容
			if(len === 0) {
    
    
				return this[0].innerHTML;
			//如果是一个参数,则设置获取到的所有元素内容
			} else if(len === 1) {
    
    
				for(var i = this.length - 1; i >= 0; i--) {
    
    
					this[i].innerHTML = arg[0];
				}
			//如果有两个参数,而且第二个参数值为 true, 则为获取到的所有元素追加内容
			} else if(len === 2 && arg[1]) {
    
    
				for(var i = this.length - 1; i >= 0; i--) {
    
    
					this[i].innerHTML += arg[0];
				}
			}
			return this;
		}
		/*
		* @name 判断类存在
		* @param val 类名
		*/
		,hasClass: function(val) {
    
    
			//如果无获取到的元素则返回
			if(!this[0]) {
    
    
				return;
			}
			//类名去除首尾空白符
			var value = A.trim(val);
			//如果获取到的第一个元素类名包含 val 则返回 true,否则返回 false
			return this[0].calssName && this[0].calssName.indexOf(value) >= 0 ?  true : false;
		}
		/*
		* @name 添加类
		* @param val 类名
		*/
		,addClass: function(val) {
    
    
			var value = A.trim(val),
				str = '';
			//遍历所有获取到的元素
			for(var i = 0, len = this.length; i < len; i++) {
    
    
				src = this[i].className;
				//如果元素类名包含添加类则为元素添加类
				if(!~str.indexOf(value)) {
    
    
					this[i].calssName += ' ' + value;
				}
			}
			return this;
		}
		/*
		* @name 移除类
		* @param val 类名
		*/
		,removeClass: function(val) {
    
    
			var value = A.trim(val),
				classNameArr,   //将元素类名转化为数组
				result;         //元素类名最终结果
			//遍历所有获取到的元素
			for(var i = 0, len = this.length; i < len; i++) {
    
    
				//如果类名包含删除类
				if(this[i].calssName && ~this[i].className.indexOf(value)) {
    
    
					//通过空格符将元素类名切割成数组
					classNameArr = this[i].calssName.split(' ');
					result = '';
					//遍历类名
					for(var j = className.length - 1; j >= 0; j--) {
    
    
						//去除类名首尾空白符
						classNameArr[j] = A.trim(classNameArr[j]);
						//如果类名存在并且类名不等于移除类,则保留该类
						result += classNameArr[j] && classNameArr[j] != value ? '' + classNameArr[j] : '';
					}
					//重置元素类名
					this[i].className = result;
				}
			}
			return this;
		}
		/*
		* @name 插入元素
		* @param parent 父元素
		*/
		,appendTo: function(parent) {
    
    
			var doms = A(parent);
			//如果获取到父元素
			if(doms.length) {
    
    
				//遍历父元素
				for(var j = this.length - 1; j >= 0; j--) {
    
    
					//简化元素克隆(cloneNode)操作,只向第一个父元素中插入子元素
					doms[0].appendChild(this[j]);
				}
			}
		}
	});
	//运动框架单体对象
	var Tween = {
    
    
		//计时器句柄
		timer: 0,
		//运动成员队列
		queen: [],
		//运动间隔
		interval: 16,
		//缓冲函数
		easing: {
    
    
			//默认运动缓存算法 匀速运动
			def: function(time, starValue, changeValue, duration) {
    
    
				return changeValue * time / duration + startValue
			},
			//缓慢结束
			easeQutQuart: function(time, startValue, changeValue, duration) {
    
    
				return -changeValue * ((time = time / duration - 1) * time * time * time - 1) + startValue;
			}
		},
		/*
		* @name 添加运动成员
		* @param instance 运动成员
		*/
		add: function(instance) {
    
    
			//添加成员
			this.queen.push(instance);
			//运动框架
			this.run();
		},
		/*
		* @name 停止框架运行
		*/
		clear: function() {
    
    
			clearInterval(this.timer);
			this.timer = 0;
		},
		/*
		* @name 运行框架
		*/
		run: function() {
    
    
			//如果在运行则返回
			if(this.timer) return;
			//重置计时器
			this.clear();
			//运行框架
			this.timer = setInterval(this.loop, this.interval);
		},
		/*
		* @name 运动框架循环方法
		*/
		loop: function() {
    
    
			//如果运动队列中没有成员
			if(Tween.queen.length === 0) {
    
    
				//停止框架运行
				Tween.clear();
				//返回
				return;
			}
			//获取当前时间
			var now = +new Date();
			//遍历运动成员
			for(var i = Tween.queen.length - 1; i >= 0; i--) {
    
    
				//获取当前成员
				var instance = Tween.queen[i];
				//当前成员已运动的时间
				instance.passed = now - instance.start;
				//如果当前成员已运动的时间小于当前成员运动时间
				if(instance.passed < instance.duration) {
    
    
					//执行当前成员主函数
					Tween.workFn(instance);
				} else {
    
    
					//结束当前成员运行
					Tween.endFn(instance);
				}
			}
		},
		/*
		* @name 运行方法
		* @param instance 运动成员
		*/
		workFn: function(instance) {
    
    
			//获取当前成员在当前时刻下的运动进程
			instance.tween = this.easing[instance.type](instance.passed,
			instance.from, instance.to - instance.from, instance.duration);
			//执行主函数
			this.exec(instance);
		},
		/*
		* @name 结束方法
		* @param instance 运动成员
		*/
		endFn: function(instance) {
    
    
			instance.pass = instance.duration;
			instance.tween = instance.to;
			this.exec(instance);
			this.distory(instance);
		},
		/*
		* @name 执行主函数
		* @param instance 运动成员
		*/
		exec: function(instance) {
    
    
			try{
    
    
				//执行当前成员主函数
				instance.main(instance.dom)
			} catch(e) {
    
    }
		},
		/*
		* @name 注销运动成员
		* @param instance 运动成员
		*/
		distory: function(instance) {
    
    
			//结束当前成员
			instance.end();
			//在运动成员队列中删除该成员
			this.queen.splice(this.queen.indexOf(instance), 1);
			//删除成员中的每一个属性
			for(var i in instance) {
    
    
				delete instance[i];
			}
        }
    }
    /**
     * @name 获取当前成员在运动成员中的位置
     * @param  instance 运动成员
    */
    Tween.queen.indexOf = function() {
    
    
        var that = this;
        //如果有该方法则返回,如果没有则创建一个
        return Tween.queen.indexOf || function(instance) {
    
    
            //遍历每个成员
            for(var i = 0, len = that.length; i < len; i++) {
    
    
                //如果该成员是需求成员,则返回该成员在队列中的位置
                if(that[i] === instance) {
    
    
                    return i;
                }
            }
            //否则返回 -1 表示不存在
            return -1;
        }
    }();
    //A.fn 对象拓展方法
    A.fn.extend({
    
    
        /**
         * @name  动画模块
         * @param obj 动画成员对象
         */
        animate: function(obj) {
    
    
                //适配运动对象
            var obj = A.extend({
    
    
                duration: 400,       //默认运行时间
                type: 'def',         //默认动画缓存函数
                from: 0,             //开始点
                to: 1,               //结束点
                start: +new Date(),  //开始时间
                dom: this,           //当前元素
                main: function() {
    
    }, //运行主函数
                end: function() {
    
    }   //结束函数
            }, obj);
            //像运动框架中载入运动成员对象
            Tween.add(obj);
        }
    });
    /**
     * @name 避免框架别名冲突(主要作用与页面中引入多核框架)
     * @param library 其他框架
     */
    A.noConflict = function(library) {
    
    
        //如果传其他框架
        if(!library) {
    
    
            //为 library 绑定 $ 别名
            window.$ = library;
        } else {
    
    
            //否则删除 $ 别名
            window.$ = null;
            delete window.$;
        }
        //返回 A 对象
        return A;
    }
    //为全局对象绑定 A 框架,并绑定别名 $
    window.$ = window.A = A;
})(window);




猜你喜欢

转载自blog.csdn.net/EcbJS/article/details/111937466