코루틴

코루틴 이란 ? 비동기적으로 실행되는 코드를 간소화 하기 위해 안드로이드에서 사용할 수 있는 동시 실행 설계 패턴입니다.

  • 경량 : 코루틴은 실행중인 스레드를 차단하지 않는 정지를 지원하므로 단일 스레드에서 많은 코루틴을 실행할 수 있습니다.
  • 메모리 누수 감소 : 구조화된 동시 실행을 사용하여 범위 내에서 작업을 실행합니다.
  • 기본으로 제공되는 취소 지원 : 실행 중인 코루틴 계층 구조를 통해 취소가 전달됩니다.
  • 이외 Jetpack 지원등..

백그라운드 스레드에서 실행

기본 스레드에서 네트워크 요청을 보내면 응답을 받을 때까지 스레드가 대기하거나 차단됩니다.

스레드가 차단될 경우 앱이 정지되고 응답없음(ANR) 대화상자가 표시될 수 있습니다.

이것의 해결방식으로 네트워크 요청을 새로운 코루틴을 만들고 I/O스레드에서 네트워크 요청을 실행할 수 있습니다.

example 1)

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun login(username: String, token: String) {
        // Create a new coroutine to move the execution off the UI thread
        viewModelScope.launch(Dispatchers.IO) {
            val jsonBody = "{ username: \\"$username\\", token: \\"$token\\"}"
            loginRepository.makeLoginRequest(jsonBody)
        }
    }
}
  • viewModelScope는 사전 정의된 CoroutineScope 입니다. 모든 코루틴은 코루틴범위 내에서 실행해야 합니다. CoroutineScope는 하나 이상의 관련 코루틴을 관리합니다.
  • launch는 코루틴을 만들고 함수 본문의 실행을 해당하는 디스패처에 전달하는 함수입니다.
  • Dispatchers.IO는 이 코루틴을 I/O작업용으로 예약된 스레드에서 해야 함을 나타냅니다.

기본 안전을 위해 코루틴 사용

기본 스레드에서 UI 업데이트를 차단하지 않는 한수를 기본 안전 함수로 간주합니다. 기본스레드에서 네트워크 요청을 하면 UI가 차단되므로 이 네트워크 요청이 담긴 함수는 기본 안전 함수가 아닙니다.

코루틴 라이브러리의 withContext() 함수를 사용하여 코루틴 실행을 다른 스레드로 이동합니다.

example 2)

class LoginRepository(...) {
    ...
    suspend fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {

        // Move the execution of the coroutine to the I/O dispatcher
        return withContext(Dispatchers.IO) {
            // Blocking network request code
        }
    }
}
  • withContext(Dispatcher.IO)는 코루틴 실행을 I/O스레드로 이동하여 호출 함수를 기본 안전 함수로 만들고 필요에 따라 UI를 업데이트 하도록 설정합니다.
  • makeLoginRequest 에는 supsend 키워드가 표시됩니다. suspend키워드는 코루틴 내에서 함수가 호출되도록 강제하는 Kotlin의 방법입니다.

example 3)

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun login(username: String, token: String) {

        // Create a new coroutine on the UI thread
        viewModelScope.launch {
            val jsonBody = "{ username: \\"$username\\", token: \\"$token\\"}"

            // Make the network call and suspend execution until it finishes
            val result = loginRepository.makeLoginRequest(jsonBody)

            // Display result of the network request to the user
            when (result) {
                is Result.Success<LoginResponse> -> // Happy path
                else -> // Show error in UI
            }
        }
    }
}

