[JS 개념정리] bind() 그리고 this

rlorxl·2022년 10월 15일
1

Javascript

목록 보기
12/12

bind

bind가 호출되면 새로운 함수를 생성한다. 받게되는 첫 인자의 value로는 this 키워드를 설정하고, 이어지는 인자들은 바인드된 함수의 인수에 제공된다.

  • 첫 인자의 value로는 this 키워드를 설정 → this키워드를 특정 this값으로 고정시킴
  • 이어지는 인자들은 바인드된 함수의 인수에 제공된다. → 함수에 초기 인수를 설정할 수 있음.

1. 메서드 호출 시 this유지

  • bind의 기본적인 사용법은 호출 방법과 관계없이 특정 this값으로 호출되는 함수를 만드는 것이다.

  • 객체 메서드가 객체 내부가 아닌 실제 호출되는 곳에서 사용될 때 본래의 this를 잃어버리는데 bind를 사용하면 this를 명시적으로 고정시킬 수 있다.

    👉 이게 무슨 말인지 알기 위해서는 일단 this가 무엇인지부터 알아야한다. 🤔


this 키워드

  • this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다.
    this는 객체 메서드 내부에서 쓰이면서 자신이 속한 객체를 가리키는 식별자가 되고, 생성자 함수를 정의하는 시점에 사용되어 생성자 함수가 생성할 인스턴스를 가리키는 특수한 식별자가 된다.
  • this는 상황에 따라 가리키는 대상이 달라진다.
    this바인딩은 함수 호출 시점에 결정되고 this가 가리키는 값, 즉 this바인딩은 함수 호출 방식에 의해 동적으로 결정된다.
호출되는 곳this바인딩
전역window(global object)
일반, 중첩, 콜백 함수window(global object)
객체 내부 메서드메서드를 호출한 객체
생성자 함수(미래에)생성할 인스턴스
화살표 함수상위 스코프의 this

📍 this를 잃어버린다는 것은 함수 호출 시점에 this바인딩이 결정되기 때문에 this가 가리키는 대상이 달라질 수 있다는 것을 의미한다.


객체 메서드 내부의 this

메서드 내부의 this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩된다.

const person = {
	name: 'Lee',
  	getName() {
    	return this.name
    }
}

이런 객체가 있을 때 person.getName()으로 호출하면 'Lee'가 나올것이다.

const anotherPerson = {
	name: 'Kim'
}

anotherPerson.getName = person.getName
console.log(anotherPerson.getName()) // Kim

anotherPerson이라는 다른 객체를 생성하고 getName메서드를 새로 생성한 객체의 메서드로 할당한 뒤 호출하면 getName메서드 내부의 this는 anotherPerson에 바인딩된다.

// error
let getMyname = person.getName
console.log(getMyname()) // Cannot read properties of undefined(reading 'name')

// OK
let getMyname = person.getName.bind(person) // Lee

📍 WHY???
메서드는 객체에 포함된 것이 아닌 독립적으로 존재하는 별도의 객체로 봐야한다. getName메서드는 person객체의 프로퍼티로 존재하며 함수 객체를 가리키고 있을 뿐이다.
그래서 메서드는 다른 객체의 프로퍼티에 할당하는 것으로 다른 객체의 메서드가 될 수도 있고 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.

bind메소드를 사용하면 this키워드를 고정시켜 호출하도록 도와준다.


let user = {
  firstName: "John"
};

function func() {
  alert(this.firstName);
}

여기서 this가 가리키는 대상은 window이기 때문에 firstName을 알 수 없어 undefined가 나온다.

func.bind(user)()

bind로 this가 user를 가리키게하면 this.firstName은 John이 된다.
bind메서드는 첫 번째 인수로 전달한 user로 this바인딩이 교체된 함수를 새롭게 생성한다.
(bind는 함수를 호출하지는 않기 때문에 명시적으로 호출해야 한다.)

let newFunc = func.bind(user)
newFunc()

아니면 이런식으로 호출할 수 있다.

let user = {
  firstName: "John"
};

function func(phrase) {
  alert(phrase + ", " + this.firstName);
}

func("Hello"); // this가 undefined

마찬가지로 this를 찾을 수 없다.

let funcUser = func.bind(user);
funcUser("Hello");

// func.bind(user)("Hello"); 이렇게 써도 똑같다.

bind로 해결 🥳


2. 초기 인수가 있는 함수 만들기

bind()는 미리 지정된 초기 인수가 있는 함수를 만든다. 초기 인수를 사전에 구성하여 바인딩 된 함수에 전달되면 바인딩 된 함수가 호출될 때마다 지정한 인수가 대상 함수의 첫번째 인수로 사용된다.

function addArguments(arg1, arg2) {
  return arg1 + arg2
}

// 첫 번째 인수를 지정하여 함수를 생성.
var addThirtySeven = addArguments.bind(null, 37);

var result2 = addThirtySeven(5); // 37 + 5 = 42

// 두 번째 인수는 무시된다.
var result3 = addThirtySeven(5, 10); // 37 + 5 = 42

커스텀 훅에서 매개변수로 받은 함수에 초기 인자를 넘겨주는 예제.

처음에 보고 너무너무 헷갈렸던 ...

// custom Hook
const useHttp = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  const sendRequest = useCallback(async (requestConfig, applyData) => {
    setIsLoading(true);
    setError(null);
    try {
      const response = await fetch(requestConfig.url, {
        method: requestConfig.method ? requestConfig.method : 'GET',
        headers: requestConfig.headers ? requestConfig.headers : {},
        body: requestConfig.body ? JSON.stringify(requestConfig.body) : null,
      });

      if (!response.ok) {
        throw new Error('Request failed!');
      }

      const data = await response.json();
      applyData(data); // ***
    } catch (err) {
      setError(err.message || 'Something went wrong!');
    }
    setIsLoading(false);
  }, []);
  return {
    isLoading,
    error,
    sendRequest,
  };
};

export default useHttp;

// NewTask
const NewTask = (props) => {
  const { isLoading, error, sendRequest: sendTaskRequest } = useHttp();

  const createTask = (taskText, taskData) => {
    // taskData는 커스텀훅에서 전달된 인수.
    const generatedId = taskData.name; 
    const createdTask = { id: generatedId, text: taskText };

    props.onAddTask(createdTask);
  };

  const enterTaskHandler = async (taskText) => {
    sendTaskRequest(
      {
        url: 'https://react-http-853e8-default-rtdb.firebaseio.com/tasks.json',
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: { text: taskText },
      },
      createTask.bind(null, taskText) // ***
    );
  };

  return (
    <Section>
      <TaskForm onEnterTask={enterTaskHandler} loading={isLoading} />
      {error && <p>{error}</p>}
    </Section>
  );
};

export default NewTask;

createTask는 커스텀훅 내에서 applyData로 호출되는데 applyData는 json데이터 하나만을 인수로 가지고 있다. 실제 applyData가 호출될 때 taskText도 가지고 호출될 수 있도록 bind로 첫 번째 인수를 지정하여 함수를 생성한다. (createTask를 enterTaskHandler외부에 두었기 때문에 taskText를 직접적으로 받을 수 없음!)

추가: props로 함수 전달할 때 인자 넘기기

함수를 props로 전달할 때 인자를 같이 넘기기 위해 화살표 함수를 사용하거나 다른 방법을 사용해야 하는데
bind()는 인자를 전달하면서 새로운 함수를 반환하기 때문에 아래와 같이 사용할 수 있다.

<Component props={func.bind('item')} />
profile
즐겜하는거죠

0개의 댓글