如jQuery般易用的api風(fēng)格代碼分享
回到正題,如jQuery般易用的api風(fēng)格?那到底是什么樣的風(fēng)格呢?個(gè)人覺得比較重要的有兩點(diǎn),一是對dom操作的鏈?zhǔn)秸{(diào)用,并且都呈隊(duì)列狀態(tài),不僅僅使代碼的可讀語義變得通俗易懂,而且省去了對同一dom元素的多個(gè)鏈?zhǔn)讲僮鲿r(shí)的回調(diào)中嵌回調(diào)的方式,這是很重要的一點(diǎn)。
二是對元素的批量操作,這是建立在它強(qiáng)大的選擇器上的。jq選擇器很強(qiáng)大,這是人所眾知的,就不多說了。而且肯定也不是一兩天就能實(shí)現(xiàn)的了的,所以下面就對我所說的這兩點(diǎn)談?wù)勎业目捶ā?
基于它強(qiáng)大的選擇器,jquery所有的dom操作都依賴于根據(jù)它選擇器取到的一個(gè)數(shù)組,很多人喜歡把這叫做jq對象。那咱們暫時(shí)也先這樣叫吧。然后所有的dom操作都是依賴于這個(gè)jq對象中每一個(gè)元素并發(fā)批量執(zhí)行的。具體到每一個(gè)dom操作,大部分的都是呈鏈?zhǔn)交卣{(diào)的狀態(tài),也就是說在這個(gè)方法鏈里面,直接根據(jù)方法調(diào)用在鏈中的先后順序就能知道他們執(zhí)行的順序。這種方法鏈并且串行的形式是它的一大特色。
以至于很多人喜歡用jquery,基本就看中它兩點(diǎn),選擇器確實(shí)很強(qiáng)大,鏈?zhǔn)秸{(diào)用確實(shí)很方便很易用,代碼邏輯瞬間變得簡單。正因?yàn)樗押芏嗟拇a邏輯都放到自己內(nèi)部去處理了,留給編碼者考慮的問題就少了很多,所以一方面你覺得好用的同時(shí),也就失去了一次鍛煉編碼邏輯的機(jī)會。因此我不建議初學(xué)者直接學(xué)習(xí)使用jquery或者其他的框架,因?yàn)樗麄儠屇銓s的理解越來越少。我的觀點(diǎn)是所有的框架或者庫都是拿來使用的,拿來提高開發(fā)效率和管理便利度的,而不是拿來學(xué)習(xí)的。(當(dāng)然,研究源碼的除外)。
那么,既然覺得jquery的api風(fēng)格好用,那我們何嘗不嘗試一下構(gòu)建這種類似的api風(fēng)格呢?(聲明:以下嘗試都僅僅是提供一種思路,代碼并不完善...)
var get = function (ids) {
var d = document, a = -1;
this.elements = [];
if (typeof ids != 'string' && !!ids.length) {
for (var i=0; i<ids.length; i++) {
var id = ids[i], o;
o = typeof id == 'string' ? d.getElementById(id) : id;
this.elements.push(o);
}
} else {
while (typeof arguments[++a] == 'string') {
this.elements.push(d.getElementById(arguments[a]));
}
}
}
然后為它擴(kuò)展一些操作dom的方法
get.prototype = {
each : function () {},
animate : function () {}
}
當(dāng)然,這種方式和jQuery看起來不太一樣,但能理解就行,jquery可能是這個(gè)樣子:
jQuery = window.jQuery = window.$ = function( selector, context ) {
return new jQuery.fn.init( selector, context );
}
jQuery.fn = jQuery.prototype = {
init: function( selector, context ) {}
}
接下來對獲取的隊(duì)列進(jìn)行批量操作,不可避免的就需要一個(gè)each的遍歷方法。
each : function (fn) {
for (var i=0; i<this.elements.length; i++) {
fn.call(this, this.elements[i])
}
return this;
},
each為get.prototype擴(kuò)展出的方法,提供一個(gè)參數(shù)function,并且遍歷dom列表,把function綁定到每一個(gè)元素上。然后讓它返回get.prototype,因?yàn)閜rototype本身具有類似于“超類”的性質(zhì),所以凡是返回給prototype對象的方法都能繼續(xù)調(diào)用prototype擴(kuò)展出來到方法。
為了使這個(gè)嘗試更有意義一點(diǎn),接下來來做一個(gè)animate的函數(shù)吧。這個(gè)函數(shù)是jquery對dom操作很常用的一個(gè)方法,有了它,大部分的動畫都變得那么簡單和容易了。下面會是一個(gè)簡單的實(shí)現(xiàn):
animate: function (config) {
if (!this.animQueue) this.animQueue = HR._animQueue = [];
var a = 0, time, tween, ease, callback;
while (arguments[++a]) {
if (typeof arguments[a] == 'number') time = arguments[a];
if (typeof arguments[a] == 'string') {
if (/^ease*/.test(arguments[a])) ease = arguments[a];
else tween = arguments[a];
}
if (HR.isFunction(arguments[a])) callback = arguments[a];
}
this.animQueue.push({
config: config,
time: time,
tween: tween,
ease: ease,
callback: callback
});
if (this.animQueue.length == 1) this.execute(this.animQueue);
return this;
},
光看這一段可能看不出什么端倪,是的,因?yàn)橐駄query一樣做成串行的方法鏈,就需要一個(gè)臨時(shí)隊(duì)列來操作,要不然即使方法鏈形成了,但這些方法都是并行的,達(dá)不到我們想要的效果。所以上面一段代碼主要是處理animate推入隊(duì)列的一個(gè)邏輯,然后對參數(shù)arguments做了一些判斷,以便在寫參數(shù)的時(shí)候能更加隨意,除了第一個(gè)參數(shù)和最后一個(gè)callback外,其余參數(shù)不用考慮位置和是否必填,以增強(qiáng)易用性。
核心的變換函數(shù)在execute上,
execute : function (queue) {
var _this = this, m = 0, n = 0,
_anim = function (el, key, from, to, at, tw, ease, cb) {
var isOP = (key == 'opacity' && !HR.support.opacity), _key = key;
if (isOP) {to = to*100; _key = 'filter'}
var s = +new Date,
d = at,
b = parseFloat(from) || 0,
c = to-b;
(function () {
var t = +new Date - s;
if (t >= d) {
n ++;
t = d;
el.style[_key] = (isOP ? 'alpha(opacity=' : '') + Tween.Linear(t, b, c, d) + (key != 'opacity' ? 'px' : '') + (isOP ? ')' : '');
!!cb && cb.apply(el);
if (m == n && _this.animQueue.length > 1) {
_this.animQueue.shift();
_this.execute(_this.animQueue);
}
return;
}
el.style[_key] = (isOP ? 'alpha(opacity=' : '') + Tween[tw][ease](t, b, c, d) + (key != 'opacity' ? 'px' : '') + (isOP ? ')' : '');
if (!HR.timers[el.id]) HR.timers[el.id] = [];
HR.timers[el.id].push(setTimeout(arguments.callee, 16));
})();
},
_q = this.animQueue[0];
return this.each(function (el) {
for (var k in _q.config) {
m ++;
_anim(el,
k,
k == 'opacity' && !HR.support.opacity ? HR.getStyle('filter', el) == '' ? 100 : parseInt(HR.getStyle('filter', el).match(/\d{1,3}/g)[0]) : HR.getStyle(k, el),
_q.config[k],
typeof _q.time == 'number' ? _q.time : 1000,
typeof _q.tween == 'string' && !/^ease*/.test(_q.tween) ? _q.tween : 'Quart',
typeof _q.ease == 'string' && /^ease*/.test(_q.ease) ? _q.ease : 'easeOut',
_q.callback)
}
});
}
這一段看起來就要復(fù)雜一些了,最基本的變化還是在_anim這個(gè)私有函數(shù)上。其余的代碼基本在做一些批量的操作,和透明度變化兼容性,以及當(dāng)前變換是否執(zhí)行完畢的功能。結(jié)合這兩段,基本就實(shí)現(xiàn)了jquery的animate的效果了。屬于一個(gè)簡化版本。
當(dāng)然,還不能忘了很重要的一點(diǎn),就是既然可以變換,那就必須有個(gè)stop的方法讓這個(gè)變換可控,要不然這個(gè)代碼的可用性會大打折扣,參考以下代碼:
stop : function (clearQueue) {
if (clearQueue) HR._animQueue.length = 0;
this.each(function (el) {
if (!!HR.timers[el.id])
for (var i=0; i<HR.timers[el.id].length; i++) clearTimeout(HR.timers[el.id][i])
});
return this;
},
針對不同的dom元素id設(shè)置專門的臨時(shí)計(jì)時(shí)器存貯,HR.timers[el.id],然后遍歷當(dāng)前dom列表,把對應(yīng)的計(jì)時(shí)器clear掉。參數(shù)clearQueue作為可選參數(shù),用來控制是否清掉后續(xù)等待執(zhí)行的animate。
為了讓這個(gè)方法更加好玩一點(diǎn),我加了幾種額外的緩動方式,jquery只有一種swing,然后所有的緩動算法放置在Tween對象中以供使用。下面是我做測試的源碼,(如有紕漏,各位見諒)
/* =========== animate js ============ */
/* @author:hongru.chen */
/* =================================== */
if (typeof HR == 'undefined' || !HR)
HR = {
extend : function (destination, source, override) {
if (override === #ff0000) override = true;
for (var property in source) {
if (override || !(property in destination)) {
destination[property] = source[property];
}
}
return destination;
}
};
(function () {
var Tween = { // 以下算子的參數(shù)分別表示: t:運(yùn)行時(shí)間,b:開始量,c:總變化量,d:總時(shí)間
Linear: function(t,b,c,d){ return c*t/d + b; },
Quad: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t + b;
},
easeOut: function(t,b,c,d){
return -c *(t/=d)*(t-2) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
}
},
Cubic: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t*t + b;
},
easeOut: function(t,b,c,d){
return c*((t=t/d-1)*t*t + 1) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
}
},
Quart: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t*t*t + b;
},
easeOut: function(t,b,c,d){
return -c * ((t=t/d-1)*t*t*t - 1) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
return -c/2 * ((t-=2)*t*t*t - 2) + b;
}
},
Quint: {
easeIn: function(t,b,c,d){
return c*(t/=d)*t*t*t*t + b;
},
easeOut: function(t,b,c,d){
return c*((t=t/d-1)*t*t*t*t + 1) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
return c/2*((t-=2)*t*t*t*t + 2) + b;
}
},
Sine: {
easeIn: function(t,b,c,d){
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
},
easeOut: function(t,b,c,d){
return c * Math.sin(t/d * (Math.PI/2)) + b;
},
easeInOut: function(t,b,c,d){
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
}
},
Expo: {
easeIn: function(t,b,c,d){
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
},
easeOut: function(t,b,c,d){
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
},
easeInOut: function(t,b,c,d){
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
}
},
Circ: {
easeIn: function(t,b,c,d){
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
},
easeOut: function(t,b,c,d){
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
},
easeInOut: function(t,b,c,d){
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
}
},
Elastic: {
easeIn: function(t,b,c,d,a,p){
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
},
easeOut: function(t,b,c,d,a,p){
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return (a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b);
},
easeInOut: function(t,b,c,d,a,p){
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
if (!a || a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
}
},
Back: {
easeIn: function(t,b,c,d,s){
if (s == undefined) s = 1.70158;
return c*(t/=d)*t*((s+1)*t - s) + b;
},
easeOut: function(t,b,c,d,s){
if (s == undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
},
easeInOut: function(t,b,c,d,s){
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
}
},
Bounce: {
easeIn: function(t,b,c,d){
return c - Tween.Bounce.easeOut(d-t, 0, c, d) + b;
},
easeOut: function(t,b,c,d){
if ((t/=d) < (1/2.75)) {
return c*(7.5625*t*t) + b;
} else if (t < (2/2.75)) {
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
} else if (t < (2.5/2.75)) {
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
} else {
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
}
},
easeInOut: function(t,b,c,d){
if (t < d/2) return Tween.Bounce.easeIn(t*2, 0, c, d) * .5 + b;
else return Tween.Bounce.easeOut(t*2-d, 0, c, d) * .5 + c*.5 + b;
}
}
}
var get = function (ids) {
var d = document, a = -1;
this.elements = [];
if (typeof ids != 'string' && !!ids.length) {
for (var i=0; i<ids.length; i++) {
var id = ids[i], o;
o = typeof id == 'string' ? d.getElementById(id) : id;
this.elements.push(o);
}
} else {
while (typeof arguments[++a] == 'string') {
this.elements.push(d.getElementById(arguments[a]));
}
}
}
get.prototype = {
each : function (fn) {
for (var i=0; i<this.elements.length; i++) {
fn.call(this, this.elements[i])
}
return this;
},
setStyle : function (p, v) {
this.each(function (el) {
el.style[p] = v;
});
return this;
},
show : function () {
var _this = this;
this.each(function (el) {
_this.setStyle('display', 'block');
})
return this;
},
hide : function () {
var _this = this;
this.each(function (el) {
_this.setStyle('display', 'none');
})
return this;
},
animate: function (config) {
if (!this.animQueue) this.animQueue = HR._animQueue = [];
var a = 0, time, tween, ease, callback;
while (arguments[++a]) {
if (typeof arguments[a] == 'number') time = arguments[a];
if (typeof arguments[a] == 'string') {
if (/^ease*/.test(arguments[a])) ease = arguments[a];
else tween = arguments[a];
}
if (HR.isFunction(arguments[a])) callback = arguments[a];
}
this.animQueue.push({
config: config,
time: time,
tween: tween,
ease: ease,
callback: callback
});
if (this.animQueue.length == 1) this.execute(this.animQueue);
return this;
},
stop : function (clearQueue) {
if (clearQueue) HR._animQueue.length = 0;
this.each(function (el) {
if (!!HR.timers[el.id])
for (var i=0; i<HR.timers[el.id].length; i++) clearTimeout(HR.timers[el.id][i])
});
return this;
},
execute : function (queue) {
var _this = this, m = 0, n = 0,
_anim = function (el, key, from, to, at, tw, ease, cb) {
var isOP = (key == 'opacity' && !HR.support.opacity), _key = key;
if (isOP) {to = to*100; _key = 'filter'}
var s = +new Date,
d = at,
b = parseFloat(from) || 0,
c = to-b;
(function () {
var t = +new Date - s;
if (t >= d) {
n ++;
t = d;
el.style[_key] = (isOP ? 'alpha(opacity=' : '') + Tween.Linear(t, b, c, d) + (key != 'opacity' ? 'px' : '') + (isOP ? ')' : '');
!!cb && cb.apply(el);
if (m == n && _this.animQueue.length > 1) {
_this.animQueue.shift();
_this.execute(_this.animQueue);
}
return;
}
el.style[_key] = (isOP ? 'alpha(opacity=' : '') + Tween[tw][ease](t, b, c, d) + (key != 'opacity' ? 'px' : '') + (isOP ? ')' : '');
if (!HR.timers[el.id]) HR.timers[el.id] = [];
HR.timers[el.id].push(setTimeout(arguments.callee, 16));
})();
},
_q = this.animQueue[0];
return this.each(function (el) {
for (var k in _q.config) {
m ++;
_anim(el,
k,
k == 'opacity' && !HR.support.opacity ? HR.getStyle('filter', el) == '' ? 100 : parseInt(HR.getStyle('filter', el).match(/\d{1,3}/g)[0]) : HR.getStyle(k, el),
_q.config[k],
typeof _q.time == 'number' ? _q.time : 1000,
typeof _q.tween == 'string' && !/^ease*/.test(_q.tween) ? _q.tween : 'Quart',
typeof _q.ease == 'string' && /^ease*/.test(_q.ease) ? _q.ease : 'easeOut',
_q.callback)
}
});
}
}
HR.extend(HR, {
get : function () {
return new get(arguments);
},
isFunction : function(o) {
return typeof(o) == 'function' && (!Function.prototype.call || typeof(o.call) == 'function');
},
getStyle : function (p, el) {
return el.currentStyle ? el.currentStyle[p] : document.defaultView.getComputedStyle(el, null).getPropertyValue(p);
},
support : (function () {
try {
var d = document.createElement('div');
d.style['display'] = 'none';
d.innerHTML = '<a style="float:left; opacity:.5;"></a>';
var a = d.getElementsByTagName('a')[0];
return {
opacity: a.style.opacity === '0.5'
}
} finally {
d = null;
}
})(),
timers : {}
});
})();
然后為了讓大家看的直觀一點(diǎn),小做了兩個(gè)demo
【demo1】
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
【demo2】
[Ctrl+A 全選 注:引入外部Js需再刷新一下頁面才能執(zhí)行]
- jQuery EasyUI API 中文幫助文檔和擴(kuò)展實(shí)例
- Jquery api 速查表分享
- 不同的jQuery API來處理不同的瀏覽器事件
- jQuery EasyUI API 中文文檔 - TreeGrid 樹表格使用介紹
- jQuery EasyUI API 中文文檔 - Tree樹使用介紹
- jQuery EasyUI API 中文文檔 - PropertyGrid屬性表格
- jQuery EasyUI API 中文文檔 - DataGrid數(shù)據(jù)表格
- jQuery EasyUI API 中文文檔 - Dialog對話框
- jQuery EasyUI API 中文文檔 - TimeSpinner時(shí)間微調(diào)器
- Dreamweaver jQuery智能提示插件,支持版本提示,支持1.6api
- jquery.combobox中文api和例子,修復(fù)了上面的小bug
- jQuery validate 中文API 附validate.js中文api手冊
- jQuery EasyUI 中文API Layout(Tabs)
- jQuery EasyUI 中文API Button使用實(shí)例
- jquery 1.4.2發(fā)布!主要是性能與API
- jquery中有哪些api jQuery主要API
相關(guān)文章
JavaScript制作windows經(jīng)典掃雷小游戲
掃雷是一款相當(dāng)大眾的小游戲,游戲目標(biāo)是在最短的時(shí)間內(nèi)根據(jù)點(diǎn)擊格子出現(xiàn)的數(shù)字找出所有非雷格子,同時(shí)避免踩雷。今天我們來看看如何使用javascript來實(shí)現(xiàn)這款小游戲2015-03-03JavaScript使用高階生成器進(jìn)行過濾以生成素?cái)?shù)
生成器大家都知道是怎么一回事,但是高階生成器又是什么東西呢,下面小編就來為大家簡單介紹一下如何使用高階生成器進(jìn)行過濾以生成素?cái)?shù)吧2024-02-02