다시 example 1을 수정한 버전을 살펴봅시다.

  • launch가 Dispatchers.IO 매개변수를 사용하지 않습니다. Dispatcher를 launch에 전달하지 않으면 코루틴은 기본 스레드에서 실행됩니다.
  • 앱이 기본스레드의 View 레이어에서 login 함수를 호출합니다.
  • launch가 기본 스레드에서 네트워크 요청을 보낼 때 코루틴을 만들며, 코루틴이 실행을 시작합니다.
  • 코루틴 내에서 makeLoginRequest 호출은 withContext 블록 실행이 끝날 때 까지 코루틴의 추가 실행을 정지합니다 (ViewmodelScope의 코루틴의 코드를 makeLoginRequest의 withContext 블록 실행이 끝날때까지 일시 정지하고 끝난 후 부터 다시 실행합니다.)
  • withContext블록이 완료되면 네트워크 요청 결과와 함께 기본 스레드에서 실행을 재개합니다.

 

출처 : https://developer.android.com/kotlin/coroutines?hl=ko 

 

Android의 Kotlin 코루틴  |  Android 개발자  |  Android Developers

Android의 Kotlin 코루틴 코루틴은 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴입니다. 코루틴은 Kotlin 버전 1.3에 추가되었으며 다른 언어에서 확

developer.android.com

 

 

실행 컨텍스트

컨텍스트란? 한국말로 ‘문맥’, 코드의 실행환경.

브라우저가 스크립트를 로딩해서 실행하는 순간 전역 컨텍스트 생성됨. 이는 페이지가 종료될 때까지 유지됨.

자바스크립트는 함수 스코프를 따름. 함수를 호출할 때마다 함수 컨텍스트가 하나씩 더 생김.

  • 전역 컨텍스트 하나가 생성 된 후 , 함수 호출시마다 컨텍스트가 생성됨.
  • 컨텍스트 생성 시 컨텍스트 안에 변수객체(arguments, variable), scope chain, this가 생성됨.
  • 컨텍스트 생성 후 함수가 실행 됨. 사용되는 변수들은 변수 객체안에서 값을 찾고 없으면 스코프 체인을 타고 올라감.
  • 함수 실행이 마무리 되면 컨텍스트는 사라짐 (클로저는 사라지지 않음), 페이지가 종료되면 전역 컨텍스트가 사라짐

전역 컨텍스트

전역 컨텍스트가 생성된 후 변수객체, scope chain, this가 들어옴.

전역 컨텍스트는 arguments가(함수의 인자) 없음. variable은 해당 스코프의 변수들임.

scope chain(자신과 상위 스코프들의 변수객체)은 자기 자신인 저역 변수 객체임.

this는 따로 설정되어 있지 않으면 window임. this를 바꾸는 방법은 new를 호출하는 것. 기본적으로 일반함수의 this는 window이며, new나 bind 같은 상황에서 this가 바뀜.

함수 컨텍스트

함수를 호출하는 순간 새로운 컨텍스트인 함수 컨텍스트가 생김. 전역 컨텍스트는 그대로. this는 따로 설정해준 적 없으면 window가 됨.

호이스팅

호이스팅이란 ? 변수를 선언하고 초기화했을 때 선언 부분이 최상단으로 끌어올려지는 현상을 의미. 함수 선언식 (ex ) function sayWow(){})으로 선언하였을 때는 식 자체가 통째로 끌어올려짐.

함수 표현식으로 선언할 경우에는 호이스팅이 일어나지 않음 (ex) var sayYeah = function() {})

클로저

클로저란 ? 비공개 변수를 가질 수 있는 환경에 있는 함수.

비공개 변수는 클로저 함수 내부에 생성한 변수도 아니고, 매개변수도 아닌 변수를 의미.

ex) function parent(){

var name = ”mom”

return function () {

console.log(name)

}

}

var closure = parent()

closure() // mom

여기서 name변수나, name변수가 있는 스코프에 대해서 클로저라고 부를 수 있음.

이를 컨텍스트적으로 분석해보면

  1. 전역 컨텍스트 생성 후 parent()함수 호출 시 parent 함수의 컨텍스트가 새로 만들어 짐
  2. var closure = parent() 할때 function을 return 하고 그 function을 선언할 때 scope chain은 lexical scoping을 따라서 [’parent 변수객체’, ‘전역 변수객체’]를 포함함. 따라서 closure을 호출할 때 scope체인은 closure 변수객체, parent 변수객체, 전역 변수객체 순으로 따라 올라가게 되고, scope chain을 통해 parent의 name 변수에 접근 할 수 있음.

