1주차 화요일, Javascript Study+코딩테스트

Edwin·2023년 2월 14일
post-thumbnail
  • 본 포스트는 항해99 13기에 대한 리뷰입니다.

오늘의 코딩테스트; 문자열 내 마음대로 정렬하기

프로그래머스, 문자열 내 마음대로 정렬하기

문자열로 구성된 리스트 strings와, 정수 n이 주어졌을 때, 각 문자열의 인덱스 n번째 글자를 기준으로 오름차순 정렬하려 합니다. 예를 들어 strings가 ["sun", "bed", "car"]이고 n이 1이면 각 단어의 인덱스 1의 문자 "u", "e", "a"로 strings를 정렬합니다.

1 나의 풀이 살펴보기

01) 일단 해보자.

  • 먼저, 배열에 들어있는 n번째 위치에 해당 되는 값에 먼저 접근해야 한다.

    string.slice(n,n+1)

  • 그리고 해당 내용을 기준으로 하여서 정렬을 해주면 되지 않을까?

위의 내용이 처음에 생각한 내용이었다.

function solution(string, n) {
  let strpart = (string.map(str => str.slice(n,n+1))).sort()
  console.log(strpart)  
}

콘솔을 확인해보면, 나오기는 했지만, n번지의 값만 가져와서 정렬을 했다. 문자열 전체가 온 것이 아니다. 즉 문제는 이해했지만 코드를 잘못 작성했다.

02) 다시 시도해보자.

let string = ["abce", "abcd", "cdx"];
let n = 1

function solution(string, n) {
  let arr = []
  
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "a"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "b"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "c"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "d"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "e"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "f"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "g"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "h"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "i"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "j"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "k"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "l"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "m"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "n"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "o"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "p"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "q"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "r"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "s"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "t"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "u"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "v"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "w"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "x"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "y"){arr.push(string[i])}}
  for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "z"){arr.push(string[i])}}

  console.log(arr
    
)}

solution(string, n)
  • 정말 단순하게 해서 문제를 풀기는 했다.
    1. 먼저 반복문을 실행하고, 실행된 결과에 따라서 빈배열인 arr에 내용이 입력되게 하였다.
    2. 반복문의 내용은 조건을 달아서, 해당 구문에서 만약 slice(n,n+1)의 값이 만약 a 일 때부터 z 일 때까지 순차적으로 진행하면서, 해당되는 조건의 값을 순차적으로 기록하도록 하였다.
    3. string[0] 부터 string[string.length -1]까지 먼저 a 를 가지고 있으면 배열에 추가하고 아니면 다음 함수로 넘어간다.
    4. string[0] 부터 string[string.length -1]까지 먼저 b 를 가지고 있으면 배열에 추가하고 아니면 다음 함수로 넘어간다.
    5. string[0] 부터 string[string.length -1]까지 먼저 c 를 가지고 있으면 배열에 추가하고 아니면 다음 함수로 넘어간다...
    6. d부터 y까지를 위와 같이 실행하고,
    7. string[0] 부터 string[string.length -1]까지 먼저 z 를 가지고 있으면 배열에 추가하고 아니면 다음 함수로 넘어간다.

문제발생, n번지 값만 비교됨

결과는 n번지의 값으로만 비교했을 뿐이고, [0-(n+1)]번지까지의 내용은 비교하기 않았다는 것이다. 예를 들어서 아래를 보자.

03) 마지막 값만 정렬해주면 된다.

여기서 결과로 산출된 값을 정렬해주려고 하니까. 접근이 되지 않았다.

단순하게, 산출된 값에 다시 sort()를 주었더니, 위의 코드를 실행할 이유가 없게 되었다. 즉 n번지의 값을 비교한 것이 의미없이, 그냥 0번지의 값으로 출력된 것이다.

그렇다면, 반대로 반복문이 실행되기 전에 먼저 값이 정렬되어지고, 정령된 값의 순서대로 위의 조건에 따른 반복문이 실행되면 되지 않을까?

