国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 語言 > JavaScript > 正文

超贊的動手創(chuàng)建JavaScript框架的詳細(xì)教程

2024-05-06 16:22:28
字體:
供稿:網(wǎng)友

這篇文章主要介紹了動手創(chuàng)建JavaScript框架的詳細(xì)教程,包括DOM和各種屬性的調(diào)試等各個方面,超級推薦!需要的朋友可以參考下

覺得Mootools不可思議?想知道Dojo是如何實現(xiàn)的?對JQuery的技巧感到好奇?在這篇教程里,我們將探尋框架背后的秘密,然后試著自己動手建立一個你所喜愛的框架的簡易版本。

我們幾乎每天都在使用各種各樣的JavaScript框架。當(dāng)你剛?cè)腴T的時候,方便的DOM(文檔對象模型)操作讓你覺得JQuery這樣的東西非常棒。這是因為:首先,對于新手來說DOM太難理解了;當(dāng)然,對于一個API來說難以理解可不是什么好事。其次,瀏覽器間的兼容性問題非常令人困擾。

我們將元素包裝成對象是因為我們想要能夠為對象添加方法。

在這個教程里,我們將試著從頭實現(xiàn)這些框架之一。是的,這會很有趣,不過在你太過興奮前我要澄清幾點:

這不會是一個功能很完善的框架。的確,我們要寫很多東西,但它還算不上JQuery。可是我們將要做的事情會讓你體驗到在真正編寫框架的感覺。

我們不打算保證全方位的兼容性。我們將要編寫的框架能夠在 Internet Explorer 8+、Firefox 5+、Opera 10+、Chrome和Safari上工作。

我們的框架不會覆蓋到所有可能的功能。比如說,我們的append和preappend方法只有在你傳給它一個我們框架的實例時才能工作;我們不會用原生的DOM節(jié)點和節(jié)點列表。

另外:盡管在教程中我們不會為我們的框架編寫測試用例,但是我已經(jīng)在第一次開發(fā)它的時候做好了。你可以從 Github上獲取框架和測試用例的代碼。

第一步: 創(chuàng)建框架模板

