해당 포스팅은 링크된 웹사이트를 보고 이해한 내용을 정리합니다.
Strict mode
function foo() {
x = 10;
}
console.log(x); // ?
위와 같은 예제에서 `foo`함수 스코프에 변수 x에 대한 선언이 없음 - `상위 스코프 검색` - `암묵적 전역 변수`
* 이때, 암묵적 전역 변수는 오류를 발생시키는 원인이 될 가능성이 커 `var`, `let`, `const` 키워드를 사용해 변수를 선언하고 사용하는 게 좋음
개발자의 숙명인 오류를 줄이기 위해 잠재적 오류 발생이 어려운 환경을 만들어 개발하게 하는 `strict mode`가 ES5부터 추가됨
JS 언어의 문법을 `엄격`하게 적용해 기존에 무시되던 오류를 발생시킬 가능성이 높거나 JS 엔진 최적화 작업에 문제를 일으킬 수 있는 코드에 대해 `명시적인 에러`를 발생시킴
e.g)ESLint 도구: 소스 코드 실행 전, 코드를 스캔해 문법적 오류 + 잠재적 오류를 발견해 오류 이유 리포팅
* strict mode가 제한하는 오류 + 코딩 컨벤션을 설정 파일 형태로 정의 || 강제 가능
strict mode 적용
전역의 선두 또는 함수 몸체의 선두에 `use struct;`를 추가: 스크립트 전체에 `strict mode` 적용
* 선두가 아니면 동작하지 않음
* `전역`에 적용할 경우 스크립트 단위로 적용됨 = 다른 스크립트에 영향을 미치지 않음
`strict mode` 스크립트와 `non-strict mode` 스크립트 혼용 = 오류 발생 가능성 O
따라서, 전역에 `strict mode`를 사용하는 것은 not good.
=> 즉시 실행 함수로 스크립트 전체를 감싸서 `scope`구분하고 즉시 실행 함수 선두에 `strict mode` 적용
// 즉시실행 함수에 strict mode 적용
(function () {
'use strict';
// Do something...
}());
함수 단위로 `strict mode` 적용
모든 함수에 일일히 적용하는 것 = 번거로움 + 각 함수마다 `strict mode`가 있는 것과 없는 것의 혼용 not good.
=> 즉시 실행 함수로 감싼 스크립트 단위로 `strict mode` 적용
`strict mode`가 발생시키는 에러
1. 암묵적 전역 변수
선언하지 않은 변수 참조 시, `ReferenceError` 발생
2. 변수, 함수, 매개변수의 삭제
`SyntaxError: Delete of an unqualified identifier in strict mode.`
3. 매개변수 이름 중복
중복된 함수 파라미터 이름 사용 시, `SyntaxError` 발생
4. with 문의 사용
`with문` 사용 시, `SyntaxError` 발생
5. 일반 함수의 this
`strict mode`에서 일반 함수로서 함수를 호출할 시, `this`에 `undefined`가 바인딩됨
* 생성자 함수가 아닌 일반 함수 내부에서는 `this` 사용할 필요가 X = 에러 발생 X
6. 브라우저 호환성
`IE 9` 이하 지원 X
함수 호출 방식에 의해 결정되는 `this`
JS의 함수는 호출될 때, 매개변수로 전달되는 인자값 외에, `argument 객체`와 `this`를 암묵적으로 전달 받음
JS에서의 `this`는 JAVA와는 다르게 함수 호출 방식에 따라 바인딩되는 객체가 달라짐
함수 호출 방식과 `this` 바인딩
* `Lexical scope`와 혼용 주의
1. 함수 호출
전역객체: 모든 객체의 유일한 최상위 객체
- 전역 스코프를 갖는 전역변수를 프로퍼티로 소유
- 글로벌 영역에 선언한 함수 = 전역객체 프로퍼티에 접근할 수 있는 전역 변수 메소드
// in browser console
this === window // true
// in Terminal
node
this === global // true
Browser-side: `window`
Server-side: `global`
기본적으로 `this`는 전역객체에 바인딩됨
=> 전역함수 && 내부함수 && 메소드의 내부함수 && callback 함수: `this`는 외부함수가 아닌 전역객체에 바인딩됨
내부함수의 `this`가 전역객체를 참조하는 것을 회피하는 방법
2. 메소드 호출
함수가 객체의 프로퍼티 값이면 메소드로 호출
메소드 내부의 `this`는 해당 메소드를 소유한 객체에 바인딩
프로토타입 객체도 메소드 소유 O
3. 생성자 함수 호출
객체를 생성
기존 함수에 `new` 연산자를 붙여 호출하면 해당 함수는 생성자 함수로 동작
이를 구분하기 위해 생성자 함수명은 첫문자 = 대문자 (위험성 회피를 위해 `Scope-safe Constructor 패턴` 사용)
`new` 연산자와 함께 생성자 함수 호출 시, 메소드나 함수 호출 때와 `this` 바인딩이 다르게 동작
1) 생성자 함수 동작 방식
a. 빈 객체 생성 및 this 바인딩
생성자 함수 코드 실행 전, 빈 객체 생성 - 생성자 함수 내에서 사용되는 `this`는 빈 객체를 가리킴 - 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정
b. this를 통한 프로퍼티 생성
생성된 빈 객체에 `this` 사용 - 동적으로 프로퍼티나 메소드 생성 가능 - `this`를 통해 생성한 프로퍼티와 메소드는 새로 생성된 객체에 추가
c. 생성된 객체 반환
반환문이 없는 경우, `this`에 바인딩된 새로 생성한 객체 반환( = 명시적으로 `this` 반환 시에도 결과 동일)
`this` 반환 X인 함수는 생성자 함수로서의 역할 수행 X = 생성자 함수는 반환문을 명시적으로 사용하지 X
2) 객체 리터럴 방식과 생성자 함수 방식의 차이
// 객체 리터럴 방식 var foo = { name: 'foo', gender: 'male' } console.dir(foo); // 생성자 함수 방식 function Person(name, gender) { this.name = name; this.gender = gender; } var me = new Person('Lee', 'male'); console.dir(me); var you = new Person('Kim', 'female'); console.dir(you);
`프로토타입 객체`
1) 객체 리터럴 방식: 생성된 객체의 프로토타입 객체가 Object.prototype
2) 생성자 함수 방싯: 생성된 객체의 프로토타입 객체가 Person.prototype
3) 생성자 함수에 `new`연산자를 붙이지 않고 호출
객체 생성 목적으로 작성한 생성자 함수를 `new`없이 호출하거나 일반함수에 `new`를 붙여 호출하면 오류 발생 O
a. 일반 함수 호출 시, `this`는 전역객체에 바인딩
b. `new`연산자와 함께 생성자 함수 호출 시, `this`는 생성자 함수가 암묵적으로 생성한 빈 객체에 바인딩
대부분의 빌트인 생성자(Object, Regex, Array 등)은 `new`연산자와 함께 호출되었는지 확인 후, 적절한 값 반환
4. apply / call / bind 호출
`this`를 특정 객체에 명시적으로 바인딩하는 방법: `Function.prototype.apply`, `Function.prototype.call` 메소드
func.apply(thisArg, [argsArray])
// thisArg: 함수 내부의 this에 바인딩할 객체
// argsArray: 함수에 전달할 argument의 배열
`apply()` 메소드를 호출하는 주체는 함수
`this`는 특정 객체를 바인딩할 뿐, 본질적 기능은 함수 호출
`apply()` 메소드의 대표적인 용도: `arguments 객체`와 같은 유사 배열 객체(배열이 아니라서 slice() 같은 배열 메소드를 사용할 수 없기 때문)에 배열 메소드를 사용하는 경우
* `Array.prototype.slice.apply(arguments)`: `Array.prototype.slice()`메소드를 호출하되, arguments 객체 자신의 메소드인 것처럼 `arguments.slice()`와 같은 형태로 호출하라는 말
`call()` 메소드의 경우, apply()와 기능은 같지만 apply()의 두번째 인자에서 배열 형태로 넘긴 것을 각각 하나의 인자로 넘김
Person.apply(foo, [1, 2, 3]);
Person.call(foo, 1, 2, 3);
* apply()와 call() 메소드는 콜백 함수의 `this`를 위해 사용되기도 함
* 콜백 함수를 호출하는 외부 함수 내부의 `this`와 콜백 함수 내부의 `this`가 상이하기 때문에 문맥상 문제 발생
= `this`를 일치시켜 주어야 하는 번거로움 발생
= `Function.prototype.bind`를 사용하는 방법 사용 가능
`Function.prototype.bind`는 함수에 인자로 전달한 `this`가 바인딩된 새로운 함수 반환
함수를 실행하지 않기 때문에 명시적으로 함수를 호출해야 함에 주의
Closure(클로저)
JS 고유 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성
"A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.
쉽게 말해, 자신을 포함하고 있는 외부 함수보다 내부 함수가 더 오래 유지되는 경우,
외부 함수 밖에서 내부 함수가 호출되더라도 외부 함수의 지역 변수에 접근할 수 있는 함수
=> "클로저는 함수`(반환된 내부 함수)`와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)`(내부 함수가 선언됐을 때의 스코프)`과의 조합이다."
=> 자신이 생성될 때의 렉시컬 환경을 기억하는 함수!!
클로저의 활용
자신이 생성될 때의 환경을 기억해야 하니, 메모리 차원에서 손해를 볼 수 있음
이를 잘 사용할 수 있는 상황에 대해 알아보자.
1. 상태 유지
현재 상태를 기억하고 변경된 최신 상태를 유지하는 경우
ex) toggle버튼을 만들 때
버튼을 눌렀을 때의 상황과 버튼이 눌리지 않았을 때의 상황이 있을 수 있겠고,
버튼이 눌리지 않은 생태를 기억해야 버튼을 눌렀다가 되돌아올 수도 있으며, 버튼이 눌렸을 때 바뀐 상황도 유지할 수 있다.
만약 클로저라는 기능이 없다면, 전역 변수를 사용해야 하는데
전역 변수는 언제든지 누구나 접근 가능해 많은 부작용을 유발해 오류의 원인이 되기 때문에 사용 억제
2. 전역 변수의 사용 억제
ex) 버튼이 클릭될 때마다 클릭한 횟수가 누적되어 화면에 표시되는 카운터를 만들 때
버튼을 눌렀을 때, 0에서 시작되어 1이 되고, 1인 상태를 기억해야 2가 될 수 있기 때문에 카운트가 계속해서 증가할 수 있다.
즉시 실행 함수는 한 번만 실행되고, increase가 호출될 때마다 변수counter가 재차 초기화되는 일은 없을 것
변수counter는 외부에서 직접 접근할 수 없는 `private` 변수이므로 전역 변수를 사용했을 때처럼 의도되지 않은 변경을 걱정할 필요도 없음
// 함수를 인자로 전달받고 함수를 반환하는 고차 함수
// 이 함수가 반환하는 함수는 클로저로서 카운트 상태를 유지하기 위한 자유 변수 counter을 기억한다.
function makeCounter(predicate) {
// 카운트 상태를 유지하기 위한 자유 변수
var counter = 0;
// 클로저를 반환
return function () {
counter = predicate(counter);
return counter;
};
}
// 보조 함수
function increase(n) {
return ++n;
}
// 보조 함수
function decrease(n) {
return --n;
}
// 함수로 함수를 생성한다.
// makeCounter 함수는 보조 함수를 인자로 전달받아 함수를 반환한다
const increaser = makeCounter(increase);
console.log(increaser()); // 1
console.log(increaser()); // 2
// increaser 함수와는 별개의 독립된 렉시컬 환경을 갖기 때문에 카운터 상태가 연동하지 않는다.
const decreaser = makeCounter(decrease);
console.log(decreaser()); // -1
console.log(decreaser()); // -2
함수 `makeCounter`를 호출해 함수를 반환할 때, 반환된 함수는 자신만의 독립된 렉시컬 환경 소유
따라서, 각각 독립된 렉시컬 환경을 소유하니 카운트를 유지하기 위한 자유 변수 `counter`를 공유하지 않아 증감 연동 X
3. 정보의 은닉
함수 `Counter`의 변수 `counter`가 `this`에 바인딩된 프로퍼티가 아닌 변수
그러면 함수 내에서 선언된 변수는 생성자 함수 `Counter` 외부에서 접근 불가
하지만 생성자 함수 `Counter`가 생성한 인스턴스의 메소드인 `increase`와 `decrease`는 클로저이니 변수에 접근 가능
이런 식으로 클래스 기반 언어의 `private` 키워드를 사용하는 것처럼 보이게 할 수 있음
'JS' 카테고리의 다른 글
JAVASCRIPT의 배열은 배열이 아니다? (0) | 2024.08.15 |
---|---|
정규표현식(Regular Expression) (0) | 2024.08.13 |
JAVASCRIPT03 (0) | 2024.08.09 |
JAVASCRIPT02 (0) | 2024.08.02 |
JAVASCRIPT01 (0) | 2024.07.30 |