sort() 의 위치를 변경시키져 적용시켰다.

for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "a"){arr.push(string[i])}}
for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "b"){arr.push(string[i])}}
for(let i=0;i<string.length; i++) {if(string[i].slice(n,n+1) == "c"){arr.push(string[i])}}
for(let i=0;i<string.length; i++) 
...  
  
for(let i=0;i<string.length; i++) {if(string.sort()[i].slice(n,n+1) == "a"){arr.push(string[i])}}
for(let i=0;i<string.length; i++) {if(string.sort()[i].slice(n,n+1) == "b"){arr.push(string[i])}}
for(let i=0;i<string.length; i++) {if(string.sort()[i].slice(n,n+1) == "c"){arr.push(string[i])}}
...

결과는 성공이다.

2 팀원 풀이 살펴보기

function solution(strings, n) {
    let answer = strings.sort((a,b) => {
        if (a[n] < b[n]) return -1
        if (a[n] > b[n]) return 1
        if (a[n] === b[n]) {
            if (a > b) return 1
            if (a < b) return -1
            return 0
        }
    })
    return answer;
}

sort() 메소드를 위와 같이 세련되게 작성할 수 있구나라는 부분에 놀랐다.

  • sort() 메소드를 보면 그 안에 조건문이 3개가 달려있다.
    1if : (a[n] < b[n]) 매개변수[n]의 값이 b가 더 크면 뒤로
    2if : (a[n] > b[n]) 매개변수[n]의 값이 a가 더 크면 앞으로
    3if : (a[n] = b[n]) 매개변수[n]의 값이 동률이면, 전체비교
  • 3if는 다시 함수표현식으로 선언
    3-1if. 매개변수 전체를 비교하는데, b가 더 크면 뒤로
    3-2if. 매개변수 전체를 비교하는데, a가 더 크면 앞으로

3 sort() 매소드 다시 살펴보기

  • 스파르타코딩클럽 DailySturdyNote
  • 팀장인 필자와, sort매소드로 풀이하신 팀원분의 협동으로 정리해보았다.


sort((a,b) => a-b) 메소드의 진행과정 따라기
첫째, 배열에 있는 [0]번지와 [1]번지를 먼저 비교한다.
1) a = 1, b =5
2) 1 - 5 = -4(음수) a보다 b가 4칸 뒤에 있다.
3) 반환된 결과는 [ 1, 5 ]

둘째, 배열에 있는 [1]번지와 [2]번지를 다음으로 비교한다.
1) a = 5, b =4
2) 5 - 4 = 1 (양수면 앞으로 n칸) a가 b보다 1칸 앞에 있다.
3) 반환된 결과는 [ 4, 5 ]
4) 이전 값까지 포함된 결과 [1 , 4, 5]

셋째, 배열에 있는 [2]번지와 [3]번지를 다음으로 비교한다.
1) a = 4, b =6
2) 4 - 6 = -2 (음수면 뒤로 n칸)
3) 반환된 결과는 [ 4 → 뒤로 2칸→ 6 ]
4) 이전 값까지 포함된 결과 [ 1, 4, 5, 6]

넷째, 배열에 있는 [3]번지와 [4]번지를 다음으로 비교한다.
1) a = 6, b = 2
2) 6 - 2 = 4 (양수면 앞으로 n칸)
3) 반환된 결과는 [ 2 ← 앞으로 4칸 ← 6 ]
4) 이전 값까지 포함된 결과 [ 1, 2, 4, 5, 6]

sort((a,b) => a-b) 메소드의 진행과정 따라기

첫째, 배열에 있는 [0]번지와 [1]번지를 먼저 비교한다.

1) a = 1, b =5
2) 1 - 5 = -4(음수) a보다 b가 4칸 뒤에 있다.
3) 반환된 결과는 [ 1, 5 ]

둘째, 배열에 있는 [1]번지와 [2]번지를 다음으로 비교한다.