클로저를 잘못 사용했을 시 성능 문제와 메모리 문제가 발생할 수 있음.

현재 상황

현재 vue.js를 이용한 SPA을 만들어 배포하고 있는데, Code Splitting을 적용한 이후로 새로 앱을 배포할 때

ChunkLoadError라는 에러를 만나게 되었다..

배포를 해야 에러를 테스트 할 수 있기 때문에 테스트도 굉장히 힘들었는데

이 에러를 해결한 과정을 정리하여 써보려고 한다.

 

 

Code Splitting이란?

webpack을 이용해 번들링을 하면, 초기 로딩시 번들링을 통해 합쳐진 js파일을 로딩하기 때문에 앱이 점점 커지면서 초기로딩이 길어지는 문제가 생긴다.

이를 해결하기 위한 것이 바로 Code Splitting, Lazy Loading이다.

 

문제점?

사용자가 앱을 사용하는 도중에 웹앱 재배포가 일어날 때, index.html은 새로 배포된 파일이 아닌 이전 파일의 정보를 담고 있기 때문에 배포 이전 파일들을 요청하게 되어 이 파일들을 찾지 못하는 상황이 일어난다. (파일은 새롭게 배포되어있는 상태이기 때문에 이전 파일들은 찾아도 찾을 수 없게 된다)

 

디버깅 테스트 과정

우리 프로젝트는 vue2+ vue router + vue CLI + webpack 4 로 구성되어 있는 상태였다.

테스트 과정에서 새롭게 안 사실은 개발모드로 빌드를 한 파일들은 파일 끝에 해시값이 붙지 않아서 위와 같은 오류가 애초에 발생하지 않는 다는 사실이다. 그래서 개발환경에서 앱을 재배포하는 과정에서는 발견되지 않은 에러가 운영 환경에서는 발견 되었던 것이다.

 

이와 같은 사실은, 개발환경에서 아이폰에서 웹앱 개발시 앱을 껐다가 켜도 계속해서 변경사항이 반영되지 않는 현상도 설명이 가능했다. 개발버전으로 빌드 시 청크파일에 해시값이 애초에 붙지 않기 때문에 이전에 배포된 파일의 이름과 새롭게 빌드되어 배포된 파일의 이름이 같아 새롭게 index.html을 배포해도 브라우저에 캐시된 새로 배포되기 이전 파일을 가져오기 때문이었다. 

 

production 모드로 빌드를 진행하면 각 chunk파일 이름 뒤에 랜덤의 해시 값이 붙게 되는데, 해시값 설정은 webpack의 config 파일에 여러가지 변경이 가능하다.

 

우리는 vue CLI를 이용하고 있었기 때문에 vue.config.js 파일의 웹팩 설정 밑에

 output: {
            filename: `[name].[chunkhash].js`,
            chunkFilename: `[name].[chunkhash].js`,
        },
 
을 추가하여 해시값을 개발모드에서도 붙여주었다.
 
참고로 해시값은 변경된 파일에 한해서만 변경이 되고, 만약 그 파일에 변경사항이 없다면 해시값을 유지함으로써 캐싱된 화면에 대해서 새롭게 로딩을 할 필요성을 줄여준다.
 
테스트는 
 
1.앱을 실행한다.

2. 특정 화면에 해당하는 파일에 변경사항을 추가한다.

3. 앱을 재 배포한다.

4. 변경사항을 추가했던 화면에 접근한다.
 
순으로 진행 되었고, 드디어 개발 모드에서 ChunkError를 만나게 될 수 있었다
 

문제 설명

0 또는 양의 정수가 주어졌을 때, 정수를 이어 붙여 만들 수 있는 가장 큰 수를 알아내 주세요.

