객체

앞서 배웠던 7가지 원시형과는 달리 객체는 다양한 데이터를 담을 수 있다. 키로 구분된 데이터 집합이나 복잡한 개체(entity)를 저장 할 수 있다. 객체는 중괄호 { }를 이용해 만들 수 있다. 중괄호 안에는 '키(key) : 값(value)' 쌍으로 구성된 프로퍼티(property)를 여러 개를 넣을 수 있으며, 키엔 문자형, 값엔 모든 자료형이 허용된다.(함수도 값으로 취급되니 가능!) 프로퍼티 키는 '프로퍼티 이름'이라고도 부른다. 객체에서는 키를 통해 프로퍼티를 쉽게 찾을 수 있으며 추가 삭제도 가능하다. 

 

빈 객체 만들기

let user = new Object(); // '객체 생성자' 문법
let user = {};  // '객체 리터럴' 문법

 

객체에 프로퍼티 조회, 추가, 삭제

// 객체 생성
let user = {     // 객체
  name: "John",  // 키: "name",  값: "John"
  age: 30        // 키: "age", 값: 30
};

// 프로퍼티 값 조회
alert( user.name ); // John
alert( user.age ); // 30

// 프로퍼티 추가
user.admin = true;

// 프로퍼티 삭제
delete user.age;

 

[상수 객체는 수정될 수 있다] 

const로 선언된 객체는 수정될 수 있다.

const user = {
  name: "John"
};

user.name = "Pete"; // (*)

alert(user.name); // Pete

 

const는 user의 값을 고정하지만 그 내용은 고정하지 않는다. 그게 그 말이 아닌가 생각이 되는데 아마 const user를 선언하면 참조값이 저장된다. 그러니 user의 참조값은 고정하지만 그 안의 데이터(프로퍼티)를 고정하지 않는 걸로 이해가 된다. 즉, 참조값을 재할당 하는 것은 불가능하다.

 

단축 프로퍼티

 

function makeUser(name, age) {
  return {
    name: name,
    age: age,
    // ...등등
  };
}

let user = makeUser("John", 30);
alert(user.name); // John

 

위 예시의 프로퍼티들은 이름과 값이 변수의 이름과 동일하다. 이렇게 변수를 사용해 프로퍼티를 만드는 경우는 아주 흔한데, 프로퍼티 값 단축 구문(property value shorthand) 을 사용하면 코드를 짧게 줄일 수 있다.name:name 대신 name만 적어주어도 프로퍼티를 설정할 수 있다.

function makeUser(name, age) {
  return {
    name, // name: name 과 같음
    age,  // age: age 와 같음
    // ...
  };
}

 

메서드와 this

결론부터 말하자면 객체의 행동(함수)을 메서드라고 한다. 객체 프로퍼티에 할당된 함수를 메서드라고 부른다.

let user = {
  name: "John",
  age: 30
};

user.sayHi = function() {
  alert("안녕하세요!");
};

user.sayHi(); // 안녕하세요!

 

위 예시에서는 sayHi가 메서드다. 아래와 같은 방식으로 이미 선언된 함수를 이용해서 메서드를 만들수 있다.

let user = {
  // ...
};

// 함수 선언
function sayHi() {
  alert("안녕하세요!");
};

// 선언된 함수를 메서드로 등록
user.sayHi = sayHi;

user.sayHi(); // 안녕하세요!

 

메서드 단축 구문

 

// 아래 두 객체는 동일하게 동작합니다.

user = {
  sayHi: function() {
    alert("Hello");
  }
};

// 단축 구문을 사용하니 더 깔끔해 보이네요.
user = {
  sayHi() { // "sayHi: function()"과 동일합니다.
    alert("Hello");
  }
};

 

위에 처럼 function을 생략해도 메서드를 정의 할 수 있다.

 

this

this키워드를 통해 객체에 접근 할 수 있다. 

let user = {
  name: "John",
  age: 30,

  sayHi() {
    // 'this'는 '현재 객체'를 나타냅니다.
    alert(this.name);
  }

};

user.sayHi(); // John

 