1) a = 5, b =4
2) 5 - 4 = 1 (양수면 앞으로 n칸) a가 b보다 1칸 앞에 있다.
3) 반환된 결과는 [ 4, 5 ]
4) 이전 값까지 포함된 결과 [1 , 4, 5]

셋째, 배열에 있는 [2]번지와 [3]번지를 다음으로 비교한다.

1) a = 4, b =6
2) 4 - 6 = -2 (음수면 뒤로 n칸)
3) 반환된 결과는 [ 4 → 뒤로 2칸→ 6 ]
4) 이전 값까지 포함된 결과 [ 1, 4, 5, 6]

넷째, 배열에 있는 [3]번지와 [4]번지를 다음으로 비교한다.

1) a = 6, b = 2
2) 6 - 2 = 4 (양수면 앞으로 n칸)
3) 반환된 결과는 [ 2 ← 앞으로 4칸 ← 6 ]
4) 이전 값까지 포함된 결과 [ 1, 2, 4, 5, 6]

오늘의 언어학습, Javascript Study

3 함수와 객체 기본문법

01) 함수

함수 이름짓기

함수은 가능한 간결하고 명확해야 한다. 함수가 어떤 동작을 하는지 설명할 수 있어야 한다. 코드로 기록된 내용을 보면 해당 함수가 어떤 기능인지 힌드를 얻을 수 있어야 한다.

  • showMessage(..) // 메시지를 보여줌
  • getAge(..) // 나이를 나타내는 값을 얻고 그 값을 반환함
  • calcSum(..) // 합계를 계산하고 그 결과를 반환함
  • createForm(..) // form을 생성하고 만들어진 form을 반환함
  • checkPermission(..) // 승인 여부를 확인하고 true나 false를 반환함

또한 함수는 동작 하나만을 담당하도록 해야 한다. 즉 독립적인 두 개의 동작은 독립된 함수 두 개에서 나눠서 수행할 수 있게 해야 한다. 현업에서 선언되는 함수명에 대한 경고가 있어서 아래에 기록해 놓고자 한다.

개발자들이 빈번히 하는 실수를 소개해 보겠습니다.

  • getAge 함수는 나이를 얻어오는 동작만 수행해야 합니다. alert 창에 나이를 출력해 주는 동작은 이 함수에 들어가지 않는 것이 좋습니다.
  • createForm 함수는 form을 만들고 이를 반환하는 동작만 해야 합니다. form을 문서에 추가하는 동작이 해당 함수에 들어가 있으면 좋지 않습니다.
  • checkPermission 함수는 승인 여부를 확인하고 그 결과를 반환하는 동작만 해야 합니다. 승인 여부를 보여주는 메시지를 띄우는 동작이 들어가 있으면 좋지 않습니다.
  • 위 예시들은 접두어의 의미가 합의되었다고 가정하고 만들었습니다. 본인이나 본인이 속한 팀에서 접두어의 의미를 재합의하여 함수를 만들 수도 있긴 하지만, 아마도 위 예시에서 사용한 접두어 의미와 크게 차이가 나진 않을 겁니다. 어찌 되었든 접두어를 사용하여 함수 이름을 지을 땐, 해당 접두어에 어떤 의미가 있는지 잘 이해하고 있어야 합니다.

함수선언

function showMessage() {
  alert( '안녕하세요!' );
}

함수를 선언하고 그 안에 기록한 let과 const는 지역변수가 되며, 함수밖으로는 나가지 못한다. 이것이 스코프에 대한 이해이다. 반면에 함수 밖에 선언된 변수는 함수 안으로 가져올 수 있는데 이는 전역변수로 let과 const가 그 스코프에 따라서 내용을 가져올 수 있기 때문이다.

함수호출과, 인자(arqument) 그리고 매개변수(parameter)

해당 내용은 아는 내용은 넘어가고, 만약 인자가 주어지지 않으면, 매개변수는 undefinded 데이터를 생성한다. 즉 내용이 없다는 것이다. 먼저 복수의 매개변수를 기록하는 것을 먼저 소개하고 이야기 해보도록 하겠다.

