0. JS 의 배열은 희소배열이기 때문에 일반적으로 자료구조에서 의미하는 밀집배열이 아니다.
자바스크립트와 다르게 일반적인 프로그래밍 언어에서는 배열을 선언할 때 배열의 크기를 알려준다.
아래와 같이 배열을 선언했다면, 운영체제는 메모리에 숫자 10개가 들어갈 수 있는 연속된 빈 공간을 찾아서 순서대로 1,2,3,4,5를 할당한다. 할당하지 않은 나머지 부분은 의미없는 쓰레기 값이 저장되어있다
int arr[10] ={1,2,3,4,5}
운영체제는 배열의 시작주소 숫자 1이 들어간 주소만 기억한다. 그래서 배열의 세번째 원소에 접근하고 싶을 때 arr[2] 인덱스로 한번에 접근가능하다. 운영체제는 배열의 시작주소를 알고있기 때문에, 배열의 시작주소인 1에서부터 두번째 떨어진 주소에서 데이터를 가져온다. 배열의 인덱스 참조는 길이에 상관없이 한번에 가져오기 때문에 O(1)의 성능을 가진다. 따라서, 읽기/쓰기 참조에서 좋은 성능을 보인다.
위와 같은 일반적인 배열을 밀집 배열이라고 한다.
1. 밀집배열(dense array)
자료구조에서 일반적으로 말하는 배열은 동일한 크기의 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조를 말한다.
즉, 배열의 요소는 하나의 데이터 타입으로 통일되어 있으며 서로 연속적으로 인접해 있다. 이러한 배열은 밀집 배열(dense array)이라 한다.
예제) int sample[5] = [11,22,33,44,55];
메모리에 들어가는 데이터 타입이 한 가지(정수를 나타내는 int는 4byte)로 통일되어있으니 모든 배열의 요소는 4바이트씩 할당되어 빈틈없이 연속적으로 나열된다.
배열의 요소들이 순서를 가지며, 각각의 요소들이 크기가 같으니 어떤 특정한 인덱스에 접근을 하려면 단 한번의 연산으로 접근이 가능하다.
접근 대상 요소의 메모리 주소 = 배열의 시작 메모리 주소 + 인덱스 * 요소의 바이트 수
인덱스가 0인 요소의 메모리 주소 : 1000 + 0 * 4 = 1000
인덱스가 1인 요소의 메모리 주소 : 1000 + 1 * 4 = 1004
인덱스가 2인 요소의 메모리 주소 : 1000 + 2 * 4 = 1008
단 한 번의 연산을 하면 되니 데이터 접근에 대한 시간 복잡도는 O(1)이며, 매우 효율적이고 빠르다
하지만 자바스크립트에서는, 배열의 크기를 처음에 지정하지 않는다. 그리고 자바스크립트의 배열은 메모리를 불연속적으로 할당한다. 불연속적으로 할당된 메모리는 내부적으로 연결해서 사용자에게는 배열인것처럼 보인다.
2. 희소배열(sparse array)
자바스크립트의 배열은 희소 배열이다. 하나 이상의 자료형을 허락하는 배열이어서 한 자료가 차지하는 메모리 공간이 불규칙 할 수 있고(배열의 요소를 위한 각각의 메모리 공간은 동일한 크기를 갖지 않아도 되며) 그때문에 연속적으로 이어져 있지 않을 수도 있다. 자바스크립트의 배열은 일반적인 배열의 동작을 흉내 낸 특수한 객체이다.
JS 배열은 희소 배열이기때문에 배열 요소를 인덱스로 접근하려는 경우 밀집 배열인 다른 언어에 비해 상대적으로 느리다는 단점이 있다. (메모리 공간이 상이할 수 있고, 연속적이지 않기 때문이다.)
희소배열에서는 특정한 데이터를 검색하려면 처음부터 끝까지 모두 검색해야 해서 데이터 검색에 대한 시간 복잡도는 O(n) 이 걸린다.
배열 요소 추가와 삭제 또한 마찬가지이다.
추가와 삭제되는 위치 뒤에 있는 요소들을 이동해야 하니 추가 및 삭제의 대한 시간 복잡도 최악의 경우는 O(n)이다
1. 배열의 메서드는 어떤 종류가 있나요?
원본 배열을 직접 변경하는 메서드 | 원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드 |
1. push(): 배열의 끝에 하나 이상의 요소를 추가합니다. 2. pop(): 배열의 마지막 요소를 제거하고 반환합니다. 3. shift(): 배열의 첫 번째 요소를 제거하고 반환합니다. 4. unshift(): 배열의 맨 앞에 하나 이상의 요소를 추가합니다. 5. splice(): 배열에서 요소를 추가하거나 제거합니다. |
1. concat(): 주어진 배열이나 값들을 현재 배열에 병합하여 새로운 배열을 반환합니다. 2. slice(): 지정한 시작 인덱스부터 끝 인덱스까지의 요소들로 구성된 새로운 배열을 반환합니다. 3. filter(): 주어진 함수의 조건을 만족하는 요소들로 구성된 새로운 배열을 반환합니다. 4. map(): 주어진 함수를 적용하여 현재 배열의 각 요소를 변환한 새로운 배열을 반환합니다. 5. sort(): 배열을 정렬한 후 정렬된 배열을 반환합니다. |
var originalArray = [1, 2, 3, 4, 5];
// 원본 배열을 직접 변경하는 메서드
originalArray.push(6);
console.log(originalArray); // 출력: [1, 2, 3, 4, 5, 6]
originalArray.pop();
console.log(originalArray); // 출력: [1, 2, 3, 4, 5]
originalArray.splice(2, 1);
console.log(originalArray); // 출력: [1, 2, 4, 5]
// 새로운 배열을 생성하여 반환하는 메서드
var newArray = originalArray.concat([6, 7, 8]);
console.log(newArray); // 출력: [1, 2, 4, 5, 6, 7, 8]
var slicedArray = originalArray.slice(1, 3);
console.log(slicedArray); // 출력: [2, 4]
var filteredArray = originalArray.filter(num => num > 3);
console.log(filteredArray); // 출력: [4, 5]
var mappedArray = originalArray.map(num => num * 2);
console.log(mappedArray); // 출력: [2, 4, 8, 10]
var sortedArray = originalArray.sort();
console.log(sortedArray); // 출력: [1,
+) Array.length
Array.length를 배열의 길이라고 알고있는 사람들이 많다. 배열의 길이보다는 해당 Array 요소의 마지막 번째 index에서 1을 더한것이라고 생각하는게 좋다.
아니 정확히 말하면, 희소배열에서는 배열의 요소 중 가장 큰 인덱스에 1을 더한 것 과 같다. 아래 예제와 같은 문제상황이 발생할 수 있기 때문에 마지막 번째 index에서 1을 더한것이라고 생각하는게 좋다.
만약 실제 배열의 길이보다 length 프로퍼티에 작은 값을 할당하게 되면, 배열의 길이가 줄어들게 됩니다.
const arr = ["apple", "banana", "orange"];
arr.length = 1;
console.log(arr.length, arr) // 1 ["apple"]
만약 실제 배열의 길이보다 큰 값을 할당하게 되면, length 프로퍼티의 값은 변경되지만, 실제 배열에는 아무런 변화가 없습니다. 값 없이 비어 있는 요소를 위한 메모리 공간을 확보하지도 않을 뿐더러 빈 요소를 생성하지도 않습니다.
const arr = ["apple", "banana", "orange"];
arr.length = 5;
console.log(arr.length, arr) // 5 ["apple", "banana", "orange", empty * 2]
희소 배열에서 길이는 가장 높은 인덱스보다 크지만 실제 요소 수를 나타내지는 않습니다.
var animals = ['cat', 'dog', , 'monkey']; // animals 배열은 희소한 배열입니다.
console.log(animals.length); // 4를 출력하지만, 실젝 요수 갯수는 3개입니다.
var words = ['hello'];
words[6] = 'welcome'; //가장 큰 인덱스는 6이고, words 배열은 희소합니다.
console.log(words.length); //7을 출력합니다, 가장 큰 인덱스가 6이기 때문입니다.
배열 길이 수정, 삭제로 요소 제거, [newIndex]로 요소 추가는 희소 배열을 만들어 잠재적인 문제의 원인이됩니다.
결과적으로 길이 값이 일치하지 않습니다.
JavaScript는 더 안전한 대안을 제공한다. 배열 끝에 요소를 추가하려면 Array.prototype.push()를 사용하고 가장 최신의 요소를 제거할려면 pop()을 사용하세요. 처음에 요소를 삽입하려면 unshift()를 사용하고 첫 번째 항목을 제거하려면 shift()를 사용하자. 더 복잡한 삽입, 삭제 또는 교체의 경우 splice() 함수를 쓰자.
2. 고차 함수에 대해서 아시나요?
고차 함수는 함수를 인자로 받거나 or 함수를 반환함으로써 작동 하는 함수를 말합니다. 간단히 말하자면, 고차 함수는 함수를 인자로 받거나 함수를 출력(output)으로 반환하는(return) 함수를 말합니다.
대부분의 고차 함수들은 파라미터로 콜백 함수를 받아 사용되기 때문에 원본 배열을 바탕으로 하는 새로운 결과값을 창조하는데 사용된다
Array.prototype.sort (원본 배열을 변경한다 - 부수효과 o)
Array.prototype.forEach
Array.prototype.map
Array.prototype.filter
Array.prototype.reduce
Array.prototype.some
Array.prototype.every
Array.prototype.find
// 다른 함수를 반환하는 코드 예제
function createMultiplier(multiplier) {
return function (number) {
return number * multiplier;
};
}
// createMultiplier 함수는 multiplier 값을 받아와서 새로운 함수를 반환합니다.
const double = createMultiplier(2);
console.log(double(5)); // 출력: 10
const triple = createMultiplier(3);
console.log(triple(5)); // 출력: 15
위의 예시에서 createMultiplier 함수는 multiplier 값을 인자로 받아와서 새로운 함수를 반환합니다. 반환된 함수는 number 값을 인자로 받아와서 number와 multiplier를 곱한 값을 반환합니다.
그리고 createMultiplier 함수를 호출하여 double 변수에 반환된 함수를 할당합니다. 이후 double(5)를 호출하면 내부의 함수가 실행되어 5 * 2의 결과인 10을 반환합니다.
마찬가지로 createMultiplier(3)을 호출하여 triple 변수에 반환된 함수를 할당하고, triple(5)를 호출하면 5 * 3의 결과인 15를 반환합니다.
이처럼 고차 함수에서 함수를 반환함으로써, 반환된 함수는 클로저(Closure)를 형성하여 반환된 함수가 생성될 때의 환경을 기억합니다. 이를 통해 함수의 상태를 보존하거나 다른 함수에 대한 커스텀 버전을 생성하는 등의 유연한 동작을 구현할 수 있습니다.
3. forEach 메서드와 map 메서드의 차이점?
forEach 와 map 메서드는 둘 다 배열의 각 요소에 대해 반복 작업을 수행하는데 사용되는 고차함수이다.
var numbers = [1, 2, 3, 4, 5];
numbers.forEach(function(num) {
console.log(num);
});
// 출력:
// 1
// 2
// 3
// 4
// 5
var numbers = [1, 2, 3, 4, 5];
var doubledNumbers = numbers.map(function(num) {
return num * 2;
});
console.log(doubledNumbers); // 출력: [2, 4, 6, 8, 10]
1. 반환값
- forEach(): forEach() 메서드는 반환 값이 없습니다. 원본 배열을 순회하며 작업을 수행하기만 하고, 새로운 배열을 반환하지 않는다. 즉, forEach() 메서드는 undefined를 반환한다. 따라서, 변수에 할당하거나 다르 곳에 사용하려고 하면 undefined가 된다.
var numbers = [1, 2, 3, 4, 5];
var result = numbers.forEach(function(num) {
console.log(num);
});
console.log(result); // 출력: undefined
- map(): map() 메서드는 새로운 배열을 반환한다. 원본 배열의 각 요소에 대해 콜백 함수를 실행하고, 각 콜백의 반환 값을 사용하여 새로운 배열을 생성한다.
2. 사용 목적
- forEach() 메서드는 배열을 순회하며 각 요소에 대한 작업을 수행하는 데 사용된다. 주로 반복 작업을 수행하고자 할 때 유용하다.
- map() 메서드는 각 요소를 변환하여 새로운 배열을 생성하는 데 사용된다. 원본 배열을 변형하지 않고 새로운 배열을 생성할 때 유용하다.
참고자료
https://github.com/junh0328/prepare_frontend_interview/blob/main/js.md#%EB%B0%B0%EC%97%B4
인프런 인강
그림으로 쉽게 배우는 자료구조와 알고리즘(기본편)
https://xpectation.tistory.com/200
https://ablue-1.tistory.com/102
https://cpro95.tistory.com/340
'프론트엔드 개발 > JavaScript' 카테고리의 다른 글
자바스크립트) 함수 (0) | 2023.06.01 |
---|---|
자바스크립트) 객체 (0) | 2023.05.28 |
자바스크립트) 자주 쓰이는 메서드 총정리 (0) | 2023.05.17 |
자바스크립트) 배열 원소 누적 합 구하기 (0) | 2023.02.28 |
자바스크립트) 원시타입 VS 참조타입 / 얕은복사 VS 깊은 복사 (0) | 2023.02.03 |