国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Lodash 源碼分析(二)“Function” Methods

cheukyin / 2095人閱讀

摘要:眾所周知,函數(shù)能夠?qū)⒁粋€(gè)集合進(jìn)行折疊。我們看到源代碼是這樣的在官方的注釋中說,對于對象,遍歷順序是無法保證的。我在閱讀源代碼的過程中也會(huì)遇到很多不理解的地方。待續(xù)下周將繼續(xù)更新源碼分析系列,接下來將會(huì)分析集合方法。

前言

這是Lodash源碼分析的第二篇文章,我們在第一篇Lodash 源碼分析(一)“Function” Methods中介紹了基本的_.after_.map,以及復(fù)雜的_.ary函數(shù)的實(shí)現(xiàn)以及我們自己的自定義輕量級版本。大概清楚了Lodash的整個(gè)代碼脈絡(luò)。這次我們繼續(xù)分析,這次我們講講_.reduce_.curry

_.reduce

我一直覺得,如果能夠理解_.map_.reduce的實(shí)現(xiàn),那么任何復(fù)雜的函數(shù)都不在話下。我們已經(jīng)介紹了_.map的實(shí)現(xiàn),知道了_.map函數(shù)中是如何處理集合,并將其逐個(gè)進(jìn)行函數(shù)處理的。我們知道在_.map函數(shù)中會(huì)把三個(gè)參數(shù)傳到給定的函數(shù)中,分別是array[index]indexarray。這次我們看看_.reduce函數(shù)。

眾所周知,_.reduce函數(shù)能夠?qū)⒁粋€(gè)集合進(jìn)行"折疊"。"折疊"理解起來比較抽象。我們可以通過代碼作為樣例說明一下:

const _ = require("lodash");
_.reduce([1,2,3],function(a,b){return a+b});
// 6

如果你不知道_.reduce到底是怎么工作的,那么你可以看看我寫的這篇文章從Haskell、JavaScript、Go看函數(shù)式編程。我們今天的目的是看看lodash是如何實(shí)現(xiàn)_.reduce的,以及和我們函數(shù)式的實(shí)現(xiàn)的區(qū)別。

我們看到lodash源代碼是這樣的:

function reduce(collection, iteratee, accumulator) {
var func = isArray(collection) ? arrayReduce : baseReduce,
    initAccum = arguments.length < 3;

  return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
}

在官方的注釋中說,對于對象,遍歷順序是無法保證的。我們不考慮這么復(fù)雜的情況,先看看Array的情況。其次,我們在調(diào)用_.reduce的時(shí)候沒有傳入第三個(gè)accumulator參數(shù),那么函數(shù)可以簡化為:

function reduce(collection, iteratee, accumulator) {
  return arrayReduce(collection, getIteratee(iteratee, 4), accumulator, true, baseEach);
}

在看看arrayReduce函數(shù):

  function arrayReduce(array, iteratee, accumulator, initAccum) {
    var index = -1,
        length = array == null ? 0 : array.length;

    if (initAccum && length) {
      accumulator = array[++index];
    }
    while (++index < length) {
      accumulator = iteratee(accumulator, array[index], index, array);
    }
    return accumulator;
  }

這里的accumulator是初始累加值,如果傳入,則"折疊"在其基礎(chǔ)上進(jìn)行,就上面的最簡單的例子而言,如果傳入第三個(gè)參數(shù)是2,那么返回值就會(huì)使8

const _ = require("lodash");
_.reduce([1,2,3],function(a,b){return a+b},8);
// 8

所以arrayReduce函數(shù)就是給定一個(gè)初始值然后進(jìn)行迭代的函數(shù)。我們真正需要關(guān)注的函數(shù)式iteratee函數(shù),即getIteratee(func, 4)這里的func就是我進(jìn)行重命名之后的自定義函數(shù)。

這個(gè)getIteratee函數(shù)在介紹_.map的時(shí)候就進(jìn)行介紹了,在func是一個(gè)function的情況下,就是返回func本身。

所以我們可以把整個(gè)reduce函數(shù)簡化為如下版本:

function reduce(array, func, accumulator) {
    var index = -1,
        length = array == null ? 0 : array.length;
    if (length) {
      accumulator = array[++index];
    }
    while (++index < length) {
      accumulator = func(accumulator, array[index], index, array);
    }
    return accumulator;
  }