function 함수이름(복수의, 매개변수는, 콤마로, 구분합니다) {
  /* 함수 본문 */
}

이번에는 매개변수에 해당되는 값에 인자를 주지 말아보자.

function showMessage(from, text = "no text given") {
  alert( from + ": " + text );
}
showMessage("Ann");

인자("Ann")이 함수호출에서 기록되어 담겨가면, function showMessage(from, text = "no text given") 함수는 담겨진 인자를 매개변수from("Ann")에 담는다. 그러나 매개변수text에 대한 값은 담겨오지 않았다. 이럴 때 매개변수text에 해당되는 초기값을 위와 같이 지정해 주면 아래와 같이 기록된다.

구 자바스크립트에서는 아래와 같이 기록했다. 그런데 구 자바스크립트? 아마도 ECMAScript 5(ES5)이전버전의 자바스크립트를 의미하는 것 같다.

## 기록방법01
function showMessage(from, text) {
  if (text === undefined) {
    text = 'no text given';}
  alert( from + ": " + text );}

## 기록방법02
function showMessage(from, text) {
  // text의 값이 falsy면 기본값이 할당됨
  // 이 방식은 text == ""일 경우, text에 값이 전달되지 않은것과 같다고 간주합니다..
  text = text || 'no text given';}  

기록방법02는 현재 자바스크립트에서도 사용할 수 있다. 이외에 모던 자바스크립트 엔진이 지원된다면, 아래의 방법도 있다고 한다.

return

let age = prompt("나이를 입력하세요.")

function showMovie(age) {
  if ( !age || age < 20 ) {
    return;}
  else {
    alert( "19영화 관람가능" );}
}

showMovie(age)

위와 같이 함수호출에 나이의 값이 입력되지 않으면, 매개변수가 undefined 이면 return 아래의 결과는 실행되지 않을 것이다.

02) 함수표현식

함수선언식과 다르게 변수 또는 객체 내에 함수를 다루려고 할 때는 부르는 용어가 달라진다. 아래의 예시를 보자.

let hey = function() {alert( "Hello" );}

콘솔에 hey() 를 입력하면 아래와 같이 기록이다.

콜백함수

부모함수 내부에서 실행하는 함수를 자녀함수라고 볼 때, 콜백함수는 자녀함수를 지칭하는 말로 이해하면 될 것 같다. 강의안에는 이 부분에 대한 설명도 언급도 없기에, 내용을 살펴보고 나의 말로 정리한 내용이다.

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();}

function showOk() {
  alert( "동의하셨습니다." );}

function showCancel() {
  alert( "취소 버튼을 누르셨습니다." );}

ask("동의하십니까?", showOk, showCancel);

먼저, 최종적으로 기록된 ask()를 통해서 함수가 전언되었다.

  • 매개변수question : "동의하십니까?"가 인자가 들어오고,
  • 매개변수yes : showOk 인자로 들어오고,
  • 매개변수no : showCancel 인자로 들어온다.

매개변수yes로 들어온 showOk 은 함수 내에서 yes() 를 통해서 함수로 작동하며, 동의하셨습니까를 실행시키고, 매개변수no 로 들어온 showCancel 은 함수 내에서 no() 를 통해서 함수로 작동하며, 취소 버튼을 누르셨습니다를 실행시킨다.

위의 있는 코드는 아래와 같이 기록할 수도 있다.

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

ask(
  "동의하십니까?",
  function() { alert("동의하셨습니다."); },
  function() { alert("취소 버튼을 누르셨습니다."); }
);

이때 함수호출 시, 인자로 전달한 함수를 익명함수라고 부른다.

화살표 함수(함수표현식)

지금까지 살펴보았던 함수표현식 아래와 같다. 익명함수로 보통 아래와 같이 기록된다.

let student = function() { 
	return ...
}

이를 더 간결하게 기록할 수 있는데, 그것이 화살표 함수이다.
위의 함수는 아래와 같이 기록할 수 있다.

let student = () => return ...

