日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

【JS】446- 你不知道的 map

發布時間:2024/3/13 javascript 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【JS】446- 你不知道的 map 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文來自【前端早讀課】,內容不錯,推薦給大家。


前言

今日早讀文章由酷家樂@Gloria投稿分享。

正文從這開始~~

作為前端工程師,你肯定用過Array.prototype.map方法。

如果你聽說過Ramda,它也提供了和Array.prototype.map方法類似的map方法。

但是這個map背后的東西可以讓你看到另外一個世界,我相信,如果你不想了解Ramda,也能從這篇文章中有所收獲。

下面我們進入到例子。

簡單的使用

像下面這樣使用這個函數。

R.map(x => x + 1, [1, 2, 3]); // [2, 3, 4]

除了數組外它還可以作用于Object:

R.map(x => x + 1, {a: 1, b: 2, c: 3}); // {a: 2, b: 3, c: 4}

你以為就完了嗎?它還能作用于函數:

R.map(x => x + 1, a => a + 1); // a => (a+1)+1

哇,作用于函數真的是沒想到,那還能作用于其它奇奇怪怪的東西嗎?

當然可以,有很多東西從某種維度上講都是同一類東西,關鍵R.map的維度是什么呢?

先別講什么亂七八糟的,接下來咱們來看一看官方文檔上都有哪些描述.

文檔上都說了啥
  • 接收一個函數和一個 functor, 將該函數應用到 functor 的每個值上,返回一個具有相同形態的 functor。

  • Ramda 為 Array 和 Object 提供了合適的 map 實現,因此 R.map 適用于 [1, 2, 3] 或 {x: 1, y: 2, z: 3}。

  • 若第二個參數自身存在 map 方法,則調用自身的 map 方法。

  • 若在列表位置中給出 transfomer,則用作 transducer 。

  • 函數也是 functors,map 會將它們組合起來(相當于 R.compose)。

行了,除了2,3能看懂,其它都是啥??!!functor??transfomer??transducer??

我們找到Ramda的源碼,看看這個map究竟都有哪些魔法?

看看ramda源碼

隱去了一些不需要了解的邏輯,下面是代碼:

var map = _dispatchable(['fantasy-land/map', 'map'], _xmap, function map(fn, functor) { /*ramda默認處理邏輯*/ switch(Object.prototype.toString.call(functor)) { case'[object Function]': returnfunction() { return fn.call(this, functor.apply(this, arguments)); }; case'[object Object]': return _reduce(function(acc, key) {acc[key] = fn(functor[key]); return acc; }, {}, keys(functor)); default: return _map(fn, functor); } });

先說說_dispatchable的邏輯:

function _dispatchable(methodNames, xf, fn): Function
  • _dispatchable返回的函數作為R.map的處理過程

  • 接收 3 個參數:methodNames(方法名數組),xf(transformer),fn(默認的ramda實現)

  • 如果 methodNames 中的方法名存在于傳進 R.map方法的最后一個參數f上,則將該方法作為處理過程 (如 f 是數組,則使用默認的處理過程)

  • 如果最后一個參數 f 是transformer,處理結果則是:一個新的transformer

  • 如果以上3,4說的情況都沒有,則使用Ramda的默認處理過程(第一個代碼塊注釋處)

總體看下來R.map有3種處理策略(按照優先級從上到下):

  • 最后一個參數f上出現在 methodNames 中的方法

  • 根據最后一個參數 f 返回新的 transformer

  • Ramda默認處理邏輯

默認的處理邏輯就不再展開了,比較容易明白,先說說2,1放在后面講。

transduce

進入正題之前,拋開ramda,看一個簡單的栗子:

const add = (a, b) => a + b; [1,2,3,4].reduce(add, 0); // 10

計算出一個數組中所有數字的和。

現在如果要對每個數字+1,再求和:

const add = (a, b) => a + b; const plusOne = a => a + 1; [1,2,3,4].map(plusOne).reduce(add, 0); // 14

上面的代碼會遍歷數組兩次,雖然代碼寫起來省事了,如果數據量比較大,這個做法看起來就有些笨拙了。但是又不能改寫add方法,萬一別的地方也用到了add。

想辦法只遍歷一次:結合add和plusOne生成一個新的函數addNPlusOne:

const addNPlusOne = (acc, value) => add(acc, plusOne(value)); [1,2,3,4].reduce(addNPlusOne, 0); // 14

