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