스파르타코딩강의안에는 특별한 설명이 없지만, return이 복잡할 때는 {중괄호}를 생략하면 안된다. 또한 매개 변수가 없다면, (소괄호)도 생략할 수 없다. 만약 소괄호에 담긴 매개변수가 하나면 (소괄호)도 생략이 가능하다.

let hey = () => alert("안녕하세요!");
hey();

조건부 삼항 연산자, 화살표 함수표기

또한, 화살표 함수는 조건부 삼항 연산자 도 사용이 가능하다.

조건부 삼항 연산자 조건 ? 결과1 : 결과2

let age = prompt("나이를 알려주세요.", 18);

let welcome = (age < 18) ?
  () => alert('청소년이네요') :
  () => alert("성인이시네요");

welcome();

복잡하게 기록하면 이렇게도 설정할 수 있다.

let ask = (question, yes, no) => (confirm(question)) ? yes() : no();

ask("동의하십니까?", () => alert("동의하셨습니다."),  () => alert("취소 버튼을 누르셨습니다."))

두 줄이지만, 쉽지 않다. 질문에 대한 사용자의 선택에 따라서 값니 true와 false가 될 것이다. true이면 yes()를 false이면 no()가 실행될 것인데, 내용을 보면 yes()는 동의하셨습니다를, no()는 취소 버튼을 누르셨습니다를 출력할 것이다.

03) 객체 기본문법

JS 자료형은 크게 8가지로 정리할 수 있다. 그러나 이 자료형은 두 개로 다시 구분되는데 다음과 같다.

  • 원시형(primitive) : 숫자열, 문자열, boolean, null, undefined 등이 있다.

그러나 객체형은 다양한 데이터를 담을 수 있는데, property(key:value) 로 구성된 자료를 가지고 있다. 키에는 문자형, 값에는 모든 자료형이 허용된다. 함수까지도 말이다.

객체생성의 두 가지 방법

## 객체 생성자 문법
let user = new Object(); // '객체 생성자' 문법

## 객체 리터럴
let user = {}; 
  • property 삭제 : delete user.age;
  • property 추가 : user.age = ""
  • property 추가 : user["age"] = ""

(기존에 없던 key:value 를 선언함으로 가능)
만약 기존에 있는 property에 입력하면, 내용이 변경된다. 표기법은 점표기법과 [대괄호] 표기법으로 나눠진다.

계산된 프로퍼티

let fruit = prompt("어떤 과일을 구매하시겠습니까?", "apple");
let bag = {};

bag[fruit] = 5;

bag에 속한 객체를 보면, 보이는 [fruit]가 바로 계산된 프로퍼티이다. bag.apple 을 실행하면, [fruit]에 있는 "apple"이 입력된 내용과 같이 prompt에 기록되어 산출될 것이다. 그리고 입력된 내용에 따라서, 기존 {[fruit]: 5}에는 apple이 기록될 것이다.

let fruit = 'apple';
let bag = {
  [fruit + 'Computers']: 5 };

또한 입력받은 값으로 key 이름을 적용시킬 수도 있다. 다시 위에서 이야기 한 부분을 언급하자면, 함수를 통해서 새로운 객체를 생성하는 것도 가능하다.

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

let user = makeUser("John", 30);

makeUser 로 선언된 user변수 객체를 만들 수 있다. 또한 예약어를 만들수도 있다.

let obj = {
  for: 1,
  let: 2,
  return: 3
};

alert( obj.for + obj.let + obj.return ); 

결과는 6이 산출될 것이다.

객체의 정보 살펴보기

let user = { name: "John", age: 30 };
alert( "age" in user );
alert( "blabla" in user );

age(key)는 있기 때문에 true가, blable(key)은 존재하지 않기 때문에 false가 나올 것이다.

객체 반복문

for (key in object) {}

해당 내용을 통해서 object객에 있는 내용을 반복하여 출력할 수 있다. 객체는 자동으로 정렬된다. 아래를 보자. 그러나 해당 방법도 key가 정수일 때만 가능하다. 정수 이외에는 기록된 순서대로 출력된다.

