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

【OpenGL】笔记二十六、高级GLSL 之前我们对于GLSL只是进行了比较简单的应用,接下来就来介绍一下更有趣的应用我们已经学会使用顶点属性、uniform和采样器来往着色器传入数据了,然而,除此之外,GLSL还定义了另外几个以gl_为前缀的变量,它们能提供给我们更多的方式来读取/写入数据。(我们已经在前面教程中接触过其中的两个了:顶点着色器的输出向量gl_Position,和片段着色器的gl_FragCoord)我们能够选用的其中一个图元是GL_POINTS,如果使用它的话,每一个顶点都是一个图元,都会被渲染为一个点。我们可以通过OpenGL的 阅读详情

本篇文章参考书籍《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);




午饭选择器 用于选择吃饭的动态选择的网页效果 吃饭的内容也是刚出去程序猿每天面对的食物 立即下载

相关推荐

舵机PWM信号转互斥继电器开关信号

-----------------本文由“麦粒电子”撰写,并提供相应产品服务。---------------- 叙述 上次的PWM转继电器开关方案得到大家的不少好评(博客链接),但是发现有些玩家需要一路PWM同时去控制两路继电器开关通断,并且两者是互斥的,好去控制两种不同的负载。所以就继续升级了如下的模块。 正常的控制部件顺序这样:遥控器按钮拨动,遥控器接收机相应通道PWM值变化,PWM转开关模块检测PWM值发生变化从而驱动相应机械继电器触点吸合释放。本模块的两个继电器动作是互斥的,也就是不会同时吸合。

Mai_LiElectronic的博客 1632

如何写出同事看不懂的Java代码?

前几天,正巧赶上组里代码review,一下午下来,感觉整个人都血压拉满了。

1212

深信服防火墙路由模式开局部署-手把手教学(小白篇)

手把手教学,拿到深信服的防火墙路由模式该如何开局?

m0_64423407的博客 2万+

如何让别人看不懂你的 JS 代码?把你当大佬!

我们在上周的文章中一种奇特的 JavaScript 编码风格:Get 一种可以用来装逼的 JavaScript 编码风格,引起了广大网友的热议。这是实际上属于一种代码混淆技术,可以让们的代码更难阅读和逆向,同时也能租网一些恶意爬虫和自动化分析。天我就带大家来看看还有哪些其他能让 JavaScript 代码变得难以分析的代码混淆技术。通过一些转换,它可以变成下面这个样子:怎么做到的呢?我们一起来看一下~...

m0_62051288的博客 834

基于Lumerical FDTD的光栅耦合器宽带特性仿真与实验验证

本文详细介绍了基于Lumerical FDTD软件对光栅耦合器进行宽带特性仿真的完整流程。通过复现一个针对1310nm波段优化的经典光栅耦合器案例,文章从结构建模、光源设置、监视器布置到仿真运行与结果分析,逐步解析了如何准确模拟其耦合效率与带宽特性,并将仿真结果与实验数据进行对比验证,为硅光芯片的设计与优化提供了实用指导。

weixin_29294597的博客 512

如何使用Ray和PyTorch训练模型

用户编写训练函数 → 定义单机训练逻辑。通过扩缩容配置 → 指定需要多少资源(Worker 数量、CPU/GPU)。训练器整合训练函数和配置 → 自动将训练函数分发到多个 Worker 上并行执行。Worker 运行训练函数 → 在分布式环境中完成模型训练。通过这种模式,Ray Train 实现了对分布式训练的抽象,用户只需关注单机训练逻辑,无需手动管理分布式通信和资源分配。

m0_62464865的博客 1922

设计模式知识连载(48)---MVC模式:

