객체
앞서 배웠던 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 |