let codes = {
  "49": "독일",
  "41": "스위스",
  "44": "영국",
  "1": "미국"};

for (let code in codes) {alert(code);}

객체의 내용을 수정하기

const user = {
  name: "John"};

## 사례1
user.name = "Pete";

## 사례2
user = 123;

const로 선언된 변수는 내용이 변경되지 않는 상수라고 배웠다. 그러나 객체에 대해서는 접근이 다른데, 사례1과 같이 property의 value를 변경한다고 하자. 변경이 된다. 그러나 사례2는 되지 않는데, 이는 user가 객체 데이터인데, 객체에 숫자형 데이터를 입력했기 때문이다. 형 자체의 변형은 const는 허용하지 않는다. 그러나 객체의 내용(value)을 변경하는 것은 허용한다.

프로퍼티 합계 구하기: 문제풀이

  • property의 value를 구하는 방법은 객체명[key]를 통해서 가능했으면, 반복문은 for(in)문 사용하면 가능하다.
let salaries = {
  John: 100,
  Ann: 160,
  Pete: 130
}

function sum(num) {
  let nums = 0;
  for (key in num) 
    {nums = nums+ num[key]}
  console.log(nums)
}

sum(salaries)

프로퍼티 값 두 배로 부풀리기

let menu = {
  width: 200,
  height: 300,
  title: "My menu"
};


function multiplyNumeric(obj) {
  for (let key in obj) {
    if (typeof obj[key] == 'number') {
      obj[key] *= 2;
    }
  }
}

multiplyNumeric(menu);
console.log(menu)

핵심은 if (typeof obj[key] == 'number') 들어온 value가 숫자형 자료인지 검색하는 것이다.

4) 참조에 의한 객체 복사

원시형 자료형의 복사는 단순했다. 아래와 같이 독립된 두개의 변수를 만들 수 있었다. 그러나 객체를 복사할 때는 방법이 다르다.

let message = "Hello!";
let phrase = message;

객체를 다음과 같이 선언하면, 예를 들어 하나의 통장에서 두개의 체크카드를 만든 것으로 이해하는 것과 동일하다. 하나의 창구를 같이 사용하는 것 뿐이다. 이를 참조에 의한 객체 복사라고 부른다.

let user = { name: "John" };

let admin = user;

일치연산자를 통해서 확인해 보면, true가 나오는 것을 볼 수 있다.

let a = {};
let b = a; // 참조에 의한 복사

alert( a == b ); // true, 두 변수는 같은 객체를 참조합니다.
alert( a === b ); // true

객체의 복사는 다음과 같이 진행된다.

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

let clone = {}; // 새로운 빈 객체

for (let key in user) {
  clone[key] = user[key];}

clone.name = "Pete"; 

반복문을 통해서 아래와 같이 새로운 빈객체에 입력해준다고 하자. 결과를 보면 clone만 수정되고 user는 변경되지 않는 것을 볼 수 있다.

여러개의 객체를 하나의 객체로 취합하기

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

Object.assign(user, permissions1, permissions2);

console.log(user)

Object.assign을 사용하면 가능하다. 가장 첫번째 객체에 나머지 값이 취합된다. 만약 기존의 값이 존재한다면, 내용이 수정변경된다.

이 방법을 활용하면, 객체를 복사하는 것도 가능하다.

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

let clone = Object.assign({}, user);

clone에는 Object.assign을 통해서 새로운 객체가 형성되었을 것이다. 그러나 이렇게 생성된 객체는 서로 동일하다. 하나의 통장, 두 개의 체크카드인 것이다. 하나의 값을 바꾸면 함께 변경된다. 즉 복사를 한 것이지 복제를 한 것이 아닌 것임을 기억하자.

5) 메서드와 this

객체에는 함수형 자료를 담을 수도 있다. 아래와 같이 말이다.

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

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

없던 내용에 sayHi 함수표현식을 추가해주었다. user.sayHi();를 선언하면 안녕하세요가 alert 될 것이다.

