12장. 함수
- 함수 : 일련의 과정을 문statement으로 구현하고, 코드블록으로 감싸서 하나의 실행단위로 정의한 것
- 함수 사용을 통해 유지보수의 편의성을 높이고 코드의 신뢰성을 높임 (+재사용성, 가독성 향상)
- 자바스크립트에서 함수는 객체 타입의 '값'이다. 즉 함수 리터럴로 생성할 수 있으며 변수에 할당할 수도 있음
함수 정의 방식
1) 함수 선언문 (표현식 x, 문)
function add(x,y){
return x + y
}
- 이름 생략 불가
- 표현식이 아닌 문statement이라서 원래는 변수에 할당할 수 없지만, 함수는 함수 리터럴 표현식이 될 수 있으므로 '문'임에도 변수에 할당할 수 있음
- 즉 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석되고, 피연산자로 사용하면 함수 선언문이 아니라 함수 리터럴 표현식으로 해석됨
=> 함수 선언문은 '표현식이 아닌 문'이고, 함수 표현식은 '표현식인 문'이다.
- 자바스크립트 엔진은 메모리에 생성된 함수를 호출하기위해 '함수 이름'과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당함 -> 즉 암묵적으로 생성된 '식별자'로 호출된 것임. (함수 이름으로 호출 x)
함수 호이스팅 (런타임 전)
- 함수 선언문이 코드의 선두로 끌어 올려진 것 처럼 동작함
- 함수 선언문으로 정의한 함수 function add(x,y){} 는 함수 선언문 이전에 호출할 수 있음 (표현식으로 정의하면 불가)
- 함수 선언문을 통해 암묵적으로 생성된 식별자는 함수 객체로 초기화됨 (var -> undefined와 다름)
- 함수 표현식으로 함수를 정의하면 함수 호이스팅이 아니라 변수 호이스팅이 발생함 -> undefined (따라서 함수 표현식 이후에 참조 또는 호출해야함)
매개변수와 인수
- 매개변수는 일반변수와 마찬가지로 undefined로 초기화된 후 인수가 순서대로 할당됨
- 인수가 부족해서 할당되지 않은 매개변수의 값은 undefined
- 매개변수의 개수는 적을수록 좋다 (최대 3개 이상을 넘지 않아야 함)
- 원시값 전달 : 값에 의한 전달(변경불가) / 객체 전달 : 참조에 의한 전달(변경가능)
- 객체 전달은 상태 변화를 추적하기 어렵고 가독성을 해침(의도치않은 객체의 변경)
- 옵저버 패턴 등으로 갗은 객체를 참조하는 모든 이들에게 변경읉 통지하고 대응해야함
-> 해결: 깊을 복사를 통해 새로운 객체를 생성하고, 재할당을 통해 교체하여 부수효과 없앰
*즉시 실행 함수도 값 반환, 인수 전달 가능
var res = (function(a,b) {
return a * b //값 반환
}(3,5)) //인수 전달
console.log(res)
반환문
- 함수 호출은 표현식임 -> 즉 return 키워드가 반환한 표현식의 평가 결과로 평가됨
- return; //undefined
콜백함수
- 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수 (함수 자체를 전달!)
고차 함수 (ex. map, filter ...)
- 매개 변수를 통해 외부에서 콜백함수를 전달받은 함수
=> 고차함수는 콜백함수의 호출 시점을 결정함 + 콜백함수에 인수도 전달할 수 있음
순수함수
- 외부 상태에 의존하지 않고 오직 '매개변수'를 통해 함수 내부로 전달된 인수에게만 의존해 값을 생성하고 반환함
- 최소 하나 이상의 인수를 전달받음
var c = 0;
function increase(n) {
return n++;
}
c = increase(c)
console.log(c) // 1
- 함수 외부 상태의 변경을 지양하는 순수함수를 자주 사용하는 것이 좋음 (사이드 이펙트 줄임)
** 함수형 프로그래밍 : 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수효과를 최소화해서 불변성을 지향하는 프로그래밍 패러다임
13장. 스코프
스코프
- 식별자가 가지는 유효한 범위 (자신이 선언된 위치에 의해 참조할 수 있는 유효 범위가 결정됨)
- 렉시컬 환경으로 코드 문맥 파악 -> 식별자 결정 (렉시컬 환경? 코드가 어디서 실행되며 주변에 어떤 코드가 있는지)
- 다른 스코프에서는 같은 이름의 식별자를 사용할 수 있음
- 전역변수, 지역변수
스코프 체인
- 함수는 중첩될 수 있다 = 지역 스코프도 중첩될 수 있다.
** => 즉 스코프가 함수의 중첩에 의해 계층적인 구조를 갖는다는 것을 의미함
- 변수를 참조할 때 자바스크립트 엔진이 그 코드의 스코프에서 시작해서 상위로 이동하며 검색함
- 렉시컬 환경에 변수가 키로 등록됨 (물리적으로 존재함)
함수 레벨 스코프
- var은 함수 레벨 스코프만 지역 스코프로 인정(블록 레벨 if, for 등은 불가)
렉시컬 스코프
- 함수를 어디서 호출했는지가 아니라, 함수를 어디서 정의했는지에 따라 상위 스코프를 결정함 (호출 위치는 영향을 주지 않음)
14장. 전역 변수의 문제점
지역 변수 생명주기
- 함수 내부에서 선언된 지역변수는 함수가 호출되면 생성되고 함수가 종료하면 소멸한다
- 즉 지역변수의 생명주기는 함수의 생명 주기와 일치함 (or 함수보다 더 오래 생존)
- 누군가 메모리 공간을 참조하고 있으면 해제되지 않고 확보된 상태로 남아있음 (스코프) -> 클로저
- 호이스팅: 스코프 단위로 동작 (지역 스코프도 ㅇ)
전역 변수 생명주기
- 전역 변수는 전역 객체의 프로퍼티가 됨 (전역 객체? 프로그램 실행 이전 가장 먼저 생성되는 특수한 객체로, 브라우저는 window, 노드에서는 global rorcp) => var 전역변수는 window의 프로퍼티가 됨 (=전역 객체의 생명주기와 일치)
- 전역변수 사용 => 가독성 나빠지고, 상태가 변경될 수 있는 위험이 높아짐, 메모리 오래 소비-
- 스코프 체인에서 가장 종점에 존재 = 검색 속도가 느림 => 사용 억제하자
=> 전역 변수 대안? 즉시 실행 함수의 지역변수로 만들기 or 객체 생성 -> 전역변수로 만들고싶은 변수를 프로퍼티로 추가 (사용 자제)
15장. let, const 키워드와 블록 레벨 스코프
var : 변수 중복 선언 허용, 함수 레벨 스코프만 인정, 호이스팅 -> 오류발생 높음
let
- 변수 중복 선언 금지, 블록 레벨 스코프 인정
- 호이스팅이 발생하지 않는 것 처럼 동작 (선언, 초기화가 분리되어 진행됨) => 선언은 런타임 이전에 실행되지만 초기화 단계는 변수 선언문에 도착했을 때 실행됨
- let 키워드로 선언한 전역변수는 전역객체(window)의 프로퍼티가 아님 => 실행컨텍스트에서 관리
const
- 선언과 동시에 초기화해야함, 재할당 금지
- 객체인 경우 값 변경 가능 (재할당 없이 직접 변경하므로)
- 즉 const는 재할당을 금지할 뿐 값의 불변을 의미하지는 않는다.
16장. 프로퍼티 어트리뷰트
프로퍼티
1) 데이터 프로퍼티 : 키/값으로 구성
2) 접근자 프로퍼티 : 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티
프로토타입
- 어떤 객체의 상위 객체의 역할을 하는 객체 (자신의 프로퍼티와 메소드를 상속함)
getter / setter 접근자 프로퍼티 추가 (데이터 프로퍼티를 참조하고 접근하기 위한 메소드들)
const person = {
firstname: "ny",
lastname: "kim",
get fullname() {
return `${this.firstname} ${this.lastname}`
},
set fullname(name) {
[this.firstname, this.lastname] = name.split(' ')
}
}
* JavaScript에서 함수 객체는 기본적으로 Function.prototype에서 여러 메서드와 프로퍼티를 상속받습니다. 그러나 Function.prototype은 기본적으로 접근자 프로퍼티 (getter/setter)를 포함하지 않습니다.
그렇다고 함수 객체 자체가 접근자 프로퍼티를 가질 수 없는 것은 아닙니다. 개발자가 직접 정의해서 사용할 수는 있습니다.
17장. 생성자 함수에 의한 객체 생성
const person = new Object();
person.name = 'kim';
person.sayHello = function(){
console.log(hi + this.name)
}
person.sayHello();
생성자 함수 : new 연산자와 함께 호출하여 객체를 생성하는 함수 (이 객체를 인스턴스라고 함)
- 객체 리터럴 {}로 생성하는게 더 일반적임.
- 그러나 리터럴로 생성하면 동일한 구조의 객체를 여러번 생성할 때 모두 기술해야함
-> 생성자 함수로 여러개 생성
//생성자 함수
function Circle(r) {
//1. 빈 객체(인스턴스) 생성 및 this바인딩 (여기서 콘솔 찍어도 Circle{} 인스턴스 반환)
//2. this에 바인딩 된 인스턴스 초기화
this.r = r;
this.getDiameter = function(){
return 2 * this.r
}
//3. 암묵적으로 this 반환
}
//인스턴스 생성
const circle1 = new Circle(5) //r이 5인 Circle 객체 생성
const circle2 = new Circle(10)
- 여기서 this 자기참조 변수는 생성자 함수가 미래에 생성할 인스턴스를 참조한다
- new 키워드를 통해 생성자 함수로 호출. 그렇지 않고 Circle(5)만 할 경우 일반 함수 호출로 가정
- 1~3과정
* Circle 인스턴스 (circle1, circle2 등)는 자체 getDiameter 메서드를 가지게 됩니다. 즉, 각 인스턴스마다 이 메서드에 대한 별도의 메모리 공간이 할당됩니다. 반면, 메서드를 프로토타입에 추가하면 모든 인스턴스가 이 메서드를 공유하게 되어 메모리 사용이 효율적입니다.
- 함수도 객체이므로 프로퍼티와 메소드를 소유할 수 있음
function foo() {}
foo.prop = 10;
foo.method = function() {
console.log(this.prop);
};
foo.method() //10
- new 연산자와 함께 함수를 호출하면 해당 함수는 생성자 함수로 동작함
즉 [[Call]]이 아니라 [[Construct]]가 호출됨
* 만약 new 로 호출하지 않아서 일반 함수가 됬다면(생성자 함수 말고) -> 함수 내부의 this는 Circle {} 인스턴스가 아닌 전역 객체인 window를 가리킴 !!
'Computer Programming > Javascript' 카테고리의 다른 글
Deep Dive 18-21 [함수와 일급 객체 | 프로토타입 | strict mode | 빌트인 객체] (0) | 2023.10.09 |
---|---|
Deep Dive 10-11 [객체 리터럴 | 원시값과 객체의 비교] (0) | 2023.10.01 |
Deep Dive 6-9 [데이터타입 | 연산자 | 제어문 | 타입변환과 단축 평가] (0) | 2023.09.30 |
json-server 사용해보기 (0) | 2023.06.30 |
타입스크립트(TypeScript)의 특징 (0) | 2023.06.27 |