JS 기술면접 5주차(0)

강연주·2024년 12월 10일

🙋‍♀️ 기술면접

목록 보기
78/112

Promise

비동기 작업이 최종적으로 성공했는지 실패했는지를 나타내는 객체.

  • 모듈화 : 독립적인 함수로 분리한다고 생각하면 되는데, 여기저기 엮여있기 보다는 분리해서 사용하기 용이함.

📲 Promise와 then()/catch()를 사용한 경우

  • 장점
  1. 체이닝으로 여러 비동기 작업을 단계적으로 나눌 수 있다.
  2. 각 .then()에서 반환 값을 통해 다음 단계로 데이터를 넘길 수 있으므로 논리적인 흐름을 분리하기 쉽다.
  3. 개별적인 .then() 블록을 함수로 분리하면 재사용성과 가독성 향상.
🖥️ javascript

function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve("data"), 1000);
    });
}

function processData(data) {
    return `${data} processed`;
}

fetchData()
    .then(processData)  // 모듈화된 함수 호출
    .then(result => console.log(result))
    .catch(error => console.error(error));
  • 단점
  1. .then() 체인이 길어지면 코드 가독성이 떨어질 수 있다.
  2. 에러 처리가 각 .then() 블록에서 제대로 이루어지지 않으면 디버깅 어려움.

🎛️ async/await를 사용한 경우

  • 장점
  1. 동기 코드처럼 작성할 수 있어 가독성이 좋아지고 논리 흐름을 이해하기 쉬워진다.
  2. 함수 내부에서 비동기 작업을 try/catch로 에러를 처리할 수 있으므로 에러 핸들링이 단순, 명확하다.
  3. 모듈화된 함수를 호출하고 결과를 변수로 저장하기 쉬워서 재사용성이 높다.
🖥️ javascript

async function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve("data"), 1000);
    });
}

function processData(data) {
    return `${data} processed`;
}

async function main() {
    try {
        const data = await fetchData();
        const result = processData(data);  // 모듈화된 함수 호출
        console.log(result);
    } catch (error) {
        console.error(error);
    }
}

main();
  • 단점
  1. 모든 비동기 작업을 await로 처리하려면 함수가 async여야 한다.
  2. 비동기 함수의 반환값이 Promise라는 점은 그대로 유지되므로
    이를 명확히 이해할 필요가 있다.

🔖 모듈화 관점에서 비교

  • Promise와 then()/catch()
    : 함수 호출 체인을 분리하고 재사용성을 확보하는 데 적합하다.
    하지만 체인 길이가 길어지거나 복잡한 로직을 포함하면
    모듈화의 장점이 가려질 수 있다.

  • async/await
    : 코드 흐름이 간결하고 직관적이므로 함수 단위로 분리하기 용이하다.
    가독성과 유지보수 측면에서 더 유리하며, 복잡한 비동기 작업에서도 코드의 명확한 모듈화 가능.

➡️ 단순한 비동기 작업이라면 Promise와 .then()도 충분히 모듈화가 가능하다. 그러나 가독성과 복잡한 작업의 처리가 필요한 경우, async/await이 모듈화와 유지보수 측면에서 더 적합하다.


Class

문법적 설탕으로서 프로토타입 체인 간소화

클래스 문법이 프로토타입 체인을 제거하거나 대체하지는 않는다.
이는 프로토타입 기반의 문법 위에 동작하는 문법적 설탕(Syntactic Sugar)이라고 보면 된다. 즉, 프로토타입 기반의 동작을 간결하고 직관적으로 표현하도록 설계된 문법.

  • 기존의 프로토타입 기반 문법
🖥️ javascript

function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function () {
    console.log(`${this.name} makes a noise.`);
};

const dog = new Animal("Dog");
dog.speak(); // Dog makes a noise.

  • ES6 클래스 문법
🖥️ javascript

class Animal {
    constructor(name) {
        this.name = name;
    }
    speak() {
        console.log(`${this.name} makes a noise.`);
    }
}

const dog = new Animal("Dog");
dog.speak(); // Dog makes a noise.

캡슐화와 데이터 보호

