ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 스코프와 바인딩
    Javascript 2024. 4. 9. 16:23

    렉시컬(정적) 스코프

    • 변수나 상수가 코드상 어디에서 지정되었는가에 따라 그 사용 범위를 결정
    • 함수가 코드상 어디에서 정의되었는가에 따라 그 상위 스코프를 결정
    • 호출한 곳을 기준으로 하는 동적 스코프 dynamic scope 와 상반되는 개념
    const x = 1;
    const y = 1;
    const z = 1;
    
    function func1 () {
      const y = 2;
      const z = 2;
    
      console.log('2', x, y, z); //2 1 2 2 
      func2();
    }
    
    function func2 () {
      const z = 3; 
    
      console.log('3', x, y, z); //3 1 1 3
    }
    
    
    console.log('1', x, y, z) //1 1 1
    func1();
    • 정의된 블록을 기준으로 상위 스코프의 값이 사용됨
    const x = 1;
    const y = 1;
    const z = 1;
    
    function func1 () {
      const y = 2;
      const z = 2;
    
      function func2 () {
        const z = 3;
    
        console.log('3', x, y, z);  //3 1 2 3
      }
    
      console.log('2', x, y, z); //2 1 2 2
      func2();
    }
    
    console.log('1', x, y, z) //1 1 1 1
    func1();

     

    렉시컬 환경

    • 전체 문서, 함수, 블록을 실행하기 전 만들어지는 내부 객체
    • 각 스코프의 고유 값들과 외부 스코프에 대한 참조를 포함
    const x = 1;
    const y = 1;
    const z = 1;
    
    function func1 (a) {
      const y = 2;
      const z = 2;
    
      function func2 (b) {
        const z = 3;
    
        console.log('3', x, y, z, b); //3 1 2 3 2
      }
    
      console.log('2', x, y, z, a); //2 1 2 2 1 
      func2(a + 1); 
    }
    
    console.log('1', x, y, z) //1 1 1 1
    func1(1);

     

    클로저

    • 내부 함수에서 외부 함수의 값에 접근할 수 있다는 개념 .. 함수 중첩시
      => 반환된 내부함수가 자신이 선언됐을 때의 환경인 스코프를 기억하여 자신이 선언됐을 때의 환경 밖에서 호출되어도 그환경에 접근할 수 있는 함수 : 자신이 생성될 때의 환경(렉시컬 스코프)를 기억하는 함수
    function func1 () {
      const word = 'Hello';
    
      function func2 () {
        console.log(word);
      }
      
      return func2;
    }
    
    const logHello = func1(); //func1안의 함수인 func2가 반환되어 저장
    //func1의 실행이 끝났음에도 해당 스코프 내의 값이 살아있음
    //func2와 func1가 선언된 환경(func1의 스코프)의 조합
    
    logHello();

     

    예시

    function createCounter (start) {
      let num = start;
    
      return function () {
        console.log(++start);
        return start;
      }
    }
    
    const count = createCounter(10);

     

    this

    • 기본적으로 자신이 속한 곳을 가리킴 - 문맥 context
    • 💡 함수의 호출 방식에 따라 가리키는 바가 달라짐 - 자바스크립트 특성

     

    1. 전역에서의 this : 전역 객체란 node 환경에서는 global 객체, 브라우저에서는 window 객체
    2. 함수안에서의 this : 여전히 전역 객체를 참조, 단 엄격모드에서는 함수안에서의 this = undefined

        3. 메서드에서의 this : 객체의 메서드에서 this호출시 this는 그 객체를 참조

    const obj = {
        name: "John",
        doSomething: function() {
            console.log(this.name)
        }
    }
    
    obj.doSomething() // John

    함수는 특정 기능을 수행하는 독립된 코드블록을 의미 : function이나 화살표 함수

    메서드는 함수의 하위 개념. 객체의 프로퍼티(property)에 함수가 할당될 때 그 함수를 method라 정의

     

    - 개별적으로 선언한 함수를 객체의 메서드로 설정한 경우

    function sayName() {
        console.log(this.name)
    }
    
    const obj = {
        name: "John",
        doSomething: sayName
    }
    
    const obj2 = {
        name: "Chris",
        doSomething: sayName
    }
    
    obj.doSomething(); // John
    obj2.doSomething(); // Chris

     

    sayName 함수를 선언하고 두 객체의 메서드로 각각 전달하였습니다. 여기서 두 개의 메서드에서의 this는 각각 obj1과 obj2를 참조

    외부 함수 sayName만 보면 위의 첫 번째로 살펴본 내용(1. 함수 안에서의 this)과 같이 this가 전역 객체를 참조할 것 같지만, 자바스크립트의 this는 런타임 시점에서 결정되기 때문에 메서드로 할당되는 순간 해당 객체를 참조

     

    5. 객체안에서의 this

    //객체 리터럴 - 해당 객체를 가리킴
    const obj = {
      x: 123,
      getX: function () {
        return this.x;
      }
    }
    
    console.log(obj.getX());
    
    //생성자 함수 - 생성될 인스턴스를 가리킴
    function Person (name, age) {
      this.name = name;
      this.age = age;
      this.introduce = function  () {
        return `저는 ${this.name}, ${this.age}세입니다.`
      }
    }
    
    console.log(
      new Person('홍길동', 20).introduce()
    );
    
    //클래스 선언 - 생성될 인스턴스를 가리킴
    class YalcoChicken {
      constructor (name, no) {
        this.name = name;
        this.no = no;
      }
      introduce () {
        return `안녕하세요, ${this.no}호 ${this.name}점입니다!`;
      }
    }
    
    console.log(
      new YalcoChicken('강남', 17).introduce()
    );

     

    this의 동적바인딩 :

    • this가 가리키는 대상이 함수의 호출 주체 또는 그 방식에 따라 달라짐
    const korean = {
      favorite: '김치',
      makeStew: function (isHot, pots) {
        return `${isHot ? '매운' : '순한'} ${this.favorite}찌개 ${pots}냄비`;
      }
    };
    
    const italian = {
      favorite: '피자'
    };
    
    console.log(
      korean.makeStew(true, 1) //매운 김치찌개 1냄비
    );
    
    // 이탈리아인에게 한국인이 찌개 끓이는 법을 알려줌
    italian.makeStew = korean.makeStew;
    
    console.log(
      italian.makeStew(false, 2) // 순한 피자찌개 2냄비
    );
    
    //함수가 누가, 어떻게 호출되었는가에 따라 this가 가리키는 대상이 달라짐

     

    해결방법들 : call, apply, bind

     

    1. call : this의 대상과 인자들을 나열

    console.log(
      italian.makeStew.call(korean, false, 2)
    ); //순한 김치찌개 2냄비

     

    2. apply : this의 대상 뒤의 인자들을 배열로

    console.log(
      italian.makeStew.apply(korean, [false, 2])
    ); //순한 김치찌개 2냄비

     

    3. bind : this의 대상이 동적으로 변하지 않는 함수를 반환

    // ⭐ this가 바인딩된 새 함수를 만듦
    italian.makeRightStew = korean.makeStew.bind(korean);
    
    console.log(
      italian.makeRightStew(false, 2) //순한 김치찌개 2냄비
    );
    
    // 💡 추가 인자들까지 바인딩 가능 
    italian.makeClassicStew = korean.makeStew.bind(korean, true, 1);
    
    console.log(
      italian.makeClassicStew() //매운 김치찌개 1냄비
    );

     

    4. 바인딩된 함수를 내보내는 함수

    const korean = {
      favorite: '김치',
      makeStew: function (isHot, pots) {
        return `${isHot ? '매운' : '순한'} ${this.favorite}찌개 ${pots}냄비`;
      },
      teachMakingStew: function () {
        return this.makeStew.bind(this);
      }
    };
    
    const italian = {
      favorite: '피자'
    };
    
    italian.makeStew = korean.teachMakingStew();
    
    console.log(
      italian.makeStew(false, 2) //순한 김치찌개 2냄비
    );

     

    5. 생성자 함수일 경우 - 함수 자체를 미리 인스턴스에 바인딩

    function Korean () {
      this.favorite = '김치';
      this.makeStew = function (isHot, pots) {
        return `${isHot ? '매운' : '순한'} ${this.favorite}찌개 ${pots}냄비`;
      };
    
      // 💡 여기서 바인딩을 고정시켜버림
      this.makeStew = this.makeStew.bind(this);
    }
    
    function Italian () {
      this.favorite = '피자';
    }
    
    const korean = new Korean();
    const italian = new Italian();
    
    italian.makeStew = korean.makeStew;
    
    console.log(
      italian.makeStew(false, 2) //순한 김치찌개 2냄비
    );

     

    - 배열 메서드의 thisArg

    • 콜백으로 주어진 함수 내에서 this가 가리킬 대상
    • 보통 콜백함수 다음 인자로 넣음 

     

    this의 정적바인딩

    const obj = {
      // function 선언 함수
      func1: function () { return true; },
    
      // 메서드
      func2 () { return true; },
    
      // 화살표 함수
      func3: () => true
    }
    
    console.log(
      obj.func1(), //true
      obj.func2(), //true
      obj.func3() //true
    );

    function 선언 함수만 생성자로 활용 가능 - 더 많은 기능이 있고 무겁다는 의미

     

    -  화살표 함수에서의  this : 객체의 메서드로 화살표 함수를 전달하면 this는 그 객체를 참조하지 않고 상위컨텍스트 참조

    1. 객체 리터럴에서

    const obj = {
        name: "John",
        doSomething: () => {
            console.log(this)
        }
    }
    
    obj.doSomething()
    //Window {window: Window, self: Window, document: document, name: "", location: Location, …}

     

    따라서 예시에서처럼 객체 외부의 컨텍스트를 그대로 이용하고 싶은 경우에는 화살표 함수를 사용

    • function 함수나 메서드의 동적 바인딩과 다르게 동작
    • 함수가 어디서 선언되었는가에 따름 - ⭐️ 가장 근접한 상위 스코프에 바인딩 고정
    •  this를 정적으로 바인딩

    2. 생성자 함수와 클래스에서

    • 기본적으로는 가리키는 대상이 동일 (해당 인스턴스)
    • ⭐ 동적으로 바인딩하는 타 방식과의 차이 확인
    function Korean () {
      this.favorite = '김치';
    
      this.makeStew = function (isHot) { // 선언함수 사용
        return `${isHot ? '매운' : '순한'} ${this.favorite}찌개`;
      };
      this.fryRice = (isHot) => { // 화살표함수 사용
        return `${isHot ? '매운' : '순한'} ${this.favorite}볶음밥`;
      };
    }
    
    function Italian () {
      this.favorite = '피자';
    }
    
    const korean = new Korean();
    const italian = new Italian();
    
    console.log(korean.makeStew(true)); //매운 김치찌개 (동적바인딩)
    console.log(korean.fryRice(true)); //매운 김치볶음밥 (정적바인딩)

     

     

     

    - 정적 바인딩의 경우 call, apply, bind의 this인자 무시

    console.log(
      korean.fryRice.call({favorite: '된장'}, true)
    ); 
    console.log(
      korean.fryRice.apply({favorite: '된장'}, [true])
    );
    console.log(
      korean.fryRice.bind({favorite: '된장'}, true)()
    );
    
    // 매운 김치볶음밥

    'Javascript' 카테고리의 다른 글

    [Javascript] 프로토타입과 상속  (0) 2024.04.10
    [Javascript] Prototype 프로토타입  (0) 2024.04.09
    [Javascript] 이터러블, 제너레이터  (0) 2024.04.08
    [Javascript] Set , Map  (0) 2024.04.08
    [Javascript] 클래스 Class  (1) 2024.04.05
Designed by Tistory.