this를 사용하지 않고 외부 변수를 참조해 객체에 접근하는 것도 가능하다.

let user = {
  name: "John",
  age: 30,

  sayHi() {
    alert(user.name); // 'this' 대신 'user'를 이용함
  }

};

 

new 연산자와 생성자 함수

 

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("보라");

alert(user.name); // 보라
alert(user.isAdmin); // false

 

옵셔널 체이닝

옵셔널 체이닝(?.)은 ?. 앞의 평가 대상이 undefined나 null 이면 평가를 멈추고 undefined를 반환한다.

//옵셔널 체이닝 사용하지 않는 경우
let user = {}; // 주소 정보가 없는 사용자

alert( user.address.street ); // street 프로퍼티를 찾을 수 없다는 에러 발생

let user = {}; // 주소 정보가 없는 사용자

alert( user?.address?.street ); // undefined, 에러가 발생하지 않습니다.

 

옵셔널 체이닝은 남용하지 않는게 좋으며 ?.는 존재하지 않아도 괜찮은 대상에만 사용해야 한다. 즉, 필수값이 아닌 값에는 사용해도 괜찮다.

위 예시에서 user는 반드시 있어야 하지만 address는 필수 값이 아니기 때문에 user.address?.street이런식으로 활용하는게 좋다. 이럴 경우 user에 값을 할당하지 않았다면 바로 에러가 뜰거기 때문에 디버깅하기 수월해진다.

숫자형

어림수 구하기

  • Math.floor : 소수점 첫째 자리에서 내림(버림). 3.1은 3, -1.1은 -2가 된다.
  • Math.ceil : 소수점 첫째 자리에서 올림. 3.1은 4, -1.1은 -1이 된다.
  • Math.round : 소수점 첫째 자리에서 반올림. 3.1은 3, 3.6은 4, -1.1은 -1이 된다. 
  • Math.runc : (Internet Explorer에서는 지원 안함) 소수부를 무시. 3.1은 3, -1.1은 -1이 된다.

소수점 n번째 수를 기준으로 어림수를 구하는 방법은 2가지가 있다.

 

1. 곱하기와 나누기

let num = 1.23456;

alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23

 

2. 소수점 n번째 수까지의 어림수를 구한 후 이를 문자형으로 반환해주는 메서드인 toFixed(n)를 사용한다.

    toFixed(n)은 Math.round와 유사하게 가장 가까운 값으로 올림 혹은 버림해준다.

let num = 12.34;
alert( num.toFixed(1) ); // "12.3"

let num = 12.36;
alert( num.toFixed(1) ); // "12.4"

let num = 12.34;
alert( num.toFixed(5) ); // "12.34000", 소수부의 길이를 5로 만들기 위해 0이 추가되었습니다.

 

기타 수학 함수

 

Math.random()

0과 1 사이의 난수를 반환한다.(1은 제외)

alert( Math.random() ); // 0.1234567894322
alert( Math.random() ); // 0.5435252343232
alert( Math.random() ); // ... (무작위 수)

 

Math.max(a,b,c) Math.min(a,b,c)

인수 중 최대/최소값을 반환한다.

alert( Math.max(3, 5, -10, 0, 1) ); // 5
alert( Math.min(1, 2) ); // 1

 

Math.pow(n, power)

n을 power번 거듭제곱한 값을 반환한다.

alert( Math.pow(2, 10) ); // 2의 10제곱 = 1024

 

문자열

JavaScript에서는 글자 하나만(char) 저장할 수 있는 별도의 자료형은 없다. 작은 따옴표와 큰 따옴표는 기능상 차이가 없으며 (`) 백틱과 같은 특별한 기능이있다.

 

백틱(`) 

표현식을 ${}로 감싸고 이를 백틱으로 감싼 문자열 중간에 넣어주면 해당 표현식을 문자열 중간에 쉽게 합입할 수 있다. 이런 방식을 템플릿 리터럴(template literal)이라고 부른다.

function sum(a, b) {
  return a + b;
}

alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.

let guestList = `손님:
 * John
 * Pete
 * Mary
`;