其實(shí)看上去很像一個(gè)”遞歸“函數(shù),因?yàn)榍懊嬉淮蔚倪\(yùn)算結(jié)果將會(huì)用于下一次函數(shù)調(diào)用,但又不是遞歸函數(shù)。我們其實(shí)完全可以寫一個(gè)遞歸版本的reduce

function reduce(array,func,accumulator){
  accumulator = accumulator == null ? array[0]:accumulator;
  if (array.length >0){
    var a = array.shift();
    accumulator = func(a,accumulator);
    return reduce(array,func,accumulator);
  }
  return accumulator
}

工作的也不錯(cuò),但在分析過程中,發(fā)現(xiàn)lodash一直在避免修改原參數(shù)的值,盡量讓整個(gè)函數(shù)調(diào)用時(shí)無副作用的。我覺得這個(gè)思想在開發(fā)過程中也有很多值得借鑒的地方。

_.curry

了解過函數(shù)式編程的同學(xué)一定聽過大名鼎鼎的柯里化,在Lodash中也有一個(gè)專門用于柯里化的函數(shù)_.curry。這個(gè)函數(shù)接受一個(gè)函數(shù)func和這個(gè)函數(shù)的部分參數(shù),然后返回一個(gè)接受剩余參數(shù)的函數(shù)func"

我們看看這個(gè)函數(shù)是怎么實(shí)現(xiàn)的:

function curry(func, arity, guard) {
   arity = guard ? undefined : arity;
   var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
   result.placeholder = curry.placeholder;
   return result;
}

我們又看到我們的老朋友createWrap了,其實(shí)這個(gè)函數(shù)我們在上一篇文章中分析過,但是我們那時(shí)候是分析_.ary函數(shù)的時(shí)候進(jìn)行了精簡,這次我們看看createWrap函數(shù)式怎么對_.curry函數(shù)進(jìn)行處理的(將無關(guān)邏輯進(jìn)行精簡):

function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
      var isBindKey = 0
      var length =  0;
      ary = undefined ;
      arity = arity === undefined ? arity : toInteger(arity);
      length -= holders ? holders.length : 0;
      var data = getData(func);
      var newData = [
        func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
        argPos, ary, arity
      ];

      if (data) {
        mergeData(newData, data);
      }
      func = newData[0];
      bitmask = newData[1];
      thisArg = newData[2];
      partials = newData[3];
      holders = newData[4];
      arity = newData[9] = newData[9] === undefined
        ? func.length
        : nativeMax(newData[9] - length, 0);
      result = createCurry(func, bitmask, arity);
      var setter = data ? baseSetData : setData;
      return setWrapToString(setter(result, newData), func, bitmask);
    }

這里面的關(guān)鍵就是createCurry函數(shù)了:

function createCurry(func, bitmask, arity) {
      var Ctor = createCtor(func);

      function wrapper() {
        var length = arguments.length,
            args = Array(length),
            index = length,
            placeholder = getHolder(wrapper);

        while (index--) {
          args[index] = arguments[index];
        }
        var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
          ? []
          : replaceHolders(args, placeholder);

        length -= holders.length;
        if (length < arity) {
          return createRecurry(
            func, bitmask, createHybrid, wrapper.placeholder, undefined,
            args, holders, undefined, undefined, arity - length);
        }
        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
        return apply(fn, this, args);
      }
      return wrapper;
    }

不得不說和createHybird函數(shù)十分相似,但是其中還有一個(gè)比較關(guān)鍵的函數(shù),就是createRecurry,這個(gè)函數(shù)返回了一個(gè)能夠繼續(xù)進(jìn)行curry的函數(shù):

function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
      var isCurry = bitmask & WRAP_CURRY_FLAG,
          newHolders = isCurry ? holders : undefined,
          newHoldersRight = isCurry ? undefined : holders,
          newPartials = isCurry ? partials : undefined,
          newPartialsRight = isCurry ? undefined : partials;

      bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG);
      bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);

      if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
        bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
      }
      var newData = [
        func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
        newHoldersRight, argPos, ary, arity
      ];

      var result = wrapFunc.apply(undefined, newData);
      if (isLaziable(func)) {
        setData(result, newData);
      }
      result.placeholder = placeholder;
      return setWrapToString(result, func, bitmask);
    }

Lodash為了實(shí)現(xiàn)curry化,進(jìn)行了多層的包裝,為了實(shí)現(xiàn)返回的是劃一的Lodash中定義的能夠curry化的函數(shù)。

