平時開發過程當中常常會用相似
each
、map
、forEach
之類的方法,Zepto自己也把這些方法掛載到$
函數身上,做爲靜態方法存在,既能夠給Zepto的實例使用,也能給普通的js對象使用。今天咱們主要針對其提供的這些api作一些源碼實現分析。javascript
具體各個api如何使用能夠參照英文文檔Zepto.js 中文文檔Zepto.jshtml
該方法主要是將連字符轉化成駝峯命名法。例如能夠將
a-b-c
這種形式轉換成aBC
,固然連字符的數量能夠是多個,a---b-----c
=>aBC
,具體實現已經在這些Zepto中實用的方法集說過了,能夠點擊查看。而其代碼也只是將camelize
函數賦值給了$.camelCasejava
$.camelCase = camelize複製代碼
$.contains(parent, node) ⇒ boolean
該方法主要用來檢測parent是否包含給定的node節點。若是parent和node爲同一節點,則返回false。node
舉例git
<ul class="list">
<li class="item">1</li>
<li>2</li>
</ul>
<div class="test"></div>複製代碼
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 兄弟節點複製代碼
源碼github
$.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
。json
若是原生不支持就須要咱們本身寫一個方法了。主要邏輯仍是經過一個while
循環,判斷傳入的node
節點的父節點是否爲parent
,若是一個循環下來,還不是最後才返回false
api
其實這裏應該是能夠作一個優化的,一進來的時候就先判斷兩個節點是否爲同一節點,不是再進行後續的判斷數組
用來遍歷數組或者對象,相似原生的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中實用的方法集說過了,能夠點擊查看。
Zepto中提供的拷貝方法,默認爲淺拷貝,若是第一個參數爲布爾值則表示深拷貝。
源碼實現
$.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
自己,從而完成複製過程。
其實就是數組的原生方法
filter
,最終結果獲得的是一個數組,而且只包含回調函數中返回true
的數組項
直接看源碼實現
$.grep = function (elements, callback) {
return filter.call(elements, callback)
}複製代碼
經過call
形式去調用原生的數組方法 filter
,過濾出符合條件的數據項。
返回數組中指定元素的索引值,沒有找到該元素則返回-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
方法。
判斷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場景下,就會出現判斷不準確的狀況.
判斷一個值是否爲函數類型
源碼實現
function isFunction(value) {
return type(value) == "function"
}
$.isFunction = isFunction複製代碼
主要仍是經過內部方法type
來實現的,詳情能夠點擊這些Zepto中實用的方法集查看。
若是傳入的值爲有限數值或一個字符串表示的數字,則返回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,排除''
空字符串的場景。
以上各類判斷下來基本就知足了這個函數原來的初衷要求。
測試對象是不是「純粹」的對象,這個對象是經過 對象常量("{}") 或者 new Object 建立的,若是是,則返回true
若是object參數爲一個window對象,那麼返回true
該兩個方法在這些Zepto中實用的方法集也聊過了,能夠點擊查看一下。
和原生的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)
形成了上述差別。
其實就是引用一個空的函數,什麼都不處理,那它到底有啥用呢?
好比。咱們定義了幾個變量,他將來是做爲函數使用的。
let doSomeThing = () => {}
let doSomeThingElse = () => {}複製代碼
若是直接這樣
let doSomeThing = $.noop
let doSomeThingElse = $.noop複製代碼
宿主環境就沒必要爲咱們建立多個匿名函數了。
其實還有一種可能用的很少的場景,在判斷一個變量是不是undefined
的時候,能夠用到。由於函數沒有返回值,默認返回undefined
,也就是排除了那些老式瀏覽器undefined能夠被修改的狀況
if (xxx === $.noop()) {
// xxx
}複製代碼
原生JSON.parse方法的別名,接收的是一個字符串對象,返回一個對象。
源碼實現
$.parseJSON = JSON.parse複製代碼
刪除字符串首尾的空白符,若是傳入
null
或undefined
返回空字符串
源碼實現
$.trim = function (str) {
return str == null ? "" : String.prototype.trim.call(str)
}複製代碼
獲取JavaScript 對象的類型。可能的類型有: null undefined boolean number string function array date regexp object error.
該方法內部實現其實就是內部的type
函數,而且已經在這些Zepto中實用的方法集聊過了,能夠點擊查看。
$.type = type複製代碼
Zepto大部分工具方法或者說靜態方法就是這些了,歡迎你們指正其中的錯誤和問題。
參考資料
文章記錄