摘要:前言這是源碼分析系列文章的第三篇,前面兩篇文章源碼分析一源碼分析二分別分析了中的一些重要函數,也給出了簡化的實現,為理解其內部機理和執行方式提供了便利。官方也對其進行了說明。
前言
這是Lodash源碼分析系列文章的第三篇,前面兩篇文章(Lodash 源碼分析(一)“Function” Methods、Lodash 源碼分析(二)“Function” Methods)分別分析了Lodash "Function" 中的一些重要函數,也給出了簡化的實現,為理解其內部機理和執行方式提供了便利。這篇文章將專注于Array,Array是Lodash中非常重要的內容,我們將分析其代碼實現以及同類似庫中的實現對比。
_.head_.head函數其實很簡單,返回一個數組的第一個元素,完全可以在兩三行代碼中實現??梢钥吹絃odash中是這么實現的:
function head(array) { return (array && array.length) ? array[0] : undefined; }
Lodash進行了簡單的判斷,然后返回了第一個元素。這么簡單的函數其實沒有什么好說的,但我拿出來說是想介紹另一個庫Ramda.js的實現:
module.exports = nth(0);
它是用nth函數實現該功能的,那么這個函數式怎么樣的呢?
module.exports = _curry2(function nth(offset, list) { var idx = offset < 0 ? list.length + offset : offset; return _isString(list) ? list.charAt(idx) : list[idx]; });
這個函數就有點意思了,用了柯里化,是一個函數式的實現,當head函數返回一個nth(0)時,其實返回的是一個柯里化之后的函數,然后再接受一個數組,判斷數組類型之后返回list[offset]的值。
再看看Lodash的nth的實現:
function nth(array, n) { return (array && array.length) ? baseNth(array, toInteger(n)) : undefined; } function baseNth(array, n) { var length = array.length; if (!length) { return; } n += n < 0 ? length : 0; return isIndex(n, length) ? array[n] : undefined; }
仔細對比兩個庫的實現,兩個庫都允許負下標的處理,但是對于Ramda而言,如果list是一個null或者undefined類型的數據的話,將會拋出TypeError,而Lodash則優雅一些。
_.join_.join函數是另一個簡單的函數:
var arrayProto = Array.prototype; var nativeJoin = arrayProto.join; function join(array, separator) { return array == null ? "" : nativeJoin.call(array, separator); }
重寫之后函數變為:
function join(array,separator) { return array == null ? "" : Array.prototype.join.call(array, separator); }
我們再對比一下Ramda的實現:
var invoker = require("./invoker"); module.exports = invoker(1, "join");
再看看invoker函數:
module.exports = _curry2(function invoker(arity, method) { return curryN(arity + 1, function() { var target = arguments[arity]; if (target != null && _isFunction(target[method])) { return target[method].apply(target, Array.prototype.slice.call(arguments, 0, arity)); } throw new TypeError(toString(target) + " does not have a method named "" + method + """); }); });
invoker函數就是為了返回一個curry化的函數,那么我們其實可以這么理解如果用Lodash實現一個函數化的join可以這么實現:
function _join(array,separator){ return Array.prototype.join.call(array,seprator); } var join = _.curry(_join);
那么我們可以和Ramda的使用方式一樣使用:
join(_,",")([1,2,3]); // 1,2,3_.remove
這個方法很有意思,我們可以看到不同的實現方式(通常實現/函數式實現),兩種實現差別很大,所以拿出來分析一下。
先看看Lodash的實現:
/** * Removes all elements from `array` that `predicate` returns truthy for * and returns an array of the removed elements. The predicate is invoked * with three arguments: (value, index, array). * * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull` * to pull elements from an array by value. * * @static * @memberOf _ * @since 2.0.0 * @category Array * @param {Array} array The array to modify. * @param {Function} [predicate=_.identity] The function invoked per iteration. * @returns {Array} Returns the new array of removed elements. * @example * * var array = [1, 2, 3, 4]; * var evens = _.remove(array, function(n) { * return n % 2 == 0; * }); * * console.log(array); * // => [1, 3] * * console.log(evens); * // => [2, 4] */ function remove(array, predicate) { var result = []; if (!(array && array.length)) { return result; } var index = -1, indexes = [], length = array.length; predicate = getIteratee(predicate, 3); while (++index < length) { var value = array[index]; if (predicate(value, index, array)) { result.push(value); indexes.push(index); } } basePullAt(array, indexes); return result; }
一定要注意的是,該方法會修改原數組。官方也對其進行了說明。該方法同_.fliter的區別也就在是否會修改原對象上。
我們分析一下Lodash是如何實現這個功能的,首先判斷數組是否合法,如果不合法就直接返回。在Lodash中的實現其實很簡單,首先得到一個predicate謂詞函數,該謂詞函數用于判斷元素是否符合條件,如果符合條件就將其從原數組中移除。邏輯也比較簡單,但是該函數會修改原array,該功能是通過basePullAt()實現的:
/** * The base implementation of `_.pullAt` without support for individual * indexes or capturing the removed elements. * * @private * @param {Array} array The array to modify. * @param {number[]} indexes The indexes of elements to remove. * @returns {Array} Returns `array`. */ function basePullAt(array, indexes) { var length = array ? indexes.length : 0, lastIndex = length - 1; while (length--) { var index = indexes[length]; if (length == lastIndex || index !== previous) { var previous = index; if (isIndex(index)) { splice.call(array, index, 1); } else { baseUnset(array, index); } } } return array; }
需要說明的是,這里的splice方法的原型是Array.prototype.splice,該方法同Array.prototype.slice的區別是,splice會修改原數組的內容,而slice不會修改原數組的內容,而僅僅做的是一次淺拷貝。
還需要說明一下的是baseUnset:
/** * The base implementation of `unset`. * * @private * @param {Object} object The object to modify. * @param {Array|string} path The property path to unset. * @returns {boolean} Returns `true` if the property is deleted, else `false`. */ function baseUnset(object, path) { path = castPath(path, object) object = parent(object, path) return object == null || delete object[toKey(last(path))] } export default baseUnset
這個方法其實很簡單,就是刪除對象中的某一個屬性/鍵。
所以Lodash的整個_.remove的脈絡就捋清楚了,按照慣例,我們需要稍微簡化一下這個函數,把核心邏輯抽取出來:
function remove(list,predicated){ var indexes = []; for(var i=0;i < list.length;i++){ if(predicated(list[i])){ indexes.push(i); } } for(var idx = indexes.length -1; idx >=0;idx--){ Array.prototype.splice.call(list,indexes[idx],1); } return list; } var a = [1,2,3,4]; remove(a,function(a){if (a == 3) return true; else return false;}); console.log(a); // [1,2,4]
恩,感覺好像也挺好用的。
但是我們不能止步于此,作為一個熱衷函數式編程的程序員,最終目標是代碼中沒有循環沒有分支。我們看看Ramda.js是怎么實現的:
/** * Removes the sub-list of `list` starting at index `start` and containing * `count` elements. _Note that this is not destructive_: it returns a copy of * the list with the changes. * No lists have been harmed in the application of this function. * * @func * @memberOf R * @since v0.2.2 * @category List * @sig Number -> Number -> [a] -> [a] * @param {Number} start The position to start removing elements * @param {Number} count The number of elements to remove * @param {Array} list The list to remove from * @return {Array} A new Array with `count` elements from `start` removed. * @example * * R.remove(2, 3, [1,2,3,4,5,6,7,8]); //=> [1,2,6,7,8] */ module.exports = _curry3(function remove(start, count, list) { var result = Array.prototype.slice.call(list, 0); result.splice(start, count); return result; });
其實Ramda就是對splice進行了curry化,什么也沒有做,毫無參考價值。沒有達到我們的預期,所以只能自己動手了:
function remove2(list,predicated){ return _remove(list,list.length-1,predicated); } function _remove(list,idx,predicated){ if(predicated(list[idx])){ list.splice(idx,1); } if (idx == 0){return list;}else{ _remove(list,idx-1,predicated); } } //調用 var a = [1,2,3,4]; remove2(a,function(a){if (a == 3) return true; else return false;}); console.log(a); //[1,2,4]
感覺舒服多了,對于JavaScript而言沒有分支語句是不可能的,但是可以把所有的循環用遞歸取代,感覺代碼也簡潔了許多,函數式能夠讓人以另一個角度思考問題,真的是一個很好的編程范式。
結語最近工作非常忙,也沒有時間寫第三篇連載,忙里抽空用午休時間將本文寫完了。成文比較匆忙難免有一些謬誤望各位看官海涵,也希望能夠直接指出我文章中的錯誤,感激不盡!
敬請期待本系列文章還有后續內容,包括數組和集合的操作,以及對象的操作,具體還沒有想好涉及哪方面內容,總之敬請期待!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91970.html
摘要:依賴源碼分析之緩存使用方式的進一步封裝源碼分析之源碼分析之源碼分析之的實現源碼分析之源碼分析的調用如果有傳遞,則先調用,使用生成要比較數組的映射數組。循環完畢,沒有在第二個數組中發現相同的項時,將該項存入數組中。 外部世界那些破舊與貧困的樣子,可以使我內心世界得到平衡?!柧S諾《煙云》 本文為讀 lodash 源碼的第十七篇,后續文章會更新到這個倉庫中,歡迎 star:pocke...
摘要:眾所周知,函數能夠將一個集合進行折疊。我們看到源代碼是這樣的在官方的注釋中說,對于對象,遍歷順序是無法保證的。我在閱讀源代碼的過程中也會遇到很多不理解的地方。待續下周將繼續更新源碼分析系列,接下來將會分析集合方法。 前言 這是Lodash源碼分析的第二篇文章,我們在第一篇Lodash 源碼分析(一)Function Methods中介紹了基本的_.after,_.map,以及復雜的_....
摘要:萬條數據依賴讀源碼之從看稀疏數組與密集數組原理的原理歸結起來就是切割和放置。尺在切割之前,需要用尺確定切割的數量。容器的長度剛好與塊的數量一致。當與塊的數量相等時,表示已經切割完畢,停止切割,最后將結果返回。 以不正義開始的事情,必須用罪惡使它鞏固。——莎士比亞《麥克白》 最近很多事似乎印證了這句話,一句謊言最后要用一百句謊言來圓謊。 本文為讀 lodash 源碼的第二篇,后續文章會...
摘要:從表中可以看到,比較運算符的優先級為,而三元表達式條件運算符的優化級為,因此可以確定比較運算符的優先級要比三元表達式的要高,循環條件其實等價于第二種寫法。從上表中也可以看出前綴自增比比較運算符的優化級要高。 我悟出權力本來就是不講理的——蟑螂就是海米;也悟出要造反,內心必須強大到足以承受任何后果才行?!睄u《城門開》 本文為讀 lodash 源碼的第十篇,后續文章會更新到這個倉庫中...
摘要:到這里,源碼分析完了。但是,有兩個致命的特性的遍歷不能保證順序會遍歷所有可枚舉屬性,包括繼承的屬性。的遍歷順序依賴于執行環境,不同執行環境的實現方式可能會不一樣。 小時候,鄉愁是一枚小小的郵票, 我在這頭, 母親在那頭。 長大后,鄉愁是一張窄窄的船票, 我在這頭, 新娘在那頭。 后來啊, 鄉愁是一方矮矮的墳墓, 我在外頭, 母親在里頭。 而現在, 鄉愁是一灣淺淺的海峽, 我在這頭, 大...
閱讀 1311·2021-11-24 10:24
閱讀 4089·2021-11-22 15:29
閱讀 1085·2019-08-30 15:53
閱讀 2788·2019-08-30 10:54
閱讀 1977·2019-08-29 17:26
閱讀 1271·2019-08-29 17:08
閱讀 605·2019-08-28 17:55
閱讀 1576·2019-08-26 14:01