예를 들어, 주어진 정수가 [6, 10, 2]라면 [6102, 6210, 1062, 1026, 2610, 2106]를 만들 수 있고, 이중 가장 큰 수는 6210입니다.

0 또는 양의 정수가 담긴 배열 numbers가 매개변수로 주어질 때, 순서를 재배치하여 만들 수 있는 가장 큰 수를 문자열로 바꾸어 return 하도록 solution 함수를 작성해주세요.

제한 사항
  • numbers의 길이는 1 이상 100,000 이하입니다.
  • numbers의 원소는 0 이상 1,000 이하입니다.
  • 정답이 너무 클 수 있으니 문자열로 바꾸어 return 합니다.
입출력 예numbersreturn
[6, 10, 2] "6210"
[3, 30, 34, 5, 9] "9534330"

 

풀이

이 문제를 풀이는 2개의 방법이 있다

 

1. 비교하는 문자를 앞뒤로 바꿔서 더 큰수를 만들 수 있는 순서대로 분류 시키는 방법 

ex) 6, 2 ,10에서 62 26를 비교, 210 102 비교, 더 큰 수를 만들 수 있는 순서대로 sorting

 

2. 각각의 요소를 4자리 수로 만들고, 이를 내림차순으로 sorting 

ex) 6,2,10에서 6666, 2222, 1010 을 만들고 이 수의 크기를 비교해 각각의 요소를 내림차순으로 sorting

 

여기서 주의할 점은 모든 요소가 0일때 0을 요소갯수만큼 붙인 문자열이 아닌 "0"으로 리턴해야 한다는 점이다.

 

나는 1번 풀이법으로 풀어보았다.

function solution(numbers) {

    
    let newNumbers = numbers.map((v) => {
        return v+""
    }).sort().reverse().sort((a,b) => {
        let tempA = a + b
        let tempB = b + a
        return parseInt(tempB) - parseInt(tempA)
    })
  
    
    
    return newNumbers.join("")[0] == "0" ? "0" : newNumbers.join("");
    
}

 

Map ?

es6에 추가된 자료형으로 Object와 비슷하지만 크게 다른점으로는

1. 순서가 있음

2 iterable 함

3. key값에 string값이 아닌 다른 타입이 들어갈 수 있음

4. 크기를 쉽게 알 수 있음

 

이렇게 있습니다.

 

그럼 위와 같은 Map이 주는 장점들로 인해 항상 Object보단 Map을 사용하는것이 좋을까요?

 

그렇지는 않습니다.

 

Object는 데이터를 저장하기 위한 굉장히 간단한 구조입니다. 그렇기 때문에 key값이 string인 간단한 데이터를 저장하기 위해서는 생성이 굉장히 쉽습니다. 또한 JSON으로 데이터를 어딘가에 전송해야 할 경우에는 Object를 사용해야합니다. Map은 아직 JSON으로 변환 되어 전달되지 않기 때문입니다.

 

그러나 Map은 Hash구조로 Object보다 순환이 빠르기 때문에 데이터를 추가하거나 수정하는 것이 빈번 할 경우 Map을 쓰는것이 좋습니다.

Map의 사용

map은 생성자를 이용하여 생성합니다.

const wrongMap = new Map()

 

set(key, value) 메서드를 이용하여 데이터를 set하고, has 메서드로 입력한 key에 해당하는 데이터 보유 유무를 확인합ㄴ디ㅏ. 또한 get 메서드를 이용하여 입력한 key에 대한 value를 얻을 수도 있습니다.

contacts.set('Jessie', {phone: "213-555-1234", address: "123 N 1st Ave"})
contacts.has('Jessie') // true
contacts.get('Hilary') // undefined

 

delete메서드를 이용하여 입력한 key에 해당하는 데이터를 지울수도 있으며 

size메서드로 map의 크기도 알 수 있습니다.

contacts.delete('Raymond') // false

 

 

 

 

Set과 마찬가지로 Map은 Map이외에 WeakMap이라는 자료형도 있습니다.

