摘要:舉例需要注意的是,此時(shí)回調(diào)函數(shù)中的指向的就是數(shù)組或者對(duì)象的某一項(xiàng)。中提供的拷貝方法,默認(rèn)為淺拷貝,如果第一個(gè)參數(shù)為布爾值則表示深拷貝。
前言
平時(shí)開(kāi)發(fā)過(guò)程中經(jīng)常會(huì)用類(lèi)似each、map、forEach之類(lèi)的方法,Zepto本身也把這些方法掛載到$函數(shù)身上,作為靜態(tài)方法存在,既可以給Zepto的實(shí)例使用,也能給普通的js對(duì)象使用。今天我們主要針對(duì)其提供的這些api做一些源碼實(shí)現(xiàn)分析。
源碼倉(cāng)庫(kù)
原文鏈接
具體各個(gè)api如何使用可以參照英文文檔Zepto.js 中文文檔Zepto.js
1. $.camelCase該方法主要是將連字符轉(zhuǎn)化成駝峰命名法。例如可以將a-b-c這種形式轉(zhuǎn)換成aBC,當(dāng)然連字符的數(shù)量可以是多個(gè),a---b-----c => aBC,具體實(shí)現(xiàn)已經(jīng)在這些Zepto中實(shí)用的方法集說(shuō)過(guò)了,可以點(diǎn)擊查看。而其代碼也只是將camelize函數(shù)賦值給了$.camelCase
$.camelCase = camelize2. $.contains
$.contains(parent, node) ? boolean該方法主要用來(lái)檢測(cè)parent是否包含給定的node節(jié)點(diǎn)。如果parent和node為同一節(jié)點(diǎn),則返回false。
舉例
let oList = document.querySelector(".list") let oItem = document.querySelector(".item") let oTest = document.querySelector(".test") console.log($.contains(oList, oItem)) // true 父子節(jié)點(diǎn) console.log($.contains(oList, oList)) // false 同一節(jié)點(diǎn) console.log($.contains(oList, oTest)) // false 兄弟節(jié)點(diǎn)
源碼
$.contains = document.documentElement.contains ? function (parent, node) { // 防止parent和node傳相同的節(jié)點(diǎn),故先parent !== node // 接著就是調(diào)用原生的contains方法判斷了 return parent !== node && parent.contains(node) } : function (parent, node) { // 當(dāng)node節(jié)點(diǎn)存在,就把node的父節(jié)點(diǎn)賦值給node while (node && (node = node.parentNode)) // 如果node的父節(jié)點(diǎn)和parent相等就返回true,否則繼續(xù)向上查找 // 其實(shí)有一個(gè)疑問(wèn),為什么開(kāi)頭不先排查node === parent的情況呢 // 不然經(jīng)過(guò)循環(huán)最后卻得到false,非常的浪費(fèi) if (node === parent) return true return false }
用了document.documentElement.contains做判斷,如果瀏覽器支持該方法,就用node.contains重新包了一層得到一個(gè)函數(shù),差別就在于如果傳入的兩個(gè)節(jié)點(diǎn)相同,那么原生的node.contains返回true,具體用法可以查看MDN Node.contains但是$.contains返回false。
如果原生不支持就需要我們自己寫(xiě)一個(gè)方法了。主要邏輯還是通過(guò)一個(gè)while循環(huán),判斷傳入的node節(jié)點(diǎn)的父節(jié)點(diǎn)是否為parent,如果一個(gè)循環(huán)下來(lái),還不是最后才返回false
其實(shí)這里應(yīng)該是可以做一個(gè)優(yōu)化的,一進(jìn)來(lái)的時(shí)候就先判斷兩個(gè)節(jié)點(diǎn)是否為同一節(jié)點(diǎn),不是再進(jìn)行后續(xù)的判斷
3. $.each用來(lái)遍歷數(shù)組或者對(duì)象,類(lèi)似原生的forEach但是不同的是,可以中斷循環(huán)的執(zhí)行,并且服務(wù)對(duì)象不局限于數(shù)組。
舉例
let testArr = ["qianlongo", "fe", "juejin"] let testObj = { name: "qianlongo", sex: "boy" } $.each(testArr, function (i, val) { console.log(i, val) }) // 0 "qianlongo" // 1 "fe" // 2 "juejin" $.each(testObj, function (key, val) { console.log(key, val) }) // name qianlongo // sex boy
需要注意的是,此時(shí)回調(diào)函數(shù)中的this指向的就是數(shù)組或者對(duì)象的某一項(xiàng)。這樣主要是方便內(nèi)部的一些其他方法在遍歷dom節(jié)點(diǎn)的時(shí)候,this很方便地就指向了對(duì)應(yīng)的dom
源碼實(shí)現(xiàn)
$.each = function (elements, callback) { var i, key // 如果是類(lèi)數(shù)組就走這個(gè)if if (likeArray(elements)) { for (i = 0; i < elements.length; i++) // 可以看到用.call去執(zhí)行了callback,并且第一個(gè)參數(shù)是數(shù)組中的item // 如果用來(lái)遍歷dom,那么內(nèi)部的this,指的就是當(dāng)前這個(gè)元素本身 // 判斷callback執(zhí)行的結(jié)果,如果是false,就中斷遍歷 // 中斷遍歷這就是和原生forEach不同的地方 // 2017-8-16添加,原生的forEach內(nèi)部的this指向的是數(shù)組本身,但是這里指向的是數(shù)組的項(xiàng) // 2017-8-16添加,原生的forEach回調(diào)函數(shù)的參數(shù)是val, i...,這里反過(guò)來(lái) if (callback.call(elements[i], i, elements[i]) === false) return elements } else { // 否則回去走for in循環(huán),邏輯與上面差不多 for (key in elements) if (callback.call(elements[key], key, elements[key]) === false) return elements } return elements }
likeArray已經(jīng)在這些Zepto中實(shí)用的方法集說(shuō)過(guò)了,可以點(diǎn)擊查看。
4. $.extendZepto中提供的拷貝方法,默認(rèn)為淺拷貝,如果第一個(gè)參數(shù)為布爾值則表示深拷貝。
源碼實(shí)現(xiàn)
$.extend = function (target) { // 將第一個(gè)參數(shù)之外的參數(shù)變成一個(gè)數(shù)組 var deep, args = slice.call(arguments, 1) // 處理第一個(gè)參數(shù)是boolean值的情況,默認(rèn)是淺復(fù)制,深復(fù)制第一個(gè)參數(shù)傳true if (typeof target == "boolean") { deep = target target = args.shift() } // $.extend(true, {}, source1, source2, source3) // 有可能有多個(gè)source,遍歷調(diào)用內(nèi)部extend方法,實(shí)現(xiàn)復(fù)制 args.forEach(function (arg) { extend(target, arg, deep) }) return target }
可以看到首先對(duì)第一個(gè)參數(shù)是否為布爾值進(jìn)行判斷,有意思的是,只要是布爾值都表示深拷貝,你傳true或者false都是一個(gè)意思。接著就是對(duì)多個(gè)source參數(shù)進(jìn)行遍歷調(diào)用內(nèi)部方法extend。
接下來(lái)我們主要來(lái)看內(nèi)部方法extend。
function extend(target, source, deep) { // 對(duì)源對(duì)象source進(jìn)行for in遍歷 for (key in source) // 如果source[key]是純對(duì)象或者數(shù)組,并且指定為深復(fù)制 if (deep && (isPlainObject(source[key]) || isArray(source[key]))) { // 如果source[key]為純對(duì)象,但是target[key]不是純對(duì)象,則將目標(biāo)對(duì)象的key設(shè)置為空對(duì)象 if (isPlainObject(source[key]) && !isPlainObject(target[key])) target[key] = {} // 如果 如果source[key]為數(shù)組,但是target[key]不是數(shù)組,則將目標(biāo)對(duì)象的key設(shè)置為數(shù)組 if (isArray(source[key]) && !isArray(target[key])) target[key] = [] // 遞歸調(diào)用extend函數(shù) extend(target[key], source[key], deep) } // 淺復(fù)制或者source[key]不為undefined,便進(jìn)行賦值 else if (source[key] !== undefined) target[key] = source[key] }
整體實(shí)現(xiàn)其實(shí)還挺簡(jiǎn)單的,主要是遇到對(duì)象或者數(shù)組的時(shí)候,并且指定為深賦值,則遞歸調(diào)用extend本身,從而完成復(fù)制過(guò)程。
5. $.grep其實(shí)就是數(shù)組的原生方法filter,最終結(jié)果得到的是一個(gè)數(shù)組,并且只包含回調(diào)函數(shù)中返回 true 的數(shù)組項(xiàng)
直接看源碼實(shí)現(xiàn)
$.grep = function (elements, callback) { return filter.call(elements, callback) }
通過(guò)call形式去調(diào)用原生的數(shù)組方法 filter,過(guò)濾出符合條件的數(shù)據(jù)項(xiàng)。
6. $.inArray返回?cái)?shù)組中指定元素的索引值,沒(méi)有找到該元素則返回-1,fromIndex是一個(gè)可選的參數(shù),表示從哪個(gè)地方開(kāi)始往后進(jìn)行查找。
$.inArray(element, array, [fromIndex]) ? number
舉例
let testArr = [1, 2, 3, 4] console.log($.inArray(1, testArr)) // 0 console.log($.inArray(4, testArr)) // 3 console.log($.inArray(-10, testArr)) // -1 console.log($.inArray(1, testArr, 2)) // -1
源碼實(shí)現(xiàn)
$.inArray = function (elem, array, i) { return emptyArray.indexOf.call(array, elem, i) }
可見(jiàn)其內(nèi)部也是調(diào)用的原生indexOf方法。
7. $.isArray判斷obj是否為數(shù)組。
我們知道判斷一個(gè)值是否為對(duì)象,方式其實(shí)挺多的,比如下面的這幾種方式
// 1. es5中的isArray console.log(Array.isArray([])) // true // 2. 利用instanceof判斷 console.log([] instanceof Array) // true // 3. 最好的方式 toString console.log(Object.prototype.toString.call([]) === "[object Array]") // true
而Zepto中就是采用的第二種方式
var isArray = Array.isArray || function (object) { return object instanceof Array } $.isArray = isArray
如果支持isArray方法就用原生支持的,否則通過(guò)instanceof判斷,其實(shí)不太清楚為什么第二種方式,我們都知道這是有缺陷的,在有iframe場(chǎng)景下,就會(huì)出現(xiàn)判斷不準(zhǔn)確的情況.
8. $.isFunction判斷一個(gè)值是否為函數(shù)類(lèi)型
源碼實(shí)現(xiàn)
function isFunction(value) { return type(value) == "function" } $.isFunction = isFunction
主要還是通過(guò)內(nèi)部方法type來(lái)實(shí)現(xiàn)的,詳情可以點(diǎn)擊這些Zepto中實(shí)用的方法集查看。
9. $.isNumeric如果傳入的值為有限數(shù)值或一個(gè)字符串表示的數(shù)字,則返回ture。
舉例
$.isNumeric(null) // false $.isNumeric(undefined) // false $.isNumeric(true) // false $.isNumeric(false) // false $.isNumeric(0) // true $.isNumeric("0") // true $.isNumeric("") // false $.isNumeric(NaN) // false $.isNumeric(Infinity) // false $.isNumeric(-Infinity) // false
源碼
$.isNumeric = function (val) { var num = Number(val), type = typeof val return val != null && type != "boolean" && (type != "string" || val.length) && !isNaN(num) && isFinite(num) || false }
首先val經(jīng)過(guò)Number函數(shù)轉(zhuǎn)化,得到num,然后獲取val的類(lèi)型得到type。
我們來(lái)回顧一下Number(val)的轉(zhuǎn)化規(guī)則,這里截取一張圖。
看起來(lái)轉(zhuǎn)化規(guī)則非常復(fù)雜,但是有幾點(diǎn)我們可以確定,
如果輸入的是數(shù)字例如1,1.3那轉(zhuǎn)化后的還是數(shù)字,
如果輸入的是字符串?dāng)?shù)字類(lèi)型例如"123", "12.3"那轉(zhuǎn)化后的也是數(shù)字
如果輸入的是空字符串""那轉(zhuǎn)化后得到的是0
如果輸入是類(lèi)似字符串"123aaa",那轉(zhuǎn)化后得到的是NaN
所以再結(jié)合下面的判斷
通過(guò)val != null排除掉null和undefined
通過(guò)type != "boolean"排除掉,true和false
通過(guò)isFinite(num)限定必須是一個(gè)有限數(shù)值
通過(guò)!isNaN(num)排除掉被Number(val)轉(zhuǎn)化為NaN的值
(type != "string" || val.length), val為字符串,并且字符串的長(zhǎng)度大于0,排除""空字符串的場(chǎng)景。
以上各種判斷下來(lái)基本就滿足了這個(gè)函數(shù)原來(lái)的初衷要求。
9. $.isPlainObject10. $.isWindow測(cè)試對(duì)象是否是“純粹”的對(duì)象,這個(gè)對(duì)象是通過(guò) 對(duì)象常量("{}") 或者 new Object 創(chuàng)建的,如果是,則返回true
如果object參數(shù)為一個(gè)window對(duì)象,那么返回true
該兩個(gè)方法在這些Zepto中實(shí)用的方法集也聊過(guò)了,可以點(diǎn)擊查看一下。
11. $.map和原生的map比較相似,但是又有不同的地方,比如這里的map得到的記過(guò)有可能不是一一映射的,也就是可能得到比原來(lái)數(shù)組項(xiàng)數(shù)更多的數(shù)組,以及這里的map是可以用來(lái)遍歷對(duì)象的。
我們先看幾個(gè)例子
let testArr = [1, 2, null, undefined] let resultArr1 = $.map(testArr, (val, i) => { return val }) let resultArr2 = $.map(testArr, (val, i) => { return [val, [val]] }) // 再來(lái)看看原生的map的表現(xiàn) let resultArr3 = testArr.map((val, i) => { return val }) let resultArr4 = testArr.map((val, i) => { return [val, [val]] })
運(yùn)行結(jié)果如下
可以看出
resultArr1和resultArr3的區(qū)別是$.map把undefined和null給過(guò)濾掉了。
resultArr2與resultArr4的區(qū)別是$.map把回調(diào)函數(shù)的返回值給鋪平了。
接下來(lái)看看源碼是怎么實(shí)現(xiàn)的。
$.map = function (elements, callback) { var value, values = [], i, key // 如果是類(lèi)數(shù)組,則用for循環(huán) if (likeArray(elements)) for (i = 0; i < elements.length; i++) { value = callback(elements[i], i) // 如果callback的返回值不為null或者undefined,就push進(jìn)values if (value != null) values.push(value) } else // 對(duì)象走這個(gè)邏輯 for (key in elements) { value = callback(elements[key], key) if (value != null) values.push(value) } // 最后返回的是只能鋪平一層數(shù)組 return flatten(values) }
從源碼實(shí)現(xiàn)上可以看出因?yàn)?b>value != null以及flatten(values)造成了上述差異。
12. $.noop其實(shí)就是引用一個(gè)空的函數(shù),什么都不處理,那它到底有啥用呢?
比如。我們定義了幾個(gè)變量,他未來(lái)是作為函數(shù)使用的。
let doSomeThing = () => {} let doSomeThingElse = () => {}
如果直接這樣
let doSomeThing = $.noop let doSomeThingElse = $.noop
宿主環(huán)境就不必為我們創(chuàng)建多個(gè)匿名函數(shù)了。
其實(shí)還有一種可能用的不多的場(chǎng)景,在判斷一個(gè)變量是否是undefined的時(shí)候,可以用到。因?yàn)楹瘮?shù)沒(méi)有返回值,默認(rèn)返回undefined,也就是排除了那些老式瀏覽器undefined可以被修改的情況
if (xxx === $.noop()) { // xxx }13. $.parseJSON
原生JSON.parse方法的別名,接收的是一個(gè)字符串對(duì)象,返回一個(gè)對(duì)象。
源碼實(shí)現(xiàn)
$.parseJSON = JSON.parse14. $.trim
刪除字符串首尾的空白符,如果傳入null或undefined返回空字符串
源碼實(shí)現(xiàn)
$.trim = function (str) { return str == null ? "" : String.prototype.trim.call(str) }15. $.type
獲取JavaScript 對(duì)象的類(lèi)型。可能的類(lèi)型有: null undefined boolean number string function array date regexp object error.
該方法內(nèi)部實(shí)現(xiàn)其實(shí)就是內(nèi)部的type函數(shù),并且已經(jīng)在這些Zepto中實(shí)用的方法集聊過(guò)了,可以點(diǎn)擊查看。
$.type = type結(jié)尾
Zepto大部分工具方法或者說(shuō)靜態(tài)方法就是這些了,歡迎大家指正其中的錯(cuò)誤和問(wèn)題。
參考資料
讀zepto源碼之工具函數(shù)
MDN trim
MDN typeof
MDN isNaN
MDN Number
MDN Node.contains
文章記錄
原來(lái)你是這樣的jsonp(原理與具體實(shí)現(xiàn)細(xì)節(jié))
誰(shuí)說(shuō)你只是"會(huì)用"jQuery?
向zepto.js學(xué)習(xí)如何手動(dòng)觸發(fā)DOM事件
mouseenter與mouseover為何這般糾纏不清?
這些Zepto中實(shí)用的方法集
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/51191.html
摘要:舉例需要注意的是,此時(shí)回調(diào)函數(shù)中的指向的就是數(shù)組或者對(duì)象的某一項(xiàng)。中提供的拷貝方法,默認(rèn)為淺拷貝,如果第一個(gè)參數(shù)為布爾值則表示深拷貝。 前言 平時(shí)開(kāi)發(fā)過(guò)程中經(jīng)常會(huì)用類(lèi)似each、map、forEach之類(lèi)的方法,Zepto本身也把這些方法掛載到$函數(shù)身上,作為靜態(tài)方法存在,既可以給Zepto的實(shí)例使用,也能給普通的js對(duì)象使用。今天我們主要針對(duì)其提供的這些api做一些源碼實(shí)現(xiàn)分析。 源...
摘要:形如源代碼在的原型上添加了相關(guān)方法。類(lèi)似源代碼每個(gè)表單的和都通過(guò)編碼最后通過(guò)符號(hào)分割有了的基礎(chǔ),就是將相應(yīng)的和都通過(guò)編碼,然后用符號(hào)進(jìn)行分割,也就達(dá)到了我們要的結(jié)果。 前言 JavaScript最初的一個(gè)應(yīng)用場(chǎng)景就是分擔(dān)服務(wù)器處理表單的責(zé)任,打破處處依賴(lài)服務(wù)器的局面,這篇文章主要介紹zepto中form模塊關(guān)于表單處理的幾個(gè)方法,serialize、serializeArray、sub...
摘要:有一個(gè)模塊,專(zhuān)門(mén)用來(lái)做數(shù)據(jù)緩存,允許我們存放任何與相關(guān)的數(shù)據(jù)。在匹配元素上存儲(chǔ)任意相關(guān)數(shù)據(jù)或返回匹配的元素集合中的第一個(gè)元素的給定名稱(chēng)的數(shù)據(jù)存儲(chǔ)的值。確定元素是否有與之相關(guān)的數(shù)據(jù)。 前言 以前我們使用Zepto進(jìn)行開(kāi)發(fā)的時(shí)候,會(huì)把一些自定義的數(shù)據(jù)存到dom節(jié)點(diǎn)上,好處是非常直觀和便捷,但是也帶來(lái)了例如直接將數(shù)據(jù)暴露出來(lái)會(huì)出現(xiàn)安全問(wèn)題,數(shù)據(jù)以html自定義屬性標(biāo)簽存在,對(duì)于瀏覽器本身來(lái)說(shuō)...
閱讀 491·2019-08-30 15:44
閱讀 905·2019-08-30 10:55
閱讀 2739·2019-08-29 15:16
閱讀 947·2019-08-29 13:17
閱讀 2812·2019-08-26 13:27
閱讀 579·2019-08-26 11:53
閱讀 2127·2019-08-23 18:31
閱讀 1895·2019-08-23 18:23