直接実現JS配列高次関数


辞書の知識

  • 高次関数
    引数または戻り関数として関数を渡す関数
    高次関数により、外部状態の変更や可変データを回避し、不変性を指すことができます.
  • コールバック関数
    パラメータによって他の関数内部に渡される関数
  • すなわち,引数に関数がある関数は高次関数である.このように,パラメータに存在する関数はコールバック関数である.そう言ってもいいです.

    高次関数、コールバック関数を使用する理由


    1.関数の効率/可読性を向上させるため


    関数の基本原則は「1つの関数が1つの役割しか担当しない」ということです.
    関数の単位が小さいほど,関数の再利用性が高くなり,関数の名前から関数の挙動を直感的に推定できる.
    次のコードを見てください.
    case 1. 一般関数の使用
    function sum1(a,b){
      console.log(a+b)
    }
    function sum2('결과는 ',a,b){
      console.log(a+b)
    }
    sum1(2,3) // 5 출력
    sum2(2,3) // "결과는 5" 출력
    case 2. コールバック関数の使用
    function sum(a,b,callback){
      return callback(a+b)
    }
    function printData(data){
      console.log(print)
    }
    function printResult(data){
      console.log('결과는 ',data)
    }
    sum(2,3,(data)=>console.log(data)) // 5 출력
    sum(2,3,printData) // 5 출력
    sum(2,3,printResult) // "결과는 5" 출력
    case 1などの場合、sum 1関数は2つの出力と加算計算を担当します.
    現在のコード量は少なく,あまり差はないが,以降コード長が長ければ長いほど担当する部分が多くなるほど結合度が高くなる.
    高い結合度は関数を柔軟にしない.
    関数の柔軟性とはどういう意味ですか.case 2は代表的な例である.case 2ではsum関数は演算のみを担当し、残りのprintData、printResult関数は出力のみを担当する.
    このようにキャラクタを分離すると、コールバック関数を変更するだけでsum結果を任意に処理できます.
    すなわち,関数が柔軟になり,再利用性が向上する.

    2.非同期処理


    コールバック関数は、非同期処理にもよく使用されます.
    case 1
    function first(){
      setTimeout(() => {
        console.log("first")
      }, 300)
    }
    function second(){
      console.log("second")
    }
    frist()
    second() // "second" 출력 => "first" 출력
    case 2
    function first(callback){
      setTimeout(() => {
        console.log("first")
        callback()
      }, 300)
    }
    function second(){
      console.log("second")
    }
    frist(second) // "first" 출력 => "second" 출력
    case 1frist()関数を明確に実行し、secondを出力する.
    うん.JavaScriptは上から下へ実行されているのではないでしょうか.JavaScriptの非同期処理方式のためsettimeout関数が最も遅く動作しているという疑問が生じるかもしれません.
    したがって、first=>secondのようにコードを順次実行するには、settimeoutのコールバック関数の位置に対応する関数を追加するだけです.

    Array.prototype.高次関数の実装


    今やっと本題に着いた.前述の知識に基づいて,配列高次関数を実現できる.

    Array.prototype.map()


    特長


    元の配列を変更せずにコールバック関数の戻り値からなる新しい配列を返す関数.

    メインインプリメンテーション(thisArgなし)


    CurrentValue、index、array、thisArgの処理が必要です.
    実装する前に、map関数がどのような形式であるかを確認します.
    ['a','b','c','d'].map((el,i,arr)=>console.log(el,i,arr))
    // a 0 ['a','b','c','d']
    // b 1 ['a','b','c','d']
    // c 2 ['a','b','c','d']
    // d 3 ['a','b','c','d']

    実施前の考え方


  • 普段何気なく犯すと、mapはコールバック関数を持つ高次関数だった.
    コールバック関数のパラメータ=>currentValue、index、array

  • mapは巡回配列の関数であるため,クエリが必要である.

  • mapは、新しい配列を返す必要があるため、関数内部に配列を作成し、新しい配列を返す必要がある場合があります.
  • インプリメンテーション

    const customMap = function(callback){
      const arr = [];
      for(let i=0;i<this.length;i++){
        callback(this[i],i,this);
        arr.push(this[i]);
      }
      return arr;
    }
    Array.prototype.customMap = customMap;
    ['a','b','c','d'].customMap((el,i,arr)=>console.log(el,i,arr)) // 기존 map과 같은 결과 나옴

  • 配列内でcallback()関数をループします.これはconsole.log(el,i,arr)部分です.

  • 整列中は、customMap内部のarrにthis[i](ここではcustomMapを呼び出すオブジェクト["a"、"b"、"c"、"d])を加える.
  • 2回目の実施(thisArgを使用)


    初めて実現した時、thisargは参加しませんでした.
    その原因はthisArgへの理解不足であり、ここで説明thisArg完了Array.prototype.map実施.
    thisArg
    コールバックを実行するときに使用する値.
    簡単に言えば、これはthisArgの位置にバインドされたオブジェクト(in callback関数)を意味します.
    より速く理解するために、例を挙げます.
    const user = {name:'jamong',age: 23}
    [1,2,3,4].map(function(){console.log(this)})
    /* Window {0: Window, window: Window, self: Window, document: document, name: '', location: Location, …} 
    ... 출력 */
    thisArgパラメータを指定しない場合、コールバック関数内のthisはグローバルオブジェクトを指します.
    const user = {name:'jamong',age: 23}
    [1,2,3,4].map(function(){console.log(this)})
    /* {name: 'jamong', age: 23} 
    ... 출력 */
    ただし、userをthisArgに入れると、このオブジェクトがthisにバインドされていることを確認できます.

    実施前の考え方


  • このバインドは、Function.prototype.bind()を使用する必要があるようです.

  • コールバック関数内部thisはthisArgにバインドする必要があるため、callback.bind(thisArg)();とともに使用できます.
  • インプリメンテーション

    const customMap = function(callback,thisArg){
      const arr = [];
      for(let i=0;i<this.length;i++){
        callback.bind(thisArg)(this[i],i,this);
        arr.push(this[i]);
      }
      return arr;
    }
    const user = {name: 'jamong'}
    Array.prototype.customMap = customMap;
    ['a','b','c','d'].customMap(function(el,i,arr){console.log(el,i,arr,this)},user) // 기존 map과 같은 결과 나옴
    注意事項
    コールバック関数が화살표 함수で記述されている場合、thisArgは何も実行しません.
    「矢印関数のthisは常に親スキャンのthisを指す」という特徴のため、矢印関数はcall、apply、bindメソッドを使用して変更できません.

    Array.prototype.filter()


    特長


    コールバック関数でテストされたすべての要素を収集し、元の配列を変更せずに新しい配列に戻る関数です.

    インプリメンテーション


    実施前の考え方

  • 前に実装したmapを適用し、テスト結果true/falseに基づいて、trueを配列に入れるだけでよい.
  • インプリメンテーション

    const customFilter = function(callback,thisArg){
      const arr = [];
      for(let i=0;i<this.length;i++){
        callback.bind(thisArg)(this[i],i,this) && arr.push(this[i]);
        
      }
      return arr;
    }
    Array.prototype.customFilter = customFilter;
    
    const user = { name: "b", age: 23 };
    let arr = ['a','b','c','d'].customFilter(function(el,i,arr){return el===this.name},user)
    console.log(arr) // ["b"] 출력
  • this.nameは「b」なのでel===this.nameelが「b」の場合trueです.
    従って,コールバック関数がtrueを返すときにarrに対応する値を追加することを実現した.
  • Array.prototype.forEach()


    特長


    元のアレイの関数を変更せずに、各アレイ要素に対して指定された関数を実行します.
    mapとforeachの最大の違いは、mapメソッドがコールバック関数の戻り値からなる新しい配列を返し、foreachメソッドが常に定義されていない配列を返すことです.
    すなわち,forEachは高次関数の代わりに配列にすぎない.

    インプリメンテーション


    実施前の考え方

  • mapの戻り部分を未定義とするとforeachとなります.
  • インプリメンテーション

    const customForEach = function(callback, thisArg) {
      const arr = [];
      for (let i = 0; i < this.length; i++) {
        callback.bind(thisArg)(this[i], i, this);
        arr.push(this[i]);
      }
      return undefined;
    }
    const user = {name: 'jamong'}
    Array.prototype.customForEach = customForEach;
    ['a','b','c','d'].customForEach(function(el, i, arr) {
      console.log(el, i, arr, this)
    }, user) // 기존 forEach와 출력 동일
  • 高次関数返却部の未定義処理
  • Array.prototype.reduce()


    特長


    元のアレイを変更せずに、アレイ内の各要素に対してreduce関数を実行し、結果値の関数を返します.
    reduce関数
    累進関数
    パラメータ:(計算機、現在値、現在のインデックス、ソース配列)

    インプリメンテーション


    実施前の考え方


  • reduce()のパラメータで
    InitialValueがある場合は、[initialValue,...기존 배열]の最初のインデックスから累積値を求める.
    initialValueがない場合は、

  • コールバック関数の戻り値の累計を続行する必要があります.
  • 第1回実施

    const customReduce = function(callback, initialValue) {
      if(this
      let acc = initialValue ? callback(initialValue, this[0], 0, this) : this[0]
      if(this.l
      for (let i = 1; i < this.length; i++) {
        acc = callback(acc, this[i], i, this);
      }
      return acc;
    }
    
    Array.prototype.customReduce = customReduce;
    
    [0,1,2,3,4].customReduce((acc,cur,i,arr)=>{
      console.log(acc,cur,i,arr)
      return acc + cur},10)
  • reduce関数はaccを内部宣言して積算値として使用する.
  • 初期値は存在するか否かにより初期値が設定されている.
  • 2回目の実施


    最初の実装が完了した後、reduceのMDNドキュメントをもう一度よく読んでみると、異常処理が行われていないところがあることに気づきました.従って、対応する異常処理が追加される.
    const customReduce = function(callback, initialValue) {
      if(this.length===0){
        if(initialValue!==undefined){
          return initialValue
        }else{
          return console.error('Uncaught TypeError: Reduce of empty array with no initial value')
        }
      }
      let acc = initialValue ? callback(initialValue, this[0], 0, this) : this[0]
      if(this.length>1){
        for (let i = 1; i < this.length; i++) {
          acc = callback(acc, this[i], i, this);
        }
      }
      return acc;
    }
    
    Array.prototype.customReduce = customReduce;
    
    [0,1,2,3,4].customReduce((acc,cur,i,arr)=>{
      console.log(acc,cur,i,arr)
      return acc + cur},10)
  • 与えられた配列が空の場合、
  • 初期値がない場合=>タイプエラー発生
  • initialValueがあれば=>コールバックなし기존 배열
  • リファレンス


    https://solveaproblem.dev/javascript-callback-function/
    https://www.daleseo.com/js-async-callback/
    https://ktpark1651.tistory.com/215
    https://tesseractjh.tistory.com/175