ES6 클래스에서는 진정한 의미의 캡슐화와 데이터 보호(즉, private 필드) 기능이 없었으나, 이후 추가된 private 필드 문법(ES2022, #으로 선언)을 통해 가능해졌다.
➡️ 캡슐화와 데이터 보호는 이후 추가된 private 필드 문법으로 가능하며, 상속은 ES6 클래스의 핵심 기능 중 하나이다.

🖥️ javascript

class Example {
    #privateField = 0; // private 필드

    increment() {
        this.#privateField++;
    }

    getPrivateField() {
        return this.#privateField;
    }
}

const example = new Example();
console.log(example.#privateField); // Error: Private field '#privateField' must be declared in an enclosing class
example.increment();
console.log(example.getPrivateField()); // 1

필드, 메서드

필드는 클래스의 상태를 저장하는 변수. ES6 클래스에는 필드 선언이 포함되지 않았으나, 이후 public, private 필드 문법이 도입됨.

🖥️ javascript

class Person {
    name = "John"; // public field
    #age = 30;     // private field
}

메소드는 클래스의 동작(로직, behavior)을 정의하는 함수이다. 클래스 메소드는 프로토타입에 연결되며, 모든 인스턴스가 공유한다.

🖥️ javascript

class Calculator {
    add(a, b) {
        return a + b;
    }
}

const calc = new Calculator();
console.log(calc.add(2, 3)); // 5

클래스의 인스턴스화

클래스는 객체를 생성하기 위한 템플릿이며, new 키워드를 사용하여 인스턴스를 생성. 클래스를 사용하여 새로운 객체를 생성하는 과정을 인스턴스화라고 하며, 생성된 객체는 클래스의 구조와 동작을 가진다.

🖥️ javascript

class Animal {
    constructor(name) {
        this.name = name;
    }
}

const cat = new Animal("Cat"); // Animal의 인스턴스
console.log(cat.name); // "Cat"

getter, setter

getter와 setter는 객체의 프로퍼티에 접근하거나 값을 설정하는 간접적인 방법을 제공하며, 이를 통해 데이터 검증, 로깅 등의 추가 로직을 구현할 수 있다.

  • Getter : 프로퍼티 값을 가져올 때 호출되는 함수.
  • Setter : 프로퍼티 값을 설정할 때 호출되는 함수.
🖥️ javascript

class User {
    constructor(name) {
        this._name = name; // 내부적으로 사용하는 변수
    }

    get name() {
        return this._name;
    }

    set name(newName) {
        if (newName.length < 3) {
            console.log("Name must be at least 3 characters.");
        } else {
            this._name = newName;
        }
    }
}

const user = new User("Alice");
console.log(user.name); // Alice
user.name = "Jo"; // Name must be at least 3 characters.
user.name = "John";
console.log(user.name); // John

➡️ JavaScript 클래스는 프로토타입 기반의 동작을 더 간결하게 표현하며, 상속, 캡슐화, 데이터 보호 등의 객체지향 프로그래밍 패턴을 지원한다. 최신 문법인 private 필드와 getter/setter를 활용하면 더욱 구조적이고 안정적인 코드를 작성할 수 있다.


CORS

과거에 브라우저는 보안을 위해 same origin policy를 강제했고
(도메인이 다르면 접근 불가) 다른 도메인에서 리소스에 접근할 수 없는 불편함 때문에 CORS 정책이 탄생했다.
CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)은
다른 소스 간에 리소스 요청을 제어할수 있는 정책으로,
simple request와 pre-flight request의 두 가지 경우로 나뉜다.

단순 요청(simple request)

Simple Request는 CORS 정책에서 추가적인 확인 절차 없이 허용되는 요청 유형이다. 브라우저가 pre-flight 요청 없이 서버로 바로 요청을 보냅니다.

Simple Request로 간주되기 위해 다음 조건을 모두 만족해야 한다.

  • HTTP 메서드가 GET, POST, HEAD 중 하나여야 함.
  • 요청 헤더가 다음에 해당해야 함.
    • Accept
    • Accept-Language
    • Content-Type (단, 값은 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나여야 함)
  • 요청에는 사용자 정의 헤더
    (e.g., Authorization, X-Custom-Header)가 포함되지 않아야 함.
🖥️ javascript

fetch('https://example.com/api/data', {
    method: 'GET',
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));

위 요청은 GET 메서드를 사용하며, 사용자 정의 헤더가 없으므로 Simple Request로 간주됨.


사전 요청(pre-flight request)

Pre-flight Request는 브라우저가 서버로 요청을 보내기 전에
OPTIONS 메서드를 사용하여 요청의 안전성을 확인하는 과정이다.

다음 조건 중 하나라도 충족하면 Pre-flight Request가 발생.

  • HTTP 메서드가 GET, POST, HEAD 이외의 메서드일 때
    (e.g., PUT, DELETE).
  • 요청 헤더에 사용자 정의 헤더
    (e.g., Authorization, X-Custom-Header)가 포함된 경우.
  • Content-Type이 application/x-www-form-urlencoded, multipart/form-data, text/plain 중 하나가 아닐 때.

작동 방식

  1. 브라우저는 서버로 OPTIONS 요청을 보낸다.
  2. 이 요청은 실제 요청 전에 서버가 특정 도메인(origin)의 요청을 허용하는지 확인한다.
  3. 서버는 응답 헤더를 통해 CORS 설정을 브라우저에 알린다.
  4. 브라우저가 서버의 응답을 확인한 후, 허용된 경우에만 실제 요청을 보낸다.
🖥️ javascript

fetch('https://example.com/api/resource', {
    method: 'PUT',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer token'
    },
    body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));

위 요청은 다음 조건을 충족하므로 Pre-flight Request가 발생한다.

  • HTTP 메서드가 PUT.
  • 요청 헤더에 Authorization(사용자 정의 헤더)이 포함됨.
  • Content-Type이 application/json으로 Simple Request의 조건을 벗어남.

Pre-flight Request의 실제 흐름

  1. 브라우저가 OPTIONS 요청을 보냄
🖥️ http

OPTIONS /api/resource HTTP/1.1
Host: example.com
Origin: https://your-website.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
  • Origin : 요청을 보낸 도메인.
  • Access-Control-Request-Method : 실제 요청에서 사용할 HTTP 메서드.
  • Access-Control-Request-Headers : 요청에 포함될 헤더.
  1. 서버가 응답을 보냄
🖥️ http

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://your-website.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
  • Access-Control-Allow-Origin : 요청을 허용하는 출처(origin).
  • Access-Control-Allow-Methods : 허용되는 HTTP 메서드.
  • Access-Control-Allow-Headers : 허용되는 요청 헤더.
  • Access-Control-Max-Age : 사전 요청의 유효기간(초 단위).
  1. 브라우저가 서버 응답을 확인하고 실제 요청을 보냄
🖥️ http

PUT /api/resource HTTP/1.1
Host: example.com
Origin: https://your-website.com
Authorization: Bearer token
Content-Type: application/json

{"key": "value"}

profile
아무튼, 개발자

0개의 댓글