1. from(), of()
•
이터러블 오브젝트 혹은 Array-like 오브젝트를 Array오브젝트로 변환하는 함수입니다.
//1. Array-like Object
const ArrayLikeObj = {0: "test1", 1:"test2", length:2}
const result1 = Array.from(ArrayLikeObj);
console.log(result1);//["test1", "test2"]
//2. Iterable Object
/*
<li class="sports">축구</li>
<li class="sports">농구</li>
*/
const nodes = document.querySelectorAll(".sports");
const show = (node) =>console.log(node.textContent);
Array.from(nodes).forEach(show);//축구\n 농구
JavaScript
복사
•
두 번째 파라미터에 함수를 작성하면 Array오브젝트에 추가 될 각각의 요소가 추가되기전 두 번째 파라미터로 받은 로직을 수행후 반환되는 값을 배열에 반환합니다.
const like = {0:"zero", 1:"one", length:2};
const change = (value) => value+"변경적용";
console.log(Array.from(like, change));
//[zero변경적용, one변경적용]
JavaScript
복사
•
세 번째 파라미터에는 this 바인딩 컴포넌트에 들어갈 컨텍스트를 파라미터로 넣어줄 수 있습니다.
•
추가된 컨텍스트는 호출된 함수에서 this로 참조할 수 있습니다.
const like = {0:"zero", 1:"one", length:2};
const change = function(value){return value+"변경적용" +this.book};
const obj = {book: "책책책"};
console.log(Array.from(like, change, obj));
//[zero변경적용책책책, one변경적용책책책]
JavaScript
복사
of()
파라미터 값을 배열로 변환하여 반환하는 함수로 파라미터에 변환 대상 값을 작성합니다. 물론 콤마로 구분해 다수 작성도 가능합니다.
const result = Array.of(1, 2, 3);
console.log(result);
console.log(Array.of());
JavaScript
복사
2. 배열 엘리먼트 복사, copyWithin()
오브젝트내의 범위 값을 복사하여 설정하는 함수로 1개의 필수 파라미터와 2개의 옵션 파라미터를 받습니다. 두 번째 파라미터의 인덱스부터 복사하여 첫 번째 인덱스부터 순서대로 설정합니다(대체) 여기서 세 번째 파라미터가 있따면 복사할 끝 인덱스를 의미하는것으로 이 파라미터가 없다면 2번째 파라미터인덱스부터 끝까지를 의미하고 파라미터가 있다면 해당 인덱스 까지를 의미합니다.
즉, .copyWithin(1,3,5) 라고 한다면 3번 인덱스부터 5번 인덱스 직전까지 복사하여 1번인덱스의 위치부터 순서대로 대체하는 것입니다.
const list = ["A","B","C","D","E"];
const copy = list.copyWithin(1, 3);
console.log(list);
console.log(copy);
JavaScript
복사
[실행 결과]
[A,D,E,D,E]
[A,D,E,D,E]
여기서 세 번째 인덱스를 작성했을 경우와 2,3번째 인덱스를 모두 작성하지 않았을 때의 코드도 살펴보겠습니다
1.
const copy = list.copyWithin(0, 2, 4);
⇒ 2번째부터 4번째까지([C,D])를 0번째 인덱스부터 대체한다는 의미로 아래 이미지와 같이 대체됩니다.
2.
const copy2 = list2.copyWithin(2);
⇒ 2, 3번째 파라미터가 둘 다 없을경우 배열 전체를 복사한다는 의미가되어 1번째 파라미터위치부터 대체를 한다는 의미가 됩니다. 이때 복사할 엘리먼트 수가 대체할 엘리먼트 수보다 많을 경우 매치되는 인덱스만 값을 대체하고 남는 것 ([D,E])는 대체하지 않습니다.
const list = ["A","B","C","D","E"];
const copy = list.copyWithin(0, 2, 4);
console.log(list);
console.log(copy);
const list2 = ["A","B","C","D","E"];
const copy2 = list2.copyWithin(2);
console.log(list2);
console.log(copy2);
JavaScript
복사
[실행 결과]
[C,D,C,D,E]
[C,D,C,D,E]
[A,B,A,B,C]
[A,B,A,B,C]
copyWithin() 함수는 얕은 복사(shallow copy)로써 같은 배열 안에서 이동하는 개념입니다. 그렇기에 새로운 내용을 만들어 할당하는 것이 아닌 현재의 메모리 주소를 복사해 참조를 넣어주기 때문에 만약, 값이 연동되지 않게 하기위해서는 깊은 복사(Deep Copy)를 해야 합니다. 그리고 위 코드에서 1번째 파라미터만 작성할 때와 같이 배열의 엘리먼트 수는 더 늘어나거 나 줄어들지 않습니다.
또한, 동작시 배열 안에서 엘리먼트 이동은 좌측에서 우측으로 이동하며 처리속도가 빠르다는 특징이 있습니다.
3. Generic
이는 copyWithin함수는 제네릭 함수로, this값이 Array객체일 필요는 없다는 말인데요, 이게 무슨말이냐면 해당 함수는 다른 오브젝트에서도 쓸 수 있다는 의미가 됩니다. 즉, Array-like한 오브젝트이거나 이터러블 오브젝트에서는 copyWithin함수가 동작하도록 개발을 해야한다는 의미입니다.
•
사용 예
const like = {0: 10, 1:20, 2:30, length: 3};
console.log(Array.prototype.copyWithin.call(like, 1, 0));
JavaScript
복사
[실행 결과]
{0: 10, 1: 10, 2: 20, 3: 30, length: 3}
1.
call()에서 this에 바인딩될 오브젝트로 like를 작성했는데 이는 Array-like 오브젝트이며 제네릭 함수인 copyWithin에서는 동작할 수 있어야 합니다. (Generic)
2.
결과값은 배열 형태가 아닌 대상 오브젝트 형태로 반환됩니다.
•
generic이 뜻하는 것은?
copyWithin() 메소드는 Array.prototype에 연결된 메소드입니다. 그렇기에 Array 오브젝트가 처리 대상이지만, 이처럼 Array-like, 이터러블 오브젝트를 처리할 수 있다는 것을 뜻합니다.
4. 같은 값, 인덱스 검색: find(), findIndex()
find()
개발을 하다보면 수많은 배열정보들을 마주하게되고 그 배열의 많은 엘리먼트들 중 원하는 값을 찾아야 하는 경우가 있습니다. 가령 예를들어 하나의 반 학생들의 성적정보가 배열로 담겨져 있다고 할 때 여기서 특정 이름의 학생을 찾고자 한다고 하면 어떻게 할까요? 보통은 배열 내부를 순회하며 이름을 비교해 찾는 방법이 있습니다.
이러한 방법을 정리해놓은 메소드가 find()입니다. Array.prototype에 연결되어있는 이 메소드는 배열의 엘리먼트를 하나씩 읽어가며 파라미터로 받은 콜백 함수를 호출하여 함수가 true를 반환할 경우 검색완료로 판단해 메소드를 종료하며 가장 마지막으로 비교한 엘리먼트 값을 반환합니다. 이를 코드로 풀어보면
const students = [
{name:"이한솔", eng:90, math:85},
{name:"김철수", eng:76, math:80},
{name:"김영희", eng:95, math:55}
];
//로직 직접 구현
function find(name, list){
for(const item of list){
if(item.name === name){
return item;
}
}
return null;
}
console.log(find("김철수", students));
//Array.prototype.find() 사용
const findConditionWithArrow = (value, index, all) => value.name === "김철수";
const result = students.find(findConditionWithArrow);
console.log(result);
JavaScript
복사
물론 둘 다 같은 값을 반환합니다. 하지만, 로직 직접 구현은 반복순회에 대한 로직과 순회의 대상인 배열도 직접 넘겨줘야 하는 반면, find()메소드는 조건검사로직(callback)만 만들어주면 되서 한결 더 편하게 사용가능합니다. 만약, 조건에 부합하는 요소를 찾지못한다면 undefined 를 반환합니다.
더하여 this 바인딩 컴포넌트에 설정될 오브젝트를 세 번째 파라미터에 넣어주면 this를 통해 해당 오브젝틀들 사용할 수 있습니다.
const students = [
{name:"이한솔", eng:90, math:85},
{name:"김철수", eng:76, math:80},
{name:"김영희", eng:95, math:55}
];
const findCondition= function(value, index, all){
return value.name === "김철수" && value.name === this.name;
}
const result = students.find(findCondition, {name: "김철수"});
console.log(result);//{name: "김철수", eng: 76, math: 80}
JavaScript
복사
두 번째 파라미터에서 콜백 함수에서this로 참조할 오브젝트({name: "김철수"})를 작성해주니 findCondition 콜백 함수에서 this.name프로퍼티를 찾으면 "김철수"를 나타내는 것이죠.
단, 이때 주의할점은 콜백 함수는 Arrow Function이 아닌 선언식 함수로 표현을 해야한다는 것인데 그 이유는 Arrow Function 에서는 this가 window를 참조하기 때문에 두 번째 파라미터로 전달한 오브젝트를 참조하지 못합니다.
findIndex()
findIndex() 메소드는 기존 find()메소드와 동일한 파라미터를 전달받고 수행되는 로직도 동일합니다. 하지만 차이점은 find()에서는 콜백함수를 통해 매칭된 엘리먼트의 요소를 반환하는 반면 findIndex()는 매칭된 엘리먼트의 인덱스를 반환합니다.
const students = [
{name:"이한솔", eng:90, math:85},
{name:"김철수", eng:76, math:80},
{name:"김영희", eng:95, math:55}
];
const findConditionWithArrow = (value, index, all) => value.name === "김철수";
console.log(students.findIndex(findConditionWithArrow));//1
JavaScript
복사
코드에서 students를 순회하며 각각의 엘리먼트 요소(오브젝트)의 name 프로퍼티가 "김철수"인 엘리먼트를 찾을 경우 해당 엘리먼트의 위치 인덱스를 반환하며 findIndex()를 종료합니다. 여기서 만일 해당 요소를 찾지 못한다면 -1을 반환합니다.
그외에 세 번째 파라미터로 this참조에 사용할 오브젝트를 전달하는 것 역시 find()와 동일합니다.
5. 대체, 포함 여부: fill(), includes()
fill()
메소드 명 그대로 Array 오브젝트 내부의 값들을 파라미터 값들로 설정해주는 메소드 입니다. 1개의 Essential 파라미터와 2개의 optional 파라미터를 받는데 첫 번째 파라미터는 오브젝트 내부를 채워줄 값으로써 두 번째 파라미터와 세 번째 파라미터로 범위를 지정할수도 있습니다. 이때 주의할점은 해당 메소드는 값을 대체하는 것이지 새로운 Array 오브젝트로 복사하는 것이아니기에 원본 오브젝트가 유지되어야할때는 복사한 뒤에 사용해야 합니다.
또한, fill()메소드는 Generic 함수이기 때문에 Array-like 오브젝트나 이터러블 오브젝트에서도 사용이 가능합니다. 반환값 역시 대상 오브젝트와 동일한 타입으로 반환됩니다.
사용 예
const list = ["A", "B", "C"];
const like = {0:"A", 1:"B", 2:"C", length:3};
//대체할 값 파라미터만 작성
console.log(list.fill("범위X"));
//시작 인덱스 파라미터 작성
console.log(list.fill("범위1", 1));
//끝 인덱스 작성
console.log(list.fill("범위2", 0, 2));
//Array-like 사용
console.log(Array.prototype.fill.call(like, "대체값", 1));
JavaScript
복사
[실행 결과]
["범위X", "범위X", "범위X"]
["범위X", "범위1", "범위1"]
["범위2", "범위2", "범위1"]
{0: "A", 1: "대체값", 2: "대체값", length: 3}
includes()
find()를 통해 배열의 엘리먼트 요소들 사이에서 특정 조건을 만족하는 요소를 찾을 수 있었습니다. 그렇다면 배열의 엘리먼트 사이에서 하나, 혹은 여러 값이 존재하는지 확인하고 싶을때를 위해 include()메소드가 있습니다. 이 메소드는 하나의 비교하려는 값 파라미터와 비교 시작 인덱스를 파라미터로 받습니다. 그리고 두 번째 파라미터는 비교를 시작하려는 인덱스를 나타내며 필수 파라미터가 아닌지라 파라미터를 작성하지 않을 경우 0번째 인덱스를 default로 설정합니다. 또한 제네릭 함수이기 때문에 Array-like나 이터러블 오브젝트도 사용 가능합니다.
•
사용 예
const list = [10, 20, 30, 40, 50];
console.log(list.includes(10));//true
console.log(list.includes(70));//false
console.log(list.includes(10, 1));//false
const like = {0:"A", 1:"B", 2:"C", length:3};
console.log(Array.prototype.includes.call(like, "C"));//true
console.log(Array.prototype.includes.call(like, "A", 1));//false
JavaScript
복사
6. 배열 차원 전환: flat(), flatMap()
ES2019 부터 사용이 가능합니다.
flat()
배열은 1차원 뿐아니라 2차원 배열및 다차원 배열이 있습니다. 학생들 성적 배열이 있다고할 때 한 차원 더나가 학생들을 반으로 구분하여 학년의 그룹 혹은 학년도 그룹핑해서 학교 학생들의 성적으로 구분할수도 있죠. 이렇게 depth가 깊은 배열의 값을 줄여서 학년구분, 혹은 반 구분없이 하나의 배열로 종합하여 순위를 매겨야할 수도있습니다. 이처럼 배열의 차원을 변환하고자 할때 flat()메소드를 사용할 수 있습니다. 해당 메소드는 배열의 차원은 변환후 새로운 배열로 설정하여 반환을 하는 함수로 파라미터는 변환할 배열의 깊이(depth)를 작성하나 필수 파라미터는 아니고 작성하지 않을 경우 default는 1로 설정됩니다.
•
사용 예
const studentAvgs = [{name:"s1", avg:76}, [{name:"s2", avg:78}], [[{name:"s3", avg:96}]]];
const studentAvgByClass = [
{name:"s1", avg:76, class:"one"}, {name:"s2", avg:78, class:"one"}, {name:"s3", avg:96, class:"one"},
[{name:"s1", avg:76, class:"two"}, {name:"s2", avg:78, class:"two"}, {name:"s3", avg:96, class:"two"}],
[{name:"s1", avg:76, class:"two"}, {name:"s2", avg:78, class:"two"}, {name:"s3", avg:96, class:"two"}],
];
console.log(studentAvgs.flat());//---(1)
console.log(studentAvgs.flat(0));//---(2)
console.log(studentAvgs.flat(2));//---(3)
const list = [1, 2, 3, , , , , [4, 5]];
console.log(list.flat());//---(4)
JavaScript
복사
1.
console.log(studentAvgs.flat())
⇒ flat에 파라미터를 작성하지 않은경우 기본값인 1로 설정되며 배열내의 하나의 차원까지는 평탄화 한다는 의미로 요소 중에 [{name:"s2", avg:78}] 는 {name:"s2", avg:78} 이 되고 [[{name:"s3", avg:96}]]는 [{name:"s3", avg:96}]이 되어 [{name:"s1", avg:76}, {name:"s2", avg:78}, [{name:"s3", avg:96}]] 이 됩니다.
2.
console.log(studentAvgs.flat(0))
⇒ 파라미터로 0을 넣게되면 1차원 배열까지 평탄화를 한다는 것인데, [1,2]를 1,2로 차원을 변환하지만 배열에 설정하여 반환할 때[1, 2]가 되므로 결국 차원 변환없이 그대로 반환됩니다.
3.
console.log(studentAvgs.flat(2))
⇒ 파라미터가 1보다 큰 값을 작성하여 2를 넣으면 3차원까지는 평탄화를 하게 됩니다. 그렇기에 [[{name:"s3", avg:96}]] 도 {name:"s3", avg:96}로 평탄화되고 반환되는 값은 [{name:"s1", avg:76}, {name:"s2", avg:78}, {name:"s3", avg:96}]입니다.
4.
console.log(list.flat())
⇒ 빈 엘리먼트는 삭제되어 유효한 엘리먼트만 설정되기 때문에 해당 인덱스가 비어있는 값일경우 무시되어
[1,2,3,4,5]가 반환됩니다.
flatMap()
flat()과 기본적인 목적은 같습니다. 다차원 배열을 차원변환으로 평탄화 하는 것인데, 차이점이 있다면, flat()이 차원 변환만 한다면 flatMap()은 map()의 기능이 추가되어 map처럼 콜백함수를 실행하는 것입니다.
•
사용 예
const list = [10, 20];
const change = (element, index, all) => element + 5;
console.log(list.flatMap(change));
console.log(list.map(change));
JavaScript
복사
[실행 결과]
[15, 25]
[15, 25]
list의 각 값들을 순회하며 change 로직을 수행 후 반환되는 값으로 배열을 설정해 반환합니다. 여기까지만 보면 기존에 map() 메소드와 차이가 없습니다. 두 메소드간의 차이는 아래 예시에서 나오는데 flatMap은 반환감이 배열일 경우 차원 변환을 (차원 감소)해서 반환합니다.
const list = [10, 20];
const change = (element, index, all) => [element + 5];
console.log(list.flatMap(change));
console.log(list.map(change));
JavaScript
복사
[실행 결과]
[[15],[25]]
[15, 25]
7. Array 이터레이터 오브젝트 생성: entries()
Array 오브젝트는 Array 이터레이터 오브젝트로 생성 및 반환이 가능합니다. 이는 Array 오브젝트 역시 이터레이터 프로토콜을 따르기 때문인데 Symbol.iterator 프로퍼티가 있을 경우 이터레이터 프로토콜을 따른다고 생각하시면 됩니다. entries()를 사용하면 배열의 엘리먼트를 [key, value] 형태로 반환하는데 여기서 key는 인덱스가되고, value는 값이 됩니다.
코드를 통한 분석
•
이터레이터 오브젝트 생성
const iter = ["A", "B"].entries();
console.log(iter.next());//{value: [0, "A"], done: false}
console.log(iter.next());//{value: [1, "B"], done: false}
console.log(iter.next());//{value: undefined, done: true}
JavaScript
복사
1.
["A", "B"].entries()
⇒ Array Iterator Object를 생성합니다. 여기서 오브젝트는 [key, value] 형태입니다.
2.
iter.next()
⇒ next()를 호출하면 {value: [0, "A"], done: false} 이런식으로 출력되는걸 확인하실 수 있는데, 여기서 value에는 [key, value]형태의 값이 설정되어 있으며 done은 전개 완료 여부입니다. false일 경우 아직 전개할 수 있다는 것이고 true이면 전개가 완료되어 더이상 next()를 호출할 수 없다는 의미입니다. done이 true일때는 value도 undefined입니다.
•
for-of 문으로 전개
const iter = ["A", "B"].entries();
for(const prop of iter){
console.log(prop);
}
const iter2 = ["C", "D"].entries();
for(const [key, value] of iter2){
console.log(`${key}: ${value}`);
}
JavaScript
복사
위처럼 전개만 할 때는 next()를 사용하면서 값을 value에서 꺼내고 done으로 전개여부를 검사하는 것은 불편합니다. 그래서 연속해서 전개를 할 경우에는 for-of문을 사용하는 것이 편리합니다.
1.
for(const prop of iter){...}
⇒ porp에 할당되는 값은 iterator 오브젝트인 iter의 value([key, value])가 할당됩니다.
값을 조금 더 편하게 사용하기위해 분할 할당을 사용할 수도 있습니다.
2.
for(const [key, value] of iter2){...}
⇒ iter2이 순회하며 할당되는 값을 분해할당하여 조금 더 편하게 사용할 수 있습니다.
•
이터레이터는 다시 반복할 수 없습니다.
const iter = ["C", "D"].entries();
for(const [key, value] of iter){
console.log(`${key}: ${value}`);
}
for(const [key, value] of iter){
console.log(`${key}: ${value}`);//undefined
}
JavaScript
복사
entries()의 주의사항이면서 iterator 오브젝트를 사용할때의 주의사항이기도 한데, 이터레이터 오브젝트는 끝까지 순회를 한 다음 다시 순회를 할 수 없습니다. 이터레이터 순회가 끝나면 그 뒤로는 next()를 아무리 호출해도 {value: undefined, done: true} 만 반환합니다.
8. Array 이터레이터 오브젝트 생성: keys(), values()
keys()
해당 메소드(keys())는 entries()메소드와 로직은 동일하게 Array오브젝트를 Array 이터레이터 오브젝트로 생성및 반환하는데 entries()와 동일합니다. 하지만 차이점은 [key, value]형태가 반환되는것이아닌 key만 반환된다는 차이점이 있습니다. 그렇기에 배열 인덱스가 key가 됩니다.
코드 분석
const iter = ["A", "B"].keys();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
JavaScript
복사
[실행 결과]
{value: 0, done: false}
{value: 1, done: false}
{value: undefined, done: true}
여기서 생성한 Array Iterator 오브젝트는 [key]형태로 value에 인덱스가 설정됩니다. for-of를 사용하면 좀 더 편하게 사용 가능하고 key만 설정되기에 따로 분할 할당을 할 필요는 없습니다.
values()
keys()와 동일하지만 value만 반환한다는 차이점이 있습니다. 그렇기에 배열의 엘리먼트 값이 value가 됩니다.
코드 분석
const iter = ["A", "B"].values();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
JavaScript
복사
[실행 결과]
{value: A, done: false}
{value: B, done: false}
{value: undefined, done: true}
이 역시 for-of고 전개가 가능하며 value만 할당되기에 분할할당을 해 줄 필요가 없습니다.
추가적으로 [Symbol.iterator]()를 사용하는것과 동일합니다.
console.log(Array.prototype.values === Array.prototype[Symbol.iterator]); // true
const iter = ["A", "B"][Symbol.iterator]();
for(const prop of iter){
console.log(prop);
}
JavaScript
복사
Array.prototype.values 와 Array.prototype[Symbol.iterator]는 동일합니다. 그렇기 때문에 values()대신 [Symbol.iterator]()를 사용해도 결과는 동일 합니다.
그리고, keys()도 동일하게 두 메소드 둘 다 값이 연동된다는 특징이 있습니다.
let list = ["A", "B"];
let iter = list.values();
list[0] = "연동";
console.log(iter.next());
console.log(iter.next());
JavaScript
복사
[실행 결과]
{value: 연동, done: false}
{value: B, done: false}
•
Array Iterator 오브젝트에서는 배열의 메모리 주소를 참조하기 때문에 값이 연동됩니다.