객체지향프로그래밍(OOP, object-oriented programming)

간략하게, 객체를 사용하여 개체를 표현하는 방식을 OOP라고 부른다. 올바른 개체를 선택하는 방법, 개체 사이의 상호작용을 나타내는 방법 등에 관한 의사결정이 이러한 설계를 기반으로 생성된다.

객체 안에 생성된 함수 짧게 작성할 수는 없을까? 가능하다.

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

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

객체 메소드 내에서의 this 사용

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

user.sayHi = function() {
  alert(this.name);
};

user.sayHi()

위의 코드를 실행하면, user객체의 name의 값이 출력된다. 여기서 this는 자기자신의 최상단 user를 가리키기 때문이다. 그러나 this는 화살표형 자료에서는 사용할 수 없다.

문제풀이, 계산기 만들기

let calculator = {
  sum() {
    return this.a + this.b;
  },

  mul() {
    return this.a * this.b;
  },

  read() {
    this.a = +prompt('첫 번째 값:', 0);
    this.b = +prompt('두 번째 값:', 0);
  }
};

calculator.read();
alert( calculator.sum() );
alert( calculator.mul() );

calculator.read();를 실행되는 발생되는 내용에 대한 부분이다. 객체메서드가 호출되면, prompt를 통해서 값이 불려와지게 된다. 해당 값이, 해당 객체 내에서 활용될 것인데, alert을 통해 호출된 객체 메소드에 따라서 sum과 mul이 산출되는 방식인 것이다.

문제풀이, 체이닝

let ladder = {
  step: 0,
  up() {
    this.step++;
  },
  down() {
    this.step--;
  },
  showStep: function() {
    alert( this.step );
  }
};

ladder.up();
ladder.up();
ladder.down();
ladder.showStep();

ladder.up() 객체 메서드를 실행하면, step: 0이 올라가고, ladder.down() 객체 메서드를 실행하면, step: 0 이 내려오는 방식이다.

이런방식으로 객체 메서드 내에서 this는 활용될 수 있다.

5) new 연산자와 생성자 함수

생성자 함수란 함수를 사용해서 새로운 객체를 생성하는 것을 말한다. 만약에 반복되어 객체를 생산해야 되는 문제가 주어진다면, 이것으로 해결이 가능한 것이다.

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

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

alert(user.name);
alert(user.isAdmin); 

생성자 함수와 new 연산자를 통해서 함수에 담겨질 인자를 설정한 값에 따라서, user.name은 "보라"가 될 것이다. 그러나 유의할 것이 있다. 생성자 함수에의 return 사용이다.

function BigUser() {
  this.name = "원숭이";
  return { name: "고릴라" };}

function SmallUser() {
  this.name = "원숭이";
  return;}

alert( new BigUser().name ); 
alert( new SmallUser().name );
  • new BigUser().name : 고릴라
  • SmallUser().name : 원숭이

왜 그럴까? 고릴라의 사례는 return에 내용이 존재하기 때문이다. 반면에 원숭이는 return의 내용이 없어서 객체에서 내용을 산출한다. 우선순위가 return에 있다.

문제풀이, 누산기 만들기

function Accumulator(startingValue) {
  this.value = startingValue;

  this.read = function() {
    this.value += +prompt('더할 값을 입력해주세요.', 0);
  };

}

let accumulator = new Accumulator(1);
accumulator.read();
accumulator.read();
alert(accumulator.value);

와... 이걸 어떻게 풀었지?

4 함수와 객체 심화문법

01) 나머지 매개변수와 스프레드(...parameter) 문법

나머지 매개변수란 간단하다. 매개변수로 설정된 값보다, 인자의 값이 많은 경우이다. 즉 여분의 인자가 발생되었다는 것이다. 역시나 교재가 조금 설명이 부족하다.
Edwin벨로그

이때 매개변수에서 해당 인자를 전부 사용하고 싶을 때는 어떻게 해야 할까. 바로 스프레드 문법으로 사용이 가능하다.

