본문 바로가기
Web/Javascript

Data structures & Algorithms with JavaScripts 스터디 1~2일차

by sjwdev 2021. 4. 29.

210428 수요일 1일차

CH1 자바스크립트 프로그래밍 환경과 모델

1.2 자바스크립트 프로그래밍 기초

1.2.1 변수 선언과 초기화 ~ 1.2.5 함수

 

어제 오전부터 자바스크립트 자료구조와 알고리즘 공부를 다시 시작했고 깃헙에 레포지토리를 만들어 코드를 올렸다.

자바스크립트에서의 함수 예제 코드까지 따라 쳤는데 다른 언어들과 크게 다른 점은 없다. switch문에 테스트 대상 표현식에서 모든 데이터형을 사용할 수 있다는 점이 다른데 switch문 자체가 효율적이지는 않다.

자바스크립트에서 모든 함수의 파라미터는 값으로 전달되며 레퍼런스 전용 파라미터는 없다.(처음에는 확실히 이해가 가지 않은 부분) 하지만 배열처럼 객체 레퍼런스를 함수 파라미터로 사용할 때는 레퍼런스로 전달된다.

 

 

번역판이라서 그런 건지 어떤 의미인지 명확히 이해가 잘 되지 않아서 더 자세히 찾아보았다. 레퍼런스 전용 파라미터라든지 객체 레퍼런스가 어떤 의미인지 알아보았다.

 

자바스크립트는 기본 타입(number, string, boolean, null, undefined)을 제외한 모든 값이 '객체'이다. 따라서 배열, 함수, 정규표현식 등은 모두 객체로 표현된다. 이것은 객체의 모든 연산이 실제 값이 아닌 참조값으로 처리되기 때문이다.(여기서는 이 마지막 문장이 앞선 문장과 어떤 연관 관계가 있다는 것인지 이해되지 않았다.)

 

"기본 타입과 참조 타입의 경우는 함수 호출 방식도 다르다. 기본 타입은 값에 의한 호출 (Call By Value)방식으로 동작한다. 함수의 인자로 기본 타입이 넘겨질 경우, 함수의 매개변수로 복사된 값이 전달된다. 따라서, 함수 내부에서 매개변수를 이용해 값을 변경해도, 실제로 호출된 변수의 값이 위 예제처럼 100에서 변경되지 않는다.
반면 객체와 같은 참조 타입의 경우, 함수를 호출할 때 참조에 의한 호출( Call By Reference ) 방식으로 동작한다. 즉, 함수를 호출할 때, 객체의 참조값이 전달되고, 함수 내부에서 참조값을 이용해서 인자로 넘긴 실제 객체의 값을 200으로 변경할 수 있다."

https://velog.io/@smp2103/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%B0%B8%EC%A1%B0-%ED%83%80%EC%9E%85%EA%B0%9D%EC%B2%B4

 

자바스크립트 참조 타입(객체)

객체

velog.io

 

이처럼 call by value, call by reference 방식에 대해서는 많이 들었기 때문에 알고 있었다.

기본 타입의 값을 함수의 파라미터로 넘겨주었을 때 그 파라미터에 어떤 다른 값을 할당하더라도 그 함수 내에서만 영향이 있는 것이다. 하지만 객체와 같은 참조 타입의 파라미터를 함수로 넘겨 주었을 경우에는 함수 안에서 객체의 속성값들을 변경할 수 있고 함수가 종료되더라도 원래 데이터에 영향을 미친다. 

하지만 새로운 객체를 따로 생성해 값 자체를 새로 할당해서 변경할 수는 없다.(이 마지막 문장에 대해서 이해가 잘 되지 않는다.)

https://emflant.tistory.com/64

 

자바스크립트 function - Call by Value, Call by Reference

자바스크립트를 공부하면서 나의 생각이 유연하지 못하구나.. 라는 생각을 자주한다. function 또한 나에게 혼란을 많이 가져왔고 아직 어려운 존재임에는 틀림이 없다. 우선 간단한 일반적인 예

emflant.tistory.com

 

위 블로그에서 이에 대한 예제 코드를 아래와 같이 제공해주었다.

배열이나 객체형을 파라미터로 전달해주지만 속성값이 아니라 그 값 자체를 변경하려고 할 때는 어떻게 될까?

변경이 되지 않는다! (자바와 같은 결과이다.) 

 