이 자료형은 마찬가지로 iterable 하지 않으며, 참조카운팅을 추가 하지도 않습니다.

 

WeakMap은 WeakSet과 달리 사용성이 좋은걸로 알고있으나.. 저는 사용해본적이 없어 사용할 일이 생긴다면 사용해보고 정리하여 따로 포스팅 하겠습니다.

 

문제

카카오톡 오픈채팅방에서는 친구가 아닌 사람들과 대화를 할 수 있는데, 본래 닉네임이 아닌 가상의 닉네임을 사용하여 채팅방에 들어갈 수 있다.

신입사원인 김크루는 카카오톡 오픈 채팅방을 개설한 사람을 위해, 다양한 사람들이 들어오고, 나가는 것을 지켜볼 수 있는 관리자창을 만들기로 했다. 채팅방에 누군가 들어오면 다음 메시지가 출력된다.

"[닉네임]님이 들어왔습니다."

채팅방에서 누군가 나가면 다음 메시지가 출력된다.

"[닉네임]님이 나갔습니다."

채팅방에서 닉네임을 변경하는 방법은 다음과 같이 두 가지이다.

  • 채팅방을 나간 후, 새로운 닉네임으로 다시 들어간다.
  • 채팅방에서 닉네임을 변경한다.

닉네임을 변경할 때는 기존에 채팅방에 출력되어 있던 메시지의 닉네임도 전부 변경된다.

예를 들어, 채팅방에 "Muzi"와 "Prodo"라는 닉네임을 사용하는 사람이 순서대로 들어오면 채팅방에는 다음과 같이 메시지가 출력된다.

"Muzi님이 들어왔습니다."
"Prodo님이 들어왔습니다."

채팅방에 있던 사람이 나가면 채팅방에는 다음과 같이 메시지가 남는다.

"Muzi님이 들어왔습니다."
"Prodo님이 들어왔습니다."
"Muzi님이 나갔습니다."

Muzi가 나간후 다시 들어올 때, Prodo 라는 닉네임으로 들어올 경우 기존에 채팅방에 남아있던 Muzi도 Prodo로 다음과 같이 변경된다.

"Prodo님이 들어왔습니다."
"Prodo님이 들어왔습니다."
"Prodo님이 나갔습니다."
"Prodo님이 들어왔습니다."

채팅방은 중복 닉네임을 허용하기 때문에, 현재 채팅방에는 Prodo라는 닉네임을 사용하는 사람이 두 명이 있다. 이제, 채팅방에 두 번째로 들어왔던 Prodo가 Ryan으로 닉네임을 변경하면 채팅방 메시지는 다음과 같이 변경된다.

"Prodo님이 들어왔습니다."
"Ryan님이 들어왔습니다."
"Prodo님이 나갔습니다."
"Prodo님이 들어왔습니다."

채팅방에 들어오고 나가거나, 닉네임을 변경한 기록이 담긴 문자열 배열 record가 매개변수로 주어질 때, 모든 기록이 처리된 후, 최종적으로 방을 개설한 사람이 보게 되는 메시지를 문자열 배열 형태로 return 하도록 solution 함수를 완성하라.

제한사항

  • record는 다음과 같은 문자열이 담긴 배열이며, 길이는 1 이상 100,000 이하이다.
  • 다음은 record에 담긴 문자열에 대한 설명이다.
    • 모든 유저는 [유저 아이디]로 구분한다.
    • [유저 아이디] 사용자가 [닉네임]으로 채팅방에 입장 - "Enter [유저 아이디] [닉네임]" (ex. "Enter uid1234 Muzi")
    • [유저 아이디] 사용자가 채팅방에서 퇴장 - "Leave [유저 아이디]" (ex. "Leave uid1234")
    • [유저 아이디] 사용자가 닉네임을 [닉네임]으로 변경 - "Change [유저 아이디] [닉네임]" (ex. "Change uid1234 Muzi")
    • 첫 단어는 Enter, Leave, Change 중 하나이다.
    • 각 단어는 공백으로 구분되어 있으며, 알파벳 대문자, 소문자, 숫자로만 이루어져있다.
    • 유저 아이디와 닉네임은 알파벳 대문자, 소문자를 구별한다.
    • 유저 아이디와 닉네임의 길이는 1 이상 10 이하이다.
    • 채팅방에서 나간 유저가 닉네임을 변경하는 등 잘못 된 입력은 주어지지 않는다.

