const fruits = ["apple", "mango", "orange"]
배열은 [대괄호] 표기법 안에 여러 개의 값을 순차적으로 나열한 자료구조를 말한다. 자바스크립트 안에서 배열의 사용은 방대한데, 이를 위해서 자바스크립트는 배열을 가공할 수 있는 여러 메서드들을 제공한다. 이를 아래에서 자세하게 살펴볼 예정이다.
배열은 엄격한 의미에서 "객체"이다. 여기서 객체라는 말은 key:value의 프로퍼티를 가지고 있다는 말이다. 그런데 배열에서의 프로퍼티는 일반객체의 프로퍼티와는 조금 다른데, key값이 index(번호)로 지정되어 있다는 점이다. 이러한 프로퍼티로 배열이 구성되어 있기 때문에, 개발자는 고유한 index(번호)로 해당 프로퍼티의 value에 접근할 수 있게 된다.
fruits.length
fruits.[fruits.length-1] // 동일한 것을 at()메서드로도 기록할 수 있다.
fruits.at()
또한 배열의 메서드 가운데 하나를 여기서 소개하고자 한다. 바로 length 메서드이다. 위에서 선언한 fruits 변수에 Array.prototype.length 메서드를 입력하면, 해당 배열이 담고 있는 요소의 개수를 알 수 있다. 요소의 개수를 안다는 것은 배열의 마지막 요소의 값이 무엇인지 접근할 수 있는 환경을 제공한다는 것을 의미하느다.
위의 배열의 길이를 구하면, 3이 나올 것이고, fruits.[fruits.length-1]를 통해서 배열의 마지막 요소의 값에 접근할 수 있다. 여기서 "fruits.length-1" 해준 것은 컴퓨터가 계수하는 숫자가 0이기 때문이다.
아래에서 재미난 실험을 하나 하고자 한다. 만약 본래 배열의 길이가 5개인데 2개로 재선언하면, 어떠한 결과를 얻게 될까? 바로 배열의 길이가 2개로 제한된 배열이 반환된다. 이런 식으로 length 메서드는 활용된다.
let num = [1,2,3,4,5];
num.length = 2;
console.log(num)
위에서 간략하게 언급했지만, 배열은 엄밀한 의미에서 객체이다. 자료를 가공한다고 할 때, 자바스크립트는 가공할 그 자료가 무엇인지 알아야 할 것을 먼저 요청한다. 왜냐하면 자료의 유형에 따라서 사용할 수 있는 메서드가 다르기 때문이다. 아래서 다룰 배열관련메서드를 문자열이나, 숫자열에서 사용하면 동작하지 않을 것이다.
즉 자바스크립트에서 자료를 다루기에 앞서서 요청되는 것은 자료의 행위를 아는 것인데, 배열의 자료를 아래와 같이 콘솔에서 물어보면 콘솔은 배열은 객체라고 말해준다.
물론 외형상의 차이는 {객체는 중괄호}, [배열은 대괄호]이지만, 이 둘을 부르는 명칭은 모두 객체인 것이다. 그럼에도 배열은 일반적인 객체와 구분되는데, 이는 배열이 가진 고유의 Key(index 번호)를 가진 프로퍼티로 구성된 객체이기 때문이다. Object.getOwnPropertyDescriptors 메서드를 통해서 배열의 프로퍼티를 조회하면 아래와 같이 객체로 구성된 배열의 본모습을 확인할 수 있다.
console.log(Object.getOwnPropertyDescriptors(["apple", "mango","orange"]))
function fruits(array, target) {
const length = array.legnth;
for(let i=0; i<length;i++) {
if(array[i] === target) return i;
}
console.log(`${target}이 배열 안에 있습니다.`);
}
fruits(["apple", "mango", "orange"], "mango");
요소에 고유한 위치값이 있기에 접근하고, 그 정보를 활용할 수 있는 것이다. 그러나 분명 단점도 존재한다. 만약 정렬되지 않은 배열 안에서 특정한 요소를 찾으려면, 처음부터 특정요소를 발 견할 때까지 차례대로 검색해야 하기에, 검색시간을 고려하면 좋은 방법이 되지 못하기 때문이다. 그러나 Javascript 개발자들은 이를 해결할 수 있는 방법을 찾았고, 활용하고 있다.
희소배열이란 배열(객체)의 프로퍼티에 key는 있지만, values가 비어있는 자료를 말한다. 이와 같이 자바스크립트는 문법적으로 희소배열을 허용한다. 이러한 허용은 배열의 길이와 요소의 수가 일치하지 않는다는 것을 기억하자. 그러나 희소배열의 허용이 추천되지 않는다는 것을 기억하자.
let num = [,2,,4,5];
console.log(Object.getOwnPropertyDescriptors(num));
배열을 생성하는 방법은 여러가지이지만, 가장 일반적으로 사용되는 방식은 배열 리터럴이다. 배열 리터럴은 새로운 무엇이 아니고 이미 아는 [대괄호] 표기에 콤마(,)를 통해서 요소를 구분하고, 변수에 할당하는 것을 말한다. 이때 [대괄호]만 입력하고, 빈배열(하단의 변수)을 만들어 주는 것도 가능하다.
let emptyarr = [];
객체(Object)를 생성자 함수를 통해서 생성할 수 있듯, 배열도 엄밀한 의미에서는 객체이기 때문에 가능하다. 그러나 유의할 점이 있다. 배열의 경우에는 전달된 인수의 개수에 따라 다르게 동작한다는 점을 기억해야 한다.
배열이 무한하게 요소를 담을 수 있는 그릇인 것은 아니다. 4,294,967,296개, 약 42억개 요소만을 담을 수 있다고 한다? 엄청난데. 반대로 요소의 고유한 위치값이 존재하기에 음수로는 배열을 생성할 수 없다.
const funcArr = new Array(10);
console.log(funcArr.length);
위와 같이 생성된 배열이 생성자 함수를 통해서 생성된 배열이다. 그러나 그 안에 요소가 없음으로 희소배열로 생성된 것을 알 수 있다.
const funcArr = new Array(1,2,3,4,5);
console.log(funcArr);
일반적인 경우에는 위와 같이 요소를 통해서 배열을 생성한다. 더 일반적인 경우에는 아래와 같이 바로 선언하기도 한다.
const funcArr = [1,2,3,4,5];
배열 요소의 참조란 배역의 고유한 위치값에 접근 하는 것을 말하는 것 같다. 예를 들면 아래와 같다. 아래와 같은 배열이 있다고 하자. 특정한 위치에 있는 요소를 추출하고자 할 때, 어떻게 할 수 있을까?
let num = [0,1,"A",3,4,5,6,7,8,9];
console.log(num[0]); ----> 콘솔 0
console.log(num[1]); ----> 콘솔 1
console.log(num[2]); ----> 콘솔 "A"
위와 같이 변수명[대괄호]를 사용해서 다가갈 수 있으며, 대괄호 안에는 고유한 위치값(정수)이 입력되면 된다. 이때, 해당 위치에서 요소를 찾을 수 없으면 undefinded가 반환된다.
let num = [0,1,2];
위와 같이 2개의 고유한 자리를 가진 배열이 있다고 하자. 해당 배열의 [2]번지에는 요소가 없다. 그렇다면, 아래와 같이 요소를 직접 추가하는 것도 가능하다.
num[2] = 2;
그런데 주의 사항이 있다. 만약에 num.length 보다 큰 고유한 위치값에 요소를 추가하면 어떻게 될까. num[10] = 10;
let num = [0, 1]
num[2] = 2;
num[10] = 10;
console.log(num)
희소배열이 존재하는 배열이 생성되는 것을 확인할 수 있다. 여기서 주의해서 보아야 하는 것은 num[대괄호]에 들어가는 값이다. 정수 혹은 "정수"로 입력했다면, 추가가 된다.
반면에, 아래에서 살펴볼 정수 이외에 방법으로 삽입한 것을 프로퍼티 값으로 추가했다고 한다. 이렇게 추가된 프로퍼티드들은 배열의 길이에 영향을 주지 못한다. 배열의 요소는 산출하되, 위치값에는 반영하지 않는다는 점이다.
const arr = [];
arr[0] =0;
arr['1'] =2;
arr[three] =3;
arr.four = 4;
arr[5.5] = 5;
arr['-6'] =6;console.log(arr)
cosole.log(arr.length)
프로퍼티로 입력된 내용들은 다음의 명령을 콘솔에 기록함으로 알 수 있는데, 아래의 이미지를 보자.
console.log(arr)에서 찾아볼 수 없는 요소들을 Object.getOwnPropertyDescriptors(arr) 에서는 찾을 수 있었다는 점을 기억하자.
만약 해당 위치에 선행되는 값이 존재하면, 그 값을 나중에 선언된 값으로 갱신한다.
delete arr[5];
간단하게 고유한 위치값 앞에 delete를 선언해 주면 된다. 다만 요소만 사라졌을 뿐, 고유한 위치값은 남아 있기에, 이러한 방색으로 삭제된 배열은 해당 위치에 휘소배열을 가지게 되는 셈임을 기억하자.
let num = [0,1,2,3,4,5]
num.slice(1,3)
console.log(num)
여기에 재미있는 요소가 하나있다. 아래를 살펴보자.
let num = [0,1,2,3,4,5]
console.log(num.splice(1,3))
잘라낸 배열의 값을 가져와서 활용하는 것도 가능해진다.
Array 생성자 함수는 정적 메서드를 제공하고, 배열 객체의 프로토타입인 Array.prototype은 프로토타입 메서드를 제공한다. 또한 배열의 메서드는 결과를 반환(return)할 때, 크게 두 가지 양상으로 구분할 수 있다.
첫째, 원본 배열을 직접 변경하는 메서드
둘째, 원본 배열을 직넙 변경하지 않고 새로운 배열을 생성하는 메서드
대표적인 사례가 배열에 내용을 추가할 때 사용하는 push메서드(첫째)와 concat메서드(둘째) 이다.
let num1 = [1,2,3]
let num2 = [1,2,3]
let num1_1 = num1.push(4)
let num2_2 = num2.concat(4)
console.log(num1)
console.log(num1_1)
console.log(num2)
console.log(num2_2)
콘솔의 num1은 num1_1의 결과로 원본 데이터가 변경되어 반환된 것을 확인할 수 있다. 반면에 num2는 num2_2에서 추가를 했을지라도 내용의 변환이 없다. 다만 num2_2는 concat() 메서드의 결과로 복제되어 생성된 배열인 것을 확인 할 수 있다. 이와 같은 메서드의 세계로 들어가보자.
ES6(2015년)에서 도입된 Array.of 메서드는 전달된 인수를 요소로 갖는 배열을 생성한다. 그러나 생성자 함수와 다른데 아래와 같이 보자.
console.log(Array.of(1))
ES6(2015년)에서 도입된 Array.from 메서드는 유사 배열 객체, 이터러블(ilteravle object) 객체를 인수로 전달받아 배열로 반환한다.
console.log(Array.from({ length: 2, 0:'edwin', 1 : 30 }))
이해해보기 위한 노력이 위와 같다. 내 생각에는 {중괄호} 안에는 먼저 배열의 길이를 설정할 수 있고, property로 고유의 위치값(index)와 요소를 기록할 수 있다.
function solution(x, n) {
return Array.from({length:n},(el, i) => x * (i+1))}
본서(모던자바스크립트 딥다이브)에서는 Array.from()을 정적메서드로 소개하였다. 그러나 Array.from()은 단순하지 않다. 모질라 사이트를 보면, Array.from()은 함수표현식과 함께 그 작용을 더 풍성하게 만들어내기 때문이다.
모질라, Array.from()
{length:n}
먼저 요소가 없는 희소배열을 만들어준다. 문제에서 n값이 5라면, 5개의 값이 없는 빈배열이 만들어진다.
(item, i) => x * (i+1)
(item, i) 배열의 길이에 해당되는 i마다 => 함수표현식 오른쪽에서 계산된 값이 item을 반환되어(?) 빈배열에 차곡차곡 기록되는 것같다.
현재 풀고 있는 문제에서도 31개의 1번부터 순차적으로 늘어나는 배열을 만들어야 하는데 아래의 방법을 사용하니 손쉽게 문제가 풀어졌다.
const date = Array.from({length:31}, (n,i) => i+1)
function sum() {
const arr = Array.from(arguments);
console.log(arr); // [1,2,3,4,5]
return arr.reduce((pre, cur) => pre + cur, 0);
}
console.log(sum(1,2,3,4,5));
console.log(sum(1,2,3,4,5)
를 통해서 함수가호출되었고 내용은 숫자열 데이터가 담겨졌다. 해당요소의 인자들은 arguments로 호출하여 함수 안에서 사용된다. 그러나 이는 ES6이전의 문법임을 기억할 필요가 있다. 이렇게 들어온 arguments를 배열로 담은 arr변수는 해당 내용을 reduce메서드를 통해서 누산하고 그 결과를 반환(return)했다. 그 결과 콘솔에 15가 기록된 것을 볼 수 있다.
나머지 매개변수와 arguments
ES6(2015년) 이후버전의 코드를 작성하고 있다면 나머지 매개변수(...parameter) 구문을 사용해야 한다.ES6 이전에는 arguments를 통해 해당 과정을 수행했는데, 인자로부터 받아온 값을 arguments[0], arguments[1]과 같이 특정해서 함수 내에서 요소가 다뤄질 수 있었다. arguments는 그러나 엄밀한 의미에서 배열이 아니다. 유사하지만 길이를 구하는 length 메서드 이외에 다른 메서드들의 사용이 제한된다. 그러기에 Array.from을 통해서 배열로의 형변환의 도움을 얻어야 비로서 기타 메서드들과의 협업이 가능해지니, 이점에 유의하자.
0부터 9까지의 숫자 중 일부가 들어있는 정수 배열 numbers가 매개변수로 주어집니다. numbers에서 찾을 수 없는 0부터 9까지의 숫자를 모두 찾아 더한 수를 return 하도록 solution 함수를 완성해주세요.
다른사람의 풀이를 살펴보다가 해당 구문으로 기록된 내용을 보았다. 아래에서 살펴보자.
let numbers = [5,8,4,0,6,7,9];
funcion solution(numbers) {
let arr = Array.from({length:10}, (elem, i) => { return numbers.idcludes(i) ? 0 : i })
return arr.reduce((acc, elem) => acc + elem, 0)
}
이제야 위의 구문이 해석되고 읽어진다. Array.from({length:10},
먼저 희소배열을 만들어주고, 그 안에 (elem, i) =>
함수를 통해서 희소배열을 찾아줄 것이다. 함수를 통해서 반환된 값이 배열의 요소 하나하나로 담겨질 것이다. 만약 해당 함수를 보면 조건부 삼항 연산자가 기록되어 있는데, numbers가 i를 포함하고 있으면 0을 그렇지 않으면 i를 반환하라는 구문이다. 이후의 것을 누산(reduce메서드)기가 작동되어 실행되어 동작하였다.
아래와 같이 문자형자료를 가지고 배열을 생성할 수도 있다. 이렇게 생성하는 배열을 이터러블 객체를 인수로 받아 생성하는 배열이다.
console.log(Array.from('Hello'))
자연수 n을 뒤집어 각 자리 숫자를 원소로 가지는 배열 형태로 리턴해주세요. 예를들어 n이 12345이면 [5,4,3,2,1]을 리턴합니다.
제한 조건
function solution(n) {
return Array.from(String(n)).reverse().map(trans => Number(trans))
}
- 숫자열 자료형을 문자열로 바꾸어 Array.from 이터러블 객체로 배열을 생성
- 배열을 뒤집고, map() 메서드로 해당 내용을 다시 숫자열로 변경했다.
function solution(n) {
return Number(Array.from(String(n)).map(tran => Number(tran)).sort((a,b) => b-a).join(""))
}
- 이번 문제는 위에서 reverse() 메서드를 사용했던 것을 sort() 메서드를 통해서 풀었다.
console.log( Array.from({ length:10 }, (_, i) => i) )
배열의 길이를 10으로 설정하고, 해당 내용의 값을 콜백함수를 통해 반복시켜 요소를 입력시켰다.
isArray는 메서드에 전달된 인수가 배열이면 true를 그렇지 아니면 false를 반환한다.
원본 배열에서 인수로 전달된 요소를 검색하여 고유한 위치값(인덱스)를 반환한다. 이때 중복되는 값이 있으면 가장 첫번째 요소의 위치값을 반환하며, 찾고자 하는 내용이 배열에 없으면 -1을 반환한다.
let num1 = [1,2,3,4,5,6,7,8]
console.log(num1.indexOf(2))
console.log(num1.indexOf(2, 2))
console.log(num1.indexOf(10))
이 메서드는 어떻게 활용할까? 매개변수로 새로운 인자를 받았을 때, 일치하지 않으면 입력(push)해 주어라 같은 경우에 유용하게 사용될 수 있다.
const fruit = ["apple","banana","orange"];
if (fruit.indexOf("mongo") === -1) {
fruit.push("mongo")
}
fruit.indexOf("mongo") === -1
은 기존의 배열에 없으면을 의미한다. indexOf가 기존의 배열에 존재하지 않으면 -1을 반환하기 때문이다.
const fruit = ["apple","banana","orange"];
if (!(fruit.includes("mongo"))) {
fruit.push("mongo")
}
includes는 그 결과는 true와 false를 반환한다. 그러기에 indexOf("mongo") === -1
처럼 수기로 내용을 입력해 줄 필요가 없다.
인수로 전달받은 모든 값을 원본 배열의 마지막 요소에 추가하고 반영된 length를 프로퍼티 값으로 반환한다. push 메서드는 원본 배열을 직접 변경한다. 그러나 push 메서드를 생각해보자. 배열의 마지막에 추가하는 것은 push메서드를 사용하지 않고 length 프로퍼티를 사용하면 직접 추가할 수도 있고, 심지어 이 방법이 더 빠르다.
const arr = [1,2];
arr[arr.length] =3;
console.log(arr)
console.log(Object.getOwnPropertyDescriptors(arr))
배열에 요소를 추가가는 방법은 다양하다. 원본 배열을 수정하지 않고 복제된 배열을 생설할 때 추가하는 concat() 메소드도 있고, 나중에 다룰 스프레드 문법(ES6, 2015년)도 있다. 이는 추후에 살펴보도록 하자.
pop 메서드는 원본 배열의 마지막 요소를 제거할 때 사용한다. 만약 빈배열이면 undefined를 반환한다.
스텍이란 자료가 들어올 때 차곡차곡 쌓여진 순서를 말한다. 나갈 때는 어떻게 될까? 역순이 된다. 이런 모습을 스텍이라고 한다.(Last in First Out)
unshift 메서드는 시기심은 많은 메서드이다. 늦게 왔음에도 1인자의 자리를 탐내는 욕심쟁이 메서드이다. 즉 unshift 메서드로 추가된 요소는 배열의 첫자리에 놓여지게 된다. 이렇게 재할당이 이뤄진 원본 배열은 unshift 메서드에 의해서 수정된다.
그러나 ES6(2015년) 이후, 스프레드 문법이 더 선호되고 있는 추세이다.
unshift 메서드와 반대이다. 정의의 사도 메서드이다. 위와같은 1인자 빌런으로 빼앗긴 자리를 되찾기 위해 첫번째 요소를 제거하신다. 그러나 이 메서드는 무섭다. 그냥 맨 앞에 있는 요소를 제거하기 때문이다. 사실 unshift 메서드와 상관없이 shift 메서드는 배열의 맨 앞자리 요소를 제거한다.
요소를 밖으로 꺼내는 방법은 스텍 말고 큐도 있다. 큐는 쌓여진 순서대로 요소를 꺼내온다(First in First Out).
concat 메서드는 인수로 전달된 값을 원본 배열의 마지막 요소에 추가한다. 그러나 push와 다른 점은 원본 배열을 직접 변경하지 못한다는 점이다. concat은 추가된 내용을 이전 내용과 함께 새로운 배열을 생성할 때 보통 생성된다. 그러나 이 메서드 역시 ES6(2015년) 이후에 스프레드 문법으로 대체되고 있는 추세이다.
push 메서드, unshift 메서드, concat 메서드는 현재 스프레드문법으로 대체가 가능해졌다. 이런 의미에서 일관성적인 부분을 언급한다면, 앞선 메서드 대신에 스프레드로 통일적으로 기록하는 것이 제안된다.
splice 메서드는 원본 배열의 처음이나 마지막에 요소를 추가하거나 제거할 때 사용된다. 앞선 메서드들이 하나의 기능을 수행했다면, splice 메서드는 복합적이다. 심지어 앞선 메서드들에서는 다룰 수 없었던 중간에 있는 요소들에 대한 변경도 splice 메서드는 가능하다.
arr.splice(#1, #2, #3, #4, #5 ...)
- #1 : 변경을 시작할 고유한 위치값을 지정한다.
- #2 : 변경을 종료할 고유한 위치값을 지정한다.
- #3 이후 : 변경을 시작할 고유한 위치값으로부터 추가할 내용이 기록된다.
이때 #3부터 기록되는 내용은 제거한 배열의 요소보다 작거나 동률이거나 심지어 커도 가능하다. 제거는 2요소를 제거했는데, 추가는 1개를 추가할 수도 2개를 추가할 수도, 더 많이 추가할 수도 있다는 내용이다. 아래의 예제를 살펴보자.
const arr = [0,1,2,3,4,5,6]
arr.splice(2,2, "A","B","C","D")
console.log(arr)
콘솔을 보자. arr를 물어봤을 때의 결과이다. 즉 splice메서드는 원본을 직접 변경한다.
이때 #2의 제거할 요소에 0을 입력하면, 제거하지 않고 해당 부분에 새로운 내용을 추가하는 것도 가능하다. 그러나 만약 #2 이하의 값을 입력하지 않으면, #1의 고유한 위치값에서부터 시작되는 이후의 모든 요소를 제거한다.
const arr = [0,1,2,3,4,5,6]
arr.splice(3)
console.log(arr)
특정위치를 제거하는 splice 메서드의 실전 사례에는 어떤 것이 있을까? 예를 들어서 이렇나 경우에 사용이 가능하다. includes 메서드를 통해서 특정 요소의 인덱스를 취득한 다음에, splice 메서드로 제거하는 일이다. 아래의 예제에서 살펴보자.
function remove(arr, item) {
const index = arr.indexOf(item)
if (index !== -1) arr.splice(index, 1)
console.log(arr)
}
const arr = [0,1,2,3,4,5,6]
remove(arr, 3)
함수가 호출되고 실행되는 내용을 보자. 먼저 index변수에 매개변수(item)로 받은 인자(3)의 값이 대입된다. 그리고 index를 얻었다면 간단하다. 만약에 index를 찾으면(index !== -1; -1이 아니면) 배열에서 splice 메서드를 통해서 1개를 지우고, 콘솔로 출력하라는 명령이다.
slice메서드는 원본배열의 특정한 위치에 있는 값을 복사해 온다. 그러나 원본 배열의 요소는 수정변경하지 않는다. 예제를 통해 아래에서 직접 살펴보자.
arr.slice(#1, #2)
- #1 : 배열에서 내용을 가져올 시작점의 고유한 위치값
- #2 : 배열에서 내용을 가져올 종료점의 고유한 위치값
이때, #2가 언급되지 않으면, #1이 지정한 위치부터 배열의 마지막 위치까지의 내용을 복사해 온다.
const arr = [0,1,2,3,4,5,6]
console.log(arr.slice(4))
console.log(arr.slice(4,6))
그런데 slice 메서드는 음수도 반영한다는 것을 기억하자. #1에 음수 -1, -2가 입력되면, 배열의 마지막 요소부터 음수로 지정된 값만큼을 복사해서 온다.
const arr = [0,1,2,3,4,5,6]
console.log(arr.slice(-3))
만약에 slice()와 같이 #1도 생략하면, 배열을 복제할 수 있다. 새로운 변수명에 대입하면, 새로운 배열이 만들어진 것이기에 arr1 === arr2 는 false이다. 즉 같지 않다.
join 메서드는 원본 배열의 모든 요소를 문자열로 변환한 후, 이를 연결한 문자열을 반환한다.
const arr = [1,2,3,4];
console.log(arr.join())
console.log(arr.join(''))
console.log(arr.join(':'))
#콘솔1은 join메서드의 가장 기본적인 결과이다. #콘솔2은 join메서드 ('') 공백단위로 붙이겠겠다는 명령인데, 이를 통해서 깔끔하게 하나의 문자열로 반환할 수 있다. #콘솔3은 ('*') 별아이콘은 가운데로 하고 배열을 이어서, 하나의 문자열로 반환한다.
reverse메서드는 원본 배열의 순서를 뒤집는데, 원본 데이터의 배열을 변경하는 것이다. 적용에 대한 사례를 아래에 기록했다.
const arr = [1,2,3,4];
const result = arr.reverse();
console.log(arr)
console.log(result)
fill 메서드는 ES6에서 도입된 내용이다. 해당 메서드는 원본 배열을 입력된 내용을 전부 채워넣는 것이다.
const arr = [1,2,3,4];
console.log(arr.fill('*'))
const arr = [1,2,3,4];
arr.fill("*", #시작위치, #종료위치)
function solution(phone_number) {
return [...phone_number].fill("*",0,phone_number.length-4).join('')
}
기존에 풀었던 문제를 fill 메서드를 공부하니 이렇게도 전환하여 사용할 수 있다는 것을 발견했다. 그전에는 slice 메서드에 repeat 메서드를 통해서만 해당문제에 접근했는데, 이렇게도 풀 수 있다는 것이 신선하다.
function solution(phone_number) {
let trans = Array.from(phone_number)
trans.splice(0, trans.length-4, "*".repeat(trans.length-4))
return trans.join('')
}
ES10(2019년)에 도입된 신선한 메서드이다. 인수로 전달받은 중접된 배열[배열] 인수를 평탄화 하는 것도 가능하다.
const arr = [1,2,3,4,5, [12, 324, 4,5 ,67], 34, 34]
console.log(arr.flat())
아래의 복잡한 배열의 배열이 존재한다고 하자.
const arr = [1,2,3,4,5, [12, [324, [4,[5]],67]], 34, 34]
console.log(arr)
console.log(arr.flat())
console.log(arr.flat(1))
console.log(arr.flat(Infinity))
먼저 자바스크립트의 함수는 일급객체에 해당된다. 여기서 말하는 일급객체라는 명칭의 두드러진 특징으로는 클로저 기능을 의미하기도 한다. 쉽게 설명해서 함수를 값처럼 인수로 전달할 수 있고 반환할 수 있다. 더 쉽게 설명하면 부모함수에서 자녀함수에게 자신이 받아 온 값을 전달하고, 자녀함수는 부모함수에게 값을 반환하는 것을 말한다. 이러한 함수의 특징을 고차함수라고 한다. 이러한 고차함수에 해당하는 sort() 메서드에 대해서 다뤄보자.
const fruits = ['Banana', 'Orange', 'apple'];
fruis.sort()
이러한 결과가 나온 이유는 sort 메서드는 가장 처음에 있는 자리의 내용만(엄밀한 의미에서 유니코드 코드포인트)을 정렬시키기 때문이다. 그러기에, 숫자를 정렬시키려면 조금더 섬세한 접근이 요구된다.
이와 같을 때에는 아래와 같은 접근을 통해서 문제를 해결할 수 있다.
#오름차순인경우
arr.sort((a,b) => a-b)
#내름차순인경우
arr.sort((a,b) => b-a)
매개변수로 받아온 arr의 a,b를 오름차순의 경우
교차비교하여 양수인 경우 뒤로 보내고, 음수인 경우 앞으로 보낸다. 내림차순의 경우
교차비교할 양수의 위치가 반대인 것을 보자. 이에 대한 선이해가 있음으로 이에 대한 설명은 여기서 마치고자 한다.
만약에 배열이 그 안에 객체를 가지고 있고, 객체에 있는 내용으로 정렬을 하고 싶다면, 아래와 같이 하면 된다. 객체 preperty의 key 값을 통해서 접근하면 된다.
arr.sort((a,b) => a[key]-b[key])
arr.sort((a,b) => a[key] > b[key] ? 1 : a[key] < b[key] -1 : 0)
함수형 프로그래밍으로서의 자바스크립트
자바스크립트가 가진 함수형 프로그래밍이란? 모함수와 그에 종속된 함수의 구조를 통해, 함수 내에 조건문과 반복문을 제거하고, 변수의 사용을 억제하여 프로그래밍의 복잡성을 해결하는 패러다임을 지양하는 특징을 말한다.
const numbers = [1,2,3];
const results = [];
for(let i=0;i<numbers.length;i++) {
results.push(numbers[i] * numbers[i])
}
console.log(results)
위의 코드는 numbers의 제곱에 대한 값을 results의 변수에 담아서 출력한 코드이다. 해당구문을 간결하게 만들기 위해 여러가지 방법들이 도입되었다. map메서드와 forEach메서드가 그 가운데 하나인데, 차례로 살펴보자.
const numbers = [1,2,3];
const results = [];
numbers.forEach(number => results.push(number * number))
console.log(results)
forEach(number =>
를 통해서 forEach메서드는 자신에게 종속된 함수로 부터 값을 콜백받아(return) 값을 산출한다. 이러한 함수를 콜백함수라고 한다. 콜백함수로 인자를 전달하고, 전달된 인자의 결과값을 반환받는다.
[100,200,300].forEach((item, index, arr) =>
console.log(`요소: ${item}, 인덱스: ${index}, this: ${arr}`))
let info = {
name : "Edwin",
age : 30}
let jsonString = JSON.stringify(info)
console.log(info)
console.log(jsonString)
JSON.stringify 메서드는 변수에 기록된 그대로 [{(괄호)}]
까지 문자열로 인코딩하여 그 결과를 반환한다.
다시 forEach 메서드로 돌아와서, forEach 메서드는 그 자체로 원본배열을 수정하지 않지만, 콜백함수를 통해서 원본 배열에 요소를 반복적으로 재할당시키면서 원본배열 자체를 수정하는 것도 가능하다.
동작하는 원리는 위에서 언급한 #arr를 호출하면 가능하다. 코드로 예를 살펴보자.
[100,200,300].forEach((item, index, arr) => {arr[index] = item/100; console.log(arr)})
코드를 읽어보면, 콜백함수를 통해서 arr[index] 마다 반복적으로 동작하며, item으로 받은 인자 하나하나의 내용을 수정해줄 것이다. 그리고 이때마다 그 결과를 콘솔로 기록한다는 말이다. 아래를 보자.
원본배열의 길이 만큼, 3번의 콜백함수가 실행되었고, 그 결과는 위의 이미지에서 보는 것처럼 순차적이다. 첫번째 forEach가 실행될 때는 arr[0]만, 그 다음에는 arr[1]가, 마지막으로 arr[2]가 동작하며 forEach 메서드는 동작을 완료하였다.
forEach메서드와 for문의 차이점이 있다는 것에 개발자는 유의해야 한다. for문은 break, continue를 통해서 반복을 제한할 수 있지만, forEach 메서드는 일괄처리를 과제로 삼기에, 이러한 섬세한 조건은 실행하지 못한다. 일단 실행하면 무엇인가를 변경한다. 조건부 삼항 연산자를 통해서 스스로에게 스스로를 덧입히더라도 말이다. 일단 실행되면 모든 요소에 간섭한다.
만약 배열의 길이가 3인데, 그 안에 [1,,3] 과 같이 희소배열이 존재한다면, ForEach 메서드는 존재하는 요소만 가공하여 2개의 길이를 가진 [1,3] 만 반환한다.
map 메서드 역시 원본 배열을 기반으로 콜백함수에 인수를 전달하고 값을 반환받아 새로운 배열을 생성한다. 이때 ForEach 메서드와 같이 배열의 모든 요소를 순회한다. 해당 숫자의 제곱근을 판별하는 Math.sqrt 메서드를 통해서 정보를 가공해보자.
console.log([1,4,9].map(item => Math.sqrt(item)))
먼저 map메서드는 언제나 콜백함수의 반환값으로 구성된 새로운 배열을 반환한다. 그 결과 원본배열과 길이가 동일하다.
반면에 forEach메서드는 단순하게 반복문을 대체하기 위한 고차함수이며, 언제나 undefined를 반환한다. 해당 이유는 현재의 이해력으로는 독해가 어렵기에 패스 strict mode(제20장)
를 공부하고 난 후에 다시 살펴보자.
간략한 소개
strict mode(제20장)
엄격모드
ES5(2009년)에서부터 적용된 엄격모드는 자바스크립트 언어가 가진 인터프리터 언어의 특성에 제약을 가하는 것을 말한다. 여기서 말하는 제한이란 오류를 발생시킬 가능성이 높거나, 엔진 최적화 작업에 부담을 야기시키는 코드에 대해서 명시적인 에러를 발생시키는 것을 말한다.
음, strict mode(제20장)
를 간략하게 살펴봐도 forEach메서드가 어떤 부분에서 undefined를 반환하는지는 아직 모르겠다. 모질라 사이트를 보더라도 아래와 같이만 되어있기에 추가 이해가 필요할 것 같다.
내가 이해한 바로는, 새로운 배열을 반환하는 map메서드와 그자체로는 undefined인 forEach메서드의 차이가 있지만 동작기능에 있어서는 동일한 것 같다. 이 부분에 대해서는 추후 학습이 필요하다.
그래도 의미있는 질문을 던지니 바로 답변을 주셨다.
로직에 있어서의 동일한 수행 : forEach메서드, map메서드
값을 반환하는 것에 있어서의 다른 결과 :
1. forEach 메서드 : 로직의 결과만을 전달
2. map 메서드 : 로직의 결과에 대한 새로운 배열을 생성 후 전달
엄밀한 의미에서 forEach 메서드는 값을 반환하지 않는다는 말이 타당하다. 대신 전달받은 인자를 console.log 엔진에 전달해주어 표시를 가능하게 하는 무엇이다. 값을 전달받은 것이 아니라 단순히 실행시켜주는 것이라고 이해하자.
그러나 map 메서드는 로직을 수행함과 함께 결과를 저장(새로운 배열을 생성)한다는 측면이 있다. forEach 메서드는 그런 의미에서 1회성인 반면에, map 메서드는 기록이 남는다고 보면 되지 않을까?
filter 메서드는 자신을 호출한 배열의 모든 요소를 살펴보며, 검색기능을 수행한다. 이후에 살펴볼 find 메서드와 다른점은 배열에 있는 모든 요소를 탐색한다는 점이다.
const num = [1,2,3,4,5,6,7,8];
console.log(num.filter(item => (item%2)==0))
여기서 볼 수 있는 것은 filter의 반환은 언제나 true 인 것만을 그 대상으로 한다는 점이다. false의 값은 반환되지 않는다. filtet 메서드로 인해서 반환되어 새롭게 생성된 배열은 그 길이가 항상 원본 배열과 동일하지 않는다. 같거나 작다.
여기서 신중하게 볼 부분이 있다. forEach, map, filtet 모두 매개변수로 (item, index, arr)를 받아서 출력할 수 있다는 점이다.
[1,2,3].filter((item, index, arr) =>
console.log(`요소값 : ${item}, 인덱스 : ${index}, this : ${JSON.stringify(arr)}`))
console.log([1,2,3,1,4,5,1,6].filter((element, index, arr) => arr.indexOf(element) === index))
구문을 해석하면 이와 같다. arr.indexOf(element) 요소를 배열에서 위치값을 찾는다. 이때 결과는 배열에서 해당 부분을 찾은 가장 첫번째 번지를 의미할 것이다. 이와 자신의 인덱스가 간으면 반환하라는 말이다.
filter 메서드를 통해서 걸러진 2개를 제외하고 5개의 요소가 새로운 배열을 통해서 반환된 것을 확인해 볼 수 있다.
reduce 메서드는 계산에 있어서 끝판왕이다. 배열의 모든 요소를 순회하며, 하나의 값을 반환한다. 예를 들어서 누산기능을 가능케 하는 것이 reduce메서드의 사용 용례 가운데 하나이다. 그러나 원본 배열을 변경하지는 않는다는 것을 기억하자.
reduce메서드는 앞선 고차함수들과 다르게 4개의 인수를 받는다. 아례의 예제를 통해서 살펴보자.
console.log( [1,2,3,4].reduce((accumulator, currentValue, index, array) => accumulator + currentValue, 0));
#accumulator : 구문의 끝부분에 있는 숫자 0의 값을 인자로 받는다. 해당 숫자는 누산기의 초기값을 설정한다. 숫자가 바뀌면 초기값이 변경된다.
#currentValue : reduce메서드를 통해서 배열에서 순차적으로 가져올 인자로, 1,2,3,4가 순차적으로 입력된다.
#index : 고유한 위치값으로 새로운 인덱스를 부여한다.
#array : reduce메서드의 this에 해당되는 배열 전체를 불러온다.
계산결과를 살펴보면 아래와 같다.
누산기 값 원본배열의 요소 값 결과 초기값 0 첫번째요소 1 1 누산값 1 세번째요소 2 3 누산값 3 네번째요소 3 6 누산값 6 다섯번째요소 4 10
중요한 것은 reduce메서드는 하나의 결과값을 반환한다는 부분이다.
const point = [90, 80, 95, 100];
const average = point.reduce((acc, cur, i,{length}) => {
return i === length - 1 ? (acc + cur)/length : acc + cur }, 0);
console.log(average)
[90, 80, 95, 100] : 4개의 성적이 담겨있는 배열이 있다. 해당 배열을 reduce메서드를 통해서 평균을 구하고자 할 때 위와 같이 기록하면 가능하다.
핵심은 4개의 매개변수 가운데 마지막이다. {length} 와 같이 입력하면, 배열의 길이를 구할 수 있는 것 같다. 위에서 살펴본 forEach메서드에서도 해당 부분에 {length}를 입력하니, 배열의 길이를 구할 수 있었다. 이는 reduce메서드에서도 동일하다.
그렇다면 배열의 마지막을 순회할 때, 누산된 값(acc)에 평균에 대한 나눔셈을 해주고, 그 이외에는 누산을 해달라는 조건부 삼항 연산자를 통해서 구문을 기록하면 위와 같은 결과를 얻을 수 있을 것이다.
const point = [90, 80, 95, 100];
const max = point.reduce((acc, cur) => (acc > cur ? acc : cur), 0);
const maxMath = Math.max(...point)
console.log(max)
console.log(maxMath)
결과는 동일하지만, 방법론에 있어서는 Math.max메서드가 훨씬 더 직관적인 것을 볼 수 있다. 다만 Math.max는 배열을 받을 수 없기에, 전개구문(spread syntax)으로 기록해주어야 한다.
이해의 차원에서는 누산을 할 때는 acc+cur을 해주었지만, acc > cur 의 경우에는 cur -> acc 에 대입되는 것 같다. 진행되며 가장 큰 숫자를 찾고,마지막 과정에서 해당 숫자보다 배열이 작으면 acc 를 그렇지 않으면, cur 을 만들어 준다고 이해하면 될까? 각설하고! 이 기능은 Math.max메서드를 사용하자. 코드는 결국 가독성인데, 이 부분은 reduce 메서드가 열등함을 본다.
const fruits = ["바나나", "딸기", "오렌지", "사과", "바나나", "바나나"];
const count = fruits.reduce((acc, cur) => {
acc[cur] = (acc[cur] || 0) +1;
return acc}, {});
console.log(count)
기록된 요소의 중복된 횟수를 각각 구할 수 있는 메서드로 활용이 가능하다. 배열에서 "바나나"는 3회, 나머지는 각각 1회씩 기록되었다.
버티컬바(|)가 교재에서는 다르게 기록되어 있어서 당황했다. 뭔가 한 10분은 찾은 것 같다. 그냥 입력해보자 했는데 버티컬바가 맞았다. 그런데 이 구문 어떻게 동작하는 것일까?
acc[cur] = (acc[cur] || 0)
하나의 property를 만들어주는 것으로 보인다. acc 누산을 하는데, acc[cur] 인자의 종류에 따라서 누산을 진행하라는 것으로 읽혀진다. 그런데acc[cur] || 0
부분의 해석이 조금 어렵다. property의 value 부분에 해당되는 내용 같은데 여기서 버티컬바가 의미하는 바와, 그 다음 줄에 있는return acc
에 대한 해석이 어렵다. 그리고 return 줄에서 {}는 acc의 초기값으로 객체를 주겠다는 말로 보인다.
현재 오는 중...
ES10(2019년)에 도입된 신선한 메소드, 15) Array.prototype.flat에 대해서 앞에서 살펴보았다. flat메서드는 다중 배열을 쉽게 평탄화 하는 배열 메소드였다.
const arr = [1,2,3,4,5, [12, [324, [4,[5]],67]], 34, 34]
console.log(arr.flat(Infinity))
해당 기능을 reduce메서드에서도 접근할 수 있는데 아래와 같다.
const arr = [1,2,[3,4,5], [12, 34], 34];
const flatten = arr.reduce((acc, cur) => acc.concat(cur), []);
console.log(flatten)
flat(Infinity)메서드를 통해서 구현했던 다중 배열에 대한 처리는 어려워보이나, 이중 배열에 대해서는 09) concat메서드와 협업을 통해서 구현할 수 있다.
정리하면 reduce메서드로도 가능하나 교드의 효율적이 부분에는 ES10(2019년) 도입된 Array.prototype.flat 메서드가 더 유용하다.
reduce 메서드는 또한 배열에 있는 중복된 값을 제거할 수도 있다.
reduce((unique, val, i, itemlist) =>
은 이제야 조금씩 구문들이 해석된다. 4개의 매개변수를 받는다. 각각 누산된 결과값, 요소, 고유한위치값, 배열전체 이다.
콜백함수 내에서 reduce는 초기값인 [배열]에 unique한 val를 하나씩 기록한다. 이때 중요한 것은 조건부 삼항 연산자에 기록된 것과 같이 해당 요소가 배열의 가장 첫번째에 등장하는 고유한 위치값(index)와 일치하는 경우이다. 그렇지 않으면, 누산기에 추가 없이 이전의 누산값만 반환한다. 이 결과에 따라 원본 배열의 4-5번지에 있는 바나나는 중복 요소이기에 이를 제외한 배열만 반환되었다.
const fruits = ["바나나", "딸기", "오렌지", "사과", "바나나", "바나나"];
console.log(fruits.filter((element, index, arr) => arr.indexOf(element) === index))
결과는 같으나, 더 입력된 코드의 내용은 filter 메서드가 더 이상적이다.
const fruits = ["바나나", "딸기", "오렌지", "사과", "바나나", "바나나"];
let new list = [...new Set(fruits)]
새로운 배열을 생성할 때, 전개구문(spread Syntax)과 함께 Set메서드를 사용하면, 중복된 배열이 삭제된 상태로 동작한다.
그런데 이 "하나의 결과"는 단순한 하나가 아니라, 취합된 결과물이라고 보아야 될 것 같다. 시작은 누산기능으로부터 시작해서, 중복을 제거한 배열 내의 객체를 반환한다는 등의 내용이다. 사실 공부는 했지만, 이를 온전히 활용하기 까지는 많은 시간이 걸릴 것으로 보인다.
물론 초기값이 "0"이면 생략하는 것이 가능하다. 그러나 안전한 결과를 반환받기 원한다면, 언제나 설정할 것을 권면된다.
some메서드는 조건문의 or을 생각하면 좋을 것 같다. 배열 안에 특정 요소를 검색하는데 하나라도 존재하면 true를 그렇지 않으면 false를 반환한다.
const num = [5, 10, 15];
console.log(num.some(item => item > 10));
위의 경우 결과로 true 가 나온다. 이유는 배열 가운데 10보다 큰 수가 2개나 존재하기 때문이다. 실제 사례에는 아래의 경우와 같이 사용되기도 할 것이다.
const num = ["바나나", "망고", "딸기"];
console.log(num.some(item => item.includes("수박")));
배열에 찾는 "수박"이 없음으로 콜백함수로 반환된 값은 false이다. 그 결과 some 메서드는 false 를 최종적으로 반환한다.
every메서드는 조건문의 and를 생각하면 좋을 것 같다. 모든 요소가 true 여야 한다. 하나라도 결과와 다르면 false를 반환한다. 그러나 만약 빈배열인 경우에는 언제나 true를 반환함으로 주의해야 한다.
예를 들어서 테스트를 해보았다.
(item.length === 0) ? false
빈배열인 경우 false를 반환하라고 했지만, 결과는 true로 나왔다. 안 되는가 보다.
find메서드는 배열에서 일치하는 요소를 발견하면 true에 해당되는 첫번째 요소를 반환한다. 찾지 못하면 undefined를 반환한다. 그러나 19) filtet메서드와 차이점이 있는데 find는 하나를 발견하면 메서드의 실행을 종료한다. 반면에 filter는 전부를 찾는다는 차이가 있다.
const students = [
{id:1, name : "Adwin"},
{id:2, name : "Bdwin"},
{id:3, name : "Cdwin"},
{id:4, name : "Ddwin"},
{id:5, name : "Cdwin"},
{id:6, name : "Cdwin"},
]
console.log(students.find(findName => findName.name === "Cdwin"))
students을 보면, Cdwin이 3명이 존재하지만, 콘솔에 출력되는 값은 id:3 뿐이다. 이는 find 메서드가 하나를 찾으면 실행을 종료하기 때문이다. 반면에 존재하지 않는다면 undefined를 반환할 것이다.
findIndex메서드는 string메서드들 가운데 하나였던 indexOf메서드와 유사하다. 차이는 그 사용할 수 있는 자료형이 객체(배열)이라는 점이다. 배열에서 특정한 값을 찾고, 해당 값이 있는 고유한 위치값(index)을 반환한다.
const students = [
{id:1, name : "Adwin"},
{id:2, name : "Bdwin"},
{id:3, name : "Cdwin"},
{id:4, name : "Ddwin"},
{id:5, name : "Cdwin"},
{id:6, name : "Cdwin"},
]
console.log(students.findIndex(findName => findName.name === "Cdwin"))
앞선 find메서드에서 메서드의 이름만 변경하였다. 결과는 2가 출력되었다. students[2]에서 해당 정보를 찾았기 때문이다.
flatMap메서드는 Map메서드와 연관이 있다. 이유는 Map메서드를 통해서 생성된 배열의 평탄화를 하기 때문이다. 즉 flatMap메서드는 그 이전에 Map메서드가 선행되어야 한다. 그러나 별도의 구문이 필요없다. flat+Map메서드는 이름에서 볼 수 있듯이 해당 메서드를 선언함으로도 단번에 가능하게 하기 때문이다. Map메서드 만으로는 할 수 없었던 평탄화를 flat+Map메서드는 수행한다.
다만 한계는 이중배열 밖에 처리를 하지 못한다는 점이다. 다중배열은 flat(Infinity) 메서드 밖에는 처리할 수 있는 메서드는 없어보인다. 만약 중첩 배열의 평탄화 깊이를 지정해야 할 경우, map메서드와 flat메서드를 각각 호출해야 할 것이 지향된다.
editor. Edwin
date. 23/02/15~17