즉, 자바스크립트는 항상 Call by value이며  객체나 배열 같은 Reference형 타입이 경우에도 실제로는 그 복사본을 만들어 value로 function에 파라미터를 전달하게 되는 것이다. 그래서 function으로 넘겨 받은 파라미터 안에 있는 속성값은 변경할 수 있어도 자기 자신의 값을 초기화해서 다시 새로운 값으로 할당할 수 없는 것이다. 

 

다시 정리해보면 함수로 보내지는 파라미터가 a라는 객체라면 파라미터는 그 a라는 객체의 참조값에 대한 복사본(Call by value에 의해)인 것이다. 

만약 함수 내에서 파라미터에 값 자체를 재할당하려고 하면 원래 객체 a를 바라보고 있던 것이 다른 참조값을 갖게 된다. 참조값 자체를 재할당하게 되는 것이다. 속성값에 접근해서 변경하는 경우에는 객체 a를 참조하고 있기 때문에 속성값 변경이 가능한 것이다. 이를 그림으로 표현하면 이와 같다.

 

a라는 변수가 가진 객체의 참조값과 객체가 가진 실제 값
함수에 b라는 파라미터로 전달되는 객체 a와 b가 가진 a의객체의 참조값(같은 객체를 가리키고 있는 것)
b에 "=" 연산자를 이용해 값을 할당하게 되면 기존에 가지고 있던 객체의 참조값이 사라지고 새로운 참조값에 1을 할당하게 된다.

 

 

 

210429 목요일

 

블로그 글을 작성하면서 1일차 복습을 완료하고 1.2.6 변수 범위부터 2일차 공부를 시작한다.

 

1.2 자바스크립트 프로그래밍 기초

1.2.6 변수 범위

변수의 범위는 어느 위치에서 변수를 접근할 수 있는지를 의미한다. 자바스크립트에서는 함수 범위로 정의된다. 함수 범위란 변수가 선언되고 정의된 함수 내에서 자유롭게 변수의 값에 접근할 수 있음을 뜻한다.

함수의 외부에서 변수를 선언하면 전역 범위를 갖는다. 어디서나 접근할 수 있다.

이러한 내용은 기존에 아는 내용과 같았다.

 

변수 정의에서 var 키워드를 사용하지 않으면 상황은 180도 달라진다. var 키워드 없이 변수를 선언하면(함수 내에 선언했다 할지라도) 그 변수는 자동으로 전역 범위를 갖는다.

 

함수에서 선언한 scope 변수에 var 키워드가 없다. 이렇게 되면 메인 프로그램의 전역 변수 scope의 값을 변경하게 된다. 따라서 이런 혼동을 피하기 위해서 항상 var 키워드를 사용해 변수를 정의해야 한다.

 

자바스크립트에서는 함수 범위를 갖는다고 했다. 즉, 자바스크립트는 블록 범위를 지원하지 않음을 의미한다.(이것은 c++, 자바 등에서 접해보았다.)

그런데 ES6(ES2015)에서  let이 지원되면서 블록 범위의 변수가 가능해졌다. 

 

"let은 변수가 선언된 블록, 구문 또는 표현식 내에서만 유효한 변수를 선언한다. 이는 var 키워드가 블록 범위를 무시하고 전역 변수나 함수 지역 변수로 선언되는 것과 다른 점이다."

developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/let

 

let - JavaScript | MDN

let let 구문은 블록 유효 범위를 갖는 지역 변수를 선언하며, 선언과 동시에 임의의 값으로 초기화할 수도 있다. let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]]; var1, var2, …, varN 변수명. 유효한

developer.mozilla.org

 

또한 let은 var와 달리 재할당 문제도 해결해준다.

 

그 밖에도 var은 호이스팅이 지원된다. 호이스팅이란

변수의 정의가 그 범위에 따라 선언과 할당으로 분리되는 것을 뜻한다.

변수가 함수 내에서 정의된 경우는 선언이 함수의 최상위로, 함수 바깥에서 정의된 경우는 선언이 전역 컨텍스트의 최상위로 변경된다는 것이다.

코드의 순서와 상관 없이 변수와 함수선언문에 대한 메모리를 먼저 확보하는 것이다. 하지만 변수가 선언되어 있을 뿐 값이 할당되지는 않은 것이므로 undefined를 출력한다. 값은 할당되지 않기 때문에 아래와 같이 함수 선언문은 호이스팅이 일어나지만 함수 표현식(값으로 할당되는)은 호이스팅이 일어나지 않는 것을 볼 수 있다.

 

 

즉, 명확하고 심플하게 말하면 JS에서 var는 function scope이지만 es6에서 추가된 const, let은 block scope이다. 

 

 