입출력 예

recordresult

["Enter uid1234 Muzi", "Enter uid4567 Prodo","Leave uid1234","Enter uid1234 Prodo","Change uid4567 Ryan"] ["Prodo님이 들어왔습니다.", "Ryan님이 들어왔습니다.", "Prodo님이 나갔습니다.", "Prodo님이 들어왔습니다."]

입출력 예 설명

입출력 예 #1
문제의 설명과 같다.

 

 

 

풀이

function solution(record) {
    let tempList = [];
    let answer = [];
    let userInfo = {}
    record.forEach((item) =>{
       let list = item.split(" ")
        tempList.push(list)
        if(list[2]){
            userInfo[list[1]] = list[2]
        }
    })

    
    tempList.forEach((item) => {
        if(item[0] === "Enter"){
            answer.push(`${userInfo[item[1]]}님이 들어왔습니다.`)
        }else if(item[0] === "Leave"){
            answer.push(`${userInfo[item[1]]}님이 나갔습니다.`)
        }
    })
    
    
    return answer
}

 

uuid로 유저를 저장하기 위해서 userInfo 라는 객체를 생성하고, for문 안에서 uuid에 해당하는 이름을 갱신하는게 포인트다.

처음엔 Leave 일시, split으로 나눴던 배열 안의 index 2번 값이 비어있는걸 고려하지 않아 오류가 났다.

if문으로 비어있는지 아닌지 체크를 해야한다. 비어있지 않을때 userInfo의 uuid의 이름을 갱신해야한다.

자바스크립트에서 event를 컨트롤 할 때 preventDefault라는 메서드를 자주 접하게 된다.

 

event.preventDefault()는 해당 event가  cancelable 할때 (cancelable이 true일 때) 해당 이벤트의 기본 동작의 수행을 막는 메서드이다. 모든 event가 cancelable 하지는 않으니 모든 이벤트에 유효한 메서드는 아니다. cancelable 하지 않은 이벤트는 preventDefault를 호출해도 아무 반응이 일어나지 않는다.

 

 

예를 들어서 체크박스의 클릭이벤트를 막아 체크박스를 체크하지 못하게 만든다거나, submit 이벤트에 사용하여 데이터를 전송하지 못하게 하는 등 다양한 방면에서 사용 가능하다.

 

그러나 preventDefault는 이벤트 전파를 막지는 못합니다. 상위요소로의 이벤트 전파를 막기 위해서는 stopPropagation() 또는 stopImmediatePropagation()을 함께 사용해야합니다.

 

참고

https://developer.mozilla.org/ko/docs/Web/API/Event/preventDefault

 

Event.preventDefault() - Web API | MDN

Event 인터페이스의 preventDefault() 메서드는 어떤 이벤트를 명시적으로 처리하지 않은 경우, 해당 이벤트에 대한 사용자 에이전트의 기본 동작을 실행하지 않도록 지정합니다.

developer.mozilla.org

 

1.map

각 요소에 대해 주어진 함수를 수행하는 결과를 모아 새로운 배열을 반환한다.

ex) let newArray = testArray.map((value, index, array) => {

           // value : 현재 요소의 값, index : 현재 요소의 인덱스, array : 원본 배열
            return value * 2;
       });

 

2.filter

각 요소에 대해 주어진 함수의 결과값이 true인 결과를 모아 새로운 배열을 반환한다.