alert(guestList); // 손님 리스트를 여러 줄에 걸쳐 작성함

 

guestList는 엔터(\n) 없이도 여러줄로 표현이 가능하다. 따옴표로 같은 방식으로 표현했을 경우 오류가 난다.

let guestList = "손님: // Error: Invalid or unexpected token
  * John";

 

대.소문자 변경하기

메서드 toLowerCase()와 toUpperCase()는 대문자를 소문자로, 소문자를 대문자로 변경 시켜준다.

alert( 'Interface'.toUpperCase() ); // INTERFACE
alert( 'Interface'.toLowerCase() ); // interface
글자 하나의 케이스만 변경하는 것도 가능합니다.

 

부분 문자열 찾기

str.indexOf(substr, position)

문자열 str의 position에서부터 시작해, 부분 문자열 substr이 어디에 위치하는지를 찾아준다. 원하는 부분 문자열을 찾으면 위치를 반환하고 그렇지 않으면 -1을 반환한다.

let str = 'Widget with id';

alert( str.indexOf('Widget') ); // 0, str은 'Widget'으로 시작함
alert( str.indexOf('widget') ); // -1, indexOf는 대·소문자를 따지므로 원하는 문자열을 찾지 못함

alert( str.indexOf("id") ); // 1, "id"는 첫 번째 위치에서 발견됨 (Widget에서 id)

let str = 'Widget with id';

alert( str.indexOf('id', 2) ) // 12

 

부분 문자열 추출하기

JavaScript엔 부분 문자열 추출과 관련된 메서드가 세 가지 있다. 

 

str.slice(start [, end])

문자열의 start부터 end까지(end는 미포함)를 반환한다.

let str = "stringify";
alert( str.slice(0, 5) ); // 'strin', 0번째부터 5번째 위치까지(5번째 위치의 글자는 포함하지 않음)
alert( str.slice(0, 1) ); // 's', 0번째부터 1번째 위치까지(1번째 위치의 자는 포함하지 않음)

 

두 번째 인수가 생략된 경우엔, 명시한 위치부터 문자열 끝까지를 반환한다.

let str = "stringify";
alert( str.slice(2) ); // ringify, 2번째부터 끝까지

 

start와 end는 음수가 될 수 있으며 음수를 넘기면 문자열 끝에서부터 카운팅을 시작한다.

let str = "stringify";

// 끝에서 4번째부터 시작해 끝에서 1번째 위치까지
alert( str.slice(-4, -1) ); // gif

 

str.substring(start [, end])

start와 end사이에 있는 문자열을 반환한다.

substring은 slice와 아주 유사하지만 start가 end 보다 커도 괜찮다는 차이가 있다.

let str = "stringify";

// 동일한 부분 문자열을 반환합니다.
alert( str.substring(2, 6) ); // "ring"
alert( str.substring(6, 2) ); // "ring"

// slice를 사용하면 결과가 다릅니다.
alert( str.slice(2, 6) ); // "ring" (같음)
alert( str.slice(6, 2) ); // "" (빈 문자열)

 

substring은 음수 인수를 허용하지 않는다. 음수는 0으로 처리된다.

 

str.substr(start [, length])

start에서부터 시작해 length 개의 글자를 반환한다. substr은 끝 위치 대신에 길이를 기준으로 문자열을 추출한다는 점에서 substring과 slice와 차이가 있다.

let str = "stringify";
alert( str.substr(2, 4) ); // ring, 두 번째부터 글자 네 개

let str = "stringify";
alert( str.substr(-4, 2) ); // gi, 끝에서 네 번째 위치부터 글자 두 개

 

배열

배열 선언

let arr = new Array();
let arr = [];

let fruits = ["사과", "오렌지", "자두"];

alert( fruits[0] ); // 사과
alert( fruits[1] ); // 오렌지
alert( fruits[2] ); // 자두

// 수정
fruits[2] = '배'; // 배열이 ["사과", "오렌지", "배"]로 바뀜

// 추가
fruits[3] = '레몬'; // 배열이 ["사과", "오렌지", "배", "레몬"]으로 바뀜

// 길이
let fruits = ["사과", "오렌지", "자두"];