1.2.7 재귀

 

JS는 재귀 함수 호출을 지원한다.

function factorial(number) {
	if(number == 1) {
    	return number;
    }
    else {
    	return number * factorial(number-1);
    }
}

console.log(factorial(5));

 

동작 순서는 다음과 같다.

5 * factorial(4)
5 * 4 * factorial(3)
5 * 4 * 3 * factorial(2)
5 * 4 * 3 * 2 * factorial(1)
5 * 4 * 3 * 2 * 1
5 * 4 * 3 * 2
5 * 4 * 6
5 * 24
120

JS가 처리할 수 없을 정도로 깊은 재귀 호출을 요하는 알고리즘은 반복 기법을 이용하자.

 

 

1.3 객체와 객체 지향 프로그래밍

이 책에서 설명하는 자료구조는 객체로 구현한다. 

객체는 생성자 함수를 이용해 정의한다. 생성자 함수는 객체의 프로퍼티, 함수 선언, 함수 정의를 포함한다. 다음은 Checking 객체의 생성자 함수 예제이다.

 

function Checking(amount) {
    this.balance = amount; // 프로퍼티
    this.deposit = deposit; // 함수
    this.withdraw = withdraw // 함수
    this.toString = toString // 함수
}

this 키워드는 각 함수와 프로퍼티를 객체 인스턴스로 연결하는 역할이다. 

 

function deposit(amount) {
	this.balance += amount;
}

function withdraw(amount) {
	if(amount <= this.balance) {
    	this.balance -= amount;
    }
    if(amount > this.balance) {
    	console.log("Insufficient funds");
    }
}

function toString(){
	return "Balance: " + this.balance;
}

이번에도 어떤 객체의 balance 프로퍼티를 참조하는 것인지 알 수 있도록 this 키워드를 사용했다.

 

윗 코드에 이어 아래와 같은 코드로 객체를 사용하게 된다.

let account = new Checking(500);
account.deposit(1000);
console.log(account.toString()); // Balance : 1500
account.withdraw(750);
console.log(account.toString()); // Balance : 750
account.withdraw(800); // "Insufficient funds" 출력
console.log(account.toString()); // Balance : 750

 

 

CH2 배열

프로그래밍에서 가장 자주 사용하는 자료구조는 배열이다. 배열은 내장 기능이므로 매우 효율적이며 데이터를 쉽게 저장할 수 있다. 자바스크립트에서는 어떻게 동작하고 언제 사용해야 할지 알아본다.

 

2.1 자바스크립트 배열 정의

배열은 정수 인덱스(오프셋)를 이용해 각 요소에 접근할 수 있게 순차적으로 요소를 저장한 집합체다. JS는 다른 언어들과 차이점이 존재한다.

 

JS의 배열은 특화된 자바스크립트 객체며 정수 인덱스(객체의 프로퍼티 이름 역할)로 객체 내 데이터 오프셋을 표현한다. 정수를 인덱스로 사용하면 객체 요구사항에 맞게 내부적으로 정수를 문자열로 변환한다.

 

엄밀히 말해 JS 배열은 JS 객체지만 내부적으로는 특화된 객체이므로 배열로 취급한다.(Array : 미리 정의된 프로퍼티와 함수 이용 가능)

 

2.2 배열 사용하기

2.2.1 배열 만들기

let numbers = []; // or [1,2,3,4,5];
console.log(numbers.length); // 0

or

let numbers = new Array(); // new Array(1,2,3,4,5);
console.log(numbers.length); // 0

or

let numbers = new Array(10); // 만약 요소 한개로 만드려면 new Array([10]);
console.log(numbers.length); // 10

 

다른 언어와 달리 한 배열이 다양한 종류의 요소를 포함할 수 있다.(대부분의 스크립트 언어가 그렇다.)

let objects = [1, "Joe", true, null];

 

Array.isArray()로 특정 객체가 배열인지 여부를 확인할 수 있다.

let numbers = 3;
let arr = [ 7, 4, 1776 ];
console.log(Array.isArray(number)); // false
console.log(Array.isArray(arr)); // true

 

자바스크립트 전문가는 Array 생성자보다 [] 사용이 더 효율적이라고 추천한다.

 

 

2.2.2 배열 요소 접근하고 값 고치기

