[ 프로그래머스 - 프론트엔드 개발을 위한 자바스크립트 온라인 스터디 ] 1주차 정리

aeong98·2022년 5월 22일
1
post-thumbnail

1주차 스터디

💡 Vanilla JS 로 DOM 을 조작하고 사용자 정의 생성자 함수로 컴포넌트를 만드는 법에 대한 내용.

✅ 1주차 스터디 요구사항


  1. new 키워드로 함수 생성해서, 파라미터로 data 넘겨 받고
  2. 해당 함수에 파라미터로 넘겨받은 data를 this 키워를 사용해 함수 내 변수로 저장해두기.
  3. 각 data 배열을 순회하며 DOM 을 생성하고, html 안에 렌더링 하기
  4. render() 함수를 호출하여 실행되게 작성하기

BONUS

  • new 키워드로 함수 인스턴스 만들 때 올바른 파라미터 넘어오지 않을 경우 에러 발생
  • new 키워드 안 붙이고 함수 실행 시 에러 발생하게 하기

💁 질문


  • 생성자 함수 내부에 프로퍼티 추가할 때 , this vs prototype 둘 중 어느 것이 좋은지 궁금합니다.
  • class 가 아닌 함수형으로 컴포넌트를 개발했는데, 내부에 프로퍼티로 정의된 함수를 외부에서 접근하지 못하게하는 방법이 있는지 궁금합니다. (예를 들면 제 코드에서 renderItems 나 todoItems 과 같이 외부에서 사용하지 않는 함수를 숨길수 있는 방법이 있는지! )

👩‍💻 관련 개념 정리


1. 사용자 정의 생성함수

  • 자바스크립트에서 객체를 만드는 방법
    • 객체 리터럴 {}
    • new Object() → bad 문제점
    • 생성자 함수
  • 사용자 정의 생성자 함수
    • 생성자 함수 내 this 라는 변수를 참조할 수 잇다.

    • 해당 함수의 프로토타입도 상속 받을 수 있다.
      - prototype 을 통해 속성이나 메서드를 추가할 수 있고, new 객체로 할당받은 인스턴스도 상속을 통해 추가된 속성이나, 메소드를 사용할 수 있다.

      Person.prototype.getName = function() {
        return this.name;
      };
      console.log(p.getName());   // kim 이 출력된다.
  • new 와 함께 생성자 함수를 호출할 경우?

    • 빈 객체가 생성된다. 이 객체는 this 를 사용할 수 있다.
    • this 로 참조되는 객체에 프로퍼티와 메소드가 추가된다.
    • 만약 리턴 값에 다른 객체를 명시하지 않으면 생성자 함수 내에 return 문을 쓰지 않더라도 암묵적으로 return 을 반환하게 되어 있다.
  • new를 빼먹은 경우에?

    • this 는 전역객체를 가르키므로 아래 코드에서 p.name 에서 에러가 나고, 의도치 않은 전역변수가 만들어지면서 값이 저장된다.
var Person = function() {
  this.name = "kim";
};

var p = Person('kim');
console.log(p.name); //error 발생
console.log(window.name); //kim 출력된다.
  • new 없이 동일한 결과를 얻으려면 → 스스로를 호출하는 생성자 패턴으로 해결할 수도 있다.
var Person = function(name) {
	// new 없이 사용했을 경우를 체크해 내부에서 new 를 통해 생성하고 처리.
  if(!(this instanceof Person)) {
    return new Person(name);
  }
  this.name = name;
};

var p = Person('kim');
console.log(p.name);  //kim 가 출력
var p1 = new Person('kim');
console.log(p1.name); //kim 가 출력
  • 객체 리터럴 방식 vs 생성자 함수 방식

    • 객체리터럴
      • 같은 형태의 객체 재생성할 수 없음.
      • 프로토타입 객체 (’proto’)
        • 객체 생성자 함수 Object()
    • 생성자 함수
      • 생성자 함수를 호출할 때, 다른 인자를 넘김으로써 같은 형태의 서로 다른 객체를 생성할 수 있다.
      • 프로토타입 객체 (’proto’)
        • 생성자 함수 자체
  • 객체 생성자 new Object() 의 함정

    • Object() 생성자가 인자를 받을 수 있다.
    • 인자로 전달되는 값에 따라, 생성자 함수가 다른 내장 생성자에 객체 생성을 위임할 수 있고 기대한것과 다른 객체가 반환되기도 한다.
