카테고리 없음

[ JavaScript ] 배열과 유사배열

람연 2021. 6. 9. 19:24

자바스크립트에는 배열과 유사배열이 있다.

이 둘의 차이와 목적에 대해 알고 사용하는게 좋을것 같아 정리한다.


배열(Array)

자바스크립트에서 배열이란 관련 있는 데이터를 하나의 변수에 할당해 관리하기 위해 사용하는 데이터 타입이다.

자바스크립트는 명시적 타입이 없기때문에, 하나의 배열은 여러 자료형을 가질 수 있는 특징이 있다.

배열을 사용하면 여러 데이터를 관련성 있게 관리할 수 있기 때문에 생산성 및 코드 가독성이 높아지고 이는 유지보수 비용의 감소로 이어진다. 이 외에도, 배열에서 기본으로 제공하는 함수를 사용할 수 있는 점, 데이터에 순차적으로 접근이 가능하다는 점 등을 비롯해 장점이 아주 많다.

 

JS(JavaScript)에서 배열을 사용하는 방법은 아래와 같다.

// array 배열 선언
// 자바스크립트 특성 상 배열의 명시적 타입이 없기 때문에, 여러 자료형을 포함할 수 있다.
let array = [ 'a', 100, { test:'test' } ]

for( let index=0; index<array.length; index++ ){
	console.log( `array[ ${index} ]`, array[ index ] )
	console.log( `typeof( array[ ${index} ] )`, typeof( array[ index ] ) )
}

배열의 요소 및 데이터 타입

 

유사배열(Array-Like Object)

유사배열(Array-Like Object)이란 이름 그대로 배열과 유사한 객체를 말한다.

유사배열은 함수에서 처리 결과로 배열을 반환하고 싶을때 또는, Array에서 기본으로 내포되어있는 기능을 제공하고 싶지 않거나 Array에 내포되어있지 않은 기능을 제공하고싶을때 사용한다.

 

유사배열의 한 예로 아래를 들 수 있다.

선택자를 이용해 네이버 뉴스기사를 영역별로 선택해 NodeList를 반환받았다.

해당 NodeList는 10개의 length를 가지고있으며, 언뜻 보기에 배열같아 보인다. 하지만 배열이 아니다.

array의 데이터 타입을 출력해보면 일반 배열과 같이 object가 출력된다.

유사배열 데이터 타입

하지만 배열은 아니라고한다.

역시 배열은 아니라고한다.

유사배열이 뭔지 대충은 알겠으나, 어떻게 써먹는걸까? 

 

JS(JavaScript)에서 유사배열을 사용하기 위해서는 몇 가지 조건을 성립해야한다.

1. 숫자 형태의 indexing이 가능할것

2. length 속성을 포함할것

이는 유사배열을 배열처럼 사용하기 위한 최소한의 조건이다.

위의 규칙을 준수해, 아래와 같이 유사배열을 선언해 사용할 수 있다.

// arrayLikeObject 유사배열 선언
//  -> 1. 숫자 형태의 indexing이 가능할것
//     2. length 속성을 포함할것
let arrayLikeObject = {
	0 : '1 Row',
	1 : '2 Row',
	2 : '3 Row',
	length : 3,
	splice : function() {}
}

// 유사배열을 for문을통해 배열처럼 사용한 케이스
for(var index=0; index<arrayLikeObject.length; index++) {
	console.log( `arrayLikeObject[ ${index} ] ` + arrayLikeObject[ index ] )
}

유사배열의 요소

this, call, apply, bind

앞서 유사배열은 배열이 아니라고했다. 따라서, 유사배열은 배열에 내포되어 있는 함수들을 사용할 수 없다.

이때 배열에 내포되어있는 기능들을 유사배열에서 사용하기 위해 call, apply, bind을 사용한다.

apply와 call은 함수를 호출하는 방법 중 하나로, 다른 객체에 내포되어있는 함수를 내것처럼 사용할 수 있게 해준다.

 

this

JS 내부적으로 함수가 만들어 졌을 때, this라 불리는 키워드가 생성된다. this는 기본 값으로 window object를 가리키며, 함수가 동작하는 곳의 오브젝트와 연결해주는 역할을 한다. 

this는 기본 값으로 window object를 가리키지만 항상 window object를 가리키는건 아니다.

this는 아래와 같이, 함수가 어떻게 호출 되었는지에 따라 그 대상을 결정한다.

// this를 출력하는 함수 testFunction
let testFunction = function ( ) {
	console.log( this )
}