for문을 이용해 모든 배열 요소에 순차적으로 접근한다. for 문에서는 숫자 대신 length 프로퍼티를 이용한다. JS 배열은 객체이므로 배열을 만든 다음에 언제든지 크기가 늘어날 수 있다. length 프로퍼티를 사용하면 현재 배열의 실제 요소 수가 정확히 반환되므로 언제나 배열의 모든 요소에 접근할 수 있다.

 

2.2.3 문자열로 배열 만들기

문자열에 split() 함수를 호출하면 배열이 생성된다. 이 함수는 문자열을 특정 구분자(공백 등)로 분리한 다음 분리된 문자열을 포함하는 배열을 반환한다.

 

 

 

2.2.4 배열 전체에 적용되는 기능

배열을 다른 배열로 할당할 수 있다.

let nums = [];
for (let i = 0; i < 10; ++i) {
    nums[i] = i + 1;
}
let samenums = nums;

 

 

배열을 다른 배열로 할당하는 것은 할당된 배열의 레퍼런스를 할당하는 것이다. 따라서 원래 배열을 바꾸게 되면 할당된 배열도 바뀐다.

let nums = [];
for (let i = 0; i < 10; ++i) {
    nums[i] = i + 1;
}
let samenums = nums;
nums[0] = 400;
console.log(samenums[0]); // 400

이와 같은 동작을 얕은 복사(shallow copy)라고 한다. 

 

 

깊은 복사(deep copy), 원래 배열 요소를 새로운 배열 요소로 복사하는 기능이 필요할 때가 있다.

function copy(arr1, arr2) {
    for (let i = 0; i < arr1.length; ++i) {
        arr2[i] = arr1[i];
    }
}

let nums = [];
for (let i = 0; i < 10; ++i) {
    nums[i] = i + 1;
}
let samenums = [];
copy(nums, samenums);
nums[0] = 400;
console.log(samenums[0]); // 1

 

그 외에는 print()도 배열 전체 적용 기능이다.

 

 

2.3 접근자 함수

배열 요소에 접근할 수 있는 다양한 함수 제공. 이들을 접근자 함수(accessor function)이라 하며 특정값을 포함하는 결과 배열을 반환한다.

 

2.3.1 값 검색하기

가장 자주 사용되는 함수 중 하나가 indexOf()이다. 인자로 제공된 값이 배열에 존재하는지 알려준다. 인자로 제공된 값이 배열에 있으면 인덱스 위치를 반환하고 없으면 -1을 반환한다. 

indexOf()로 찾으려는 값이 여러 개면 첫 번째로 발견한 인자의 인덱스를 반환한다. lastIndexOf()는 배열에서 일치하는 값 중 마지막 인자의 위치를 찾고 없으면 -1을 반환한다.

 

 

2.3.2 배열을 문자열로 표현하기

join(), toString() 함수는 배열을 문자열 형식으로 반환하는 함수이다. 두 함수 모두 배열의 요소를 콤마로 구분하는 문자열을 반환한다.

// join, toString : 배열을 문자열 형식으로 표현
let names = ["D", "C", "R", "L", "J"];

let namestr = names.join();
console.log(namestr);

namestr = names.toString();
console.log(namestr);

 

console.log()의 인자로 배열의 이름을 제공하면 자동으로 배열의 toString(0이 호출된다.

 

 

2.3.3 기존 배열을 이용해 새 배열 만들기 

concat(), splice()는 기존 배열을 이용해 새 배열을 만드는 함수다. concat()은 두 개 이상의 배열을 합쳐 새 배열을 만들고, splice()는 기존 배열의 서브셋으로 새 배열을 만든다.

 

기존의 배열에 concat()을 호출하면 인자로 또 다른 기존 배열을 제공한다. 그러면 인자로 제공된 배열이 원래 concat() 함수를 호출한 배열 뒤로 추가된다.

 

처음엔 dmpDept가 뒤에 붙고 그 다음은 앞에 붙는다.

 

 

splice()는 기존 배열의 요소로 새 배열을 만든다. splice()는 사용할 첫 요소의 위치, 기존 배열에서 사용할 요소의 수를 인자로 받는다.

dmpDept의 결과물을 보면 L부터 시작되고 있는데 기존 배열의 요소의 위치를 3으로 했기 때문이다. itDiv[3]은 실제로 네 번째 요소를 가리킨다. 그리고 두 번째 인자로 3을 주었기 때문에 첫 요소부터 3개의 요소를 뽑아서 새로운 배열을 만들어준 것이다.

그리고 해당 요소들이 제거된 기존 배열 itDiv을 보면 원하는 요소를 제거하는 용도로도 사용할 수 있음을 알 수 있다.

댓글