<body> <h3>设计模式知识连载(48)---MVC模式:</h3> <div> <p> 模型(model)-视图(view)-控制器(controller),用一种将业务逻辑、数据、视图分离的方式组织架构代码 </p></div> <hr> <script type="text/javascript"> /** * 案例一:,方式一:初始

yuzhiboyouzhu的博客 212

CanMV K230实战:用MicroPython玩转AI视觉识别(附完整代码)

本文详细介绍了如何使用MicroPython在CanMV K230开发板上实现AI视觉识别,包括环境搭建、摄像头配置、模型部署及优化技巧。通过实战案例(如智能门禁和工业分拣机器人),展示如何快速开发低门槛的嵌入式AI应用,特别适合初学者和嵌入式开发者。

x8y9z0的博客 1142

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

文章目录前言前期准备如何才能让别人看不懂1.四两拨千斤2.指桑骂槐 前言   上一期我们,浅尝辄止,稍微介绍了下代码如何写才能更高级,更有内涵,主要是需要配合继承,或是某些数据特性来说。这一节咱们放个大招,运用兵法来写代码,保证初学者看不懂,既便能看懂,也不知道为什么这样做,即便知道为什么这样做,没有系统学习过的人也做不好。   这部兵法就是–设计模式。 前期准备   各位乡绅,读这本书前,个人建议要对 继承 , 类 (虽然JS在ES6之前没有类,但是可以通过一些手段实现类), 构造函数 ,

不见浅诗的博客 623

OmniQuant:全方位校准实现大语言模型高效量化与移动端部署

模型量化是一种通过降低模型权重和激活值的数值精度来压缩模型大小、减少内存占用和计算开销的核心技术。其原理在于将高精度浮点数映射到低精度整数表示,从而在保持模型性能的同时,显著提升存储和计算效率。这项技术的价值在于,它使得大规模语言模型能够在消费级硬件和资源受限的移动设备上运行,极大地拓宽了AI应用场景。OmniQuant作为一项前沿的量化技术,通过引入可学习权重裁剪和可学习等价变换机制,实现了对权重分布不均匀和激活值异常值问题的协同优化,从而在极低的比特数下仍能保持优异的模型性能。本文深入探讨了OmniQu

weixin_30719711的博客 484

基于QT+Halcon实现线序识别

一、线序识别算法步骤。 1.按照检测区域m_pItem1的外接矩形(rtBount)的大小, 截取感兴趣区域图像hRoi。 2.把截取出来的感兴趣区域图像hRoi, 分离出R、G、B三个通道hRed、hGreen、hBlue。 3.把hRed、hGreen、hBlue三个通道转换成cielab颜色空间, 得到hChannelL、hChannelA、hChannelB三个颜色图。 4.设m_pItem1的宽为iItemW, 高为iItemH. 分别申请大小为iItemW的pSumL、pSumA、pSumB内

专注现代C++实战,从核心语法到高性能编程,助力开发者深入掌握系统级编程精髓。 1748

从600个空间到一支蜡烛,她用二十六年找到了一条通往人心的路

有人问张晓玲:"你做蜡烛和做景观设计,有什么不一样?她想了一下,说:"做景观设计,你改变的是一个空间。做域见抚苏,你陪伴的是一个人。空间不会跟你说话,但人会。有人做完一支蜡烛之后跟我说,这是她这周最安静的一个小时。听到这个的时候,我觉得值了。从600个空间到一支蜡烛,张晓玲走了二十六年。这条路看起来很长,但对她来说,每一步都是必要的。没有那600个空间,她不会懂得"场域"的力量。没有天赋教育的跨界,她不会说出"信任本自具足"这句话。没有芳香疗法的训练,她找不到连接人心的那个媒介。

YJFS888的博客 212

CAPL控制程控电源

汽车电子测试过程中,除了实车测试,还有台架测试,在进行台架测试时,都需要通过电源来模拟汽车电瓶给ECU供电,如果要进一步进行比较高效的压力测试或自动化测试,就需要用到程控电源,所以本章主要介绍CAPL是如何来控制程控电源的。

测试领域技术分享 4388

Python入门(二十六)测试(一)

编写函数或类时,还可为其编写测试。通过测试,可确定代码面对各种输入都能够按要求的那样工作。测试让我们深信,即便有更多人使用你的程序,它也能正确地工作。在程序中添加新代码时,也可以对其进行测试,确认不会破坏程序既有的行为。程序员都会犯错,因此每个程序员都必须经常测试其代码,在用户发现问题前找出它们。因此我们要学习如何使用Python模块unittest中的工具来测试代码,还将学习编写测试用例,核实一系列输入都将得到预期的输出。

小幽余生不加糖 1645

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

自制操作系统日志——第二十六天

今天,我们主要的目标就是进一步的优化命令行窗口,以及我们改善一下窗口的移动速度。

qq_43696276的博客 497

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

(二十六)再见理想

(二十六)再见理想不用上班,睡了两天懒觉后,第三天上午,钱三还在春梦中的时候,手机的一个来电却把钱三吵醒了。钱三睁开惺忪睡眼,看看来电号码,是广州的电话。接通电话后,对方是一女的,自称是广州一家猎头公司的职员,她说在网上搜索到了钱三的简历,所以特意地打电话来想跟钱三谈一下,了解一下钱三的有关情况,他们有一个职位想推荐给钱三。还有这等好事?钱三马上提起精神,清了清嗓音。对方先简单地介绍了

混在天涯 2446

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

第二十六节 测试函数

编写函数或类时,还可为其编写测试。通过测试,可确定代码面对各种输入都能够按要求的那样工作。测试让你深信,即便有更多人使用你的程序,它也能正确地工作。在程序中添加新代码时,也可以对其进行测试, 确认不会破坏程序既有的行为。程序员都会犯错,因此每个程序员都必须经常测试其代码,在用户发现问题前找出它们。在本章中,你将学习如何使用Python模块unittest 中的工具来测试代码,还将学习编写测试用例,核实一系列输入都将得到预期的输出。你将看到测试通过了是什么样子,测试未通过又是什么样子,还将知道测试未通过如何有

sunissnow的博客 881

代码随想录算法训练营第二十六天 | LeetCode 39. 组合总和、40. 组合总和 II、131. 分割回文串

【代码】代码随想录算法训练营第二十六天 | LeetCode 39. 组合总和、40. 组合总和 II、131. 分割回文串。

Hsusan的博客 261

第二十六节 线程

Linux 线程

picassocao的博客 175

二十六 我在软件园的那些日子里

也不知刚才电话里是谁,牛二心中嘀咕着。自己正琢磨着这事,理不出思绪来。也不知哪里没有注意,惹怒了哪路神仙,牛二百思不解,摇了摇头无可奈何的有些倦怠。 "来,抽一根烟。"说话的是景纯,估计还不知牛二不抽烟,跑到抽烟区只是为了释放心情而已。 "哦,谢了,我不抽烟。"牛二表示谢意,景纯只好给自己取了一根,点燃。 "我看你们一天还挺忙乎的,在忙些什么呢?"景纯问道。 "也没啥,就是之前布置的

程序员入门进阶 1411

AskPython 博客中文翻译(二十六)

原文:AskPython Blog 协议:CC BY-NC-SA 4.0 Python Tkinter:一个简单的电子邮件切片器 原文:https://www.askpython.com/python-modules/tkinter/gui-email-slicer 你好,初学者!今天我们将使用 Python 中的 tkinter 模块构建一个简单的电子邮件切片器。在我们开始这个项目之前,让我们知道什么是电子邮件切片机。 什么是电子邮件切片机? 电子邮件切片器是一个简单的工具,其中电子邮件地址作为输

龙哥盟 1032

IDEA之便捷开发总结

目录 一、完全热部署JRebel插件 二、Inject language(快速编写SQL/JSON/JS等格式代码) 三、快捷键移动分割线(先选中当前文件后Ctrl+Shift+左右箭头) 四、自动收尾(Ctrl+Shift+Enter) 五、try/catch,if/else等快速生成(Ctrl+Alt+T) 六、当前页面搜索与替换(Ctrl+F/R) 七、文件/类变量方法等快速...

绣花针 520

二十六、用电脑上微信

如果手机不是智能机,又要玩微信,那也不是完全没有办法,可以在电脑上装微信。电脑装微信的过程比较麻烦,比想象的麻烦多了,因为网上教的虽然也有,但照着做也会遇到很多问题。想想自己三个月前装微信,就是费了好大的周折,不过总算装上了,装上了还是很开心的。 当时是照着http://jingyan.baidu.com/article/295430f11655c00c7e005006.html做的,因为本子上

762

一个屌丝程序猿的人生(二十六)

  心中带着疑惑,林萧接起了电话说道:“喂,哪位?”   “哥。是我啊。”   听到这熟悉的声音,林萧立马便猜出了对方是谁,这声音,可不就是自己的表弟宋刚吗?   “是小刚啊。我就说这北京还能有谁给我打电话,原来是你小子。”   不过林萧转念一想,又不免有些疑惑,表弟的手机号自己是有的,这怎么突然间换了个陌生的号码?   想到这,林萧忍不住好奇道:“小刚,你怎么换号码了?我记得我存了你的手机号啊。”   “噢。这个啊。我刚换了个手机,所以就换号码了。嘿嘿。”   林萧听得出来,表弟在说这话的时

Java's paradise 4821

android逆向之路二十六

我们发现我们在破解一些软件的时候,如果某些参数涉及多个,几个还好,如果是十几个类中呢,那我们每次每个都要去修改一下他的判断,这样子岂不是非常麻烦,那有没有比较简洁的方法呢?答案是有的,我们可以通过对比,在一些软件中每个判断从smail语句翻译成java语句都应该是有着一个boolean值,那么,我们就可以直接根据定位,去修改确定一下他的布尔值,强制定值为某一个真值,那每次要调用这个方法的判断的时候...

luoawai的博客 227

东软实训(二十六)

项目已经彻底完成了!终于等到你啊..现在的

一只被射手情感绑架多年的天蝎 713
上一篇: 我的2020
下一篇: 使用npm更新所有依赖包到最新版本
不见浅诗~
博客等级 码龄7年 506粉丝 129原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值