嗯,解決了。但是還不夠通用,將add視為reducer,plusOne視為對value的預處理函數fn,通過結合fn和reducer生成一個新的reducer提供給reduce

const makeMapReducer = fn => reducer => (acc, value) => reducer(acc, fn(value)); const addNPlusOne = makeMapReducer(plusOne)(add); [1,2,3,4].reduce(addNPlusOne); // 14
transducer

makeMapReducer(plusOne)就是一個transducer。

在之前的基礎上:如果需要先篩選出小于等于2的數值,然后再給每一項+1,最后統計出數組中所有數的和。

需要再添加一個filterTransducer:

const makeFilterReducer = fn => reducer => (acc, value) => fn(value)? reducer(acc, value) : acc; const filterTransducer = makeFilterReducer(a => a <= 2); const addNPluslteTwo = filterTransducer(addNPlusOne); [1,2,3,4].reduce(addNPlusltTwo); // 5

好了,也就是說如果你不使用任何第三方庫,這個生成transducer的函數需要你自己去實現。

在Ramda中

在Ramda中你可以這樣實現上面的栗子:

R.transduce(R.map(a => a+1), (acc, value) => acc + value, 0, [1,2,3,4]); // 14 R.transduce(R.pipe(R.map(a => a+1),R.filter(a => a <= 2), ), (acc, value) => acc+value, 0, [1,2,3,4]); // 5

再簡化一點:

R.transduce(R.map(R.inc), R.add, 0, [1,2,3,4]); // 14 R.transduce(R.pipe( R.map(R.inc),R.filter(R.gte(2)), ), R.add, 0, [1,2,3,4]); // 5

之前的例子,我們自己實現了transducer。

而對于ramda來說,很多作用于數組的api都會有默認的生成transducer的實現,比如map,filter,find等等api。