這個(gè)函數(shù)要求接受相應(yīng)的參數(shù)列表,即代碼中的data。在curry化的過程中有一個(gè)非常重要的東西,就是占位符placeholder。在對curry化的函數(shù)進(jìn)行調(diào)用時(shí)也可以用占位符進(jìn)行占位:

var curried = _.curry(abc);
curried(1)(2)(3);
// => [1, 2, 3]
curried(1, 2)(3);
// => [1, 2, 3]
curried(1, 2, 3);
// => [1, 2, 3]
// Curried with placeholders.
curried(1)(_, 3)(2);
// => [1, 2, 3]

可以用下劃線_作為占位符占位。我們且不看lodash為我們做的很多復(fù)雜的預(yù)處理和特殊情況的處理,我們就分析_.curry函數(shù)實(shí)現(xiàn)的主要思想。首先_.curry函數(shù)有一個(gè)屬性存儲(chǔ)了最初的函數(shù)的接受函數(shù)參數(shù)的個(gè)數(shù)。然后有一個(gè)參數(shù)數(shù)組用于存儲(chǔ)部分參數(shù),如果參數(shù)個(gè)數(shù)沒有滿足調(diào)用函數(shù)需要的個(gè)數(shù),就繼續(xù)返回一個(gè)重新curry化的函數(shù)。

根據(jù)上面的思想我們可以寫出一個(gè)簡化的curry化代碼:

/**
 *
 *var abc = function(a, b, c) {
 *    return [a, b, c];
 *};
 *
 *var curried = curry(abc);
 *
 *curried(1)(2)(3);
 * // => [1, 2, 3]
 *
 * curried(1, 2)(3);
 * // => [1, 2, 3]
 *
 * curried(1, 2, 3);
 * // => [1, 2, 3]
 *
 * // Curried with placeholders.
 * curried(1)("_", 3)(2)
 * 這就無法處理了
 * // => [1, 3, 2]
 */

function curry(func){
  function wrapper(){
    func.prototype.that = func.prototype.that ? func.prototype.that : this;
    func.prototype.paramlength = func.prototype.paramlength ? func.prototype.paramlength: func.length ;
    func.prototype.paramindex = func.prototype.paramindex ?func.prototype.paramindex : 0;
    func.prototype.paramplaceholder = func.prototype.paramplaceholder ?  func.prototype.paramplaceholder : Array(func.length);
    for (var i = 0 ; i < arguments.length; i++) {
      if (arguments[i] == "_"){
        continue;
      }else{
        func.prototype.paramplaceholder[func.prototype.paramindex] = arguments[i];
        func.prototype.paramindex += 1;
      }
    }
    if (func.prototype.paramindex == func.prototype.paramlength){
      func.prototype.paramindex = 0;
      return func.apply(func.prototype.that,func.prototype.paramplaceholder)
    }
    return wrapper;
  }
  return wrapper;
}

我們雖然可以借助Lodash的思想實(shí)現(xiàn)我們一個(gè)簡單版本的curry函數(shù),但是這個(gè)簡單版本的函數(shù)有一個(gè)問題,那就是,這個(gè)函數(shù)是借助閉包實(shí)現(xiàn)的,在整個(gè)執(zhí)行過程當(dāng)中,只要被柯里化的函數(shù)沒有執(zhí)行結(jié)束,那么它就會(huì)一直存在在內(nèi)存當(dāng)中,它的一些屬性也會(huì)一直存在。第二個(gè)問題是,沒有辦法實(shí)現(xiàn)Lodash的"真正"的占位符,只是在遇到"_"的時(shí)候?qū)⑵涮^了。

一個(gè)真正有效的柯里化函數(shù)實(shí)現(xiàn)起來有很多細(xì)節(jié)需要考慮,這就是Lodash存在的意義。我們應(yīng)該在理解其實(shí)現(xiàn)原理的前提下,享受Lodash帶來的便利。

小結(jié)

閱讀Lodash源碼真的能夠了解很多代碼實(shí)現(xiàn)上的細(xì)節(jié),Lodash在性能優(yōu)化上面做了很多工作,也給我們學(xué)習(xí)一個(gè)優(yōu)秀的js庫提供了非常好的參考。我在閱讀Lodash源代碼的過程中也會(huì)遇到很多不理解的地方。但是細(xì)細(xì)琢磨發(fā)其實(shí)它的代碼還是非常清晰易懂的。

待續(xù)