我們將從一些包裝代碼開始,它將容納我們的整個框架。這是典型的立即函數(shù)(IIFE).

 

 
  1. window.dome = (function () { 
  2. function Dome (els) { 
  3. var dome = { 
  4. get: function (selector) { 
  5. }; 
  6. return dome; 
  7. }()); 

你可以看到,我們的框架叫做dome,因為它是一個基本的DOM框架。沒錯,基本(lame有“瘸子”、“不完整”的意思,dom加lame等于dome)的。

我們已經(jīng)有了一些東西。 首先,我們有了一個函數(shù);它將成為構(gòu)造框架的對象實例的構(gòu)造函數(shù);那些對象將會包含我們選擇和創(chuàng)建的元素。

然后,我們有了一個dome對象,它就是我們的框架對象;你可以看到它最終作為函數(shù)的返回值返回給了函數(shù)調(diào)用者(譯注:賦值給了window.dome)。這里還有一個空的get函數(shù),我們將用它從頁面里選取元素。那么,我們來填充代碼吧。

第二步: 獲取元素

dome的get函數(shù)只有一個參數(shù),但是它可以是很多東西。如果它一個string(字符串),我們將假定它是一個CSS(層疊樣式表)選擇器;不過我們也可能得到一個DOM節(jié)點或者DOM節(jié)點列表。

 

 
  1. get: function (selector) { 
  2. var els; 
  3. if (typeof selector === "string") { 
  4. els = document.querySelectorAll(selector); 
  5. else if (selector.length) { 
  6. els = selector; 
  7. else { 
  8. els = [selector]; 
  9. return new Dome(els); 

我們用document.querySelectorAll來簡單的選擇元素:當(dāng)然,這將限制我們的瀏覽器兼容性,不過對于這種情況還是可以接受的。如果selector不是string類型,我們將檢查它的length屬性。如果存在,我們就知道我們得到的是一個節(jié)點列表;否則,就是一個單獨的元素,我們將它放到一個數(shù)組里。這是因為我們要在下面向Dome傳遞一個數(shù)組。你可以看到,我們返回了一個新的Dome對象。讓我們回到Dome函數(shù)并且為它填充代碼。

第三步: 創(chuàng)建Dome實例

這是Dome函數(shù):

 

 
  1. function Dome (els) { 
  2. for(var i = 0; i < els.length; i++ ) { 
  3. this[i] = els[i]; 
  4. this.length = els.length; 

我強烈建議你去深入研究一些你喜歡的框架

這非常簡單:我們只是遍歷了els的所有元素,并且把它們存儲在一個以數(shù)字為索引的新對象里。然后我們添加了一個length屬性。

但是這有什么意義呢?為什么不直接返回元素?因為:我們將元素包裝成對象是因為我們想要能夠為對象添加方法;這些方法能夠讓我們遍歷這些元素。實際上這正是JQuery的解決方案的濃縮版。

我們的Dome對象已經(jīng)返回了,現(xiàn)在讓我們來為它的原型(prototype)添加一些方法。我會直接把那些方法寫在Dome函數(shù)下面。

第四步:添加幾個實用工具

要添加的第一批功能是些簡單的工具函數(shù)。由于Dome對象可能包含至少一個DOM元素,那么我們需要在幾乎每一個方法里面都遍歷所有元素;這樣,這些工具才會給力。

我們從一個map函數(shù)開始:

 

 
  1. Dome.prototype.map = function (callback) { 
  2. var results = [], i = 0; 
  3. for ( ; i < this.length; i++) { 
  4. results.push(callback.call(thisthis[i], i)); 
  5. return results; 
  6. }; 

當(dāng)然,這個map函數(shù)有一個入?yún)ⅲ粋€回調(diào)函數(shù)。我們遍歷Dome對象所有元素,收集回調(diào)函數(shù)的返回值到結(jié)果集中。注意我們是怎樣調(diào)用回調(diào)函數(shù)的:

 

 
  1. callback.call(thisthis[i], i)); 

通過這種方式,函數(shù)將在Dome實例的上下文中被調(diào)用,并且函數(shù)接收到兩個參數(shù):當(dāng)前元素和元素序號。

我們也想要一個foreach函數(shù)。事實上這很簡單:

 

 
  1. Dome.prototype.forEach(callback) { 
  2. this.map(callback); 
  3. return this
  4. }; 

由于map函數(shù)和foreach函數(shù)之間的不同僅僅是map需要返回些東西,我們可以僅僅將回調(diào)傳給this.map然后忽略返回的數(shù)組;代替返回的是,我們將返回this,來使我們的庫呈鏈?zhǔn)健oreach會被頻繁的調(diào)用,所以,注意當(dāng)一個函數(shù)的回調(diào)被返回,事實上,返回的是Dome實例。例如,下面的方法事實上就返回了Dome實例:

 

 
  1. Dome.prototype.someMethod1 = function (callback) { 
  2. this.forEach(callback); 
  3. return this
  4. }; 
  5. Dome.prototype.someMethod2 = function (callback) { 
  6. return this.forEach(callback); 
  7. }; 

還有一個:mapOne。很容易就知道這個函數(shù)是做什么的,但是真正的問題是,為什么需要它?這就需要一些我們稱之為"庫哲學(xué)"的東西了。

一個簡短的"哲學(xué)"闡釋

首先,對于一個初學(xué)者來說,DOM很讓人糾結(jié);它的API不完善。

如果構(gòu)建一個庫僅僅是寫代碼,那就不是什么難事。但是當(dāng)我開發(fā)這個庫時,我發(fā)現(xiàn)那些不完善的部分決定了一定數(shù)量的方法的實現(xiàn)方式。

很快,我們要去構(gòu)建一個返回被選擇元素的文本的text方法。如果Dome對象包含多個DOM節(jié)點(比如dome.get("li")),返回什么?如果你就像jQuery那樣($("li").text())很簡單的編寫,你將得到一個字符串,這個字符串是所有元素的文本的直接拼接。有用嗎?我認(rèn)為沒用,但是我不認(rèn)為沒有更好的辦法。

對于這個項目,我將以數(shù)組方式返回多個元素的文本,除非數(shù)組里只有一個元素,那么我僅僅返回一個文本字符串,而不是一個包含了一個元素的數(shù)組。我想你會經(jīng)常去獲取單個元素的文本,所以我們優(yōu)化了那種情況。但是,如果你想去獲取多個元素的文本,我們的返回你也會用著很爽。

回到代碼

那么,mapOne方法僅僅是簡單的運行map函數(shù),然后返回數(shù)組,或者一個數(shù)組里的元素。如果你仍然不確定這是如何有用,堅持一下,你就會看到!

 

 
  1. Dome.prototype.mapOne = function (callback) { 
  2. var m = this.map(callback); 
  3. return m.length > 1 ? m : m[0]; 
  4. }; 

第5步: 處理Text和HTML

接著,讓我們來添加文本方法。就像jQuery,我們可以傳遞一個string值,設(shè)置節(jié)點元素的text值,或者通過無參方法得到返回的text值。

 

 
  1. Dome.prototype.text = function (text) { 
  2. if (typeof text !== "undefined") { 
  3. return this.forEach(function (el) { 
  4. el.innerText = text; 
  5. }); 
  6. else { 
  7. return this.mapOne(function (el) { 
  8. return el.innerText; 
  9. }); 
  10. }; 

如你所料,當(dāng)我們設(shè)置(setting)或者得到(getting)value值時,需要檢查text的值。要注意的是如果justif(文本)方法不起作用,是因為text為空字符串是一個錯誤的值。

如果我們設(shè)置(setting)時,可是使用一個forEach 遍歷元素,設(shè)置它們的innerText屬性。如果我們得到(getting)時,返回元素的innerText屬性。在使用mapOne方法是要注意:如果我們正在處理多個元素,將返回一個數(shù)組;其他的則還是一個字符串。

如果html方法使用innerHTML屬性而不是innerText,它將會更優(yōu)雅的處理涉及text文本的事情。

 

 
  1. Dome.prototype.html = function (html) { 
  2. if (typeof html !== "undefined") { 
  3. this.forEach(function (el) { 
  4. el.innerHTML = html; 
  5. }); 
  6. return this
  7. else { 
  8. return this.mapOne(function (el) { 
  9. return el.innerHTML; 
  10. }); 
  11. }; 

就像我說過的:幾乎相同的。

第六步: 修改類

下一步,我們想對class進行操作,所以添加能addClass()和removeClass()。addClass()的參數(shù)是一個class名稱或者名稱的數(shù)組。為了實現(xiàn)動態(tài)參數(shù),我們需要對參數(shù)的類型進行判斷。如果參數(shù)是一個數(shù)組,那么遍歷這個數(shù)組,將元素添加上這些class名稱,如果參數(shù)是一個字符串,則直接加上這個class名稱。函數(shù)需要確保不將原來的class名稱弄亂。

 

 
  1. Dome.prototype.addClass = function (classes) { 
  2. var className = ""
  3. if (typeof classes !== "string") { 
  4. for (var i = 0; i < classes.length; i++) { 
  5. className += " " + classes[i]; 
  6. else { 
  7. className = " " + classes; 
  8. return this.forEach(function (el) { 
  9. el.className += className; 
  10. }); 
  11. }; 

很直觀吧?嘿嘿

現(xiàn)在,寫下removeClass(),同樣簡單。不過每次只允許刪除一個class名稱。

 

 
  1. Dome.prototype.removeClass = function (clazz) { 
  2. return this.forEach(function (el) { 
  3. var cs = el.className.split(" "), i; 
  4. while ( (i = cs.indexOf(clazz)) > -1) { 
  5. cs = cs.slice(0, i).concat(cs.slice(++i)); 
  6. el.className = cs.join(" "); 
  7. }); 
  8. }; 

對于每一個元素,我們都將el.className 分割成一個字符串?dāng)?shù)組。那么我們使用一個while循環(huán)連接,直到cs.indexOf(clazz)返回值大于-1。我們將得到的結(jié)果join成el.className。

第七步: 修復(fù)一個IE引起的BUG

我們處理的最糟瀏覽器是IE8.在這個小小的庫中,只有一個IE引起的BUG需要去修復(fù); 并且謝天謝地,修復(fù)它非常簡單.IE8不支持Array的方法indexOf;我們需要在removeClass方法中使用到它, 下面讓我們來完成它:

 

 
  1. if (typeof Array.prototype.indexOf !== "function") { 
  2. Array.prototype.indexOf = function (item) { 
  3. for(var i = 0; i < this.length; i++) { 
  4. if (this[i] === item) { 
  5. return i; 
  6. return -1; 
  7. }; 

它看上去非常簡單,并且它不是完整實現(xiàn)(不支持使用第二個參數(shù)),但是它能實現(xiàn)我們的目標(biāo).

第8步: 調(diào)整屬性

現(xiàn)在,我們想要一個attr函數(shù)。這將很容易,因為它幾乎和text方法或者h(yuǎn)tml方法是一樣的。像這些方法,我們都能夠設(shè)置和得到屬性:我們將設(shè)置一個屬性的名稱和值,同時只通過參數(shù)名來得到值。

 

 
  1. Dome.prototype.attr = function (attr, val) { 
  2. if (typeof val !== "undefined") { 
  3. return this.forEach(function(el) { 
  4. el.setAttribute(attr, val); 
  5. }); 
  6. else { 
  7. return this.mapOne(function (el) { 
  8. return el.getAttribute(attr); 
  9. }); 
  10. }; 

如果形參有一個值,我們將遍歷元素并通過元素的setAttribute方法設(shè)置屬性值。另外,我們將使用mapOne返回通過getAttribute方法得到參數(shù)。

第9步: 創(chuàng)建元素

像任何一個優(yōu)秀的框架一樣,我們也應(yīng)該能夠創(chuàng)建元素。當(dāng)然,在Demo實例中沒有一個好的方法,所以讓我們來把方法加入到demo工程中。

 

  1. var dome = { 
  2. // get method here 
  3. create: function (tagName, attrs) { 
  4. }; 

正如你所看到的:我們需要兩個形參:元素名,和一個參數(shù)對象。大多數(shù)的屬性通過我們的arrt方法被使用,但是tagName和attrs卻有特殊待遇。我們?yōu)閏lassName屬性使用addClass方法,為text屬性使用text方法。當(dāng)然,我們首先要創(chuàng)建元素,和Demo對象。下面就是所有的作用:

 

  1. create: function (tagName, attrs) { 
  2. var el = new Dome([document.createElement(tagName)]); 
  3. if (attrs) { 
  4. if (attrs.className) { 
  5. el.addClass(attrs.className); 
  6. delete attrs.className; 
  7. if (attrs.text) { 
  8. el.text(attrs.text); 
  9. delete attrs.text; 
  10. for (var key in attrs) { 
  11. if (attrs.hasOwnProperty(key)) { 
  12. el.attr(key, attrs[key]); 
  13. return el; 

如上,我們創(chuàng)建了元素,將他發(fā)送到新的Dmoe對象中。接著,我們處理所有屬性。注意:當(dāng)使用完className和text屬性后,我們不得不刪除他們。這將保證當(dāng)我們遍歷其他的鍵時,它們還能被使用。當(dāng)然,我們最終通過返回這個新的Demo對象。

我們創(chuàng)建了新的元素,我們想要將這些元素插入到DOM,對吧?

第10步:尾部添加(Appending)與頭部添加(Prepending)元素

下一步,我們來實現(xiàn)尾部添加與頭部添加方法。考慮到多種場景,實現(xiàn)這些方法可能有些棘手。下面是我們的想要達到的效果:

 

  1. dome1.append(dome2); 
  2. dome1.prepend(dome2); 

IE8對我們來說就是一奇葩。

尾部添加或頭部添加,包括以下幾種場景:

單個新元素添加至單個或多個已存在元素中

多個新元素添加至單個或多個已存在元素中

單個已存在元素添加至單個或多個已存在元素中

多個已存在元素添加至單個或多個已存在元素中

注意:這里的”新元素“表示還未加入DOM中節(jié)點元素,”已存在元素“指已存在于DOM中的節(jié)點元素。

現(xiàn)在讓我們一步步來實現(xiàn)之:

 

 
  1. Dome.prototype.append = function (els) { 
  2. this.forEach(function (parEl, i) { 
  3. els.forEach(function (childEl) { 
  4. }); 
  5. }); 
  6. }; 

假設(shè)參數(shù)els是一個DOM對象。一個功能完備的DOM庫應(yīng)該能處理節(jié)點(node)或節(jié)點序列(nodelist),但現(xiàn)在我們不作要求。首先遍歷需要被添加進的元素 (父元素),再在這個循環(huán)中遍歷將被添加的元素 (子元素)。

如果將一個子元素添加至多個父元素,需要克隆子元素(避免最后一次操作會移除上一次添加操作)。可是,沒必要在初次添加的時候就克隆,只需要在其它循環(huán)中克隆就可以了。因此處理如下:

 

 
  1. if (i > 0) { 
  2. childEl = childEl.cloneNode(true); 

變量i來自外層forEach循環(huán):它表示父級元素的序列號。第一個父元素添加的是子元素本身,而其他父元素添加的都是目標(biāo)子元素的克隆。因為作為參數(shù)傳入的子元素是未被克隆的,所以,當(dāng)將單個子元素添加至單個父元素時,所有的節(jié)點都是可響應(yīng)的。

最后,真正的添加元素操作:

 

 
  1. parEl.appendChild(childEl); 

因此,組合起來,我們得到以下實現(xiàn):

 

 
  1. Dome.prototype.append = function (els) { 
  2. return this.forEach(function (parEl, i) { 
  3. els.forEach(function (childEl) { 
  4. if (i > 0) { 
  5. childEl = childEl.cloneNode(true); 
  6. parEl.appendChild(childEl); 
  7. }); 
  8. }); 
  9. }; 

prepend方法

我們按照相同的邏輯實現(xiàn)prepend方法,其實也相當(dāng)簡單。

 

 
  1. Dome.prototype.prepend = function (els) { 
  2. return this.forEach(function (parEl, i) { 
  3. for (var j = els.length -1; j > -1; j--) { 
  4. childEl = (i > 0) ? els[j].cloneNode(true) : els[j]; 
  5. parEl.insertBefore(childEl, parEl.firstChild); 
  6. }); 
  7. }; 

不同點在于添加多個元素時,添加后的順序會被反轉(zhuǎn)。所以不能采用forEach循環(huán),而是用倒序的for循環(huán)代替。同樣的,在添加至非第一個父元素時需克隆目標(biāo)子元素。

第十一步: 刪除節(jié)點

對于我們最后一個節(jié)點的操作方法,從dom中刪除這些節(jié)點,很簡單,只需要:

 

 
  1. Dome.prototype.remove = function () { 
  2. return this.forEach(function (el) { 
  3. return el.parentNode.removeChild(el); 
  4. }); 
  5. }; 

只需要通過節(jié)點的迭代和在他們的父節(jié)點調(diào)用刪除子節(jié)點方法。比較好的是這個dom對象依然正常工作(感謝文檔對象模型吧)。我們可以在它上面使用我們想使用的方法,包括插入,預(yù)插回DOM,很漂亮,不是嗎?

第12步:事件處理

最后,卻是最重要的一環(huán),我們要寫幾個事件處理函數(shù)。

如你所知,IE8依然使用舊的IE事件,因此我們需要為此作檢測。同時,我們也要做好使用DOM 0 級事件的準(zhǔn)備。

查看下面的方法,我們稍后會討論:

 

 
  1. Dome.prototype.on = (function () { 
  2. if (document.addEventListener) { 
  3. return function (evt, fn) { 
  4. return this.forEach(function (el) { 
  5. el.addEventListener(evt, fn, false); 
  6. }); 
  7. }; 
  8. else if (document.attachEvent) { 
  9. return function (evt, fn) { 
  10. return this.forEach(function (el) { 
  11. el.attachEvent("on" + evt, fn); 
  12. }); 
  13. }; 
  14. else { 
  15. return function (evt, fn) { 
  16. return this.forEach(function (el) { 
  17. el["on" + evt] = fn; 
  18. }); 
  19. }; 
  20. }()); 

在這里,我們用到了立即執(zhí)行函數(shù)(IIFE),在函數(shù)內(nèi)我們做了特性檢測。如果document.addEventListener方法存在,我們就使用它;另外我們也檢測document.attachEvent,如果沒有就使用DOM 0級方法。請注意我們?nèi)绾螐牧⒓磮?zhí)行函數(shù)中返回最終函數(shù):其最后會被分配到Dome.prototype.on。在做特性檢測時,與每次運行函數(shù)時檢測相比,這樣的方式分配適合的方法更加方便。

事件解綁方法off與on方法類似:.

 

 
  1. Dome.prototype.off = (function () { 
  2. if (document.removeEventListener) { 
  3. return function (evt, fn) { 
  4. return this.forEach(function (el) { 
  5. el.removeEventListener(evt, fn, false); 
  6. }); 
  7. }; 
  8. else if (document.detachEvent) { 
  9. return function (evt, fn) { 
  10. return this.forEach(function (el) { 
  11. el.detachEvent("on" + evt, fn); 
  12. }); 
  13. }; 
  14. else { 
  15. return function (evt, fn) { 
  16. return this.forEach(function (el) { 
  17. el["on" + evt] = null
  18. }); 
  19. }; 
  20. }()); 

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表

圖片精選

主站蜘蛛池模板: 陵水| 平乐县| 潞西市| 大同市| 肥西县| 乌拉特后旗| 石嘴山市| 开封县| 诸城市| 崇义县| 当阳市| 鞍山市| 华亭县| 武城县| 鄂温| 平阳县| 东源县| 乐亭县| 莒南县| 五家渠市| 五河县| 姜堰市| 静安区| 吉水县| 长岛县| 墨玉县| 聊城市| 兰坪| 迭部县| 富宁县| 胶州市| 汾西县| 乐业县| 枣强县| 赤壁市| 遂川县| 太仓市| 阿合奇县| 道孚县| 城口县| 武穴市|