// 함수 testFunction를 갖는 객체
let obj = {
	testFunction: testFunction
}

// testFunction
obj.testFunction()

// window
testFunction() 

이러한 형식을 묵시적 바인딩(implicit binding) 이라 한다.

 

call

call은 첫 번째 인자로 this를 대채할 대상 그리고 파라미터를 리스트형태로 전달한다.

// a, b, c 합계를 구하는 함수 testFunction
let testFunction = function ( a, b, c ) {
	return a + b + c
}

testFunction.call( null, 4, 7, 9 )

위에서 정리했듯 this는 기본적으로 window를 가리키는데, call의 첫 번째 인자는 그 this를 바꿀 수 있다.

아래 예시를 보면 첫 test는 'one'를 출력하지만, 두번째 test는 'two'를 출력한다. 이처럼 다른 객체(object1)의 함수를 마치 내것(object2)마냥 사용할 수 있다.

let object1 = {
	string: 'one',
    // this가 가르키는 string 변수를 출력한다.
	test: function() {
		console.log( this.string )
	}
}

let object2 = {
	string: 'two'
}

// 'one' 출력
object1.test()
// 'two' 출력
object1.test.call( object2 )

 

apply

apply는 첫 번째 인자로 this를 대채할 대상 그리고 파라미터를 배열로 묶어 전달한다.

call과 내부적으로 동작하는 방식은 같으나, 인자를 전달하는 방식에서 차이가 있다.

이는, 상황에 맞게 편리한것을 사용하면 되는데 가령 반환된 배열 자체를 인자로 전달하고 싶을때 등 사용한다.

call과 마찬가지로 첫 번째 인자로 this를 대체할 대상을 받는다.

// a, b, c 합계를 구하는 함수 testFunction
let testFunction = function ( a, b, c ) {
	return a + b + c
}

testFunction.apply( null, [ 4, 7, 9 ] )

 

이제 call과 apply를 이용해 배열에 내포되어있는 기능을 유사배열에서 사용할 수 있다.

Array 배열 객체에 내포되어있는 forEach 함수를 가져와 사용하는 것.

 

아래의 코드는 유사배열을 Array에서 제공하는 forEach 함수를 사용하는 방법을 보여준다.

여기서 Array 객체의 forEach를 사용했을때는 오류가 발생하지 않지만, 유사배열 arrayLikeObject의 forEach문을 사용하면 정의되지 않은 forEach문을 사용해 오류가 발생하는것을 확인할 수 있다.

let arrayLikeObject = {
	0 : '1 Row',
	1 : '2 Row',
	2 : '3 Row',
	length : 3,
	splice : function() {}
}

// row 1 Row
// row 2 Row
// row 3 Row
Array.prototype.forEach.call( arrayLikeObject, row => {
	console.log( 'row', row )
} )

// Error
//  - arrayLikeObject.forEach is not a function
arrayLikeObject.forEach( row => {
	console.log( 'row', row )
} )

 

bind

bind는 call, apply와 다르게 실행을 하지 않고 가리키는 this만 바꾼다.

아래 예에서 test에 object1이 가지고있는 test 함수를 object2로 가르켜 반환한 것.

따라서, test를 실행하면 two가 console에 찍히게 된다.

let object1 = {
	string: 'one',
	test: function() {
		console.log( this.string )
	}
}

let object2 = {
	string: 'two'
}

let test = object1.test.bind( object2 )

// two
//  - test 실행 후, console.log 실행
test()

bind으로 반환받은 객체는 ECMAScript2015(ES6)에서 특이 함수 객체로 분류되며 바인딩한 함수를 호출하면 래핑된 함수가 호출된다.

바인딩한 함수는 내부적으로, call을 통해 호출되게 된다. 따라서, bind한 원본이 수정되는 경우 영향을 받을 수 있다.

 

유사배열을 사용해야하는 이유

만일 유사배열을 사용하지 않고, 기존 배열 객체를 이용한다고 가정한다.

그렇다면 아마 제공하려는 함수를 배열 객체에 추가해서 사용을 할텐데, 이는 배열 객체에 직접 추가되기 때문에 관련이 없는 다른 배열 객체에서도 마치 사용 가능한 함수처럼 표시가 될 것 이고 오작동을 유발할 수 있다.

또한, JS 버전업으로 인해 추가된 메소드 명이 prototype으로 추가한 메소드 명과 같은경우가 생길 수 있다.

이게 유사배열을 사용해야 하는 이유이다.

// 유사배열을 사용하지 않는 경우
Array.prototype.명 = function ( ) {
	...
}