한 걸음씩
자바스크립트 중급 총정리 본문
1. 변수, 호이스팅, TDZ(Temporal Dead Zone)
let | 1. 선언 단계 2. 초기화 단계 3. 할당 단계 |
블록 스코프(block-scoped) ex) 함수, if문, for문, while문, try/catch문 등 |
호이스팅이 발생하지만 ReferenceError발생 → Temporal Dead Zone(코드를 예측가능하게하고, 에러 줄임) |
const | 1. 선언 + 초기화 + 할당 | ||
var | 1. 선언 및 초기화 단계 2. 할당 단계 |
함수 스코프(function-scoped) | 한 번 선언된 변수를 다시 선언할 수 있다 선언하기 전에 사용할 수 있다(호이스팅→undefined) |
▷ 호이스팅
- 스코프 내부 어디서든 변수 선언은 최상위에 선언된 것처럼 행동
2. 생성자 함수
▷ 객체 리터럴
let user = {
name: 'Mike',
age: 30,
}
▷ 생성자 함수
- 첫 글자는 대문자로 User
- new 연산자를 사용해서 호출
- 생성자 함수가 와플 기계라고 생각하면 와플을 찍어낼 수 있도록 하는 역할
function User(name, age){
this.name = name
this.age = age
}
let user1 = new User('Mike', 30) // User {name: 'Mike', age: 30}
let user2 = new User('Jane', 22) // User {name: 'Jane', age: 22}
let user3 = new User('Tom', 17) // User {name: 'Tom', age: 17}
// 생성자 함수 실행 순서
function User(name, age){
// this = {} // 2. 빈객체를 만들고 this에 할당
// 3. this에 프로퍼티들을 추가
this.name = name
this.age = age
// return this // 3. this를 반환
}
new 함수명() // 1. 생성 및 호출
function User(name, age){
// this = {}
this.name = name
this.age = age
this.sayName = function(){
console.log(this.name)
}
// return this
}
let user5 = new User('Han', 40)
user5.sayName() // 'Han'
3. 객체 메소드(Object methods), 계산된 프로퍼티(Computed property)
▷ Computed property
- 키 값에 어떤걸 할당할지 모를 때
let a = 'age'
const user = {
name: 'Mike',
// 변수 a에 할당된 값이 들어감
[a]: 30 // age: 30
}
const user = {
[1 + 4]: 5,
['안녕'+'하세요']: 'Hello'
}
user
// {5: 5, 안녕하세요: 'Hello'}
▷ Object methods
- Object.assign() : 객체 복제
const user = {
name: 'Mike',
age: 30
}
const info1 = {
gender: 'male'
}
// 빈 객체는 초기값
// {} + {name: 'Mike', age: 30}
const newUser = Object.assign({}, user)
// 초기값이 존재하는 경우
// { gender: 'male'} + {name: 'Mike', age: 30}
Object.assign({ gender: 'male'}, user)
// 초기값이 같은 경우 -> 덮어씀
// { name: 'Mike', age: 30}
Object.assign({ name: 'Tom'}, user)
// 두 개 이상의 객체 합치기
Object.assign(user, info1)
- Object.keys() : 키 배열 반환
const user = {
name: 'Mike',
age: 30,
gender: 'male',
}
Object.keys(user) // ['name', 'age', 'gender']
- Object.values() : 값 배열 반환
const user = {
name: 'Mike',
age: 30,
gender: 'male',
}
Object.values(user) // ['Mike', 30, 'male']
- Object.entries() : 키/값 배열 반환
const user = {
name: 'Mike',
age: 30,
gender: 'male',
}
Object.values(user)
// [['name', 'Mike'], ['age', 30], ['gender', 'male']]
- Object.fromEntries() : 키/값 배열을 객체로
const arr =
[
['name', 'Mike'],
['age', 30],
['gender', 'male']
]
Object.fromEntries(arr)
// { name: 'Mike', age: 30, gender: 'male'}
4. 심볼
- Symbol은 유일한 식별자
- 유일성 보장
const a = symbol() // new를 붙이지 않는다
const b = symbol()
console.log(a === b) // false
console.log(a == b) // false
▷ propety key : 심볼형
const a = symbol('id')
const user = {
name: 'Mike',
ageL 30,
[id]: 'myid
}
user // {name: 'Mike', age: 30, Symbol(id): 'myid'}
▷ Symbol.for() : 전역 심볼
- 하나의 심볼만 보장받을 수 있음
- 없으면 만들고, 있으면 가져오기 때문
- Symbol 함수는 매 번 다른 Symbol 값을 생성하지만,
- Symbol.for 메서드는 하나를 생성한 뒤 키를 통해 같은 Symbol을 공유
const id1 = Symbol.for('id')
const id2 = Symbol.for('id')
id1 === id2 // true
Symbol.keyFor(id1) // 'id'
▷ description
const id = Symbol('id 입니다.')
id.description // 'id 입니다'
▷ 숨겨진 Symbol key 보는 법
const a = symbol('id')
const user = {
name: 'Mike',
ageL 30,
[id]: 'myid
}
Object.getOwnPropertySymbols(user) // [Symbol(id)]
Reflect.ownKeys(user) // ['name', 'age', Symbol(id)]
// 다른 개발자가 만들어 놓은 객체
const user = {
name: 'Mike',
age: 30,
}
// 내가 작업
const showName = Symbol('show name')
user[showName] = function(){
console.log(this.name)
}
user[showName]()
// 사용자가 접속하면 보는 메시지
for (let key in user){
console.log(`His ${key} is ${user[key]}.`)
}
// Mike
// His name is Mike.
// His age is 30.
5. 숫자, 수학 메서드
▷ toString()
- 10진수 → 2진수 / 16진수
let num = 10
// 숫자 → 문자
num.toString() // '10'
// 10진수 → 이진수
num.toString(2) // '100'
let num2 = 255
// 10진수 → 16진수
num2.toString(16) // 'ff'
▷ Math.ceil() : 올림
let num1 = 5.1
let num2 = 5.7
Math.ceil(num1) // 6
Math.ceil(num2) // 6
▷ Math.floor() : 내림
let num1 = 5.1
let num2 = 5.7
Math.floor(num1) // 5
Math.floor(num2) // 5
▷ Math.round() : 반올림
let num1 = 5.1
let num2 = 5.7
Math.round(num1) // 5
Math.round(num2) // 6
// 요구사항: 소수점 둘째자리까지 표현(셋째 자리에서 반올림)
let userRate = 30.1234
Math.round(userRate * 100) / 100 // 30.12
userRate.toFixed(2) // '30.12' (문자열로 반환)
▷ isNaN() : 숫자가 아님 is Not a Number
let x = Number('x') // NaN
▷ parseInt()
let margin = '10px'
parseInt(margin) // 10
Number(margin) // NaN
let redColor = 'f3'
parseInt(redColor) // NaN
parseInt(redColor, 16) // 243
parseInt('11', 2) // 3
▷ parseFloat()
let padding = '18.5%'
parseInt(padding) // 18
parseFloat(padding) // 18.5
▷ Math.random() : 0 ~ 1 사이 무작위 숫자 생성
// 1 ~ 100 사이 임의의 숫자를 뽑고 싶다면?
Math.floor(Math.random() * 100) + 1
// Math.random()으로 생성한 랜덤 수가 0.6789라고 할 때,
// 0.6789 * 100 = 67.89
// Math.floor(67.89) = 67
// 67 + 1 (랜덤수가 0.xx가 나올경우를 대비하여 +1) = 68
▷ Math.max() / Math.min() : 최대 / 최소
Math.max(1, 4, -1, 5, 10, 9, 5.54) // 10
Math.min(1, 4, -1, 5, 10, 9, 5.54) // -1
▷ Math.abs() : 절댓값
Math.abs(-1) // 1
▷ Math.pow(n, m) : 제곱
Math.pow(2, 10) // 1024
▷ Math.sqrt() : 제곱근
Math.sqrt(16) // 4
6. 문자열 메서드
▷ length : 문자열 길이
let desc = '안녕하세요.'
desc.length // 6
▷ 특정 위치에 접근
let desc = '안녕하세요.'
desc[2] // '하'
▷ toUpperCase() / toLowerCase()
let desc = 'Hi guys'
desc.toUpperCase() // 'HI GUYS'
desc.toLowerCase() // 'hi guys'
▷ str.indexOf(text)
let desc = 'Hi guys'
desc.indexOf('guys') // 3
// 찾는 문자가 없는 경우 -1 반환
desc.indexOf('hey') // -1
// if문에서 0은 false이기 때문에 '> -1'조건이 필요하다
if(desc.indexOf('Hi') > -1){
console.log('Hi가 포함된 문장입니다.')
}
▷ str.slice(n, m)
- n: 시작점
- m: 없으면 문자열 끝까지 / 양수면 그 숫자까지(포함하지 않음) / 음수면 끝에서부터 셈
let desc = 'abcdefg'
desc.slice(2) // 'cdefg'
desc.slice(0, 5) // 'abcde'
desc.slice(2, -2) // 'cde'
▷ str.substring(n, m)
- n과 m사이 문자열 반환
- slice와 유사하지만 substring은 n과 m을 바꿔도 동작함
- 음수는 0으로 인식
let desc = 'abcdefg'
desc.substring(2, 5) // 'cde'
desc.substring(5, 2) // 'cde'
▷ str.substr(n, m)
- n부터 시작해서 m개를 가져옴
let desc = 'abcdefg'
desc.substr(2, 4) // 'cdef'
desc.substr(-4, 2) // 'de'
▷ str.trim() : 앞 뒤 공백 제거
let desc = ' coding '
desc.trim() // 'coding'
▷ str.repeat(n) : n번 반복
let hello = 'hello'
hello.repeat(2) // hellohellohello
▷ 문자열 비교
'a' < 'c' // true
https://ko.wikipedia.org/wiki/ASCII → 아스키코드 표 확인해 보기
7. 배열 메서드
▷ arr.splice(n, m) : 특정 요소 지움
- 인덱스 n부터 m개 삭제
let arr = [1, 2, 3, 4, 5]
arr.splice(1, 2) // 인덱스 1부터 2개 지우기
console.log(arr) // [1, 4, 5]
▷ arr.splice(n, m, x) : 특정 요소 지우고 추가
- 인덱스 n부터 m개 삭제하고 x추가
let arr = [1, 2, 3, 4, 5]
arr.splice(1, 3, 100, 200) // 인덱스 1부터 3개 지우고 100, 200추가
console.log(arr) // [1, 100, 200, 5]
▷ arr.slice(n, m) : n부터 m까지 반환
let arr = [1, 2, 3, 4, 5]
arr.slice(1, 4) // [2, 3, 4]
let arr2 = arr.slice() // 배열 복사
console.log(arr2) // [1, 2, 3, 4, 5]
▷ arr.concat(arr2, arr3 ...): 합쳐서 새배열 반환
let arr = [1, 2]
arr.concat([3, 4]) // [1, 2, 3, 4]
arr.concat([3, 4], [5, 6]) // [1, 2, 3, 4, 5, 6]
arr.concat([3, 4], 5, 6) // [1, 2, 3, 4, 5, 6]
▷ arr.forEach(fn) : 배열 반복
let users = ['a', 'b', 'c']
users.forEach((item, index) => {
console.log(item, index)
})
// a 0
// b 1
// c 2
▷ arr.indexOf() / arr.lastIndexOf()
let arr = [1, 2, 3, 4, 5, 1, 2, 3]
// 앞에서부터 탐색
arr.indexOf(3) // 2
// 첫 번째 요소는 탐색 위치 : 인덱스 3부터 탐색
// 두 번째 요소는 찾는 인수 : 3
arr.indexOf(3, 3) // 7
// 끝에서부터 탐색
arr.lastIndexOf(3) // 7
▷ arr.includes() : 포함하는지 확인
let arr = [1, 2, 3]
arr.includes(2) // true
arr.includes(8) // false
▷ arr.find(fn) / arr.findIndex(fn)
- 첫 번째 true 값만 반환하고 끝
- 만약 없으면 undefined를 반환
let arr = [1, 2, 3, 4, 5]
const result = arr.find((item) => {
return item % 2 === 0
}
console.log(result) // 2
let userList = [
{name: 'Mike', age: 30},
{name: 'Jane', age: 27},
{name: 'Tom', age: 10}
]
const result = userList.find((user) => {
if(user.age < 19){
return true
}
return false
})
console.log(result) // {name: 'Tom', age: 10}
const result2 = userList.findIndex((user) => {
if(user.age < 19){
return true
}
return false
})
console.log(result2) // 2
▷ arr.filter(fn)
- 모든 요소를 배열로 반환
const arr = [1, 2, 3, 4, 5, 6]
const rusult = arr.filter((item) => {
return item % 2 === 0
})
console.log(result) // [2, 4, 6]
▷ arr.reverse() : 역순으로 재정렬
let arr = [1, 2, 3, 4, 5]
arr.reverse() // [5, 4, 3, 2, 1]
▷ arr.map(fn) : 함수를 받아 특정 기능을 시행하고 새로운 배열을 반환
let userList = [
{name: 'Mike', age: 30},
{name: 'Jane', age: 27},
{name: 'Tom', age: 10}
]
let newUserList = userList.map((user, index) => {
return Object.assign({}, user, {
id: index + 1,
isAdult: user.age > 19,
})
})
console.log(newUserList)
// 0: {name: 'Mike', age: 30, id: 1, isAdult: true}
// 1: {name: 'Jane', age: 27, id: 2, isAdult: true}
// 2: {name: 'Tom', age: 10, id: 3, isAdult: false}
▷ arr.join()
- 배열 → 문자열
let arr = ['안녕', '나는', '철수야']
let result = arr.join(',')
console.log(result) // '안녕 나는 철수야'
▷ arr.split()
- 문자열 → 배열
const users = 'Mike,Jane,Tom,Tony'
const result = users.split(',')
console.log(result) // ['Mike','Jane','Tom','Tony']
▷ Array.isArray()
let user = {
name: 'Mike',
age: 30
}
let userList = ['Mike', 'Jane', 'Tom']
console.log(typeof user) // object
console.log(typeof userList) // object
console.log(Array.isArray(user)) // false
console.log(Array.isArray(userList)) // true
▷ arr.sort()
- 배열 재정렬
- 배열 자체가 변경되는 것에 주의
let arr = [1, 5, 4, 3, 2]
arr.sort((a, b) => a - b) // a와 b를 비교해서 a가 작으면 b보다 앞으로
console.log(arr) // [1, 2, 3, 4, 5]
▷arr.reduce()
let arr = [1, 2, 3, 4, 5]
arr.reduce((acc, cur) => acc + cur, 0)
console.log(arr) // 15
8. 구조 분해 할당(Destructuring assignment)
- 구조 분해 할당 구문은 배열이나 객체의 속성을 분해해서 그 값을 변수에 담을 수 있게 하는 표현식▷
let users = ['Mike','Tom','Jane']
let [user1, user2, user3] = users
console.log(user1) // 'Mike'
console.log(user2) // 'Tom'
console.log(user3) // 'Jane'
let [a, b, c] = [1, 2]
// c = undefined
// 기본값을 사용해 undefined가 나오지 않도록
let [a = 3, b = 4, c = 5] = [1, 2]
// 일부 반환값 무시
let [user1, , user2] = ['Mike', 'Tom', 'Jane', 'Tony']
console.log(user1) // 'Mike'
console.log(user2) // 'Jane
// 바꿔치기
let a = 1
let b = 2
[a, b] = [b, a]
// 객체 구조 분해
let user = {name: 'Mike', age: 30}
let {age, name} = user
console.log(name) // 'Mike'
console.log(age) // 30
// 새로운 변수 이름으로 할당
let user = {name: 'Mike', age: 30}
let {age: userAge, name: userName} = user
console.log(userName) // 'Mike'
console.log(userAge) // 30
// 기본값
let user = {name: 'Mike', age: 30}
// gender는 undefined
let {age, name, gender} = user
// 기본값 할당
let {age, name, gender = 'male'} = user
9. 나머지 매개변수(Rest parameters) , 전개 구문(Spread syntax)
▷ arguments
- 함수로 넘어온 모든 인수에 접근
- 함수 내에서 이용 가능한 지역 변수
- length / index
- Array 형태의 객체
- 배열의 내장 메서드 없음(forEach, map)
▷ 나머지 매개변수(Rest parameters)
- 정해지지 않은 인수들을 '...'으로 나타냄
function add(...numbers){
let result = 0
numbers.forEach((num) => (result += num))
// let result = numbers.reduce((prev, cur) => prev + cur, 0)
console.log(result)
}
add(1, 2, 3) // 6
add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 55
// 나머지 매개변수는 항상 마지막에
function User(name, age, ...skills){
this.name = name
this.age = age
this.skills = skills
}
const user1 = new User('Mike', 30, 'html', 'css')
const user2 = new User('Tom', 28, 'Js', 'React')
const user3 = new User('Jane', 10, 'English')
console.log(user1) // User{name: 'Mike', age: 30, skills: ['html', 'css']}
console.log(user2) // User{name: 'Tom', age: 28, skills: ['Js', 'React']}
console.log(user3) // User{name: 'Jane', age: 10, skills: ['English']}
▷ 전개 구문(Spread syntax)
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
let result = [0, ...arr1, ...arr2, 7, 8, 9]
console.log(result) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let arr = [1, 2, 3]
let arr2 = [...arr] // [1, 2, 3]
let user = {name: 'Mike', age: 30}
let user2 = {...user}
user2.name = 'Tom'
console.log(user.name) // 'Mike'
console.log(user2.name) // 'Tom'
10. 클로저
- 함수와 렉시컬 환경의 조합
- 렉시컬 환경: 변수, 함수 선언 밑 매개변수와 같은 정보를 저장하는 곳
- 함수가 생성될 당시의 외부 변수를 기억
- 생성 이후에도 계속 접근 가능
▷ 자바스크립트는 어휘적 환경(Lexical Environment)을 갖는다
// makeAdder함수는 x라는 매개변수를 가지고 있고, 이 함수는 내부적으로 또 다른 함수를 반환한다.
// 반환된 함수는 y라는 매개변수를 가지고 있고, 상위 함수인 makeAdder에서 정의된 x변수에 접근할 수 있다
function makeAdder(x){
return function(y){ // function(y)는 y를 가지고 있고 상위함수인 makeAdder의 x에 접근 가능
return x + y
}
}
const add3 = makeAdder(3) // add3은 x가 3인 함수를 나타냄
// add3(2)를 호출하면 y에 2가 전달되고, x는 이전에 3으로 정의된 값, 따라서 답은 5
console.log(add3(2)) // 5 ; add3함수가 생성된 이후에도 상위함수인 makeAdder의 x에 접근 가능
const add10 = makeAdder(10) // add10은 x가 10인 함수를 나타냄
// add10(5)를 호출하면 y에 5가 전달되고, x는 이전에 10으로 정의된 값, 따라서 답은 15
console.log(add10(5)) // 15
// add3(1)을 호출하면 y에 1이 전달되고, x는 이전에 3으로 정의된 값, 따라서 답은 4
console.log(add3(1)) // 4
- 클로저는 함수가 정의된 스코프(변수와 함수가 유효한 범위를 나타내는 개념)의 렉시컬 환경을 기억하는 것을 의미한다
- 이를 통해 함수는 자신이 정의된 스코프 밖에 있는 변수들에 접근할 수 있게 된다.
- 클로저를 이용하면 함수가 이미 반환된 후에도 함수가 선언됐을 때의 스코프에 있는 변수들을 계속 사용할 수 있다
호이스팅은 변수 및 선언들을 해당 스코프의 최상단으로 끌어올리는 동작을 말한다. 변수의 선언 부분이 호이스팅 되는 것이지, 변수에 할당된 값이 호이스팅 되는 것이 아니다.
11. setTimeout / setInterval
▷ setTimeout
- 일정 시간이 지난 후 함수를 실행
function showName(name){
console.log(3)
}
setTimeout(showName, 3000, 'Mike')
// clearTimeout : 예정된 작업 삭제
const tId = function showName(name){
console.log(3)
}
setTimeout(showName, 3000, 'Mike')
clearTimeour(tId) // 3초가 지나기전에 실행되기 때문에 아무일도 발생하지 않음
▷ setInterval
- 일정 시간 간격으로 함수를 반복
function showName(name){
console.log(name)
}
const tId = setInterval(showName, 3000, 'Mike') // 3초마다 'Mike'가 출력됨
clearInterval(tId) // 위의 동작을 중단하고 싶을 때
// delay = 0
setTimeout(function(){
console.log(2) // 2가 나중에 출력
}, 0)
console.log(1) // 1이 먼저 출력되고
// 왜? 브라우저는 기본적으로 4ms의 대기시간이 있기 때문에 0이라도 나중에 실행되는 것
12. call, apply, bind
- 자바스크립트의 모든 함수가 상속하는 메서드로, 함수의 실행 문맥을 변경하거나 함수를 바인딩하는 데 사용한다
- 함수 호출 방식과 관계없이 this를 지정할 수 있음
▷ call
- 함수를 호출하면서 지정한 객체를 해당 함수의 실행 문맥으로 설정한다.
- 함수에 필요한 인수들은 개별적으로 전달된다.
const mike = {
name: 'Mike'
}
const tom = {
name: 'Tom'
}
function showThisName(){
// this는 window를 가리킴
console.log(this.name)
}
showThisName()
showThisName.call(mike) // Mike
showThisName.call(Tom) // tom
function update(birthYear, occupation){
this.birthYear = birthYear
this.occupation = occupation
}
// 첫 번째 매개변수는 this를 가리키고, 두 번째, 세 번째 매개변수들은 함수가 사용할 매개변수를 가리킨다
update.call(mike, 1999, 'singer')
console.log(mike) // {name: 'Mike', birthYear: 1999, occupation: 'singer'}
update.call(tom, 2002, 'teacher')
console.log(tom) // {name: 'Tom', birthYear: 2002, occupation: 'teacher'}
▷ apply
- apply는 함수 매개변수를 처리하는 방법을 제외하면 call과 완전히 같다
- call은 일반적인 함수와 마찬가지로 매개변수를 직접 받지만, apply는 매개변수를 배열로 받는다
const mike = {
name: 'Mike'
}
const tom = {
name: 'Tom'
}
function showThisName(){
// this는 window를 가리킴
console.log(this.name)
}
showThisName()
showThisName.call(mike) // Mike
showThisName.call(Tom) // tom
function update(birthYear, occupation){
this.birthYear = birthYear
this.occupation = occupation
}
// 첫 번째 매개변수는 this를 가리키고, 두 번째, 세 번째 매개변수들은 함수가 사용할 매개변수를 가리킨다
update.apply(mike, [1999, 'singer'])
console.log(mike) // {name: 'Mike', birthYear: 1999, occupation: 'singer'}
update.apply(tom, [2002, 'teacher'])
console.log(tom) // {name: 'Tom', birthYear: 2002, occupation: 'teacher'}
const nums = [3, 10, 1, 6, 4]
// null은 this로 사용될 값
const minNum = Math.min.apply(null, nums)
const maxNum = Math.max.apply(null, nums)
const maxNum = Math.max.call(null, ...nums) // 10
console.log(minNum) // 1
console.log(maxNum) // 10
▷ bind
- 함수를 호출하면서 지정한 객체를 해당 함수의 실행 문맥으로 설정한다
- 하지만, 함수를 즉시 호출하지는 않고, 새로운 함수를 반환한다.
- 이 새로운 함수는 이후에 호출될 때 원본 함수의 실행 문맥과 함께 지정한 객체가 유지된다
- 함수의 this 값을 영구히 바꿀 수 있다
const mike = {
name: 'Mike'
}
function update(birthYear, occupation){
this.birthYear = birthYear
this.occupation = occupation
}
const updateMike = update.bind(mike)
updateMike(1980, 'police')
console.log(mike) // {name: 'Mike, birthYear: 1980, occupation: 'police'}
const user = {
name: 'Mike',
showName: function(){
console.log(`hello, ${this.name}`)
}
}
user.showName() // hello.Mike
let fn = user.showName
fn() // hello,
fn.call(user) // hello, Mike
fn.apply(user) // hello, Mike
let boundFn = fn.bind(user)
boundFn() // hello, Mike
.call(), .apply()는 함수를 호출할 때 바로 실행되는 반면, .bind()는 새로운 함수를 반환하기 때문에 이후에 호출할 수 있다. 이들 메서드는 함수의 실행 문맥을 원하는 대상으로 바꾸거나, 함수를 나중에 호출하기 위해 사용된다. 각각의 사용 사례에 따라 편리하게 적용할 수 있으며, 주로 함수의 호출 방식을 조정하거나 콜백 함수를 다룰 때 유용하게 활용된다. |
13. 상속, Prototype
▷ hasOwnProperty()
- 객체가 특정 속성(프로퍼티)을 직접 소유하고 있는지를 확인하는 메서드
- 이 메서드는 객체가 상속받은 속성을 제외하고, 객체 자체에서 정의한 속성만을 고려한다.
const user = {
name: 'Mike'
}
user.name // 'Mike'
user.hasOwnProperty('name') // true
user.hasOwnProperty('age') // false
▷ __proto__
- 모든 객체는 '__proto__'라는 내부 속성을 가지고 있다
- 객체가 생성될 때 해당 객체의 프로토타입을 가리킨다
- 해당 객체의 프로토타입을 참조하는 링크 역할을 한다
- 직접적으로 프로토타입을 변경하는 데 사용되지 않아야 하고, 프로토타입 체인을 이해하는 데에만 사용되어야 한다
const car = {
wheels: 4,
drive(){
console.log('drive...')
}
}
const bmw = {
color: 'red',
navigation: 1,
}
const benz = {
color: 'black',
}
const audi = {
color: 'blue',
}
// bmw, benz, audi는 car의 상속을 받는다.
bmw.__proto__ = car
benz.__proto__ = car
audi.__proto__ = car
// bmw객체에 wheels가 없다면 탐색을 멈추고 __proto__에서 확인을 한다
bmw.wheels
const x5 = {
color: 'white',
name: 'x5'
}
// 상속은 이어질 수 있다. x5는 bmw의 상속을 받는다
x5.__proto__ = bmw
// 이렇게 상속을 받는 것은 'Prototype Chain'이라고 한다
x5.navigation // 1
x5.wheels // 4
▷ prototype
- 함수 객체는 prototype이라는 속성을 가지고 있다
- 이 속성은 해당 함수로부터 생성된 모든 객체들의 프로토타입으로 사용된다
// Bmw는 생성자 함수로서 객체를 생성하는 역할을 한다
// Bmw함수로부터 생성된 객체들은 color라는 속성을 갖는다
const Bmw = function(color){
this.color = color
}
// Bmw.prototype은 Bmw함수로부터 생성된 객체들의 프로토타입으로 사용된다
// Bmw.prototype에 추가된 속성 wheels, drive는 모든 Bmw객체들이 공유하는 속성과 메서드가 된다
Bmw.prototype.wheels = 4
Bmw.prototype.drive = function(){
console.log('drive...')
}
Bmw.prototype.navigation = 1
Bmw.prototype.stop = function(){
console.log('STOP!')
}
// x5, z4는 Bmw생성자 함수를 사용하여 생성된 객체이다
// 프로토타입 체인을 통해 Bmw.prototype의 속성과 메서드를 사용할 수 있다
const x5 = new Bmw('red')
const z4 = new Bmw('blue')
x5.wheels // 4
x5.drive() // drive...
// Bmw를 이용해서 z4로 만들었는지
z4 instanceof Bmw // true
// constructor는 생성자, 즉 Bmw를 가리킨다
z4.constructor === Bmw // true
// 기존에 Bmw.prototype에 이미 정의된 속성과 메서드가 있다면 그 값을 덮어 쓰지 않고 확장하는 방식으로 동작
Bmw.prototype.wheels = 4
Bmw.prototype.drive = function(){
console.log('drive...')
}
Bmw.prototype.navigation = 1
Bmw.prototype.stop = function(){
console.log('STOP!')
}
// Bmw.prototype을 완전히 새로운 객체로 교체하는 방식으로 동작
// Bmw.prototype객체와 연결된 모든 객체와 프로토타입 체인이 끊어짐
// 그래서 constructor속성을 수동으로 설정하여 원래 생성자 함수와 연결해줌
// constructor속성은 기본적으로 프로토타입 객체에 존재하는 속성으로 생성자 함수를 가리키는 참조
Bmw.prototype = {
constructor: Bmw, // 수동으로 명시
wheels: 4,
drive(){
console.log('drive...')
},
navigation: 1,
stop(){
console.log('STOP!')
},
}
14. Class
// 생성자 함수
const User = function(name, age){
this.name = name
this.age = age
// 객체 내부에 showName이 존재
//this.showName = function(){
// console.log(this.name)
//}
}
// class와 동일하게 동작하려면 다음와 같이 코드를 작성해야한다
User.prototype.showName = function(){
console.log(this.name)
}
const mike = new User('Mike', 30)
const mike = User('Mike', 30) // undefined: new없이 생성하더라도 에러가 발생X
// class
class User2{
constructor(name, age){
this.name = name
this.age = age
}
// __proto__ 내부에 showName이 있다
showName(){
console.log(this.name)
}
}
const tom = new User2('Tom', 19)
const tom = User2('Tom', 19) // TypeError : class에서 new없이 생성하면 에러발생하도록 설계
▷ Class 상속
// extends
class Car{
constructor(color){
this.color = color
this.wheels = 4
}
drive(){
console.log('drive..')
}
stop(){
console.log('STOP!')
}
}
class Bmw extends Car {
park(){
console.log('PARK')
}
}
const z4 = new Bmw('blue')
// Car 클래스에서 상속을 받아 drive메서드를 찾음
z4.drive() // drive
▷ Class 메서드 오버라이딩(method overriding)
- 부모 클래스에서 정의된 메서드를 자식 클래스에서 재정의하는 것을 말한다
- 이때, 자식 클래스에서 재정의된 메서드가 부모 클래스의 메서드를 '가리고' 새로운 동작을 수행하게 된다
- 자식 클래스가 부모 클래스의 메서드를 그대로 사용하는 것이 아니라, 같은 이름의 메서드를 새로 정의하여 부모 클래스의 메서드를 덮어쓰는 것이 오버라이딩!
- 오버라이딩은 객체지향 프로그래밍에서 다형성(Ploymorphism)을 구현하는 중요한 개념이다. 이를 통해 자식 클래스는 부모 클래스의 특정 동작을 유지하면서도 필요에 따라 동작을 수정하거나 추가할 수 있다. 이는 코드의 재사용성과 유지보수성을 높여주는 중요한 기법이다.
// extends
class Car{
constructor(color){
this.color = color
this.wheels = 4
}
drive(){
console.log('drive..')
}
stop(){
console.log('STOP!')
}
}
class Bmw extends Car {
park(){
console.log('PARK')
}
stop(){
super.stop() // Car의 stop을 사용할 수 있다
console.log('OFF')
}
}
const z4 = new Bmw('blue')
// 오버라이딩
// 동일한 이름의 메서드를 사용하면 덮어쓴다 : super.stop()이 없는 경우
z4.stop() // OFF
// overriding
class Car{
constructor(color){ // 부모 클래스를 실행할 때는 빈객체를 생성 후 실행하는반면
this.color = color
this.wheels = 4
}
drive(){
console.log('drive..')
}
stop(){
console.log('STOP!')
}
}
class Bmw extends Car { // 자식 클래스를 실행할 때는 빈객체를 생성하지 않기 때문에
constructor(color){
// 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출하는 역할
// 자식 클래스 생성자에서 super()를 호출해야 부모 클래스의 생성자가 실행되고,
// 부모 클래스에서 정의된 프로퍼티들을 초기화할 수 있다
super(color) // super()를 호출하지 않으면 this.color, this.wheels는 undefined
this.navigator = 1
}
park(){
console.log('PARK')
}
}
constuctor안에 선언된 변수는 해당 클래스의 인스턴스(객체)가 생성될 때마다 독립적으로 해당 클래스의 소유가 되는 인스턴스 변수이다. 각 인스턴스마다 서로 다른 값을 가질 수 있으며, 인스턴스마다 다른 상태를 유지하기 위해 사용된다 |
클래스 외부(constructor외부)에 선언된 변수나 함수는 해당 클래스와는 관련 없이 독립적으로 존재하며, 모든 클래스나 코드에서 접근할 수 있다. 이러한 변수나 함수들은 클래스와 무관한 범용적인 역할을 수행하며, 여러 곳에서 공유되어 사용될 수 있다. 이러한 함수들을 클래스의 정적 메서드나 외부 유틸리티 함수로 사용하면 클래스의 인스턴스와 상관 없이 독립적인 기능을 구현할 수 있다 |
15. 프로미스
// resolve는 성공, reject는 실패했을 때 실행되는 함수
const pr = new Promise((resolve, reject) => {
// code
}
// 어떤 일이 완료된 후 실행되는 함수를 callback 함수라고 한다
// 이 코드는 pending이었다가 3초 후에 fulfilled로 바뀐다
// result는 undefined에서 'ok'로 바뀐다
const pr = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
}, 3000)
})
pr.then(
function(result){ // 이행 되었을때 실행
console.log(result + ' 가지러 가자.')
}
function(err){ // 거부 되었을때 실행
console.log('다시 주문해주세요.')
}
)
// 위의 pr.then을 catch문으로 바꿔 사용할 수 있다 ; 가독성이 더 좋음
pr.then(
function(result){ // 이행 되었을때 실행
console.log(result + ' 가지러 가자.')
}
).catch(
function(err){ // 거부 되었을때 실행
console.log('다시 주문해주세요.')
}
)
pr.then(
function(result){}
).catch(
function(err){}
).finally( // 실행/거부와 상관없이 항상 실행됨
function(){
console.log('-- 주문 끝 --')
}
)
// 이 코드는 pending이었다가 3초 후에 rejected로 바뀐다
// result는 undefined에서 'error'로 바뀐다
const pr = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('error..'))
}, 3000)
})
▷ promise를 사용한 callback 지옥
// Promise Chain
const f1 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('1번 주문 완료')
}, 1000)
})
}
const f2 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('2번 주문 완료')
}, 3000)
})
}
const f3 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('3번 주문 완료')
}, 2000)
})
}
console.log('시작')
f1()
.then((res) => f2(res))
.then((res) => f3(res))
.then((res) => console.log(res))
.cacth(console.log)
.finally(() => {
console.log('끝')
}
// 시작
// 1번 주문 완료
// 2번 주문 완료
// 3번 주문 완료
// 끝
▷ Promise.all
- 모든 작업이 완료될 때까지 기다림
const f1 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('1번 주문 완료')
// rej(new Error('xx')) : all을 사용할 때 Error발생(모든게 실행되어야 ok)
}, 1000)
})
}
const f2 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('2번 주문 완료')
}, 3000)
})
}
const f3 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('3번 주문 완료')
}, 2000)
})
}
// Promise.all : 동시에 실행
Promise.all([f1(), f2(), f3()]).then(res => {
console.log(res)
})
// ['1번 주문 완료','2번 주문 완료','3번 주문 완료']
▷ Promise.race
- 하나라도 가장 먼저 완료되면 종료
Promise.race([f1(), f2(), f3()]).then(res => {
console.log(res)
})
// 1번 주문 완료
16. async, await
- async, await를 사용하면 Promise의 then메서드를 chain형식으로 호출하는 것보다 가독성이 좋아진다
▷ async
// async를 붙이면 항상 Promise를 반환한다
async function getName(){
return 'Mike'
}
console.log(getName()) // Promise {<fulfilled>: 'Mike'}
async function getName(){
return Promise.resolve('Tom')
}
getName().then((name) => {
console.log(name)
})
// Tom
async function getName(){
throw new Error('err')
}
getName().catc h((name) => {
console.log(name)
})
// Error
▷ await
- 반드시 async 안에서 사용해야 한다
- await 옆에 Promise
function getName(name){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(name)
}, 1000)
})
}
async function showName(){
const result = await getName('Mike')
console.log(result)
}
console.log('시작')
showName()
// 시작
// (1초 후) Mike
const f1 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('1번 주문 완료')
// rej(new Error('xx')) : all을 사용할 때 Error발생(모든게 실행되어야 ok)
}, 1000)
})
}
const f2 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('2번 주문 완료')
}, 3000)
})
}
const f3 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('3번 주문 완료')
}, 2000)
})
}
// async & await
console.log('시작')
async function order(){
const result1 = await f1()
const result2 = await f2(result1)
const result3 = await f3(result2)
console.log(result3)
console.log('종료')
}
order()
// 시작
// 1번 주문 완료
// 2번 주문 완료
// 3번 주문 완료
// 종료
const f1 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('1번 주문 완료')
// rej(new Error('xx')) : all을 사용할 때 Error발생(모든게 실행되어야 ok)
}, 1000)
})
}
const f2 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
rej(new Error('err'))
}, 3000)
})
}
const f3 = (message) => {
console.log(message)
return new Promise((res, rej) => {
setTimeout(() => {
res('3번 주문 완료')
}, 2000)
})
}
// async & await
console.log('시작')
async function order(){
try{
const result1 = await f1()
const result2 = await f2(result1)
const result3 = await f3(result2)
console.log(result3)
}catch(e){
console.log(e)
}
console.log('종료')
}
order()
// 시작
// 1번 주문 완료
// Error
// 종료
// 위의 코드 박스에서 Promise.all 사용
console.log('시작')
async function order(){
try{
const result = await Promise.all([f1(), f2(), f3()])
console.log(result)
}catch(e){
console.log(e)
}
console.log('종료')
}
order()
// 시작
// ['1번 주문 완료', '2번 주문 완료', '3번 주문 완료',]
// 종료
17. Generator
- 제너레이터는 다른 작업을 하다가 다시 돌아와서 next()해주면 진행이 멈췄던 부분부터 이어서 실행 (Redux saga)
- 함수의 실행을 중간에 멈췄다가 재개할 수 있는 기능
- next(), return(), throw()
function* fn(){
console.log(1)
yield: 1
console.log(2)
yield: 2
console.log(3)
console.log(4)
yield: 3
return 'finsh'
}
const a = fn()
console.log(a) // fn {<suspended>}
// next()
// next를 사용하면 가장 가까운 yield문을 만날 때까지 실행하고 데이터 객체를 반환
console.log(a.next())
// 1
// {value: 1, done: false} : value는 yield 오른쪽 값, 생략하면 undefined
console.log(a.next())
// 2
// {value: 2, done: false}
console.log(a.next())
// 3
// {value: 3, done: false}
console.log(a.next())
// {value: 'finish', done: true} : 리턴문에의해 value는 finish가 되고, done은 true
console.log(a.next())
// {value: undefined, done: true}
// return()
console.log(a.return('end'))
// {value: 'end', done: true}
console.log(a.next())
// {value: undefined, done: true}
function* fn(){
try{
console.log(1)
yield: 1
console.log(2)
yield: 2
console.log(3)
console.log(4)
yield: 3
return 'finsh'
}catch(e){
console.log(e)
}
}
const a = fn()
console.log(a.next())
// 1
// {value: 1, done: false} : value는 yield 오른쪽 값, 생략하면 undefined
console.log(a.next())
// 2
// {value: 2, done: false}
// throw
console.log(a.throw(new Error('err')))
// Error
// {value: undefined, done: true}
console.log(a.next())
// {value: undefined, done: true}
▷ iterable
- Symbol.iterator 메서드가 있다
- Symbol.iterator는 iterator를 반환해야 한다
▷ iterator
- next 메서드를 가진다
- next 메서드는 value와 done 속성을 가진 객체를 반환한다
- 작업이 끝나면 done은 true가 된다
function* fn(){
yield 4
yield 5
yield 6
}
const a = fn()
// 제너레이터의 Symbol.iterator 메서드를 실행한 값()이 자기자신이다
// 즉, 제너레이터는 이터러블 객체
console.log(a[Symbol.iterator]() === a)
// true
// 그렇다면 for ... of 사용 가능
for (let num of a){
console.log(num)
}
// 4
// 5
// 6
const str = 'hello'
console.log(str[Symbol.iterator])
// f [Symbol.iterator]() {[native code]}
const xx = str[Symbol.iterator]()
console.log(xx.next())
// {value: 'h', done; false}
...
// {value: undefined , done; true}
// 문자열도 이터러블
for (let s of xx){
console.log(s)
}
// h
// e
// l
// l
// o
▷ next() 에 인수 전달
- 제너레이터는 외부로부터 값을 입력받을 수도 있다
function* fn(){
const num1 = yield '첫 번쨰 숫자를 입력해주세요'
console.log(num1)
const num2 = yield '두 번쨰 숫자를 입력해주세요'
console.log(num2)
return num1 + num2
}
const a = fn()
console.log(a.next())
// {value: '첫 번째 숫자를 입력해주세요', done: false}
console.log(a.next(2))
// 2
// {value: '두 번째 숫자를 입력해주세요', done: false}
console.log(a.next(4))
// 4
// {value: 6, done: false}
▷ 제너레이터는 값을 미리 만들어 두지 않는다
function* fn(){
let index = 0
while(true){
yield index++
}
}
const a = fn()
console.log(a.next())
// {value: 0, done: false} ...
// 제너레이터 함수를 사용하지 않았다면 break가 없는 while true문
▷ yield* 을 이용
function* gen1(){
yield 'w'
yield 'o'
yield 'r'
yield 'l'
yield 'd'
}
// gen1함수 호출
function* gen2(){
yield 'Hello'
yield* gen1()
yield '!'
}
console.log(...gen2()) // Hello, w o r l d !
18. 추가 기능
▷ replaceAll()
const str1 = 'Hello World'
console.log(str1.replaceAll('l', '~')) // He~~o Wor~d
▷ Promise.any
- 여러 개의 프로미스 중 하나라도 성공한 경우, 그중 가장 먼저 성공한 프로미스를 반환하는 메서드
- 여러 개의 프로미스가 실행되고 있는데, 그중에서 가장 먼저 성공한 프로미스의 결과만을 반환한다.
- 만약 모든 프로미스가 실패하는 경우에만 'AggregateError'를 반환한다
const rejPromise = new Promise((res, rej) => {
setTimeout(() => {
rej('fail..')
}, 1000)
})
const resPromise = new Promise((res, rej) => {
setTimeout(() => {
res('success')
}, 2000)
})
// Promise.race: 프로미스 중에 가장 먼저 완료된 결과값으로 이행/거부
Promise.race([rejPromise, resPromise])
.then(() => console.log('성공'))
.catch(2 => console.log(e))
// fail..
// Promise.any: 프로미스 중에 가장 먼저 이행된 객체 반환
Promise.any([rejPromise, resPromise])
.then(() => console.log('성공'))
.catch(2 => console.log(e))
// success
▷ Logical assignment Operators 논리 할당 연산자
- || : 앞의 값이 falsy이면 뒤의 값
- ?? : 앞의 값이 null이나 undefined이면 뒤의 값
function add(num1, num2){
num1 = num1 || 0
num2 ||= 0 // 앞의 num1과 같은 결과값을 가진다
console.log(num1, num2)
}
console.log(add()) // 0
name = name && `Hello ${name}`
name &&= `Hello ${name}`
name = name ?? 'Mike'
name ??= 'Mike'
let num = 0
let a = num || 3
console.log(a) // 3
let b = num ?? 3
console.log(b) // 0
▷ Numeric separators 숫자 구분자
// 쉽표 대신 언더스코어를 사용하여 구분한다
let billion = 1_000_000_000
▷ WeakRef = weak reference
- 자바스크립트의 내장 클래스로, 약한 참조를 구현하는 데 사용된다
- 약한 참조란? 다른 객체를 가리키지만, 가비지 컬렉터가 해당 객체를 수거할 수 있는 참조
- 참조하는 객체가 더 이상 다른 객체에서 참조되지 않으면 가비지 컬렉터에 의해 수거된다
- 보통 메모리 누수를 방지하고 성능을 최적화하기 위해 사용된다. 주로 큰 객체나 객체 간에 순환 참조가 발생하는 경우에 유용하다
// 객체 생성
const obj = { data: "Hello, World!" };
// WeakRef 생성
const weakRef = new WeakRef(obj);
// WeakRef에서 객체를 가져옴 (가비지 컬렉터에 의해 수거되지 않은 경우)
const retrievedObj = weakRef.deref();
console.log(retrievedObj.data); // Output: Hello, World!
// 객체를 가비지 컬렉터에 수거함 (= 객체가 더 이상 사용되지 않고 접근할 수 없음)
obj = null;
// retrievedObj를 가져오려고 시도하면, 가비지 컬렉터에 의해 이미 수거되어 가져올 수 없음
console.log(retrievedObj); // Output: undefined
※ WeakRef 사용 시 고려해야 할 주요 사항
- 객체가 언제 가비지 컬렉터에 의해 수거될지 알 수 없다
- 약한 참조를 유지하고 있는 객체는 언제 메모리에서 해제될지 예측할 수 없다
- 약한 참조를 유지하는 동안 객체의 소유권 보장이 어렵다
- 강한 참조가 없다면 다른 곳에서 객체가 해제될 수 있다
- 특정 상황에서 메모리 누수 가능성
- 약한 참조로 인해 객체가 더 이상 강한 참조를 받지 않더라도 메모리에서 해제되지 않을 수 있어서 메모리 누수 가능성이 있다
[출처] 코딩앙마 | https://youtu.be/ocGc-AmWSnQ
'JS' 카테고리의 다른 글
[JS] Modal 만들기 (0) | 2023.07.10 |
---|---|
[JS] FAQ(자주 묻는 질문) 페이지 만들기 ✅ (0) | 2023.07.10 |
[JS] 반응형 Navbar, Sidebar 만들기 (0) | 2023.07.07 |
[JS] Carousel 만들기 (0) | 2023.07.06 |
[JS] Number Counter 만들기 (0) | 2023.07.06 |