alert( fruits.length ); // 3

// 배열 요소 전체 출력
let fruits = ["사과", "오렌지", "자두"];

alert( fruits ); // 사과,오렌지,자두

 

배열 요소의 자료형엔 제약이 없다.

// 요소에 여러 가지 자료형이 섞여 있습니다.
let arr = [ '사과', { name: '이보라' }, true, function() { alert('안녕하세요.'); } ];

// 인덱스가 1인 요소(객체)의 name 프로퍼티를 출력합니다.
alert( arr[1].name ); // 이보라

// 인덱스가 3인 요소(함수)를 실행합니다.
arr[3](); // 안녕하세요.

 

pop push, shift unshift

pop : 배열 끝 요소를 제거하고, 제거한 요소를 반환한다.

let fruits = ["사과", "오렌지", "배"];

alert( fruits.pop() ); // 배열에서 "배"를 제거하고 제거된 요소를 얼럿창에 띄웁니다.

alert( fruits ); // 사과,오렌지

 

push : 배열 끝에 요소를 추가한다.

let fruits = ["사과", "오렌지"];

fruits.push("배");

alert( fruits ); // 사과,오렌지,배

 

shift : 배열 앞 요소를 제거하고, 제거한 요소를 반환한다.

let fruits = ["사과", "오렌지", "배"];

alert( fruits.shift() ); // 배열에서 "사과"를 제거하고 제거된 요소를 얼럿창에 띄웁니다.

alert( fruits ); // 오렌지,배

 

unshift : 배열 앞에 요소를 추가한다.

let fruits = ["오렌지", "배"];

fruits.unshift('사과');

alert( fruits ); // 사과,오렌지,배

 

push 와 unshift는 요소 여러 개를 한번에 더해줄 수도 있다.

let fruits = ["사과"];

fruits.push("오렌지", "배");
fruits.unshift("파인애플", "레몬");

// ["파인애플", "레몬", "사과", "오렌지", "배"]
alert( fruits );

 

반복문

for문은 배열을 순회할 때 쓰는 가장 오래된 방법이다. 순회시 인덱스를 사용한다.

let arr = ["사과", "오렌지", "배"];

for (let i = 0; i < arr.length; i++) {
  alert( arr[i] );
}

 

배열에 적용할 수 있는 또 다른 순회 문법으로 for .. of가 있다.

let fruits = ["사과", "오렌지", "자두"];

// 배열 요소를 대상으로 반복 작업을 수행합니다.
for (let fruit of fruits) {
  alert( fruit );
}

 

배열과 메서드

splice메서드

arr.splice(index[, deleteCount, elem1, ..., elemN])

 

splice 메서드를 사용할 경우 배열의 수정, 추가, 삭제 모두 수행할 수 있다. index인수는 조작을 할 요소값, deletecount는 삭제할 대상, elem1,..., elemN은 배열에 추가할 요소다.

let arr = ["I", "study", "JavaScript"];

arr.splice(1, 1); // 인덱스 1부터 요소 한 개를 제거

alert( arr ); // ["I", "JavaScript"]

 

let arr = ["I", "study", "JavaScript", "right", "now"];

// 처음(0) 세 개(3)의 요소를 지우고, 이 자리를 다른 요소로 대체합니다.
arr.splice(0, 3, "Let's", "dance");

alert( arr ) // now ["Let's", "dance", "right", "now"]

 

splice는 삭제된 요소로 구성된 배열을 반환한다.

let arr = ["I", "study", "JavaScript", "right", "now"];

// 처음 두 개의 요소를 삭제함
let removed = arr.splice(0, 2);

alert( removed ); // "I", "study" <-- 삭제된 요소로 구성된 배열

 

splice 메서드의 deleteCount 0으로 설정하면 요소를 제거하지 않으면서 새로운 요소를 추가할 수 있다.

let arr = ["I", "study", "JavaScript"];

// 인덱스 2부터
// 0개의 요소를 삭제합니다.
// 그 후, "complex"와 "language"를 추가합니다.
arr.splice(2, 0, "complex", "language");