ex) let newArray = testArray.filter((value, index, array) => {

           // value : 현재 요소의 값, index : 현재 요소의 인덱스, array : 원본 배열
            return value * 2 > 0 //boolean 값 이어야 한다.
       });

 

map과는 다르게 return 값이 boolean 이고, true인 결과를 모아 새로운 배열을 반환하는데, 메서드 이름 그대로 , 어떤 배열을 조건에 맞게 필터링할 때 (ex validation을 체크해서 새로운 배열을 만들 때) 사용하기 쉽다. 

 

3.reduce

배열 각 요소에 대해 reducer함수를 실행하고 배열이 아닌 하나의 결과값을 반환한다.

ex) const numberList = [1, 2, 3, 4];
 const sum = numberList.reduce((acc, value) => { 
        //acc : 축적된 데이터 value : 현재 요소의 값
        return acc + cur; 
    }, 10 // 초기 값);
예를들어 위와 같이 사용하였을 때는 10 + 1 + 2 + 3+ 4 의 계산결과인 20이 sum에 할당 되게 된다.
초기값에 빈 배열을 할당하고 value를 그 배열에 push 하는 형태로 사용하면 새로운 배열을 만들어 낼 수 있다는 점에서 filter와 map을 다 아우를 수 있는 메서드이다. 다양한 방면에 적용이 가능하고 퍼포먼스 적인 측면에서도 가장 낫기 때문에 가장 많이 쓰인다. 

 

 
 

setTimeout 이란?

일정 시간이 지나고 지정한 코드를 실행시키는 메서드로 보통 아래와 같은 형태로 자주 사용한다.

setTimeout(() => {console.log("첫 번째 메시지")}, 5000);
setTimeout(() => {console.log("두 번째 메시지")}, 3000);
setTimeout(() => {console.log("세 번째 메시지")}, 1000);

위에서 , 뒤에 있는 숫자가 지연 시간을 나타내는데, 단위는 밀리세컨드이다. (1000 = 1초)

 

정확히 지정한 시간 이후 코드가 실행될까?

그런데 실제로 사용해보면 정확하게 지정한 시간이 지났을때 코드가 실행되지 않고 지연되는 경우가 발생한다. 글쓴이의 경우 스크롤을 이동시킬 때 setTimeout에 딜레이를 0으로 주고 그 안에서 scrollTo메서드를 사용할 때와 setTimeout으로 감싸지 않고 scrollTo를 사용할 때 결과에 차이가 발생하여(setTimeout으로 감싼 경우에만 스크롤이 이동하였다) setTimeout에대해 더 찾아보게 되었고, 그 결과 다음과 같은 문서를 보게 되었다. 실제로 찾아보면 scroll이동 이벤트는 많은 사람들이 setTimeout과 함께 사용하는 것을 추천하고 있다.

 

https://developer.mozilla.org/ko/docs/Web/API/setTimeout#%EB%94%9C%EB%A0%88%EC%9D%B4%EA%B0%80_%EC%A7%80%EC%A0%95%ED%95%9C_%EA%B0%92%EB%B3%B4%EB%8B%A4_%EB%8D%94_%EA%B8%B4_%EC%9D%B4%EC%9C%A0

 

setTimeout() - Web API | MDN

전역 setTimeout() 메서드는 만료된 후 함수나 지정한 코드 조각을 실행하는 타이머를 설정합니다.

developer.mozilla.org

 

위 문서에 나온 내용을 요약해보면

1. 중첩 타임아웃 사용시

브라우저는 setTimeout 호출이 5번 이상 호출된 경우 4ms의 최소 타임아웃을 강제함

2. 비활성탭의 타임아웃

백그라운드 탭으로 인한 부하(와 그로 인한 배터리 사용량)를 경감하기 위해, 브라우저는 비활성 탭에서의 최소 딜레이에 최소 값을 강제함

3. 페이지, 운영체제, 브라우저가 다른 작업으로 인해 바쁠 경우

페이지, 운영체제, 브라우저가 다른 작업으로 인해 바쁠 경우 타임아웃이 예상보다 늦게 실행될 수 있습니다.

 

나의 경우 3번에 해당하는 경우라고 추정하였다. (서버에서 데이터를 불러오고, 이를 이용하여 렌더링을 마치고 나서 스크롤이 생긴 이후 스크롤을 이동시켜야 하기 때문에)

 

이를 더 자세히 이해하기 위해서는 자바스크립트 런타임에 대한 이해가 필요한데, 이는 다음장에서 설명하도록 하겠다.

문제 설명

명함 지갑을 만드는 회사에서 지갑의 크기를 정하려고 합니다. 다양한 모양과 크기의 명함들을 모두 수납할 수 있으면서, 작아서 들고 다니기 편한 지갑을 만들어야 합니다. 이러한 요건을 만족하는 지갑을 만들기 위해 디자인팀은 모든 명함의 가로 길이와 세로 길이를 조사했습니다.

아래 표는 4가지 명함의 가로 길이와 세로 길이를 나타냅니다.

명함 번호가로 길이세로 길이
1 60 50
2 30 70
3 60 30
4 80 40

가장 긴 가로 길이와 세로 길이가 각각 80, 70이기 때문에 80(가로) x 70(세로) 크기의 지갑을 만들면 모든 명함들을 수납할 수 있습니다. 하지만 2번 명함을 가로로 눕혀 수납한다면 80(가로) x 50(세로) 크기의 지갑으로 모든 명함들을 수납할 수 있습니다. 이때의 지갑 크기는 4000(=80 x 50)입니다.

모든 명함의 가로 길이와 세로 길이를 나타내는 2차원 배열 sizes가 매개변수로 주어집니다. 모든 명함을 수납할 수 있는 가장 작은 지갑을 만들 때, 지갑의 크기를 return 하도록 solution 함수를 완성해주세요.


제한사항
  • sizes의 길이는 1 이상 10,000 이하입니다.
    • sizes의 원소는 [w, h] 형식입니다.
    • w는 명함의 가로 길이를 나타냅니다.
    • h는 명함의 세로 길이를 나타냅니다.
    • w와 h는 1 이상 1,000 이하인 자연수입니다.

입출력 예sizesresult
[[60, 50], [30, 70], [60, 30], [80, 40]] 4000
[[10, 7], [12, 3], [8, 15], [14, 7], [5, 15]] 120
[[14, 4], [19, 6], [6, 16], [18, 7], [7, 11]] 133

입출력 예 설명

입출력 예 #1
문제 예시와 같습니다.

입출력 예 #2
명함들을 적절히 회전시켜 겹쳤을 때, 3번째 명함(가로: 8, 세로: 15)이 다른 모든 명함보다 크기가 큽니다. 따라서 지갑의 크기는 3번째 명함의 크기와 같으며, 120(=8 x 15)을 return 합니다.

입출력 예 #3
명함들을 적절히 회전시켜 겹쳤을 때, 모든 명함을 포함하는 가장 작은 지갑의 크기는 133(=19 x 7)입니다.

 

풀이

푸는 방법은 각각 너비와 높이를 주어진 2차원 배열의 길이 만큼 for문을 돌려서 비교한다음 큰수는 maxList에 작은수는 minList에 담아 마지막에는 maxList minList에서 가장 큰 값들을 곱해 정답을 구하는 방식으로 풀었다

 

function solution(sizes) {

var answer = 0;

var maxList = [];

var minList = [];

 

 

for(let i = 0; i < sizes.length; i++){

maxList.push(Math.max(sizes[i][0], sizes[i][1]))

minList.push(Math.min(sizes[i][0], sizes[i][1]))

} //큰수는 큰수끼리, 작은수는 작은수끼리 모아보기

 

maxList.sort((a,b)=> a-b)

minList.sort((a,b)=> a-b)

 

answer = maxList[maxList.length-1] * minList[minList.length-1]

 

 

 

return answer;

}

+ Recent posts