javascript
自制javascript库
是否曾對(duì)Mootools的魔力感到驚奇?是否有想知道Dojo如何做到那樣的?是否對(duì)jQuery感到好奇?在這個(gè)教程中,我們將了解它們背后的東西并且動(dòng)手創(chuàng)建一個(gè)超級(jí)簡單的你最喜歡的庫。
我們其乎每天都在使用JavaScript庫。當(dāng)你剛?cè)腴T時(shí),利用jQuery是一件非常奇妙的事,主要是因?yàn)樗?/span>DOM操作。首先,DOM對(duì)于入門者來說可能是相對(duì)困難的事情;其次用它我們幾乎可以不用考慮跨瀏覽器兼容的問題。
在這個(gè)教程中,我們將試著從頭開始實(shí)現(xiàn)一個(gè)很簡單的庫。是的,它非常有意思,但是在你高興之前讓我申明幾點(diǎn):
·????????這不會(huì)是全功能的庫。我們有很多方法要寫,但是它不是jQuery。我們將會(huì)做足工作來讓你感受到在你創(chuàng)建一個(gè)庫時(shí)會(huì)遇到的各種問題。
·????????我們不會(huì)完全解決所有瀏覽器的兼容性問題。我們寫的代碼能支持IE8+,Firefox5+,Opera10+,Chrome和Safari。
·????????我們不會(huì)覆蓋使用我們庫的所有可能性。比如我們的append和prepend方法只在你傳入一個(gè)我們庫的實(shí)例時(shí)才有效,它們不支持原生的DOM節(jié)點(diǎn)或節(jié)點(diǎn)集合。
步驟1: 創(chuàng)建庫樣板文件Creating the Library Boilerplate
我們以一些封裝代碼開始,它將會(huì)包含我們整個(gè)庫。它就是你經(jīng)常用到的立即執(zhí)行函數(shù)表達(dá)式。
?
| 1 2 3 4 5 6 7 8 9 | window.dome = (function() { ????????functionDome (els) { ????????} ????????vardome = { ????????????????get: function(selector) { ????????????????} ????????}; ????????returndome; }()); |
如你所見,我們把我們的庫叫Dome,因?yàn)樗饕褪且粋€(gè)針對(duì)DOM的庫,是的,它很不完整。
到此我們做了兩件事。首先,我們定義了一個(gè)函數(shù),它最終會(huì)是實(shí)例化我們庫的構(gòu)造函數(shù),這些對(duì)象將會(huì)封裝我們選擇或創(chuàng)建的元素。
接下來我們創(chuàng)建了dome對(duì)象,它是我們實(shí)際的庫對(duì)象;你能看到,它在最后被返回。它有一個(gè)空的get函數(shù),我們將用它來從頁面中選擇元素。所以,讓我們現(xiàn)在來填充它的代碼。
步驟2: 獲取元素
dome.get函數(shù)傳入一個(gè)參數(shù),但是它可以有好幾種情況。如果它是一個(gè)字符串,我們假定它是一個(gè)CSS選擇器;但是我們也可以傳入單個(gè)DOM節(jié)點(diǎn)或是一個(gè)NodeList。
?
| 1 2 3 4 5 6 7 8 9 10 11 | get: function(selector) { ????????varels; ????????if(typeofselector === "string") { ????????els = document.querySelectorAll(selector); ????????} elseif(selector.length) { ????????????????els = selector; ????????} else{ ????????????????els = [selector]; ????????} ????????returnnewDome(els); } |
我們使用document.querySelectorAll來簡化元素的查找:當(dāng)然這有瀏覽器兼容性問題,但是對(duì)于我們的例子來說它是ok的。如果selector不是字符串,我們將檢查它的length屬性。如果它存在,我們就知道它是一個(gè)NodeList;否則它是單個(gè)元素然后我們將它放到一個(gè)數(shù)組中。這就是我們下面需要將調(diào)用Dome的結(jié)果傳給一個(gè)數(shù)組的原因;你可以看到我們返回一個(gè)新的Dome對(duì)象。所以讓我們回頭看看Dome函數(shù)并填充它。
步驟3: 創(chuàng)建Dome實(shí)例
下面是Dome函數(shù):
?
| 1 2 3 4 5 6 | functionDome (els) { ????????for(vari = 0; i < els.length; i++ ) { ????????????????this[i] = els[i]; ????????} ????????this.length = els.length; } |
它確實(shí)很簡單:我們只是遍歷我們選擇的元素并把它們附到帶有數(shù)字索引的新對(duì)象中。然后我們添加一個(gè)length屬性。
但是這的關(guān)鍵是什么呢?為什么不直接返回元素?我們將元素封裝到一個(gè)對(duì)象因?yàn)槲覀兿霝檫@個(gè)對(duì)象創(chuàng)建方法;這些方法可以讓我們與這些元素交互。這實(shí)際上就是jQuery采用的方法的簡化版本。
所以,我們返回了Dome對(duì)象,讓我們?cè)谒脑蜕咸砑右恍┓椒āN野堰@些方法直接寫在Dome函數(shù)中。
步驟4: 添加一些常用工具函數(shù)
我們要寫的第一個(gè)方法是一個(gè)簡單的工具函數(shù)。因?yàn)槲覀兊?/span>Dome對(duì)象可以封裝多個(gè)DOM元素,幾乎每個(gè)方法都需要遍歷每個(gè)元素;所以,這些工具函數(shù)會(huì)非常便利。
讓我們以一個(gè)map函數(shù)開始:
?
| 1 2 3 4 5 6 7 | Dome.prototype.map = function(callback) { ????????varresults = [], i = 0; ????????for( ; i < this.length; i++) { ????????????????results.push(callback.call(this, this[i], i)); ????????} ????????returnresults; }; |
當(dāng)然,map函數(shù)傳入單個(gè)參數(shù),一個(gè)回調(diào)函數(shù)。我們遍歷數(shù)組中的每一項(xiàng),收集回調(diào)函數(shù)返回的所有內(nèi)容放到results數(shù)組中。注意我們?nèi)绾握{(diào)用回調(diào)函數(shù):
?
| 1 | callback.call(this, this[i], i)); |
這樣函數(shù)就會(huì)在我們的Dome實(shí)例的上下文中被調(diào)用,它接受兩個(gè)參數(shù):當(dāng)前元素,以及索引號(hào)。
我們也想要一個(gè)forEach函數(shù)。它確實(shí)非常簡單:
?
| 1 2 3 4 | Dome.prototype.forEach(callback) { ????????this.map(callback); ????????returnthis; }; |
map和forEach間的唯一區(qū)別是map需要返回一些東西,因此我們也可以只傳入我們的回調(diào)函數(shù)給this.map并忽略返回的數(shù)組,我們將返回this來使得我們的庫支持鏈?zhǔn)讲僮鳌N覀儗⒔?jīng)常使用forEach。所以,注意當(dāng)返回我們的this.forEach對(duì)函數(shù)的調(diào)用時(shí),我們事實(shí)上是返回了this。例如,下面的方法實(shí)際上返回相同的東西:
?
| 1 2 3 4 5 6 7 | Dome.prototype.someMethod1 = function(callback) { ????????this.forEach(callback); ????????returnthis; }; Dome.prototype.someMethod2 = function(callback) { ????????returnthis.forEach(callback); }; |
另外:mapOne。很容易看出這個(gè)函數(shù)是干什么的,但是問題是為什么我們需要它?它需要一些你可以叫做“庫哲學(xué)”的東西來解釋。www.thy818.com
一個(gè)簡單的“哲學(xué)的”迂回
如果創(chuàng)建一個(gè)庫只是寫代碼,那就不是什么難的工作了。但是我正在做這個(gè)項(xiàng)目,我發(fā)現(xiàn)困難的部分是決定一些方法應(yīng)該如何工作。
很快,我們將建一個(gè)text方法,它返回我們選擇元素的文本。如果我們的Dome對(duì)象封裝幾個(gè)DOM節(jié)點(diǎn)(如dome.get("li")),它會(huì)返回什么呢?如果你在jQuery做類似的事情($("li").text()),你將會(huì)得到一個(gè)所有元素的文本拼起來的字符串。它有用嗎?我認(rèn)為沒用,但是我不知道更好的返回是什么。
在這個(gè)項(xiàng)目中,我將以數(shù)組形式返回多個(gè)元素的文本,除非數(shù)組中只有一個(gè)元素,那我們就返回一個(gè)文本字符串,而不是只有一個(gè)元素的數(shù)組。我想你最常用的是獲取單個(gè)元素的文本,所以我們對(duì)這個(gè)情況進(jìn)行優(yōu)化。然而,如果你獲取多個(gè)元素的文本,我們也會(huì)返回一些你能操作的東西。
回到代碼
所以,mapOne方法只是簡單的運(yùn)行map,然后要么返回?cái)?shù)組,要么返回單元素?cái)?shù)組中的元素。如果你還是不確定這有什么用,等一會(huì)你會(huì)發(fā)現(xiàn)的!
?
| 1 2 3 4 | Dome.prototype.mapOne = function(callback) { ????????varm = this.map(callback); ????????returnm.length > 1 ? m : m[0]; }; |
步驟5: 處理文本和HTML
接下來,讓我們添加text方法。就像jQuery一樣,我們可以給它傳入一個(gè)字符串并設(shè)置元素的文本,或不傳參數(shù)來獲取元素的文本。
?
| 1 2 3 4 5 6 7 8 9 10 11 | Dome.prototype.text = function(text) { ????????if(typeoftext !== "undefined") { ????????????????returnthis.forEach(function(el) { ????????????????????????el.innerText = text; ????????????????}); ????????} else{ ????????????????returnthis.mapOne(function(el) { ????????????????????????returnel.innerText; ????????????????}); ????????} }; |
?
| 1 2 3 4 5 6 7 8 9 10 11 | Dome.prototype.text = function(text) { ????????if(typeoftext !== "undefined") { ????????????????returnthis.forEach(function(el) { ????????????????????????el.innerText = text; ????????????????}); ????????} else{ ????????????????returnthis.mapOne(function(el) { ????????????????????????returnel.innerText; ????????????????}); ????????} }; |
你可能也想到了,我們需要檢查text的值來看它是要設(shè)置還是要獲取。注意如果只是用if(text)會(huì)有問題,因?yàn)榭兆址畷?huì)被判斷為false。
如果我們?cè)谠O(shè)置值,我們將對(duì)元素調(diào)用forEach并且設(shè)置它們的innerText屬性為text。如果我們要獲取,我們將返回元素的 innerText屬性。注意我們使用mapOne方法:如果我們?cè)谔幚矶鄠€(gè)元素,它將返回一個(gè)數(shù)組,否則它將就是一個(gè)字符串。
html方法幾乎與text一樣,除了它使用innerHTML屬性而不是innerText。
?
| 1 2 3 4 5 6 7 8 9 10 11 12 | Dome.prototype.html = function(html) { ????????if(typeofhtml !== "undefined") { ????????????????this.forEach(function(el) { ????????????????????????el.innerHTML = html; ????????????????}); ????????????????returnthis; ????????} else{ ????????????????returnthis.mapOne(function(el) { ????????????????????????returnel.innerHTML; ????????????????}); ????????} }; |
就像我說的:幾乎完全一樣。
步驟6: 調(diào)整樣式
再接下來,我們希望能添加和刪除樣式,因此讓我們來寫一個(gè)addClass和removeClass方法。
我們的addClass方法將接收一個(gè)字符串或是樣式名稱的數(shù)組。為了做到這點(diǎn),我們需要檢查參數(shù)的類型。如果是數(shù)組,我們將遍歷它并創(chuàng)建一個(gè)樣式名的字符串。否則,我們就簡單的在樣式名前加一個(gè)空格,這樣它就不會(huì)和元素已有的樣式混在一些。然后我們遍歷元素并且將新的樣式附加到className屬性后面。www.qw518.com
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | Dome.prototype.addClass = function(classes) { ????????varclassName = ""; ????????if(typeofclasses !== "string") { ????????????????for(vari = 0; i < classes.length; i++) { ????????????????????????className += " "+ classes[i]; ????????????????} ????????} else{ ????????????????className = " "+ classes; ????????} ????????returnthis.forEach(function(el) { ????????????????el.className += className; ????????}); }; |
很直接,對(duì)嗎?
那如何刪除樣式呢?為了保持簡單,我們只允許一次刪除一個(gè)樣式。
?
| 1 2 3 4 5 6 7 8 9 | Dome.prototype.removeClass = function(clazz) { ????????returnthis.forEach(function(el) { ????????????????varcs = el.className.split(" "), i; ????????????????while( (i = cs.indexOf(clazz)) > -1) { ????????????????????????cs = cs.slice(0, i).concat(cs.slice(++i)); ????????????????} ????????????????el.className = cs.join(" "); ????????}); }; |
對(duì)每個(gè)元素,我們將el.className分隔成一個(gè)數(shù)組。然后,我們使用一個(gè)while循環(huán)來剔除我們傳入的樣式,直到cs.indexOf(clazz)返回-1。我們這樣做是為了處理同樣的樣式在一個(gè)元素中出現(xiàn)的不止一次的特殊情況:我們必須保證它真的被刪除了。一旦我們確保刪除每個(gè)樣式的實(shí)例,我們用空格連接數(shù)組的每一項(xiàng)并把它設(shè)置到el.className。
步驟7: 修正一個(gè)IE的Bug
我們正在處理的最糟糕的瀏覽器是IE8。在我們的小小的庫中,只有一個(gè)IE bug需要我們處理,很幸運(yùn)它很簡單。IE8不支持Array的indexOf方法;我們?cè)?/span>removeClass中使用到它,所以讓我們修復(fù)它:
?
| 1 2 3 4 5 6 7 8 9 10 | if(typeofArray.prototype.indexOf !== "function") { ????????Array.prototype.indexOf = function(item) { ????????????????for(vari = 0; i < this.length; i++) { ????????????????????????if(this[i] === item) { ????????????????????????????????returni; ????????????????????????} ????????????????} ????????????????return-1; ????????}; } |
它非常簡單,并且這不是一個(gè)完全的實(shí)現(xiàn)(不支持第二個(gè)參數(shù)),但是能達(dá)到我們的目的。
步驟8: 調(diào)節(jié)屬性
現(xiàn)在,我們想要一個(gè)attr函數(shù)。這很容易,因?yàn)樗c我們的text或html方法非常類似。像那些方法一樣,我們能夠獲取或設(shè)置屬性值:我們可以傳入元素名和值來設(shè)置,也可以只傳入屬性名來獲取。
?
| 1 2 3 4 5 6 7 8 9 10 11 | Dome.prototype.attr = function(attr, val) { ????????if(typeofval !== "undefined") { ????????????????returnthis.forEach(function(el) { ????????????????????????el.setAttribute(attr, val); ????????????????}); ????????} else{ ????????????????returnthis.mapOne(function(el) { ????????????????????????returnel.getAttribute(attr); ????????????????}); ????????} }; |
如果val有一個(gè)值,我們將遍歷這些元素并且將選擇的屬性設(shè)置為這個(gè)值,使用元素的setAttribute方法。否則,我們使用mapOne通過getAttribute方法來返回屬性值。
步驟9: 創(chuàng)建元素
像很多好的庫一樣,我們應(yīng)該能夠創(chuàng)建新的元素。當(dāng)然它作為一個(gè)Dome實(shí)例的一個(gè)方法不是很好,所以讓我們直接把它掛到dome對(duì)象上去。www.meysky.com?
?
| 1 2 3 4 5 | vardome = { ????????// get method here ????????create: function(tagName, attrs) { ????????} }; |
你已經(jīng)看到,我們使用兩個(gè)參數(shù):元素的名字,和屬性值對(duì)象。大部分屬性能過attr方法賦值,但是兩種方法可以做特殊處理。我們使用addClass方法操作className屬性,以及text方法操作text屬性。當(dāng)然,我們首先需要?jiǎng)?chuàng)建元素和Dome對(duì)象。下面是整個(gè)操作的代碼:
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | create: function(tagName, attrs) { ????????varel = newDome([document.createElement(tagName)]); ????????????????if(attrs) { ????????????????????????if(attrs.className) { ????????????????????????????????el.addClass(attrs.className); ????????????????????????????????deleteattrs.className; ????????????????????????} ????????????????if(attrs.text) { ????????????????????????el.text(attrs.text); ????????????????????????deleteattrs.text; ????????????????} ????????????????for(varkey inattrs) { ????????????????????????if(attrs.hasOwnProperty(key)) { ????????????????????????????????el.attr(key, attrs[key]); ????????????????????????} ????????????????} ????????} ????????returnel; } |
我們創(chuàng)建元素并將它傳給一個(gè)新的Dome對(duì)象。然后中我們處理屬性。注意在操作完它們后我們必須刪除className和text屬性。這樣可以避免當(dāng)我們?cè)?/span>attrs中遍歷剩下的key值時(shí)被應(yīng)用為屬性。當(dāng)然我們最后要返回這個(gè)新建的Dome對(duì)象。
但是現(xiàn)在只是創(chuàng)建了新的元素,我們希望把它插入到DOM中對(duì)嗎?
步驟10: 附加元素
下一步,我們將寫append和prepend方法。這些確實(shí)是有點(diǎn)難搞的函數(shù),主要是因?yàn)橛泻芏喾N使用情況。以下是我們希望能做到的:www.thy818.com?
?
| 1 2 | dome1.append(dome2); dome1.prepend(dome2); |
使用情況如下:我們可能想要append或prepend
·????????一個(gè)新的元素到一個(gè)或多個(gè)已存在的元素
·????????多個(gè)新元素到一個(gè)或多個(gè)已存在的元素
·????????一個(gè)已存在的元素到一個(gè)或多個(gè)已存在的元素
·????????多個(gè)已存在的元素到一個(gè)或多個(gè)已存在的元素
注意:我使用“新”來表示元素還沒有在DOM中;已存在的元素是已經(jīng)在DOM中有的。
讓我們一步一步來:
?
| 1 2 3 4 5 6 | Dome.prototype.append = function(els) { ????????this.forEach(function(parEl, i) { ????????????????els.forEach(function(childEl) { ????????????????}); ????????}); }; |
我們期望els參數(shù)是一個(gè)Dome對(duì)象。一個(gè)完整的DOM庫可以接受一個(gè)節(jié)點(diǎn)或nodelist作為參數(shù),但是我們暫時(shí)不這樣做。我們必須遍歷我們每一個(gè)元素,并且在它里面,我們還要遍歷每個(gè)我們需要append的元素。
如果我們將els到多個(gè)元素,我們需要克隆它們。然而,我們不想在他們第一次被附加的時(shí)候克隆節(jié)點(diǎn),而時(shí)隨后再說。所以我們這樣:
?
| 1 2 3 | if(i > 0) { ????????childEl = childEl.cloneNode(true); } |
這個(gè)i來自外層的forEach循環(huán):它是當(dāng)前父元素的索引。如果我們不是附加到第一個(gè)父元素,我們將克隆節(jié)點(diǎn)。這樣,真正的節(jié)點(diǎn)將會(huì)放到第一個(gè)父節(jié)點(diǎn)中,其它父節(jié)點(diǎn)將獲得一個(gè)拷貝。這樣很好用,因?yàn)閭魅氲?/span>Dome對(duì)象將只會(huì)擁有原始的節(jié)點(diǎn)。所以如果我們只是附加單個(gè)元素到單個(gè)元素,使用的所有節(jié)點(diǎn)都將是各自Dome對(duì)象的一部分。
最后,我們終于可以附加元素:www.qw518.com
?
| 1 | parEl.appendChild(childEl); |
所以,匯總起來是這樣
?
| 1 2 3 4 5 6 7 8 9 10 | Dome.prototype.append = function(els) { ????????returnthis.forEach(function(parEl, i) { ????????????????els.forEach(function(childEl) { ????????????????????????if(i > 0) { ????????????????????????????????childEl = childEl.cloneNode(true); ????????????????????????} ????????????????????????parEl.appendChild(childEl); ????????????????}); ????????}); }; |
prepend方法
我們想要prepend方法也滿足同樣的情況,所以這個(gè)方法非常類似:
?
| 1 2 3 4 5 6 7 8 | Dome.prototype.prepend = function(els) { ????????returnthis.forEach(function(parEl, i) { ????????????????for(varj = els.length -1; j > -1; j--) { ????????????????????????childEl = (i > 0) ? els[j].cloneNode(true) : els[j]; ????????????????????????parEl.insertBefore(childEl, parEl.firstChild); ????????????????} ????????}); }; |
當(dāng)prepend時(shí)所不同的是如果你順次prepend一系列元素到另外一個(gè)元素時(shí),它們是倒序的。因?yàn)槲覀儾荒芊聪?/span>forEach,我將使用for循環(huán)反向遍歷。同樣,我們將克隆節(jié)點(diǎn)如果它不是我們第一個(gè)要附件到的父節(jié)點(diǎn)。
步驟11: 移除節(jié)點(diǎn)
對(duì)于我們最后一個(gè)節(jié)點(diǎn)處理方法,我們想要從DOM中刪除節(jié)點(diǎn)。其實(shí)很簡單:
?
| 1 2 3 4 5 | Dome.prototype.remove = function() { ????????returnthis.forEach(function(el) { ????????????????returnel.parentNode.removeChild(el); ????????}); }; |
就是遍歷節(jié)點(diǎn)并在每個(gè)元素的parentNode上調(diào)用removeChild方法。這里漂亮的地方在于這個(gè)Dome對(duì)象還將正常工作;我們可以在它上面使用任何方法,包括重新放回到DOM中去。www.meysky.com
步驟12: 處理事件
最后,但是肯定不是用得最少的,我們將寫一些函數(shù)處理事件。你可以知道,IE8使用老式的IE事件,所以我們需要檢查它。同時(shí),我們將拋出DOM 0事件,就因?yàn)槲覀兛梢浴?/span>
簽出方法,然后中我們將討論它:
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Dome.prototype.on = (function() { ????????if(document.addEventListener) { ????????????????returnfunction(evt, fn) { ????????????????????????returnthis.forEach(function(el) { ????????????????????????????????el.addEventListener(evt, fn, false); ????????????????????????}); ????????????????}; ????????} elseif(document.attachEvent)? { ????????????????returnfunction(evt, fn) { ????????????????????????returnthis.forEach(function(el) { ????????????????????????????????el.attachEvent("on"+ evt, fn); ????????????????????????}); ????????????????}; ????????} else{ ????????????????returnfunction(evt, fn) { ????????????????????????returnthis.forEach(function(el) { ????????????????????????????????el["on"+ evt] = fn; ????????????????????????}); ????????????????}; ????????} }()); |
在這,我們使用了一個(gè)立即執(zhí)行函數(shù)表達(dá)式,在函數(shù)里面我們做了特征檢查。如果document.addEventListener存在,我們將使用它;否則我們檢查document.attachEvent或者求助于DOM 0事件。注意我們?nèi)绾畏祷刈詈蟮暮瘮?shù):它將在結(jié)束時(shí)被賦給Dome.prototype.on。當(dāng)做特征檢測時(shí),非常方便地像這樣賦給合適的函數(shù),而不是每次函數(shù)運(yùn)行時(shí)都得檢查一次。www.thy818.com
off函數(shù)用于卸載事件,它與前面非常類似。
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Dome.prototype.off = (function() { ????????if(document.removeEventListener) { ????????????????returnfunction(evt, fn) { ????????????????????????returnthis.forEach(function(el) { ????????????????????????????????el.removeEventListener(evt, fn, false); ????????????????????????}); ????????????????}; ????????} elseif(document.detachEvent)? { ????????????????returnfunction(evt, fn) { ????????????????????????returnthis.forEach(function(el) { ????????????????????????????????el.detachEvent("on"+ evt, fn); ????????????????????????}); ????????????????}; ????????} else{ ????????????????returnfunction(evt, fn) { ????????????????????????returnthis.forEach(function(el) { ????????????????????????????????el["on"+ evt] = null; ????????????????????????}); ????????????????}; ????????} }()); |
就是這樣!
我希望你能試一試我們的小小的庫,并且能稍稍擴(kuò)展一點(diǎn)點(diǎn)。就像我之前是提到的一樣,我把它放到Github上了。可以免費(fèi)fork,玩一玩,并且發(fā)送一個(gè)pull請(qǐng)求。
讓我再申明一下,這個(gè)教程的目的不是說建議你總是要寫一個(gè)自己的庫。
有專業(yè)的團(tuán)隊(duì)在做一個(gè)龐大的,穩(wěn)定的越來越好的庫。這里我們只是想讓大家看看一個(gè)庫內(nèi)部是什么樣子的,希望你能在這學(xué)到一些東西。
?
總結(jié)
以上是生活随笔為你收集整理的自制javascript库的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: javaScript call 函数的用
- 下一篇: javascript判断ie浏览器