下周將繼續(xù)更新Lodash源碼分析系列,接下來將會(huì)分析Lodash集合方法。

? 版權(quán)所有,禁止一切形式轉(zhuǎn)載。順便宣傳一下個(gè)人博客http://chenquan.me

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/85011.html

相關(guān)文章

  • Lodash 源碼分析(三)Array

    摘要:前言這是源碼分析系列文章的第三篇,前面兩篇文章源碼分析一源碼分析二分別分析了中的一些重要函數(shù),也給出了簡化的實(shí)現(xiàn),為理解其內(nèi)部機(jī)理和執(zhí)行方式提供了便利。官方也對其進(jìn)行了說明。 前言 這是Lodash源碼分析系列文章的第三篇,前面兩篇文章(Lodash 源碼分析(一)Function Methods、Lodash 源碼分析(二)Function Methods)分別分析了Lodash F...

    ZoomQuiet 評論0 收藏0
  • lodash源碼分析之?dāng)?shù)組的差集

    摘要:依賴源碼分析之緩存使用方式的進(jìn)一步封裝源碼分析之源碼分析之源碼分析之的實(shí)現(xiàn)源碼分析之源碼分析的調(diào)用如果有傳遞,則先調(diào)用,使用生成要比較數(shù)組的映射數(shù)組。循環(huán)完畢,沒有在第二個(gè)數(shù)組中發(fā)現(xiàn)相同的項(xiàng)時(shí),將該項(xiàng)存入數(shù)組中。 外部世界那些破舊與貧困的樣子,可以使我內(nèi)心世界得到平衡。——卡爾維諾《煙云》 本文為讀 lodash 源碼的第十七篇,后續(xù)文章會(huì)更新到這個(gè)倉庫中,歡迎 star:pocke...

    Noodles 評論0 收藏0
  • lodash源碼分析之自減的兩種形式

    摘要:作用與用法是的內(nèi)部函數(shù),之前在源碼分析之緩存介紹過一種這樣的數(shù)據(jù)結(jié)構(gòu)這是一個(gè)二維數(shù)組,每項(xiàng)中的第一項(xiàng)作為緩存對象的,第二項(xiàng)為緩存的值。 這個(gè)世界需要一個(gè)特定的惡人,可以供人們指名道姓,千夫所指:全都怪你。——村上春樹《當(dāng)我談跑步時(shí)我談些什么》 本文為讀 lodash 源碼的第六篇,后續(xù)文章會(huì)更新到這個(gè)倉庫中,歡迎 star:pocket-lodash gitbook也會(huì)同步倉庫的更新...

    Keven 評論0 收藏0
  • lodash源碼分析之compact中的遍歷

    摘要:到這里,源碼分析完了。但是,有兩個(gè)致命的特性的遍歷不能保證順序會(huì)遍歷所有可枚舉屬性,包括繼承的屬性。的遍歷順序依賴于執(zhí)行環(huán)境,不同執(zhí)行環(huán)境的實(shí)現(xiàn)方式可能會(huì)不一樣。 小時(shí)候,鄉(xiāng)愁是一枚小小的郵票, 我在這頭, 母親在那頭。 長大后,鄉(xiāng)愁是一張窄窄的船票, 我在這頭, 新娘在那頭。 后來啊, 鄉(xiāng)愁是一方矮矮的墳?zāi)梗?我在外頭, 母親在里頭。 而現(xiàn)在, 鄉(xiāng)愁是一灣淺淺的海峽, 我在這頭, 大...

    dmlllll 評論0 收藏0
  • 源碼分析】給你幾個(gè)鬧鐘,或許用 10 分鐘就能寫出 lodash 中的 debounce &

    摘要:最簡單的案例以最簡單的情景為例在某一時(shí)刻點(diǎn)只調(diào)用一次函數(shù),那么將在時(shí)間后才會(huì)真正觸發(fā)函數(shù)。后續(xù)我們會(huì)逐漸增加黑色鬧鐘出現(xiàn)的復(fù)雜度,不斷去分析紅色鬧鐘的位置。 序 相比網(wǎng)上教程中的 debounce 函數(shù),lodash 中的 debounce 功能更為強(qiáng)大,相應(yīng)的理解起來更為復(fù)雜; 解讀源碼一般都是直接拿官方源碼來解讀,不過這次我們采用另外的方式:從最簡單的場景開始寫代碼,然后慢慢往源碼...

    余學(xué)文 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<