Preview
오늘은 내일 있을 자바스크립트 수업을 위해서 12장 함수 ~ 15장 let, const와 블록 레벨 스코프에 해당하는 교재의 내용을 읽어보고 개인적으로 궁금한 내용들을 찾아보며 공부해보았다.
우선 강사님이 함수 부분이 가장 중요하다고 하셔서 이 부분을 읽을때 좀 더 집중해서 읽어보았다.
다시 한 번 읽어보고 싶은 내용에 대해서 다시 한 번 정리를 해두었다.
Review
이번 수업에서 가장 중요한 개념은 중첩함수와 콜백함수의 개념이였다.
함수(fuction)
자바스크립트에서 함수는 객체이다.
자바스크립트의 함수객체는 다른 일반 객체와는 다르다. 일반 객체는 호출할 수 없지만, 함수 객체는 호출할 수 있다.
자바스크립트에서 함수는 객체이기 때문에 함수 객체만이 가질 수 있는 고유한 속성(property)를 가지고 있다.
아래 첨부한 노트의 첫 번째를 참고하자. 첫 번째 노트의 우측 상단부를 보면, 함수 객체가 가지는 고유속성 5가지(arguments, caller, length, name, prototype)가 있는 것을 알 수 있다.순차적으로 언급한 순서로 함수 객체의 고유 속성에 대해서 알아보도록 하자.
arguments : 함수의 인수로 iterable한 유사배열 객체이다. 함수 내부에서 사용되는 지역변수이며, 가변 인자((넘겨받을 인수의 갯수가 정해지지 않았을때 함수 내부에서 사용될 수 있다)
과거에 가변 인자의 사용으로 인해 argument라는 속성을 사용했지만, ES6에서 rest parameter의 개념이 도입되었다. (…args)
caller : ECMAScript 사양에 포함되지 않은 비표준 property이다.
length : 함수로 전달된 매개변수(parameter)의 갯수
prototype : proto type object에 접근이 가능하게 해준다.
함수는 객체 타입의 값이다.
함수 선언문과 함수 리터럴
함수 리터럴에는 표현식이 아닌 문(함수 선언문)과 표현식인 문(함수 리터럴 표현식)으로 구분
단독으로 사용된 함수 리터럴은 함수 선언문으로 해석되며, 그룹 연산자()내에 있는 함수 리터럴은 함수 리터럴 표현식으로 해석된다.
함수 리터럴에서 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자이다. (함수를 가리키는 식별자가 없다. 즉 외부에서 함수 선언문을 호출할 수 없다)
함수 선언문에서 함수 이름은 자바스크립트 엔진이 암묵적으로 같은 이름으로 식별자로서 생성한다. 따라서 외부에서 함수 선언문의 함수 이름을 통해서 호출이 가능하다.
함수는 함수의 이름으로 호출되는 것이 아니라 함수 객체를 가르키는 식별자로 호출되는 것이다.
자바스크립트의 함수는 일급 객체(first-class object)
- 무명의 리터럴로 생성이 가능하다. (선언과정 없이 정의만으로 함수를 생성하는 것이 가능하다는 말 (정의는 Runtime 과정에서 발생한다))
- 변수나 객체/배열 등의 자료구조에 담을 수 있다.
- 함수의 매개 변수로 전달할 수 있다.
- 함수의 반환값으로도 사용할 수 있다.
함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있다.
함수 리터럴의 함수 이름은 생략할 수 있다. (익명함수)
함수 객체 생성
함수 선언문을 해석해서 함수 객체를 생성한다.
(첨부한 첫 번째 노트의 우측 상단을 참고)
구체적으로 어떤 흐름으로 자바스크립트에서 함수객체를 만들어내는지 알아보도록 하자.
우선, 자바스크립트가 프로토타입 기반의 언어라는 것을 이해해야 한다. 프로토타입 기반이라는 말은객체를 만들때 자신을 만드는데 사용된 원형인 프로토 타입 객체를 이용하여 객체를 만든다
는 말이다.
다른 언어에는 클래스 개념이 있기 때문에 클래스라는 하나의 틀을 만들어서 인스턴스 변수의 생성을 통해 객체 인스턴스를 만들어낸다.
하지만 자바스크립트에는 클래스가 없다. 따라서 기본적으로 상속기능도 없다고 할 수 있다. 그냥 클래스와 상속을 흉내낸 것이다.그럼 ES6의 표준에서 Class 키워드를 사용한 문법이 추가되었는데 클래스 기반 언어로 변화된 것일까?
아니다! 여전히 프로토 타입 기반의 언어이며, 문법만 새롭게 추가된 것이다.
함수 객체 생성에 대해서 알아보기 전에 함수의 정의에 대해서 우선 알아보자.
함수가 정의되면 어떤 일이 일어날까?
아래 첨부한 노트의 첫 번째 페이지 하단의 내용을 같이 참조하도록 하자.
함수를 정의하게 되면 우선 기본적으로 함수의 내부에는prototype
이라는 함수객체 고유의 속성을 갖게 된다. 동시에 이 prototype 속성은 Prototype Object(프로토 타입 객체)를 가르키게 되고, Prototype Object 내의 생성자(constructor)는 prototype을 속성으로 가지는 정의한 함수를 가르키게 된다. 프로토 타입 객체의 내부에는 생성자와__proto__
라는 모든 객체가 가지고 있는 속성을 가지게 된다.(함수의 proto type object 생성 및 연결)
__proto__
라는 속성은 원형 프로토 타입 객체를 사용해서 생성한 객체의 내부에도 존재한다. 이 속성은 객체가 만들어지기 위해 사용된 원형인 프로토타입 객체를 숨은 링크로 참조하는 역할을 한다.(이 숨겨진 참조링크를 프로토 타입이라고 정의한다)
prototype 객체에 동적으로 런타임에 멤버를 추가
1
2
3
4
5
6
7
8function Person() {}
Person.prototype.eyes = 2;
Person.prototype.nose = 1;
Person.prototype.getType = function () {
return 'human';
};
console.log(Person.prototype); // Object{eyes: 2, nose: 1, getType: f}추가된 prototype 멤버 속성은 같은 원형을 복사해서 생성한 모든 객체에서 공유할 수 있다. (멤버 속성을 추가하기 전에 생성한 객체에서도 새롭게 추가된 멤버 속성을 사용할 수 있다)
어떻게 prototype object에 존재하는 eyes 속성의 참조가 가능한 것인지?
(두 번째 필기노트 참조)
원형 프로토 타입 객체를 복제해서 새로 객체를 생성하게 되면, 새로 생성된 객체의 속성에는 모든 객체가 가지고 있는 proto라는 속성을 갖게 된다. 앞서 이미 설명했듯이 proto라는 속성은 객체를 생성할때 복제한 프로토 타입 객체 원형을 숨은 링크로 참조하는 역할을 하게 되므로, 프로토 타입 객체에 추가한 멤버 속성을 참조할 수 있는 것이다.객체를 생성할때 복제한 프로토타입 객체의 proto 속성은 최상위 Object prototype object를 가르킨다.
(첨부한 두 번째 필기 노트 참고)
복제한 프로토 타입 객체의 proto 속성이 최상위 Object prototype object를 가르키고 있기 때문에 기본적으로 모든 객체는 Object prototype object이 가지고 있는 모든 속성을 사용할 수 있다. 예를 들어 toString()과 같은 함수가 있다.proto는 객체가 생성될 때 조상이었던 함수의 prototype object를 가르킨다.
아래에 첨부한 두 번째 필기 노트를 보면 Person이라는 함수를 통해서 Lee라는 새로운 객체를 만들었다. Lee 객체는 Person 함수로부터 생성되었기 때문에, Person함수의 prototype object를 가르키고 있다. (
프로토타입 체인
)
객체의 동결
Object.freeze()
아래와 같이 객체 리터럴로 객체를 생성하고 const 키워드를 사용하여 상수 변수로 선언해준다고 하더라도 객체의 속성의 추가/삭제는 가능하다. 따라서 수정할 수 없는 객체를 만들어주기 위해서는
Object.freeze()
를 사용해서 값을 동결시키고 싶은 객체를 얼릴 수 있다.참고 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
즉시 실행 함수
그룹 연산자 내부에서 함수 리터럴 뒤에 괄호를 써서 처리해주면, 즉시 실행할 수 있는 함수가 된다.
(연산자 내부에는 값만 올 수 있다. 따라서 연산자 내부에서 함수 선언문을 작성하면 함수 리터럴이 된다)
중요한 이유는 클로저(Closure) 개념을 이해하기 위해서 알아야 하는 개념이기 때문이다.
화살표 함수가 등장하면서 붙여주는 괄호의 위치가 그룹 연산자 내부가 아닌, 그룹 연산자 외부에 붙여주도록 한다.
1 | (function (){ |
중첩 함수
외부 함수(outer)에서 중첩 함수(inner)는 외부 함수의 내부에서만 사용할 수 있도록 정의된 함수이다. 정보은닉의 측면에서 실제 사용되는 범위(스코프)내부에서 정의하도록 한다. 중첩함수를 개별 함수의 형태로 작성하는 이유는 중첩함수의 이름을 통해서 구체적으로 어떤 처리를 하는 부분인지 유추할 수 있기 때문이다.
로직/행위의 재사용을 위해 함수를 작성한다.
예시코드 작성
콜백 함수
어떤 일에 대한 추상화는 함수를 통해 구현한다. 재사용 가능한 함수를 구현할때에 인자로 넘겨주는 함수 인자를 callback(나중에 호출되는 함수)라고 정의한다.
용어 살펴보기
콜백함수를 인자로 받아주는 함수를 고차 함수(Higher-Order Function, HOF)라고 정의한다. 여기서 고차 함수란 콜백함수를 인자로 받거나 함수를 결과값으로 반환해주는 함수를 정의하는 용어이다.
(프론트엔드 개발자)배열의 고차함수를 자유자재로 사용할 줄 알아야 한다.
map함수에서 numbers의 갯수만큼 인자로 받은 callback함수(화살표 함수)를 map이 반복 실행한다.
1
2// 배열의 고차함수
result = numbers.map((item) => item + 1);
순수함수
함수는 최대한 단순하고 기능은 최소한으로 해서 작성을 해줘야 한다.
테스트하기 쉬운 함수를 작성하기 위해서는 순수함수를 작성해야 한다.
항상 일관된 결과를 출력해야 테스트하기에 용이하다.
비순수 함수의 경우, 예를들어 입력되는 날짜에 따라 출력되는 결과가 다를경우, 이러한 함수를 비순수 함수라 정의한다.
스코프
전역코드가 실행되면 전체 스코프가 생성이 된다.(Lexical Environment)
함수가 실행이 되면, 함수 스코프가 생성이 된다. (함수가 호출된 다음에 해당 함수의 스코프가 생성이 된다)
함수 내부에서 사용중인 변수가 해당 내부 스코프에 존재하지 않는다면 상위 스코프로 이동해서 해당 변수를 찾는다.(우선적으로 자신이 속해있는 스코프에서 탐색을 시작한다)
1 | var x = 1; |
함수 스코프에서 상위 스코프(전역 스코프)로 이동해서 탐색을 하기 위해서는 스코프 간의 관계가 성립되어 있어야 한다.(단방향 연결 리스트(링크드 리스트) 구조 - 스코프 체인) (하위 스코프로는 이동을 하지 않는다)
스코프 생성 순서 (구체적으로)
제일 먼저 전역 스코프에 있는 변수와 함수의 선언과 정의(런타임)가 발생된다. 내부 함수가 호출/실행된 뒤에 내부에 선언된 함수의 스코프 내부에 존재하는 변수와 함수의 선언과 정의(런타임)가 발생한다.
내부 함수의 스코프 상에서 사용된 변수를 탐색할 때에는 우선적으로 내부 함수 스코프를 탐색하고 존재하지 않을 경우에는 외부 함수로 탐색을 이어간다. 외부에도 탐색하는 변수가 존재하지 않을 경우, Reference Error가 발생된다.함수 레벨 스코프 (var키워드로 선언된 변수의 스코프)
var 키워드로 변수를 선언할 경우, if 문 내에 존재하는 var 키워드 선언 변수일지라도 if문 내부 블럭에 존재하는 변수가 아닌 외부 스코프에 존재하는 변수이다.
블럭 레벨 스코프 (let, const 키워드로 선언된 변수의 스코프)
let, const로 변수를 선언하게 되면 if 문 내에 존재하는 변수의 경우, if문 내부 블럭에만 허용되는 변수로써 존재한다.
1
2
3
4
5// 변수 a가 덮어써진다.
var a = 10;
if (true) {
var a = 10;
}상위 스코프의 결정 (함수가 정의된 위치를 기준으로 상위 스코프 결정**)
함수가 정의된 위치를 기준으로 상위 스코프를 결정한다.(렉시컬 스코프 방식)
아래의 코드에서 boo()함수의 상위 스코프는 foo()함수의 지역 스코프가 아닌 전역 스코프이다.1
2
3
4
5
6
7
8
9var x = 20;
function foo() {
var x = 10;
boo();
}
function boo() {
console.log(x);
}함수 호출이 종료되면(return-반환) 정의되었던 스코프가 사라진다.
어플리케이션의 성능면을 고려했을때, 전역변수를 사용하지 않고 지역변수로 처리를 해서 함수의 생명주기를 짧게 만들어줘야 한다. (전역변수의 경우 브라우저와 동일한 생명주기를 갖는다)
또한 함수를 만들때에는 하나의 동작만 하도록 작성을 해줘야 좋은 함수이다.
cf) 동적 스코프 방식 : 어디서 호출되었느냐를 기준으로 스코프를 결정한다.
ex) Perl렉시컬 환경(Lexical Environment)
https://meetup.toast.com/posts/86
전역변수를 사용하지 않는 방법
전역변수는 되도록 사용하지 않도록 해야한다.
즉시 실행함수로 감싸서 전역변수를 처리한다.(지역 변수화 - classic한 방법)
ES6의 module 방식으로 코드를 작성한다.(modern한 방식)
모듈을 사용하는 것이 권장되는 방식이다.
사용되는 변수는 일단 const로 선언하고 재할당이 필요하다고 인지했을때, let으로 변경
의외로 재할당을 할 필요가 없다. 따라서 자바스크립트에서는 let보다는 const를 사용한다.
Remind를 위해서 예습하면서 필기했던 노트를 첨부한다.