建立起總的架構
創建一個沙箱保護私有變量,避免全域污染 (function(window){})(winow)
把 window 作為形參傳進去的兩個優點:
1、減少作用域的搜尋
2、提高壓縮效率
建立一個建構函數,function Guoqi(){} // 作用:每一個 Guoqi 物件都是一個偽陣列
在原型物件上建立一個 init 物件在構造函數中直接 return new Guoqi.prototype.init();
就可以成功的隱藏 new 關鍵字,
但是最關鍵的一步,一定要將 Guoqi.prototype.init.prototype = Guoqi.prototype的實例都可以用 Guoqi.prototype 原型上的方法啦
最後把 window.Guoqi = window.Q = Guoqi 把 Guoqi 和 Q 作為介面掛在window上,方便在外部直接調用外部直接調用一個對象,放在原型中,預設值為0,確保 Q 對象為一個偽數組
用替換原型對象的方法 Guoqi.prototype = {}; 注意:constructor 屬性要指向建構子本身
為了方便我們書寫,不用每次創建的實例都帶有new 關鍵字,有一個小技巧
init 方法的充實, Q 對像中可以傳入多個參數需要進行判斷
如果以'
否則(傳入的是DOM元素)的話將作為選擇器用qsa 的方法,獲取到對象
傳入的是字符串的話,那麼將分情況
傳入的是DOM 物件的話 selector.nodeType 那麼直接存放到物件中 this[0] = selector;this.length = 1; 保證其是一個偽數組
傳入的是Q 物件的話,selector.conoror .name = Guoqi (因為Q 物件都是用 Guoqi 建構函數創建出來的),直接返回 selector
傳入的函數 function 的話,暫時先不做操作 typeof selector === "function",jQuery中的入口 例如函數 $(function(){})
傳入的是數組(偽數組)的話,selector.length >=0 , 把數組的值,依次傳入到this中
否則的話不管傳入的是什麼,this[0] = selector; this.length = 1;
這樣就確保,不管 Q( ) 傳入的什麼,我們都得到了一個__偽數組__
添加擴展方法的功能 extend( obj ) 在extend中傳入一個物件
Guoqi.extend = Guoqi.prototype.extend = function(){ } 利用聯等符號,代表分別為建構函數,與建構函數原型新增extend方法
extend 所使用的混入繼承的原理,把obj 的功能拷貝到 this 物件上去,(注意:此時兩個this的指向不同)
Guoqi.extend({}) 給建構函數擴充的方法稱為靜態方法,作為工具性的方法使用(each,map,next...)
給建構函式新增的 each 方法,要注意:this指向遍歷的目前對象,而且一旦傳回值為 false 的話,就停止遍歷;即
if( callback.call ( array[i], i, array[i] ) === false ) break;map 方法this指向的是window,只有當返回值不為 undefined 或 null 的時候,才往返回的新數組中添加已遍歷的元素,否則的話即:if( callback( array[i], i ) != undefined){ newArr.push( callback( array[i], i ) ) }; 最後__return newArr__;
給實例新增的方法,語法:Q().each(function(){}); 所以要把 each 方法轉給Q實例對象,例如appendTo, each, map
如果傳入了一個 index ,那麼返回 index 對應值的DOM元素
支持傳入負數,那麼將從後面倒著往前數,例如:傳入-1,那麼對象的就是 length- 1 的DOM元素
toArray 功能:將取得的Q物件(偽數組) 傳回為一個 DOM 元素的真數組
get(index) 功能:如果沒有傳入 index 索引,那麼將傳回一個DOM元素的真元素的真數組
DOM 操作模組
首先在沙箱中定義一個 function parseHtml( str ){} 創建根據傳入的標籤,創建Q對象
創建的時候,可以隨意創建一個把標籤,把我們想要創建的標籤,把我們想要創建的標籤,把我們想要創建的標籤,把我們想要創建的標籤標籤,加入div中,div.innerHTML = str;
然後把div的 childNodes 返回就可以,但是要__注意__,childNodes會隨著我們渲染在頁面上之後,length會改變
所以,我們需要建立一個空數組,把childNodes 裡面的DOM元素,依序放到數組中,
那麼,parseHtml 函數,返回的就是一個__真的數組__,在init方法中'
appendTo(selector) 實例實例方法功能:將this對象追加到selector的子元素中
該方法也要根據傳入的對象, DOM元素,DOM對象,Q對象,偽數組... 做出判斷★(所以前提是init方法的完善)
完善之後,先把selector 轉換為一個Q物件---> Q(selector)
建立一個陣列objQ,和一個變數tempDom,(為了下面的回傳值考量)
對this 和selector 進行嵌套循環,並且要對this[i]進行克隆,注意克隆的數量,要保證本體為最後一個,不要克隆多了
並且不斷將this 的值賦值給tempDom保存起來, tempDom = j === Q(selector).length-1 ? this[i]:this[i].cloneNode(true)
再把 tempDom 加到數組objQ中去,
考慮回傳值的問題,我們的回傳值應該是,加入selector中所有的this元素,包括克隆的,所以objQ中的元素,便是我們應該返回的值
這樣的話,我們的鍊式編程的鍊便遭到了破壞,所以我們就要創建一個_ _end()__方法,來返回上一個的鏈對象
這裡的話,我們就需要將當前this保存下來,以便於調用end()方法可以返回
即:將objQ轉換為Q對象,給其加入一個preObj屬性指向this,然後把Q(obj)return出去
end() 實例方法,當鏈遭到破壞的時候,呼叫一下可以找到上一級的鏈(只能找到一級)
我們上面的工作已經做得非常充實,所以end()方法只要 return this.preObj || this ; (注意:如果沒有上一級的話,把自己return出去)
pushStack()實例方法, 就是appendTo方法後面保存上一層物件的內容,
因為可能有很多方法會遭到鏈破壞,所以我們封裝一個方法,實現復用的效果
即:
function pushStack(arr){ var newObjQ = Q( arr); newObjQ.preObj = this; return newObjQ; }
注意:該方法雖然實例不會直接調用,但是,因為在appendTo方法中應該是this調用的,才能找到this的上一級鏈對象(雖然之後可以藉用call/apply方法實現,但是這樣的話就更麻煩了);
就是說,不能直接當私有函數(例如parseHtml)放到沙箱中,這樣的話上一級對象就是window,實現起來更麻煩
prependTo append preprend remove ... 實例方法實作都是和appendTo一樣的原理
實作方法: 本來用this.nextElementSibling 就可以實現,但是有相容問題
所以我們給Guoqi構造函數擴展了一個next(dom)方法,循環遍歷dom元素,尋找其下一個節點,若是nodeType === 1的話,直接return dom;否則的話return null;
即
實作 this.each(function(){ this. parentNode.removeChild( this ); })
實作 Q(selector).prependTo(this); return this; 鏈不會遭到破壞
實作 Q(selector).appendTo(this); return this; 鏈不會遭到破壞
實作 selector[j].insertBefore( tempDom, selector[j].firstChild );
注意:此方法和appendTo方法一樣,鏈會遭到破壞
prependTo 功能物件:thisselector子元素的最前面
append this物件的子元素加入selector的物件
prepend this物件的子元素最前面加上selector的物件
remove 方法找到this中父節點刪除其中的所有this元素,包括this本身
next 方法找到this物件中緊鄰的下一個元素(而不是所有的同輩(兄弟)元素) 鏈會遭到破壞
function next(dom){ while( dom = dom.nextSibling ){ if( dom.nodeType === 1 ){ return dom; } } return null; }
然後next實例方法,只需要 return pushStack(this.map(function (v){ return Guoqi.next(v) }) );
filter(selector) 方法為this實例物件和selector找到相同的元素
準備好一個ret空數組,對this實例和Guoqi(selector )進行巢狀迴圈遍歷,若this[i] == Guoqi(selector)[j],就加入到陣列ret中去
最後把ret 這個陣列轉換為實例物件回傳即:return Guoqi(ret);
unique() 功能:去除相同的元素
準備一個空數組arr, 呼叫this.toArray()方法,把實例物件轉換為真正的陣列thisArr,然後進行循環遍歷
if( arr.indexOf( thisArr,然後進行循環遍歷
if( arr.indexOf( thisArr,然後進行循環遍歷
if( arr.indexOf( thisArr,然後進行循環遍歷
🎜if( arr.indexOf(thisAthis (i) )== -1 ){ arr.push(thisArr[i]) };🎜🎜然後return Guoqi( arr );🎜🎜children(selector): 功能:若沒有傳入參數就查找到所有的子元素(僅限於兒子輩的),若傳入了參數,就找到與selector相符的子元素🎜原理:找到this实例对象中的所有的子元素,组成一个数组,找到selector中的所有元素 组成一个数组,然后寻找两个数组之间相同的元素即可(调用filter方法),注意:涉及到了unique去重的问题
首先
var subList = this.map(function(v){ return Guoqi.map(v.children,function(v1){ return v1 }) }); // 此时 sunList是一个二维数组,即:数组里面套数组
需要把subList 合成一个 一维数组,借用concat方法:var newSub = [].concat.apply([],subList);
然后对 newSub进行去重(需要将数组转换为Q对象才能调用unique方法), var subQ = Guoqi(newSub).unique();
需要对selector进行判断,如果 selector存在的话,那么 return subQ.filter( Guoqi(selector) ); 如果不存在的话,那么直接 return subQ;
find(selector) : 功能:find一般是找指定的元素,在this实例对象的所有后代元素中找,所以一般都传入参数
find方法和children方法一样,只需要把
var subList = this.map(function(v){ return Guoqi.map(__v.querySelectorAll(selector)__,function(v1){ return v1 }) });
用v.querySelectorAll(selector); 来找到所有的后代元素(应该还有更好的办法,欢迎来补充)
nextAll 工具方法 跟next方法如出一辙:
即
function next(dom){ while( dom = dom.nextSibling ){ if( dom.nodeType === 1 ){ return dom; } } return null;}
next,prev,nextAll,prevAll 方法 都常会在 封装实例方法中用到,所以可以封装为工具方法; prevAll,prev 两个方法和next,nextAll 原理一样,所以不再详细赘述
注意:nextAll,prevAll,children 这些链都遭到了破坏,不过可以使用end恢复
nextAll 实例方法 既然有了上面的nextAll的工具方法,那么相对应的实例方法就简单了很多,
只需要把this实例对象的每一个dom元素调用 nextAll(dom),组成一个数组(用map方法简单),
然后把这些数组用concat组合在一起进行__去重(unique)__,进而转换为Guoqi对象即可
siblings 实例方法
有了prevAll 和 nextAll方法,siblings就变得简单多了,只要两者组合在一起即可
var nextSiblings = this.nextAll().toArray(); var prevSiblings = this.prevAll().toArray();
返回 Guoqi( prevSiblings.concat( nextSiblings ) );
事件操作 模块
on 事件的实现 (我们选择先实现on事件,是因为on事件是通用的,实现on事件之后,其他的具体事件都可以用on事件来实现)
on 事件 语法:Guoqi.fn.extend(on: function( 事件列表,callback ){ });
事件列表 可以有实现多个事件 中间用空格隔开
这就意味着 我们要对this实例对象进行遍历,也要对事件列表进行遍历
添加事件的时候,我们选择使用 addEventListener("事件名",callback);
各个事件的实现
首先我们可以从控制台打印出来所有的事件
创建一个DOM对象div,for( var k in div ){ if( k.slice(0,1) === "on" ){ arr.push(k) } }; 这样就把所有事件放到arr数组中去了
循环遍历数组中每个值,只保留事件名的部分,即:v = v.slice(2);
然后添加在原型上:Guoqi.fn[v] = function(callback){ return this.on(v,callback) };
css 样式操作模块的实现
首先我们需要考虑css的参数情况 语法:Guoqi.fn.css = function( name,value){ }
字符串( typeof name === "string" ),即:需要获取实例对象的值:一般情况下,我们获取的是实例对象中第一个元素的值
(this[0] / this.get(0)) 可以直接 return this[0].style[name] ,但是值得注意的是,这样只能获取行内样式的值
所以 我们还需要 || window.getComputedStyle(this[0])[name]; 但是getComputedStyle在低版本的IE浏览器中(currentStyle)不支持,
如果考虑严谨的话,可以封装一个getStyle获取样式的函数
只有一个参数 即:value == undefined 可能是对象,也可能是一个字符串
function getStyle(dom,style){ return dom.currentStyle?dom.currentStyle[style]: window.getComputedStyle(dom)[style]}
这样的话,只需要循环遍历this对象,即:this[i].style[name] = value; 并且要把this实例对象返回回去,实现链式编程
对象 不仅需要循环遍历this实例对象,还需要遍历name这个键值对的值:做到for( var k in name ){ this[i].style[k] = name[k] };
注意要返回 this实例对象,便于链式编程
有两个参数 name value 设置单个样式
属性 样式操作模块的实现 (与css实现原理相似)
hasClass(className) 判断实例对象 有没有该类名,
实现:我们需要分别对this实例对象进行遍历,和他们的所有的类名进行遍历,
为了方便操作,需要把实例对象的类名转换为数组,并去除前后空格(遍历检查),然后使用__indexOf()__方法,若为-1,则返回false, >=0或者!=-1则为true;
即:var classNameValues = this(指的是实例对象中的dom).className.trim().split(" "); classNameValus.indexOf(className) >=0 --->true
可以使用some方法,(只要有一个满足条件直接放回true) 即:return this.toAArray().some(function(v){ return v.trim().split(" ")indexOf(className) >= 0 };
addClass(className) 添加类名
又需要分清况讨论了,若是没有类名:即classNameValues.length == 0,则直接添加 this.className = className;
若是有class,但是没有该类名,则需要追加;即:if( classNameValues.indexOf(className) == -1 ){this.className += " "+classNaame};__(注意要用空格分隔开)__;
若是已经有该类名了,则什么都不需要做(不能重复添加)
addClass可是直接用数组来实现简单一些:只要if( classNameValues.indexOf(className) == -1 ){ classNameValues.push(className) };this.className = classNameValues.join(" ");
removeClass(className) 删除类名
就是把指定的类名给删除掉,需要进行循环遍历所有类名数组 classNameValues,然后用splice来把指定的类名从数组中给截取掉;
即var index;(来记录查到指定类名的索引)
js while( (index= classNameValues.indexof(className))>=0 ){ classNameValues.splice(index,1);} this.className = classNameValues.join(" ");
还可以使用 map方法; 即:this.className = classNameValues.map(function(v){if(v != className){return v;}}).join(" "); 利用map方法产生一个新数组,简单一些
toggleClass(className) 切换类名
直接进行判断
if(this.hasClass(className)) { this.removeClass(className) }else{ this.addClas s(className) };
对上面的代码实现复用,减少代码冗余; 如果有该类名的话,直接删除,没有类名的话,就添加
attr(name,value) 对属性样式的设置 和css 原理一样:还是要分情况:
一个参数时,类型为字符串,获取属性值:用getAttribute(name);
一个参数时,类型为对象,数值多个属性值 ,循环遍历该对象用setAttribute(k,name[k])来进行设置
两个参数时,设置单个属性值,直接进行设置即可:setAttribute(name,value);
prop(name,value) 与上述情况一样,分类进行考虑,
但是注意的是。prop针对的是input标签这些单属性的值,值为布尔类型,例如disabled,checked,selected
设置和获取的时候不用setAttribute,getAttribute,直接进行赋值即可,this[name] = value;
注:如果对其进行赋值,例如disabled,不论赋值为true还是false,都不可编辑,除非移除该属性removeProp
removeAttr与removeProp,个人认为实现原理一样,都是使用removeAttribute
即:遍历this实例对象,然后进行 this.removeAttribute(name);
入口函数,即init方法中selector传入的数函数的情况
方法一:直接使用window.addEventListener("load",selector); 可以实现累加,开辟不同的入口函数,互不影响
方法二:利用函数的技巧:记录下来window.onload的值,进行判断如果是一个函数的话,则他执行,在执行selector传入的,否则的话,直接把selector赋值给window.onload
即:
var oldFn = windiw.onload ; if(typeof oldFn == "function"){ window.onlad = function(){ oldFn();selector(); }}else{ window.onload = selector(); }
方法三:利用数组的技巧:建立一个私有数组,var loads = []; 直接把一个个的selector给push到数组中去,
然后定义一个
window.onload = function(){ Guoqi.each(loads,function(){ this(); }) } // 把数组中的selector依次执行