var obj = new Object();
console.log(obj.constructor === Object); //true

// 숫자
var obj = new Object(10);
console.log(obj.constructor === Number) ; //true
obj.toFixed(2); //1.00 이 기록

// 문자열 객체
var obj = new Object("I am a String");
console.log(obj.constructor === String); // true
// 일반적인 객체에는 substring() 이라는 메서드가 없지만 문자열 객체에는 있다.
console.log(typeof obj.substring()); 

// 불린 객체
var obj= new Object(true);
console.log(obj.constructor === Boolean): // true

2. this

this 란

  • this 는 strict mode 와 non-strict mode 에 차이가 있다.
  • this 는 함수를 호출한 방법에 의해 결정된다.
  • ES5 에서 함수를 어떻게 호출했는지 상관하지 않고 this 값을 설정할 수 있는 bind 메서드를 도입했다.
  • ES5 에서부터는 화살표 함수를 통해 렉시컬 컨텍스트 안의 this 값을 유지할 수 있다.

this의 이해

  • this`` 가 어떤 값과 연결되는지는 this` 바인딩을 통해서 확인할 수 있다.
  • 바인딩이란, this 의 호출 방식에 따라 this 각 특정 ‘객체' 에 연결되는 것이다.
  • this 의 바인딩은 일반함수 내부, 메서드 내부, 생성자 함수 내부, call, apply, bind 등을 통한 ‘호출 방식' 으로 나눠서 살펴볼 수 있다.

1. 일반 함수 내부에서의 this 는 글로벌 객체와 바인딩된다.

console.log(this === window); // true;

a = 30;
console.log(window.a); // 30

function x() {
  return this;
}

x() === window; // true

2. 메서드 내부에서의 this 는 메서드를 호출한 객체와 바인딩된다.

let ryan = {
  firstName: "Ryan",
  lastName: "Kim",
  driveCar() {
    console.log(`${this.firstName} drives a car.`)
  }
}

ryan.driveCar(); // 'Ryan drives a car.'

3. 생성자 함수 내부에서 this 는 생성자 함수가 생성할 인스턴스와 바인딩된다.

function person() {
  this.firstName = "Ryan",
  this.lastName = "Kim",
    this.start = function() {
console.log(`${this.firstName} drives a car.`)};
};

let person1 = new person();
console.log(person1); // person { firstName: 'Ryan', lastName: 'Kim', start: ƒ (), __proto__: person { constructor: ƒ person() } }

4. Call, Apply, Bind 메서드 사용시, 메서드 첫 번째 인수로 전달하는 객체에 바인딩 된다.

  • 해당 메서드를 사용해 함수를 ‘실행' 하면 함수의 첫 번째 인자로 전달하는 객체에 this를 ‘바인딩' 할 수 있다.
  • Call
    • Call 을 사용하면, 함수를 실행하고 함수의 첫 번째 인자로 전달하는 값에 this 를 바인딩 한다.
      function logName(a, b, c) {
        console.log(this.name);
        console.log(this.nationality);
        console.log(a + b + c);
      }
      
      const person = {
        name: 'Ryan',
        nationality: 'South Korea'
      }
  • Apply
    • apply 를 사용하면 함수를 실행하고 함수의 첫번째 인자로 전달하는 값에 this 를 바인딩한다.
    • call 과의 차이점은 인자를 배열의 형태로 전달한다는 것이다.
      function logName(a, b, c) {
        console.log(this.name);
        console.log(this.nationality);
        console.log(a + b + c);
      }
      
      const person = {
        name: 'Ryan',
        nationality: 'South Korea'
      }
      
      const nums = [1, 2, 3];
      
      logName.apply(person, nums );
      // 'Ryan'
      // 'South Korea'
      // 6
      
      // 요즘에는 apply를 사용하지 않고, spread operator와 call을 활용한다.
      logName.call(person, ...nums);
      // 'Ryan'
      // 'South Korea'
      // 6
  • Bind
    • bind 는 함수의 첫 번째 인자에 this 를 바인딩한다는 점은 같지만
    • 함수를 실행하지 않고 새로운 함수를 반환한다.
    • 반환된 새로운 함수를 실행해야 원본 함수가 실행된다.
      function logName(a, b, c) { // 원본 함수
        console.log(this.name);
        console.log(this.nationality);
        console.log(a + b + c);
      }
      
      const person = {
        name: 'Ryan',
        nationality: 'South Korea'
      }
      
      const Ryan = logName.bind(person); // 새로운 함수
      
      Ryan(1,2, 3);
      // 'Ryan'
      // 'South Korea'
      // 6

this 정리

  • this 는 함수 호출 방식에 따라서 동적으로 결정된다.
  • 일반 함수로 호출 → 글로벌 객체 가르킴
  • 메서드로 호출 → 이를 호출한 객체를
  • 생성자 함수 → 생성자 함수가 생성할 인스턴스를
  • call, apply, bind 메서드 사용 시 → 메서드 첫 번째 인수로 전달하는 객체에 바인딩

✅ 1주차 질문에 대한 피드백


Q1. 생성자 함수 내부에 프로퍼티 추가할 때 , this vs prototype 둘 중 어느 것이 좋은지 궁금합니다.

A.

  • Prototype 은 인스턴스의 private 속성을 통해 인스턴스 간에 하나의 메서드와 값을 공유합니다.
  • this는 생성자를 호출한 객체에 bind 되어. 인스턴스마다 다른 함수, 값을 가지게 됩니다.

따라서, 결론적으로 각 인스턴스마다 달라져야 하는 값들은 this 를 사용하고, 모든 인스턴스들이 공유해도 되는 경우에는 prototype을 사용하는게 메모리 관리차원에서 좋은 방향일 것 같습니다.

this 와 prototype의 차이

Use of 'prototype' vs. 'this' in JavaScript?

하지만 리드 멘토이신 로토님께서는 this 를 주로 사용하신다는 답변을 받았는데, 아마 다음과 같이, 값을 인스턴스끼리 공유할 경우 변경될 수 있다는 점에서 this 를 사용하는 것이 아닐까하는 것이 나의 추측이다..

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

Q1. class 가 아닌 함수형으로 컴포넌트를 개발했는데, 내부에 프로퍼티로 정의된 함수를 외부에서 접근하지 못하게하는 방법이 있는지 궁금합니다

A. 클로저를 이용한 기법을 많이 사용합니다.

function SomeComponent({ $target }) {
  // renderItems는 외부에서 접근 불가능
  const renderItems = () => {.... }

  // render는 외부에서 접근 가능
  this.render = () => {...}
}

클로저를 이용해서 프라이빗 메소드 (private method) 흉내내기

자바와 같이 몇몇 언어는 메소드를 프라이빗으로 선언할 수 있는 기능을 제공하는데, 이는 같은 클래스 내부의 다른 메소드에서만 그 메소드를 호출할 수 있다는 의미이다.

자바스크립트는 태생적으로는 이런 방법을 제공하지는 않지만 클로저를 이용해, 프라이빗 메소드를 흉내내는것이 가능하다

var counter = (function() {
			// 클로저
      var privateCounter = 0;

			// 클로저
      function changeBy(val) {
        privateCounter += val;
      }
      return {
        increment: function() {
          changeBy(1);
        },
        decrement: function() {
          changeBy(-1);
        },
        value: function() {
          return privateCounter;
        }
      };
    })();

    console.log(counter.value()); // logs 0
    counter.increment();
    counter.increment();
    console.log(counter.value()); // logs 2
    counter.decrement();
    console.log(counter.value()); // logs 1

위의 예제서 privateCounter, changeBY 는 둘다 익명함수 외부에서 접근할 수 없다. 따라서, 이 두개 변수와 함수는 익명 함수에서 반환된 세개의 퍼블릭 함수를 통해서만 접근가능하다.

이런 디자인 패턴을 모듈 패턴 이라고 하는데, 위와 같이 클로저나, 익명함수를 사용해 객체를 리턴하고 변수를 함수 안에 선언하여 보호할 수 있다고 한다.

마무리


Vaniila JS 함수 컴포넌트 기반으로 SPA 를 구현해보며, 놓지고 있었던 부분을 되돌아 볼 수 있는 매우 유익한 시간이었다..
나는 회사에서 Vanilla JS 클래스형으로 SPA 를 구현해본적도 있는데, 막상 과제 요구사항을 제로베이스에서부터 구현해보려니 생각보다 막막한 부분이 많았다. ㅜ^ㅠ 내가 너무 리액트라는 편리한 프레임워크에 익숙해져 있던 탓이었겠지!

참고

profile
프린이탈출하자

0개의 댓글