[JavaScript] 자바스크립트 - (2)

배창민·2025년 11월 20일
post-thumbnail

자바스크립트 핵심 정리 2

스코프·생성자·프로토타입·strict mode·배열


1. 스코프와 변수 키워드 (var, let, const)

1-1. 전역 스코프와 지역 스코프

var x = 'global x';
var y = 'global y';

function outer() {
  var z = "outer's local z";
  console.log(x); // global x
  console.log(y); // global y
  console.log(z); // outer's local z

  function inner() {
    var x = "inner's local x";
    console.log(x); // inner's local x
    console.log(y); // global y
    console.log(z); // outer's local z
  }

  inner();
}

outer();
console.log(x); // global x
// console.log(z); // ReferenceError
  • 전역(global): 코드의 가장 바깥 영역
    → 전역 스코프, 전역 변수는 어디서나 참조 가능
  • 지역(local): 함수 몸체 내부
    → 지역 스코프, 해당 함수와 하위 함수에서만 유효

1-2. 함수 레벨 스코프와 블록 레벨 스코프

// 함수 밖에서 var → 전역 변수
var i = 0;

for (var i = 0; i < 10; i++) {}
console.log(i); // 10
  • var함수 몸체만 지역 스코프로 인정
    → if, for, while 같은 블록은 스코프를 만들지 않음 (함수 레벨 스코프)
  • let, const모든 코드 블록을 스코프로 인정
    → 블록 레벨 스코프

1-3. var의 문제점

  1. 중복 선언 허용

    var msg = '안녕하세요';
    var msg = '안녕히가세요'; // 덮어씀
  2. 함수 레벨 스코프

    var i = 0;
    for (var i = 0; i < 10; i++) {}
    console.log(i); // 10
  3. 변수 호이스팅

    console.log(test); // undefined
    test = '반갑습니다';
    console.log(test); // 반갑습니다
    var test;

    선언이 스코프의 선두로 끌어올려진 것처럼 동작해 흐름을 헷갈리게 만든다.

1-4. let과 const의 특징

  • let

    • 같은 스코프 내 중복 선언 불가
    • 블록 레벨 스코프
    • 호이스팅은 되지만, 초기화 전 접근 시 에러 (Temporal Dead Zone)
  • const

    • 선언과 동시에 초기화 필수
    • 재할당 불가 → 상수 표현에 사용
    • 스코프, 호이스팅 동작은 let과 동일

정리하면, ES6 기준으로는

  • 기본은 const
  • 재할당 필요할 때만 let
  • var는 사용하지 않는 방향이 가장 안전하다.

2. 객체 생성자 함수 (Object Constructor Function)

2-1. Object 생성자와 리터럴

const student = new Object(); // 빈 객체
student.name = '유관순';
student.age = 16;

new Object()로도 객체를 만들 수 있지만, 대부분의 경우 더 간단한 객체 리터럴 {}을 사용한다.

2-2. 사용자 정의 생성자 함수

function Student(name, age) {
  this.name = name;
  this.age = age;
  this.getInfo = function () {
    return `${this.name}(은)는 ${this.age}세이다.`;
  };
}

const student = new Student('장보고', 30);
console.log(student.getInfo());
  • 동일한 구조의 객체를 여러 개 만들어야 할 때
    → 생성자 함수가 훨씬 효율적이다.

2-3. new로 인스턴스가 생성되는 과정

function Student(name, age) {
  // 1. 빈 객체 생성, this에 바인딩
  console.log(this);

  // 2. this에 프로퍼티 추가
  this.name = name;
  this.age = age;

  // 3. this가 자동으로 반환
}
const student = new Student('홍길동', 20);
  • new 없이 호출하면 그냥 일반 함수처럼 동작
  • new.target으로 생성자 호출 여부를 방지할 수 있다.

3. 프로토타입과 상속

3-1. [[Prototype]]과 상속

모든 객체는 숨겨진 프로퍼티 [[Prototype]]을 가진다.
이 값이 다른 객체를 참조하면, 그 객체를 프로토타입(prototype) 이라고 부른다.

const user = {
  activate: true,
  login: function () {
    console.log('로그인 되었다.');
  }
};

const student = {
  passion: true
};

student.__proto__ = user;

