본문 바로가기

Computer Programming/Javascript

Deep Dive 12-17 [함수 | 스코프 | 전역 변수의 문제점 | let, const | 프로퍼티 어트리뷰트 | 생성자 함수에 의한 객체 생성]

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를 가리킴 !!