alert( arr ); // "I", "study", "complex", "language", "JavaScript"

 

배열 탐색하기

  • arr.indexOf(item, from) : 인덱스 from부터 시작해 item(요소)을 찾는다. 요소를 발견하면 해당 요소의 인덱스를 반환하고, 발견하지 못했으면 -1을 반환한다.
  • arr.lastIndexOf(item, from) : 위 메서드와 동일한 기능을 하는데, 검색을 끝에서부터 시작한다는 점만 다르다.
  • arr.includes(item, from) : 인덱스 from부터 시작해 item이 있는지를 검색하는데, 해당하는 요소를 발견하면 true를 반환한다.
let arr = [1, 0, false];

alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1

alert( arr.includes(1) ); // true

 

find와 findIndex

let result = arr.find(function(item, index, array) {
  // true가 반환되면 반복이 멈추고 해당 요소를 반환한다.
  // 조건에 해당하는 요소가 없으면 undefined를 반환한다.
});

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

let user = users.find(item => item.id == 1);

alert(user.name); // John

 

filter

filter find와 문법이 유사하지만, 조건에 맞는 요소 전체를 담은 배열을 반환한다는 점에서 차이가 있다.

let results = arr.filter(function(item, index, array) {
  // 조건을 충족하는 요소는 results에 순차적으로 더해진다.
  // 조건을 충족하는 요소가 하나도 없으면 빈 배열이 반환된다.
});

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

// 앞쪽 사용자 두 명을 반환합니다.
let someUsers = users.filter(item => item.id < 3);

alert(someUsers.length); // 2

 

배열을 변형하는 메서드

map

배열 요소 전체를 대상으로 함수를 호출하고, 함수 호출 결과를 배열로 반환해준다.

let result = arr.map(function(item, index, array) {
  // 요소 대신 새로운 값을 반환합니다.
});

let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6

 

sort

배열의 요소를 정렬해준다. 배열 자체가 변경된다. 메서드를 호출하면 재정렬 된 배열이 반환되는데, 이미 arr 자체가 수정되었기 때문에 반환 값은 잘 사용되지 않는 편이다.

let arr = [ 1, 2, 15 ];

// arr 내부가 재 정렬됩니다.
arr.sort();

alert( arr );  // 1, 15, 2

 

split & join

str.split(delim)을 이용하면 우리가 원하는 것을 정확히 할 수 있다. 이 메서드는 구분자(delimiter) delim을 기준으로 문자열을 쪼개준다.

let names = 'Bilbo, Gandalf, Nazgul';

let arr = names.split(', ');

for (let name of arr) {
  alert( `${name}에게 보내는 메시지` ); // Bilbo에게 보내는 메시지
}

 

split메서드는 두번째 인자로 숫자를 받을 수 있는데 이는 배열의 길이를 제한한다.

let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);

alert(arr); // Bilbo, Gandalf

 

arr.join(glue) split과 반대 역할을 하는 메서드이다. 인수 glue를 접착제처럼 사용해 배열 요소를 모두 합친 후 하나의 문자열을 만들어준다.

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];

let str = arr.join(';'); // 배열 요소 모두를 ;를 사용해 하나의 문자열로 합칩니다.

alert( str ); // Bilbo;Gandalf;Nazgul

 

구조 분해 할당 (destructuring assignment)

배열 분해하기

// 이름과 성을 요소로 가진 배열
let arr = ["Bora", "Lee"]

// 구조 분해 할당을 이용해
// firstName엔 arr[0]을
// surname엔 arr[1]을 할당하였습니다.
let [firstName, surname] = arr;

alert(firstName); // Bora
alert(surname);  // Lee

 

할당 연산자 좌측에는 ‘할당할 수 있는(assignables)’ 것이라면 어떤 것이든 올 수 있다.

let user = {};
[user.name, user.surname] = "Bora Lee".split(' ');

alert(user.name); // Bora

 

'...'로 나머지 요소 가져오기