console.log(student.activate); // true
student.login();               // 로그인 되었다.
  • student에는 activate, login이 없지만
    프로토타입 user를 통해 상속받는다.
  • 이렇게 상속받은 프로퍼티를 상속 프로퍼티라고 부른다.

페이지 4의 다이어그램을 보면, student → user → Object.prototype → null 순으로 화살표가 이어지는 프로토타입 체인 구조가 그려져 있다. 프로퍼티 검색 시 이 체인을 위로 따라 올라가며 찾는 방식으로 동작한다.

3-2. 프로토타입 체인 특징

const greedyStudent = {
  class: 11,
  __proto__: student
};

console.log(greedyStudent.activate); // user에서 상속
console.log(greedyStudent.passion);  // student에서 상속
  • 체인은 순환 참조가 허용되지 않는다.
  • __proto__에는 객체나 null만 들어갈 수 있다.

프로토타입은 프로퍼티 읽기에만 사용된다.

const student2 = {
  __proto__: user
};

student2.id = 'user01';
student2.login(); // this는 항상 . 앞의 객체, 여기서는 student2
  • 메서드 내부의 this는 프로토타입이 아니라 호출한 객체를 가리킨다.

3-3. for...in 과 hasOwnProperty

for (let prop in student) {
  let isOwn = student.hasOwnProperty(prop);

  if (isOwn) {
    console.log(`자신의 프로퍼티: ${prop}`);
  } else {
    console.log(`상속 프로퍼티: ${prop}`);
  }
}
  • for...in은 상속 프로퍼티도 함께 순회한다.
  • hasOwnProperty직접 가진 프로퍼티인지 구분할 수 있다.

3-4. 함수의 prototype과 constructor

function Student() {}

// 기본 prototype 객체에는 constructor 프로퍼티만 있다.
// Student.prototype = { constructor: Student }

console.log(Student.prototype.constructor === Student); // true

const student = new Student();
console.log(student.constructor === Student); // true
  • 함수는 정의되는 순간 자동으로 prototype 프로퍼티를 가진다.
  • prototype 객체 안에는 기본적으로 constructor가 들어 있고,
    이는 함수 자신을 가리킨다.

3-5. 빌트인 객체의 프로토타입 체인

const obj = {};
console.log(obj.__proto__ === Object.prototype); // true

const num = new Number(100);
console.log(num.__proto__ === Number.prototype);                  // true
console.log(num.__proto__.__proto__ === Object.prototype);        // true
console.log(num.__proto__.__proto__.__proto__);                   // null
  • Object.prototype 위에는 아무 것도 없고, null에서 체인이 끝난다.
  • Number, String, Function 등 내장 객체도 모두 자신의 prototype에 메서드를 저장하고, 그 위에 Object.prototype이 올라가는 구조를 가진다.

페이지 8의 그림은 String.prototype, Function.prototype, Number.prototype이 모두 Object.prototype을 통해 null로 이어지는 구조를 시각적으로 보여준다.

3-6. 현대적인 프로토타입 조작 메서드

const user = { activate: true };

// [[Prototype]]이 user인 객체 생성
const student3 = Object.create(user);
console.log(student3.activate); // true

console.log(Object.getPrototypeOf(student3) === user); // true

Object.setPrototypeOf(student3, {});
console.log(Object.getPrototypeOf(student3) === user); // false
  • Object.create(proto)
  • Object.getPrototypeOf(obj)
  • Object.setPrototypeOf(obj, proto)

__proto__ 직접 사용은 여러 이유로 권장되지 않고, 위 메서드를 사용하는 것이 좋다.


4. strict mode (엄격 모드)

4-1. strict mode가 필요한 이유

function test() {
  // 암묵적 전역
  x = 10;
}
test();
console.log(x); // 10

키워드를 쓰지 않고 값을 대입하면, 의도와 무관하게 암묵적 전역 변수가 만들어질 수 있다.
이런 잠재적인 버그를 줄이기 위해 ES5에서 strict mode가 도입되었다.

4-2. strict mode 적용 방법

// 전역 전체에 적용
'use strict';