function sumAll(...args) {
  let sum = 0;
  for (let arg of args) sum += arg;
  return sum;
}
alert( sumAll(1, 2, 3) );

또한 사용하고 싶은 매개변수1, 매개변수2, 나머지 매개변수를 기록하는 것도 가능하다. 그러나 나머지 매개변수의 위치는 항상 마지막 이어야 한다.

function showName(firstN, lastN, ...etc) {
	console.log(firstN, lastN);
  	console.log(...etc)
}

showName("edwin", "park", 0, 1234, 4567)

arguments 객체

function showName() {
  console.log( arguments.length );
  console.log( arguments[0] );
  console.log( arguments[1] );
}

showName("Bora", "Lee");
showName("Bora");

그러나 기억하자, 설정에 따라서 매개변수가 부족하면, ()에는 없지만, 코드를 함수 내에서 실행할 때 없는번지[1]의 매개변수 요청하면 해당 값은 undefinded이 된다. 그러나 위에서 실행된 부분은 조금독특하다. 그렇지 않은가? 이는 나머지 매개변수라는 개념이 등장하기 전에 사용되었던 arguments 객체를 사용하여, 복수의 인자를 받아서 활용하는 예제이다. 그러나 기억하자. 요구하는 값이 없으면 undefinded이 된다. 뿐만 아니다. arguments 객체는 연속되는 매소드를 사용할 수 없다.

반면에 나머지 매개변수는 연속되는 매소드를 사용할 수 있는데, 아래와 같다.

function showName(name,...num) {
  let pointlist = [...num.map((pointplus) => pointplus+10)]
  console.log(name)
  console.log(pointlist.reduce((current,points) => current+points))  
}
showName("Edwin", 1234,5678,9012);
gh

showName("Edwin", 1234,5678,9012) 과 같은 값이 주어진다고 하자. 맨 앞에 있는 것은 매개변수 name에 사용할 부분이고, 뒤에 이어지는 숫자는 과거에 쌓인 포인트들이다. 해당 포인트들에 +10을 해주고, 총합을 계산해보자는 내용이 문제로 주어진다고 하자. 그럴 때 위에서 배운 나머지 매개변수를 사용할 수 있을 것인데? 문제는, arquments로는 해당 내용을 접근할 수 없다는 것이다.

매개변수의 활용은 참으로 다양하다. 혹시 누적된 포인트 가운데 최고 포인트가 몇점인지 살펴볼 수 있을까? 위의 코드에 아래의 내용을 추가해보자.

console.log(Math.max(...num))  

결론은 9012가 산출되는 것을 볼 수 있을 것이다. 스프레드 문법은 이렇게 활용이 가능하다. 또한 여로 곳으로 나눠진 배열을 하나의 배열로 취합하는 것도 가능하다.

let numlist1 = [1,2,3,4,5,6,7,8,9]
let numlist2 = [1,2,3,4,5,6,7,8,9]
let numlist3 = [1,2,3,4,5,6,7,8,9]

console.log(...numlist1, ...numlist2, ...numlist3)

그런데 유의할 점이 있다. 하나의 "문자열"을 스프레드 문법으로 기록하면, 하나씩 나뉘어진 배열로 입력된다는 것을 기억하자.

let numlist1 = [1,2,3,4,5,6,7,8,9]
let numlist2 = [1,2,3,4,5,6,7,8,9]
let numlist3 = "Edwin"

또한 스프레트 문법을 사용하여, 이전의 배열을 복사하는 것도 가능하다.

let arr = [1, 2, 3];
let arrCopy = [...arr];

arr.push(4);
console.log(arr)
console.log(arrCopy);

변수arr와 변수arrCopy는 분명 다르다. 그리고 이러한 스프레드는 객체를 복사하는 것도 같다. 복사된 객체에는 원본의 객체에 내용이 추가된다 하더라도, 추가되지 않는 별개의 객체로 형성되어 관리할 수 있다는 말이다.

profile
신학전공자의 개발자 도전기!!

0개의 댓글