배열 앞쪽에 위치한 값 몇 개만 필요하고 그 이후 이어지는 나머지 값들은 한데 모아서 저장하고 싶을 때가 있다. 이럴 때는 점 세 개 ...를 붙인 매개변수 하나를 추가하면 ‘나머지(rest)’ 요소를 가져올 수 있다.

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar

// `rest`는 배열입니다.
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2

 

기본값

할당하고자 하는 변수의 개수가 분해하고자 하는 배열의 길이보다 크더라도 에러가 발생하지 않는다. 할당할 값이 없으면 undefined로 취급되기 때문이다.

let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined

 

=을 이용하면 할당할 값이 없을 때 기본으로 할당해 줄 값인 '기본값(default value)'을 설정할 수 있다.

// 기본값
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name);    // Julius (배열에서 받아온 값)
alert(surname); // Anonymous (기본값)

 

객체 분해하기

구조 분해 할당으로 객체도 분해할 수 있다.

let {var1, var2} = {var1:…, var2:…}

 

할당 연산자 우측엔 분해하고자 하는 객체를, 좌측엔 상응하는 객체 프로퍼티의 '패턴’을 넣는다. 분해하려는 객체 프로퍼티의 키 목록을 패턴으로 사용하는 예시를 살펴보자.

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, width, height} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

 

할당 연산자 좌측엔 좀 더 복잡한 패턴이 올 수도 있다. 분해하려는 객체의 프로퍼티와 변수의 연결을 원하는 대로 조정할 수도 있다.

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

// { 객체 프로퍼티: 목표 변수 }
let {width: w, height: h, title} = options;

// width -> w
// height -> h
// title -> title

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

 

프로퍼티가 없는 경우를 대비하여 =을 사용해 기본값을 설정하는 것도 가능하다.

let options = {
  title: "Menu"
};

let {width = 100, height = 200, title} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

 

나머지 패턴 '...'

let options = {
  title: "Menu",
  height: 200,
  width: 100
};

// title = 이름이 title인 프로퍼티
// rest = 나머지 프로퍼티들
let {title, ...rest} = options;

// title엔 "Menu", rest엔 {height: 200, width: 100}이 할당됩니다.
alert(rest.height);  // 200
alert(rest.width);   // 100

 

변수의 유효범위와 클로저

코드블록

코드블록 안에서 {...} 선언한 변수는 코드블록 안에서만 사용할 수 있다.

{
  // 지역 변수를 선언하고 몇 가지 조작을 했지만 그 결과를 밖에서 볼 수 없습니다.

  let message = "안녕하세요."; // 블록 내에서만 변숫값을 얻을 수 있습니다.

  alert(message); // 안녕하세요.
}

alert(message); // ReferenceError: message is not defined

 

중첩 함수

함수 내부에서 선언한 함수는 '중첩 함수'라고 부른다.

function sayHiBye(firstName, lastName) {

  // 헬퍼(helper) 중첩 함수
  function getFullName() {
    return firstName + " " + lastName;
  }

  alert( "Hello, " + getFullName() );
  alert( "Bye, " + getFullName() );

}

 

화살표 함수

화살표 함수는 this가 없다. 화살표 함수 본문에서 this에 접근하면 외부세어 값을 가져온다. 

let group = {
  title: "1모둠",
  students: ["보라", "호진", "지민"],

  showList() {
    this.students.forEach(
      student => alert(this.title + ': ' + student)
    );
  }
};

group.showList();

 

일반 함수를 사용시 에러가 뜨는 것을 확인 할 수 있다.

let group = {
  title: "1모둠",
  students: ["보라", "호진", "지민"],

  showList() {
    this.students.forEach(function(student) {
      // TypeError: Cannot read property 'title' of undefined
      alert(this.title + ': ' + student)
    });
  }
};

group.showList();

 

 

'항해99 플러스 > 사전스터디' 카테고리의 다른 글

[JavaScript] 1주차 발제  (1) 2025.06.20
[TypeScript] 1주차 발제  (1) 2025.06.19
[TDD] 2주차  (0) 2025.03.18
[TDD] Kent Beck - 테스트 주도 개발  (0) 2025.03.11
[TDD방법론 사전스터디] 2주차  (2) 2024.09.03

+ Recent posts