function test() {
  x = 10; // ReferenceError
}
function test() {
  'use strict';
  x = 10; // ReferenceError
}
  • 전역의 선두 또는 함수 몸체의 선두에 'use strict';
  • 선두가 아니면 strict mode가 제대로 동작하지 않는다.
  • 서드파티 라이브러리와 섞일 때는 IIFE로 감싸서 적용하는 방식이 많이 사용된다.

4-3. 에러가 되는 사례

(function () {
  'use strict';

  // 1) 암묵적 전역
  x = 1; // ReferenceError

  // 2) 변수/함수/매개변수 삭제
  var y = 1;
  // delete y; // SyntaxError

  // 3) 중복 매개변수
  // function test(a, a) {} // SyntaxError

  // 4) with 사용
  // with ({ x: 1 }) { console.log(x); } // SyntaxError
})();

strict mode에서는 애매한 문법이나 권장되지 않는 기능들을 아예 에러로 막는다.

4-4. 동작 방식이 달라지는 부분

(function () {
  'use strict';

  function test() {
    console.log(this);
  }

  test();      // undefined
  new test();  // test {}
})();
  • 일반 함수로 호출할 때 this는 암묵적으로 전역 객체가 아니라 undefined가 된다.
(function (x) {
  'use strict';
  x = 2;
  console.log(arguments); // [1] 유지
})(1);
  • strict mode에서는 매개변수와 arguments가 더 이상 서로 연동되지 않는다.

5. 배열(array)

5-1. 배열 기초와 생성 방법

배열은 여러 개의 값을 순서대로 나열한 자료구조이다. 요소는 어떤 타입이든 올 수 있다.

// 배열 리터럴
const arr = ['바나나', '복숭아', '키위'];

// 배열 생성자 함수
const arr2 = new Array();        // []
const arr3 = new Array(10);      // 길이만 10
const arr4 = new Array(1, 2, 3); // [1, 2, 3]

// Array.of
const arr5 = Array.of(10);              // [10]
const arr6 = Array.of(1, 2, 3);         // [1, 2, 3]
const arr7 = Array.of('hello', 'js');   // ['hello', 'js']

5-2. 인덱스와 length

const arr = ['바나나', '복숭아', '키위'];

console.log(arr[0]); // 바나나
console.log(arr.length); // 3

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
  • 요소는 0부터 시작하는 인덱스를 가진다.
  • length 프로퍼티에 요소 개수가 들어간다.
  • typeof 배열 → object (별도의 타입이 아니라 객체의 한 종류)

5-3. 자바스크립트 배열의 특징

자바스크립트 배열은 일반적인 저수준 배열과 다르게 동작하는 특수한 객체이다.

console.log(Object.getOwnPropertyDescriptors([1, 2, 3]));
/*
'0': { value: 1, ... },
'1': { value: 2, ... },
'2': { value: 3, ... },
length: { value: 3, ... }
*/
  • 인덱스를 나타내는 문자열 키 + length 프로퍼티를 가진 객체일 뿐이다.
  • 모든 타입의 값이 요소로 들어갈 수 있다.
  • 요소 삽입/삭제는 빠른 편이지만, 진짜 배열처럼 메모리가 연속적이지 않을 수 있다.

5-4. length 조작과 희소 배열

const arr = [1, 2, 3, 4, 5];

arr.length = 3;
console.log(arr); // [1, 2, 3]

arr.length = 10;
console.log(arr); // [1, 2, 3, <7 empty items>]
const sparse = [, 2, , 4];
console.log(sparse.length); // 4
  • length를 줄이면 뒤 요소가 잘린다.
  • 늘리면 빈 칸이 생긴 희소 배열이 된다.
  • 희소 배열은 length와 실제 요소 개수가 일치하지 않을 수 있어 주의가 필요하다.

6. 배열 메서드 정리

배열 인스턴스는 모두 Array.prototype을 상속받고, 여기에 다양한 메서드가 정의된다.

6-1. 검색 관련: indexOf / lastIndexOf / includes

const foodList = ['물회', '삼계탕', '냉면', '수박', '물회'];

foodList.indexOf('물회');        // 0
foodList.indexOf('물회', 1);     // 4
foodList.lastIndexOf('물회');    // 4
foodList.includes('물회');       // true
foodList.includes('삼겹살');     // false
  • indexOf(value, fromIndex?)
  • lastIndexOf(value, fromIndex?)
  • includes(value)

