call、apply、bind関数

46623 ワード

🙋 Call


.call(受信した値、[optional]が呼び出す関数のパラメータのパラメータ値として、...)

  • callメソッドは、すべての関数に使用でき、特定の値として指定できます.
  • また,継承やsuperなどの手法を用いずに関数を独立してオブジェクトに交換する場合にはcallを用いる.
    function showThisName() {
      // 여기서 this는 window를 가리킨다.
      // window.name은 "" 빈문자열
      console.log(this.name);
    }
    
    showThisName(); // "" 혹은 undefined
    
    const mike = { name: "Mike" };
    
    showThisName.call(mike); // Mike
    
    JavaScript環境では、windowはグローバル変数、ノードであることに注意してください.js環境ではglobalはグローバル変数となる.
    次のコードを見てみましょう!
    let kim = { name: "kim", first: 100, second: 20 };
    let lee = { name: "kim", first: 10, second: 20 };
    
    function sum() {
      return this.first + this.second;
    }
    
    // sum();
    sum.call();
    // 둘은 같다. 그런데, 왜 구지 .call()을 사용해야할까?
    // 모든 함수는 .call()이라는 메소드가 내장되있다.
    sum.call(kim);
    // sum() 안의 this 값은 kim의 this를 가리킨다.
    
    console.log(sum.call(kim));  // 출력값 :  120
    
    // sum()은 kim 이라는 객체의 member가 아니었는데, 
    // call()이라는 특이한 메소드를 통해서, sum()이라는 함수가 kim의 member 메소드가 된 것이다.
    
    console.log(sum.call(lee)); // 출력값 :  30
    
    // .call() 이라고 하는 것은 인자를 몇 개 더 받을 수 있는데
    function sum2(prefix, another = "") {
      return prefix + another + (this.first + this.second);
    }
    
    // prefix의 값이 없기 때문에, NaN
    console.log(sum2.call(kim));
    console.log(sum2.call(lee));
    
    // [필수인자] call()이라는 함수의 첫번째 인자로는 
    // call()을 붙여줄 함수의 내부적으로 this를 뭘로 해줄 것인가가 오고,
    // [optional] 2번째 인자부터는 우리가 호출하려고 하는 함수의 파라미터로 들어갈 인자값들이 들어간다.
    
    console.log(sum2.call(kim, "=> ")); // 출력값 : => 120
    console.log(sum2.call(lee, "두번째 인자 ", "세번째 인자 ")); 
    // 출력값 : 두번째 인자 세번째 인자 30
    
    // call()
    
    // 상속과 super 등을 사용하지 않고 함수를 독립적으로 뺴서 객체에게 사용할때 call을 사용한다.
    
    // call의 유사품으로는 apply가 있는데, 사용법은 거의 비슷하다.
    

    🙋 apply


    apply(オブジェクト、配列)


    applyに入る前に、辞書の知識を整理します.

  • 関数もオブジェクトです.
  • オブジェクトに値が含まれている場合、propertyオブジェクトに関数が含まれている場合はメソッドと呼ばれます.

  • を選択します.コールと同じです.applyという名前の組み込みメソッドがあります.

  • spread演算子、restパラメータが誕生する前にapplyをよく使用します.
  • 次のコードは同じsum()関数の使い方です.
    function sum(arg1, arg2) {
      return arg1 + arg2;
    }
    
    console.log(sum.apply(null, [1, 2])); // 3
    
    console.log(sum(1, 2));  // 3
    
    しかし、なぜapplyを最初のパラメータnullとして使用するのでしょうか.

    なぜapplyを使うのですか?

    function sum() {
      let _sum = 0;
    
      for (const name in this) {
        // for문이 this를 열거하는 과정에서 sum이라는 함수도 더하기를 시도하기 때문에
        // 에러 방지를 위해서 if 절이 필요하다.
        if( typeof this[name] !== 'function' )
        _sum += this[name];
      }
      return _sum;
    }
    
    let o1 = {  val1: 1, val2: 2, val3: 3, sum:sum }
    
    上記のコードから分かるように、o1オブジェクトにはsumの方法がある.すなわち、オブジェクトでsumを使用し、sumを使用する場合は、各オブジェクトにsumをインストールする必要があります.これはかなり面倒です.
    だから、現れたのはapply()です.
    // apply를 사용하는 구체적인 이유
    
    let o1 = { val1: 1, val2: 2, val3: 3 };
    
    let o2 = { v1: 10, v2: 50, v3: 100, v4: 25 };
    
    function sum() {
      let _sum = 0;
      // for in 문으로 this라고 하는 객체의 값들을 하나씩 꺼네서, 더한다.
      // 이 맥락에서 this는 현재 정해져있지 않다.
      // this는 호출 할 때, 정해진다.
      for (const name in this) {
        _sum += this[name];
      }
      return _sum;
    }
    
    // 첫번째 인자로 null 대신에 객체를 짚어넣었다.
    // sum()에서 this = o1 이다.
    console.log(sum.apply(o1));  // 6
    console.log(sum.apply(o2));  //  185
    

    apply(オブジェクト、配列)の使い方

  • callとは異なり、2番目のパラメータ値は配列であり、これらの特徴を利用してのみ適用されます.
  • let obj = { num:2 };
    let obj2 = { num:5 };
    
    const addToThis = function (a,b,c) {
      return this.num + a + b + c;
    };
    
    let arr = [1,2,3, 4];
    console.log( addToThis.apply(obj, arr) ); // 8
    // => addToThis() 는 a,b,c라는 3개의 인자만의 받는다. 
    // 그래서, arr 배열 안에서 첫 3개의 요소만이 obj의 num값과 더해진 것이다.
    
    console.log( addToThis.apply(obj2, arr) ); // 11
    

    applyの使用例


    以下にapplyを用いて配列因子を放出する例を示す.

    例1

    // null을 this로 지정합니다. Math는 생성자가 아니므로 this를 지정할 필요가 없다.
    Math.max.apply(null, [5,4,1,6,2]) // 6 
    
    // spread operator의 도입으로 굳이 apply를 이용할 필요가 없어졌다.
    Math.max(...[5,4,1,6,2]) // 6
    次の例では、Prototypeを使用して実行します.

    例2

    // '피,땀,눈물'을 this로 지정한다.
    ''.split.call('피,땀,눈물', ',')
    
    // 다음과 정확히 동일한 결과를 리턴한다.
    '피,땀,눈물'.split(',')

    より実用的な例

    let allDivs = document.querySelectorAll('div'); // NodeList라는 유사 배열이다.
    // allDivs를 this로 지정한다.
    [].map.call(allDivs, function(el) {
      return el.className
    })
    
    // allDivs는 유사 배열이므로 map 메소드가 존재하지 않는다. 
    // 그러나, Array prototype으로부터 map 메소드를 빌려와 this를 넘겨 map을 실행할 수 있다.
    これでapplyを用いて,オブジェクトはパラメータを受け入れない独立関数を利用できるようになった.
    コール()の使い方や用途も似ています.ただし、call()との違いは、2番目のパラメータとして配列を得ることができるため、オブジェクトでなくても、関数がパラメータを受け入れない場合にその関数の配列形式のパラメータを指定することができることである.
    すなわち,関数の再利用性を最大化するためにapply()が出現した.
    ここまでcallとapplyについてまとめて、以下のようにします.
    this는 누군가에의해 호출되기 전까진 window 를 가리킨다
    그 누군가를 명확히 하기위해 call과 apply라는 메소드를 사용한다
    
    차이점은 apply는 연산에 필요한 인자값을 배열로 받는반면 
    
    call은 단일 인수로 받는다
    
    bob.call(bill, 2,’goodboy’)
    Bob.apply(bill,[1,-/:;])
    
    ではbindは何ですか?

    🙋 bind


    .bind(これは受信する値、[optional]は私たちが呼び出す関数のパラメータのパラメータ値です...)

  • bindはこれを固定するための方法です.
  • コールとかなり混同しているので、両者の違いを整理してみましょう!
    callでは、実行関数のこの値を必要なオブジェクトに置き換えて実行できます.
    bindは、関数を実行するこの値を必要なオブジェクトに固定する新しい関数を作成します.
    call:外部の機能を内部に引っ張って使用できます.
    bind:コピーした親関数に影響を及ぼさずに、新しい関数をクローンして()のオブジェクトに置き換えます.
    let kim = { name: "kim", first: 100, second: 20 };
    let lee = { name: "kim", first: 10, second: 20 };
    
    function sum(prefix, another = "") {
      return prefix + another + (this.first + this.second);
    }
    
    let kimSum = sum.bind( kim, '-> ' );
    console.log( kimSum() );
    
    callとbindは違うようで、
    callは実行時に関数のこの値を変更し、bindはある関数の内部で値が永久に変更された新しい関数を返します.

    call vs bind


    上の説明だけでは足りないので、下のコードを見て理解してください.
    筆者にとって,callとbindの違いは以下のコードで明確に理解できる.
    let obj = { num: 2 };
    
    const addToThis = function (a, b, c) {
      return this.num + a + b + c;
    };
    
    console.log(addToThis.bind(obj, 1, 2, 3)); // function
    
    // bind는 함수를 return한다. => 이 함수는 this값이 obj의 num으로 지정되있다.
    
    // bind로 call과 같은 출력값을 내려면,
    console.log(addToThis.bind(obj, 1, 2, 3)()); // 8
    
    console.log(addToThis.bind(obj)(1, 2, 3)); // 8
    
    console.log(addToThis.call(obj, 1, 2, 3)); // 8
    
    // call은 출력값(결과값)을 return한다.
    
    // bind는 원본에 영향을 주지 않는 독립된 새로운 함수를 return하기 때문에, 
    // 변수 담아뒀다가 필요할 때마다 재사용하는 방식을 많이 쓴다.
    let bound = addToThis.bind(obj);
    console.log(bound(1, 2, 3)); // 8
    
    
    後でreactでは.bind(this)形をよく使いますが、後でreactを整理するときに詳しく調べてみましょう.

    bindを使用するいくつかの有用な例


    bindはcall,applyに比べて多くの比較的有用な使用例がある.まず2つの例を見てみましょう.

    Case 1:イベントハンドラ


    bindは、イベントハンドラがイベントオブジェクトではなく別の値を渡す場合に便利です.次のように仮定します.
    複数のボタンを動的に作成し、イベントハンドラごとに異なる値をバインドする必要がある場合は、を考慮します.
    次の例では、各ボタンをクリックするとalertにユーザ情報が表示されることが望ましい.
    https://codesandbox.io/s/bind-method-hagseub-nlmzi?from-embed
    前述したように、コードを記述する場合は、動的に生成された各ボタンをクリックすると、ユーザーに必要な値が正しく渡されません.(console.logでuserが実際に何を撮ったか確認)
    この場合bindを使用してパラメータを指定できますが、すぐに実行することはできません.この場合、この値は重要ではないのでnullのように何も渡さないことができます.

    Solution 1:

    users.forEach(user => {
      let btn = document.createElement('button');
      btn.textContent = user.name;
      btn.onclick = handleClick.bind(null, user);
      target.appendChild(btn);
    });

    Solution 2:


    bindではなく匿名関数を使用して問題を解決することもできます.
    users.forEach(user => {
      let btn = document.createElement('button')
      btn.textContent = user.name
      btn.onclick = () => {
        handleClick(user)
      }
      target.appendChild(btn)
    });

    case 2: setTimeout


    settimeoutは、遅延が発生した後に関数を非同期で実行する関数です.この関数は、Windowsオブジェクトを常に明示的にこのオブジェクトにバインドする特徴があります.このため、次の問題が発生する可能性があります.

    class Rectangle {
      constructor(width, height) {
        this.width = width
        this.height = height
      }
      
      getArea() {
        return this.width * this.height
      }
    
      printArea() {
        console.log('사각형의 넓이는 ' + this.getArea() + ' 입니다')
      }
      
      printSync() {
        // 즉시 사각형의 넓이를 콘솔에 표시한다
        this.printArea()
      }
      
      printAsync() {
        // 1초 후 사각형의 넓이를 콘솔에 표시한다
        setTimeout(this.printArea, 2000)
      }
    }
    
    let box = new Rectangle(40, 20)
    box.printSync() // '사각형의 넓이는 800 입니다'
    box.printAsync() // 에러 발생!
    これがRectangleのインスタンスではないことをエラーで確認できます.
    Uncaught TypeError: this.getArea is not a function
        at printArea (<anonymous>:12:36)

    n.問題

  • の場合、この値はいくらですか?printArea関数の導入部分にconsole.log(this)を追加し、直接検証します.
  • bindを使用してこの問題を解決できます.printasyncセクションを次のように変更します.

    Solution 1:

    printAsync() {
      // 1초 후 사각형의 넓이를 콘솔에 표시한다
      setTimeout(this.printArea.bind(this), 2000)
    }
    矢印関数を入力します.次に、正常に動作している例を示します.矢印関数のthisに何か違いがあるか知っていますか.

    Solution 2:

    printAsync() {
      // 1초 후 사각형의 넓이를 콘솔에 표시한다
      setTimeout(() => {
        this.printArea()
      }, 2000)
    }

    🙋 call apply bindクリーンアップ


    - call()



    =>callは、1番目のパラメータとしてthisの値を指定するオブジェクトを受信し、2番目のパラメータから追加するパラメータを追加できます.
    =>callは、関数を直ちに実行し、対応する結果値を返します.

    - apply()



    =>applyはすべての点でcallと同じであるが、添加された因子は配列形態に加えられるべきである.
    =>applyは、関数を直ちに実行し、対応する結果値を返します.

    - bind()



    =>bindは、この値を指定したオブジェクトを最初のパラメータとして受信する.2番目の因子はcallのように入れることができます.

    =>ただし、bindは、関数のreturn値ではなく、最初のパラメータのオブジェクト値がthisであることを指定する独立した新しい関数を返します.
    =>通常、この関数は再使用のために変数に割り当てられ、必要に応じて使用されます.

    資料の出所と参考資料


  • JavaScriptミドルチュートリアル#13 call、apply、bind byエンコーディング

  • javaScript call apply and bind

  • JavaScriptオブジェクト向けプログラミング-14.2。生活コード

  • JavaScript関数の呼び出し(1/2):生活コード別アプリケーションの概要

  • JavaScript-呼び出し関数(2/2):適用された生活符号化

  • JavaScriptオブジェクト向けプログラミング-14.3。bindby生活コード

  • React - 16.4. 生活符号化によるイベントbind関数の理解