한 걸음씩

자바스크립트 중급 총정리 본문

JS

자바스크립트 중급 총정리

winter17 2023. 7. 28. 10:43

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