好了,好像扯遠了,我們再回到R.map上,看一看這里的transformer是啥意思。

  • 根據最后一個參數f返回新的transformer

  • 回到開始的話題

    當你調用R.transduce的時候,它會把第二個參數R.add,轉化為一個對象,這個對象上存在方法@@transducer/step,這個方法返回的是R.add(acc, value)。存在方法@@transducer/step的對象就叫做transformer。

    其實你可以這樣理解:transformer是一個函數的載體,transformer['@@transducer/step']就是這個函數。

    好了,如果當R.map的第二個參數是一個transformer的時候:

    // _xwrap是ramda內部函數,用于將函數轉為transformer R.map(R.inc)(_xwrap(R.add)) // 跟下面是等價的 R.map(R.inc, _xwrap(R.add))

    R.map(R.inc)其實就是上面我們說的transducer(transducer還能組合起來,不再展開了,有興趣的同學可以加群討論)

    transducer + transformer = transformer,所以上面兩行代碼返回的結果依然是一個transformer,這個transformer的@@transducer/step方法最終效果是下面這樣:

    XMap.prototype['@@transducer/step'] = function(acc, value) { return R.add(acc, R.inc(value)); };

    這個transformer代表的就是最終的reducer函數的容器

    R.transduce(R.map(R.inc), R.add, 0, [1,2,3,4]); // 與下面是等價的 const xf = R.map(R.inc)(_xwrap(R.add)); R.reduce(xf['@@transducer/step'], 0, [1,2,3,4]);

    總結一下

    為了減少遍歷次數,用transduce替代reduce,把之前reduce過程的前置操作比如map,filter,find等操作在一次遍歷中完成。

    為了實現這個transduce,以及在其上map,filter,find這種操作的可組合性,引入了transducer+transformer的概念。

    這個transducer的概念最早是在Clojure里出現,有興趣的同學可以看看:https://video.tudou.com/v/XMjMxNTY2MDgzNg==.html?__fr=oldtd

    fantasyland/map
  • 最后一個參數?f上出現在?methodNames中的方法

  • 根據最后一個參數?f返回新的?transformer

  • ramda默認處理邏輯

  • 既然第2點講完了,開始這篇文章的最后一部分,這一部分與上面講的transducer沒有任何關系,這一部分也是本文想著重介紹的。

    var map = _dispatchable(['fantasy-land/map', 'map'],...)

    從上面R.map的實現中可以看到,傳入_dispatchable的methodsName中,第一個方法名是fantasyland/map。

    如果R.map(fn, obj),obj上有fantasyland/map方法,則R.map(fn, obj)等價于 obj['fantasyland/map'](fn)。

    那么methodsName中另一個map和這個fantasyland/map有啥區別?為啥還有這么長的一個名字?

    fantasyland規范

    其實fantasyland/map這個名字是有特殊含義的,fantasyland/map沒有特定的實現,不過,如果你要實現這么一個方法,你需要遵循fantasyland規范。

    所謂的fantasyland規范,其實就是一個文檔,這個文檔里規定了一些代數結構在javascript里實現的約束

    Fantasy Land Specificationaka "Algebraic JavaScript Specification"

    如果你在大學有接觸過《離散數學》的話,其中的一些概念會在這個規范中有具體的javascript定義,比如:二元關系(等價關系,全序關系),群,半群。當然,除了這3類數據結構,還有范疇以及在基礎代數結構上衍生出來的其它結構。

    類型簽名

    接下去我們會著重看一下與fantasy-land/map相關的定義,不過,在此之前有一些簡單的類型簽名,需要提前了解一下(下面的類型簽名解釋,是個人翻譯版本,如果你有興趣,可以直接看github上英文原版的解釋):

    :: :“a屬于類型b”

    e :: t:可以理解成:“e屬于類型t”

    true :: Boolean:“ true 屬于 Boolean 類型”

    42 :: Integer,Number :“42既屬于 Integer 也屬于 Number 類型”

    通過類型構造函數可以構造一個新的類型

    類型構造函數接受0個或多個參數

    Array 就是一個類型構造函數,它接受一個類型作為參數

    Array String 是存放著字符串的數組,像這幾個數組都是屬于 Array String :[],['foo', 'bar', 'baz']

    Array(Array String) 是存放著數組的數組,存放的數組里面又存放著字符串,像這幾個數組都是屬于 Array(Array String):[],[[], []],[[], ['foo'], ['bar`, 'baz']]

    小寫字母是類型變量

    類型變量可以代表任何類型,除非用胖箭頭(下面有介紹)對它做類型約束

    ->(箭頭)函數的類型構造函數

    -> 是一個中綴類型構造函數,這個類型構造函數接受兩個參數,箭頭左邊的參數是輸入類型,右邊的參數是輸出類型

    -> 可以接受0個或多個輸入類型作為左邊的參數。語法:() ->,中的多個類型以“ , ”分隔。一元函數輸入參數旁邊的括號可以省略,比如:String -> Boolean,(String, String) -> Boolean

    String -> Array String 對應一類函數:接受一個 String 類型的參數,然后返回一個類型為 Array String 的值

    String -> Array String -> Array String 代表著一類函數:接受一個類型為String的輸入,輸出一個類型為 Array String -> Array String 的函數,這個輸出的函數接受一個類型為 Array String 的參數,輸出類型為 Array String 的值

    (String, Array String) -> Array String代表著一類函數:接受兩個參數,第一個是String 類型,第二個是 Array String 類型,輸出類型為 Array String 的值

    () -> Number 代表著一類函數:不接受輸入,返回一個類型為 Number 的值

    ~>(波浪箭頭)方法的類型構造函數

    當一個函數是一個對象的屬性時,它被叫做這個對象上的“方法”。所有的“方法”都擁有一個隱含的參數類型-所在對象的類型

    a ~> a -> a 代表著一類方法:是類型為 a 的對象上的方法,且這個方法接受一個類型為a 的參數,返回一個類型為 a 的值

    =>(胖箭頭)胖箭頭用來對類型變量做類型約束

    比如有這么一個方法 a ~> a -> a ,在這個方法的類型簽名中,a 可以代表任何類型。Semigroup a => a ~> a -> a,而這個類型簽名中就對類型變量 a 做了類型約束,使得類型 a 必須滿足類型類 Semigroup 。當一個類型滿足一個類型類的意思是,這個類型實現了所有類型類指定的函數/方法。

    就拿這次我們要說的fantasy-land/map舉例:

    fantasy-land/map
    fantasy-land/map解析

    先不管下面這部分

    Functoru'fantasy-land/map' is equivalent to u (identity)u'fantasy-land/map') is equivalent to u'fantasy-land/map''fantasy-land/map' (composition)

    直接看規范中對fantasy-land/map的定義:

    fantasy-land/map :: Functor f => f a ~> (a -> b) -> f b

    Functor是一個類型類,f 必須滿足 Functor, f a 代表了以 f 作為類型構造函數,類型 a 作為構造參數生成的類型,比如 Array String,代表字符串數組,Array 就是 f ,它滿足Functor類型類。

    如果一個對象,是Functor實例(具體的值)。那么這個對象上需要存在一個名為 fantasy-land/map 的方法,這個方法必須接受一個函數作為參數:

    u['fantasy-land/map'](f) // 舉個例子 [1,2,3]['fantasy-land/map'](f)
    f 必須是一個函數
    • 如果 f 不是一個函數,fantasy-land/map 的行為是不確定的

    • f 可以返回任何類型的值

    • 不應該檢測 f 的返回類型

    fantasy-land/map 方法,必須返回一個相同的Functor(比如 [1,2,3]'fantasy-land/map'?必須返回也一個數組:Array)

    其實可以類比 Array.prototype.map 方法,只是換了個名字而已。

    那么說了這么多,Functor 是個什么東東?除了 Array 以外,還有什么是 Functor ?

    其實 Function 也是 Functor ,驚喜嗎?

    不賣關子了,Functor 的中文名是“函子”,接下來講講“函子”。

    啥是函子

    “函子”是范疇論中的概念,所以,在準備完全理解“函子”之前,你需要明白啥是“范疇”?

    范疇

    其實,在生活中,無處不充斥著范疇,只不過范疇論把這些東西抽象成了數學結構。

    范疇此一概念代表著一堆數學實體和存在于這些實體間的關系。--維基百科

    范疇的定義其實很簡單,就是實體的集合+實體間的關系。

    那么什么是“實體”?這取決于你怎么看。

    從集合的角度來說,實體是 a set of values ,首先它得是一個集合(set),其次,這個集合是由有好多的值組成(value)。

    還是比較抽象,再具體一點,比如:一個類型可被看作為值的集合(a set of values),類型與類型之間的關系就是函數,所以一堆類型+類型之間的函數,就是范疇。

    比如有下面這些函數:

    fn1 :: Number-> String const fn1 = (a: number) => `${a}1`; fn2 :: String-> Boolean const fn2 = (a: string) => a === '1'; ...

    這些函數都是定義在Number和String上的映射關系。Number,String和Boolean,以及它們之間的映射關系,構成下面這個范疇

    范疇

    在范疇論中,圖片中的 NUMBER , STRING 和 BOOLEAN 叫做“對象”(Object),fn1 和 fn2 叫做“態射”(Morphism), fn2 * fn1 叫做“態射復合”, NUMBER -> NUMBER 叫做單位態射。

    明白什么是范疇之后,接下來說一說我們的主角:函子

    函子

    先來看看維基上的解釋:

    在范疇論中,函子是范疇間的一類映射。函子也可以解釋為小范疇范疇內的態射。--維基百科

    范疇和范疇也會有映射關系,如果把范疇視作一個對象時,函子就是范疇之間的態射。然后組成了一個范疇的范疇。

    舉個例子:考慮一個基礎類型的范疇A,一個數組范疇B。

    兩個范疇

    思考以下幾個問題:

    • Number 和 Array?之間的關系

    • String 和 Array?之間的關系

    • Number 到 String 的態射與 Array?到 Array?的態射的關系

    之前介紹過 Array 是類型構造函數:

    • 將 Number 傳進 Array ,構造出 Array

    • 將 String 傳進 Array ,構造出 Array

    • 可通過 Array 上的 map 方法會保持 Number -> String 映射到?Array<Number>->Array<String>

    再回顧一下上文對函子的定義:

    在范疇論中,函子是范疇間的一類映射。

    上面例子中,范疇A到范疇B的映射其實就是類型構造函數 Array ,所以說, Array 就是函子。

    函子

    這里省去了對公式上的定義的match,爭取大家對這個概念有感性的認識,如果想知道函子嚴謹的定義,可以看這里

    回到fantasy-land/map

    了解了函子的感性定義之后,回到嚴謹的規范上來。

    之前解析 fantasy-land/map 的時候,有個定義一直沒有提及,就是 Functor , fantasy-land/map 在文檔中的位置其實是Functor的子標題,現在再來回顧一下。

    Functor 1. u['fantasy-land/map'](a => a) is equivalent to u (identity) 2. u['fantasy-land/map'](x => f(g(x))) is equivalent to u['fantasy-land/map'](g)['fantasy-land/map'](f) (composition)

    通過對比函子的公式定義,解析Functor需滿足的條件(F即函子):

    保持著單位態射(id即單位態射,idX即對象X上的單位態射)

    保持著態射的復合

    總結一下fantasyland規范中對函子的定義

    如果實現一個函子,你需要在函子上實現 fantasy-land/map 方法,這個方法的類型簽名應該是這樣的:

    fantasy-land/map :: Functor f => f a ~> (a -> b) -> f b

    函子實例調用方法 fantasy-land/map 時,需同時保持單位態射和態射的復合。

    結尾

    這篇文章不知不覺寫得有些長了,從Ramda文檔->源碼->transducer->fantasyland規范->范疇論->函子,算是自己完整的探索過程,希望能夠帶給你一些不一樣的東西。

    參考文章

    • JavaScript玩轉Clojure大法之Transducer

    • Wikipedia 范疇論

    • Wikipedia 函子

    關于本文作者:@Gloria原文:https://zhuanlan.zhihu.com/p/96059965

    原創系列推薦

    1. JavaScript 重溫系列(22篇全)

    2. ECMAScript 重溫系列(10篇全)

    3. JavaScript設計模式 重溫系列(9篇全)

    4.?正則 / 框架 / 算法等 重溫系列(16篇全)

    5.?Webpack4 入門(上)||?Webpack4 入門(下)

    6.?MobX 入門(上)?||??MobX 入門(下)

    7.?59篇原創系列匯總

    回復“加群”與大佬們一起交流學習~

    總結

    以上是生活随笔為你收集整理的【JS】446- 你不知道的 map的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 中文字幕av专区 | 亚洲精选免费 | 天天操天天干视频 | www.youjizz.com日本| 污污网站在线播放 | 91高潮大合集爽到抽搐 | 天堂a√在线 | 天堂网久久| 91亚洲国产成人精品性色 | 伊人日日夜夜 | 男人午夜视频 | 在线免费观看小视频 | 国产中文一区二区 | 国产99在线播放 | 欧洲视频一区 | 亚洲一区二区三区欧美 | 激情文学亚洲色图 | 亚洲午夜福利在线观看 | 国产乱淫av麻豆国产 | 国产精品美女av | 亚洲欧洲色 | 成年人在线视频观看 | 97超碰福利 | japanese21ⅹxx日本| 黄网在线 | 国精产品一区二区 | 国产精品成人无码 | 美日韩久久 | 97在线视频人妻无码 | 久久人妻精品白浆国产 | 欧美狠狠干 | 麻豆免费在线观看 | 91网址在线观看 | 日本h片在线观看 | 精品久久免费观看 | 夏晴子在线 | 日韩精品一区二区三区中文在线 | 91高跟黑色丝袜呻吟在线观看 | 欧美 日韩 国产 成人 | 欧美成在线 | 欧美精品播放 | 欧美一级黄视频 | 久热这里只有精品6 | 亚洲成人第一 | 韩国午夜av | 精品乱 | 婷婷久久久久 | 福利一区二区在线观看 | 国产欧美久久久久久 | 亚洲图片自拍偷拍区 | 熟女俱乐部一区二区 | 操人视频免费 | 香蕉视频免费在线播放 | 亚洲视频中文字幕在线观看 | 捆绑裸体绳奴bdsm亚洲 | 在线www色| 玩日本老头很兴奋xxxx | 日韩精品999| 久久婷婷一区二区 | 国产黄色av网站 | 国产一线二线三线女 | 亚洲成人av在线播放 | 欧洲亚洲女同hd | 久久亚洲综合网 | 欧美亚洲一二三区 | 久久精品视频在线免费观看 | 毛片无遮挡高清免费观看 | 国产91在线播放九色 | 亚洲成人黄色网 | 国产一级aa大片毛片 | 日本久久久网站 | 中文字幕在线观看免费视频 | 熟女少妇a性色生活片毛片 亚洲伊人成人网 | 九色porny原创自拍 | 在线午夜av| 天天想你在线观看完整版电影高清 | 婷婷一区二区三区四区 | 久久精品一日日躁夜夜躁 | 午夜精品一区二区三区在线播放 | 欧美色图第一页 | 一级黄色性视频 | 91蝌蚪九色 | 亚洲欧美电影 | 欧亚成人av | 在线观看亚洲 | 一区二区三区视频网 | 亚洲高清影院 | 亚洲成人黄色网 | 全黄一级播放 | 黄色片中文字幕 | 久久久久久久一 | 亚洲少妇一区 | 手机av片| 欧美爽妇| 黑人一级女人全片 | 在线观看免费视频黄 | 欧美性生活xxx | 在线免费观看黄网站 | 免费av一级片 |