6-2. 앞뒤로 넣고 빼기: push/pop, unshift/shift

const chinese = ['짜장면', '짬뽕', '우동'];

chinese.push('탕수육');   // 뒤에 추가
chinese.pop();            // 뒤에서 제거

const chicken = ['양념', '후라이드', '파닭'];

chicken.unshift('간장');  // 앞에 추가
chicken.shift();          // 앞에서 제거
  • push / pop → 뒷부분에서 스택처럼 사용
  • unshift / shift → 앞부분에서 요소 조작

6-3. 배열 합치기·자르기: concat / slice / splice / join / reverse

// concat
const idol1 = ['아이브', '오마이걸'];
const idol2 = ['트와이스', '에스파'];

const mix = idol1.concat(idol2);
// ['아이브', '오마이걸', '트와이스', '에스파']

// slice(시작, 끝)
const front = ['HTML', 'CSS', 'JavaScript', 'jQuery'];
front.slice(1, 3); // ['CSS', 'JavaScript'], 원본 유지

// splice(index, deleteCount, ...items)
front.splice(3, 1, 'React'); // jQuery 삭제, React 추가

// join
const snacks = ['사탕', '초콜릿', '껌'];
snacks.join('/'); // '사탕/초콜릿/껌'

// reverse
[1, 2, 3].reverse(); // [3, 2, 1]
  • concat은 원본을 건드리지 않고 새 배열을 리턴
  • slice는 잘라낸 부분으로 새 배열을 만들어 리턴
  • splice는 원본 배열을 직접 수정한다.

6-4. 고차 함수들

sort

let numbers = [10, 2, 33, 4];

numbers.sort();              // ['10','2','33','4'] 기준의 문자열 정렬
numbers.sort((a, b) => a - b); // 숫자 오름차순
numbers.sort((a, b) => b - a); // 숫자 내림차순

기본 정렬은 문자열 기준이라 숫자 정렬 시 비교 함수를 넘기는 편이 안전하다.

forEach

numbers = [1, 2, 3, 4, 5];
numbers.forEach(item => console.log(item * 10));
  • 단순 반복에 적합, 반환값은 사용하지 않는다.

map

const types = [true, 1, 'text'].map(item => typeof item);
// ['boolean', 'number', 'string']
  • 각 요소를 변환해 새 배열을 만든다.

filter

const odds = [1, 2, 3, 4, 5].filter(item => item % 2);
// [1, 3, 5]
  • 조건을 통과한 요소만 모아 새 배열을 만든다.

reduce

const nums = [1, 2, 3, 4, 5];

const sum = nums.reduce((prev, cur) => prev + cur);
// 15

const max = nums.reduce((prev, cur) => (prev > cur ? prev : cur));
// 5
  • 배열을 하나의 값으로 접어서 만들어낼 때 사용한다.

some / every

[1, 5, 3, 2, 4].some(item => item > 3);  // true
[1, 5, 3, 2, 4].every(item => item > 0); // true
  • some: 조건을 만족하는 요소가 하나라도 있는지
  • every: 모든 요소가 조건을 만족하는지

find / findIndex

const students = [
  { name: '유관순', score: 90 },
  { name: '홍길동', score: 80 },
  { name: '장보고', score: 70 }
];

students.find(s => s.name === '유관순');
// { name: '유관순', score: 90 }

students.findIndex(s => s.name === '유관순');
// 0

students.filter(s => s.score >= 60);
// 조건을 만족하는 모든 요소 배열
  • find / findIndex → 조건을 만족하는 첫 번째 요소/인덱스만 반환
  • 여러 개를 모으고 싶으면 filter를 사용한다.

마무리

이번 묶음에서 다루는 흐름을 다시 정리하면 다음과 같다.

  1. 스코프와 let/const로 변수의 유효 범위와 안전한 선언 방식 이해
  2. 생성자 함수와 프로토타입으로 객체를 대량으로 만들고, 상속 구조를 재사용하는 방법 이해
  3. strict mode로 애매한 문법을 막고 잠재 버그 줄이기
  4. 배열과 고차 함수들로 데이터를 모으고, 변환하고, 검색하는 패턴 익히기
profile
개발자 희망자

0개의 댓글