[JavaScript] 4. Closure, 내장 객체

rin·2020년 12월 6일
0
post-thumbnail

https://poiemaweb.com/coding

Closure

클로저(closure)는 자바스크립트 고유의 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어(Erlang, Scala, Haskell, Lisp ...)에서 사용되는 특성이다.

MDN은 클로저를 다음과 같이 정의하고 있다.

클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.

function outerFunc() {
  var x = 10;
  var innerFunc = function () { console.log(x); };
  innerFunc();
}

outerFunc(); // 10

위 예제에서 innerFunc은 outerFunc에서 정의된 x를 사용할 수 있는데, 이는 자신이 속한 렉시컬 스코프를 참조할 수 있기 때문이다.

  • 이 스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정되며 이를 렉시컬 스코핑(Lexical scoping)이라고 한다.

위와 같은 경우에 실행 컨텍스트는 어떻게 작동될까?

  1. 내부함수 innerFunc 호출 시, 실행 컨텍스트가 스택에 쌓임
  2. 변수 객체, 스코프 체인, this에 바인딩할 객체가 결정된다.
  3. 스코프 체인은 전역 객체, 함수 outerFunc의 활성 객체, 함수 innerFunc의 활성 객체를 순차적으로 바인딩한다.
  4. innerFunc 함수 스코프 내에서 변수 x를 검색하면 실패한다.
  5. 이어서 outerFunc의 스코프에서 변수 x를 검색하면 성공한다.

이 때, 스코프 체인이 바인딩한 객체가 바로 렉시컬 스코프의 실체이다.

function outerFunc() {
  var x = 10;
  var innerFunc = function () { console.log(x); };
  return innerFunc;
}

/**
 *  함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
 *  그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
 */
var inner = outerFunc();
inner(); // 10

innerFunc을 반환하는 함수로 outerFunc을 변경하고 실행하면, outerFunc이 호출 됐을 때 자신의 할 일을 마치며 실행 컨텍스트 스택에서 제거되므로 때문에 변수 x가 유효하지 않을 것 같다.

하지만 위 코드는 정상적으로 작동한다.

👉👉👉 이처럼 자신을 포함하고 있는 외부함수보다 자신(내부함수)이 더 오래 유지되는 경우, 외부함수 밖에서 내부함수가 호출되더라고 외부함수의 지역 변수에 접근할 수 있는 함수클로저라고 부른다.

이는 클로저(함수)가 자신이 선언됐을 때의 환경인 스코프를 기억하여 그 밖에서 호출되어도 해당 환경에 접근 할 수 있기 때문에 가능하다. 간단히 말하면 클로저는 자신이 생성될 때의 환경(Lexical environment)을 기억하는 함수이다.

  • 이 때 클로저에 의해 참조되는 외부함수(outerFunc)의 변수(x)를 자유 변수라고 부른다.
  • Lexical environment를 기억해야하므로 메모리 차원에서 손해를 볼 수 있다.

클로저가 유용하게 사용되는 상황은 다음과 같다.

  1. 현재 상태를 기억하고 변경된 최신 상태를 유지하는 경우
  2. 전역 변수의 사용을 억제
  3. 생성자 함수 내에서 자유 변수 선언을 통해 클래스 기반 언어의 private키워드를 흉내낼 수 있다.

다음으로 클로저를 사용할 때 자주 발생할 수 있는 실수를 살펴보자 🤔

var arr = [];

for (var i = 0; i < 5; i++) {
  arr[i] = function () {
    return i;
  };
}

for (var j = 0; j < arr.length; j++) {
  console.log(arr[j]());
}

해당 예제에서 변수 i가 전역 변수이므로 결과는 5 5 5 5 5를 출력한다.

위 코드가 제대로 동작하게 하려면 다음처럼 수정해야한다.

var arr = [];

for (var i = 0; i < 5; i++){
  arr[i] = (function (id) { // ②
    return function () {
      return id; // ③
    };
  }(i)); // ①
}

for (var j = 0; j < arr.length; j++) {
  console.log(arr[j]());
}
  1. 배열 arr에는 즉시실행함수(가장 바깥의 익명함수)에 의해 함수가 반환된다.
  2. 즉시실행함수는 i를 인자로 전달받고, 매개변수 id에 할당한 후 내부 함수를 반환하는데, 매개변수 id가 자유변수가된다.
  3. 배열 arr에 할당된 함수는 id를 반환한다. id가 상위 스코프(즉시실행함수)의 자유변수이므로 그 값이 유지된다.

물론 다음과 같은 방식으로도 해결할 수 있다.

✔️ let 키워드를 사용

const arr = [];

for (let i = 0; i < 5; i++) {
  arr[i] = function () {
    return i;
  };
}

for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]());
}

✔️ 함수형 프로그래밍으로 작성

const arr = new Array(5).fill();

arr.forEach((v, i, array) => array[i] = () => i); 
// v: 값, i: 인덱스, array: arr 배열

arr.forEach(f => console.log(f()));

내장 객체 (Built-in Object)

네이티브 객체

개념

네이티브 객체 = Native objects = Built-in object = Global Objects
어플리케이션의 환경과 관계없이 사용할 수 있는 전역의 공통 기능을 제공하는 객체

Object, String, Number, Function, Array, RegExp, Date, Math와 같은 객체 생성에 관계가 있는 함수 객체와 메소드로 구성된다.

주의할 점은

  • 네이티브 객체를 Global Objects로 부르기도 한단 것인데 이는 전역 객체(Global Object)와 다른 의미이다.
  • 전역 객체는 모든 객체의 최상위 객체로써 Browser-side의 window, Server-side(Node.js)의 global 객체를 의미한다.

Object

  • 객체를 생성한다.
  • 생성자 인수값이 null or undefined이면 빈 객체를 반환한다.
  • 생성자 함수의 인수값에 따라 강제 형변환된 객체가 반환된다.
  • 단, 객체를 생성하는 경우 특수한 상황이 아니라면 객체 리터럴 방식을 사용하는 것이 일반적이다.
    • var o = {};
new Object()동일한 생성자
new Object('String')new String('String')
new Object(123)new Number(123)
new Object(true)new Boolean(123)

Function

  • 자바스크립트의 모든 함수는 Function 객체이다.
new Function(arg1, arg2, ... argN, functionBody)

Boolean

  • 원시타입 boolean을 위한 wrapper 객체.
var x = new Boolean(false);
if (x) { // x는 객체로서 존재한다. 따라서 참으로 간주된다.
  // . . . 이 코드는 실행된다.
}
  • Boolean 객체는 객체이지 boolean 값과 혼동하지 않도록 주의한다.

Number

https://poiemaweb.com/js-number

  • 원시타입 number를 다룰 때 유용한 프로퍼티와 메소드를 제공하는 레퍼 객체이다.
  • new Number(value) 생성자로 생성할 수 있다.
    • 인자가 숫자로 변환될 수 없으면 NaN을 반환한다.
    • new 연산자를 붙이지 않아 생성자로 사용하지 않으면 Number 객체가 아닌 number를 반환한다.

✔️ 프로퍼티
static property로써 Number 객체를 생성하지 않고 Number.${propertyName}의 형태로 바로 사용가능하다.

namedescription
EPSILONJS에서 표현할 수 있는 가장 작은 수
부동소수점 비교에서 사용한다.
MAX_VALUEJS에서 사용 가능한 가장 큰 숫자를 반환한다. 이보다 큰 수는 Infinity(전역 객체의 프로퍼티) 이다.
MIN_VALUEJS에서 사용가능한 가장 작은 숫자. 이보다 작은 수는 0으로 변환된다.
POSITIVE_INFINITY양의 무한대 Infinity를 반환한다.
NEGATIVE_INFINITY음의 무한대 -Infinity를 반환한다.
NaN숫자가 아님(Not-a-Number)를 나타내는 숫자값. window.NaN 프로퍼티와 동일하다.

✔️ 메소드

namedescription
Number.isFinite(testValue: number): boolean매개변수에 전달된 값이 정상적인 유한수인지를 검사.
전역함수 isFinite()가 인수를 숫자로 변환하여 검사하는 반면 해당 메소드는 인수를 변환하지 않으므로 숫자가 아닌 인수에 대해서는 무조건 false를 반환한다.
Number.isInteger(testValue: number): boolean매개변수에 전달된 값이 정수(Integer)인지 검사.
검사전에 인수를 숫자로 변환하지 않는다.
Number.isNaN(testValue: number): boolean매개변수에 전달된 값이 정수(Integer)인지 검사.
검사전에 인수를 숫자로 변환하지 않는다.
Number.isSafeInteger(testValue: number): boolean매개변수에 전달된 값이 안전한(safe) 정수값인지 검사.
검사전에 인수를 숫자로 변환하지 않는다.
${Number.prototype}.toExponential(fractionDigits?: number): string대상을 지수표기법 (ex. 1234 = 1.234e+3)으로 변환해 반환한다.
${Number.prototype}.toFixed(fractionDigits?: number): string매개변수로 지정된 소숫점자리를 반올림하여 문자열로 반환한다.
${Number.prototype}.toPrecision(precision?: number): string매개변수로 지정된 전체 자릿수까지 유효하도록 나머지 자릿수를 반올림하여 문자열로 반환한다.
지정된 전체 자릿수로 표현할 수 없는 경우 지수 표기법으로 결과를 반환한다.
${Number.prototype}.toString(radix?: number): string숫자를 문자열로 변환하여 반환한다.
${Number.prototype}.valueOf(): numberNumber 객체의 원시 타입 값(primitive value)을 반환한다.

Math

https://poiemaweb.com/js-math

Date

https://poiemaweb.com/js-date

String

https://poiemaweb.com/js-string

RegExp

https://poiemaweb.com/js-regexp

Array

https://poiemaweb.com/js-array

Error

  • error 객체를 생성한다.
  • error 객체의 인스턴스는 런타임 에러 발생 시 throw 된다.
try {
  // foo();
  throw new Error('Whoops!');
} catch (e) {
  console.log(e.name + ': ' + e.message);
}

Symbol

원시 타입과 래퍼객체(Wrapper Object)

네이티브 객체는 각자의 프로퍼티와 메소드를 가지는데 원시 타입 값에 대해 표준 빌트인 객체의 메소드를 호출해도 정상적으로 작동한다.

이는 원시 타입 값이 연관된 객체(Wrapper 객체)로 일시 변환 되기 때문에 가능하다. 메소드 호출이 종료되면 객체로 변환된 원시 타입 값은 다시 복귀한다.

Wrapper 객체는 String, Numver, Boolean이 있다.
참조 : Prototype: 6.원시 타입(Primitive data type)의 확장

호스트 객체

브라우저 환경에서 제공하는 window, XmlHttpRequest, HTMLElement 등의 DOM 노드 객체와 같이 호스트 환경에 정의된 객체
(브라우저와 Node.js는 다른 호스트 객체를 사용할 수 있다.)

전역 객체 (Blobal Object)

모든 객체의 유일한 객체

  • Browser-side : window
  • Server-side(Node.js) : global
  • 실행 컨텍스트에 컨트롤이 들어가기 이전에 생성되며 constructor가 없기때문에 개발자가 전역 객체를 생성하는 것은 불가능하다.
  • 전역 스코프
  • 전역 객체의 자식 객체를 사용할 때는 전역 객체의 기술은 생략 가능
    • window.document ... == document ...
    • 표준 빌트인 객체도 전역 객체의 자식 객체이다.
// window.alert('Hello world!');
alert('Hello world!');
  • 전역 변수는 전역 객체의 프로퍼티이며, 전역 함수는 전역 객체의 메소드이다.
var ga = 'Global variable';
console.log(ga);
console.log(window.ga);

function foo() {
  console.log('invoked!');
}
window.foo();

✔️ 전역 프로퍼티 = 전역 객체의 프로퍼티

propertydescription
Infinity양/음의 무한대를 나타내는 숫자값
NaN숫자가 아님(NaN)
undefined원시타입 undefined

✔️ 전역 함수 = 전역 객체의 메소드

methoddescription
eval(string)매개변수에 전달된 문자열 구문 또는 표현식을 평가 또는 실행한다.
var foo = eval('2 + 2');
console.log(foo); // 4
보안에 취약하므로 사용은 가급적으로 금지되고 있다.
ifFinite(testValue)매개변수에 전달된 값이 정상적인 유한수인지 검사하여 Boolean으로 반환한다.
isNaN(testValue)매개변수에 전달된 값이 NaN인지 검사하여 Boolean으로 반환한다.
parseFloat(string)매개변수에 전달된 문자열을 부동소수점 숫자로 변환하여 반환한다.
문자열의 첫 숫자만 반환되며 전후 공백은 무시된다.
parseFloat(' 60 99'); // 60
parseInt(string, radix)매개변수에 전달된 문자열을 정수형 숫자로 해석하여 반환한다.
첫번째 매개변수 : 파싱 대상 문자열
두번째 매개변수 : 진법을 나타내는 기수 (default 10, 2~36)
radix는 파싱 대상 문자열의 진법을 표현하는 용도로써 반환값은 무조건 10진수이다.
encodeURI(uri)
decodeURI(encodedUri)
매개변수로 전달된 URI를 인코딩/디코딩한다.
encodeURIComponent(uri)
decodeURIComponent(encodedUri)
매개변수로 전달된 URI component를 인코딩/디코딩한다.
encodeURIComponent()는 인수를 쿼리스트링의 일부로 간주하여 구분자인 '= ? &'를 인코딩하나 encodeURI()는 인수를 URI 전체라고 간주하여 구분자를 인코딩하지 않는다.

인코딩이란❗️

URI 문자들을 이스케이프 처리하는 것을 의미한다. 즉, 네트워크를 통해 정보를 공유할 때 어떤 시스템에서도 읽을 수 있는 ASCII character-set으로 변환하는 것이다. UTF-8 특수문자의 경우, 1문자당 1~3byte, UTF-8 한글 표현의 경우, 1문자당 3btye이다. 예를 들어 특수문자 공백(space)은 %20, 한글 ‘가’는 %EC%9E%90으로 인코딩된다.

단, 알파벳, 0~9의 숫자, - _ . ! ~ * ' ()는 이스케이프 처리에서 제외된다.

BOM (Browser Object Model)

브라우저 탭 또는 브라우저 창의 모델을 생성한다.
최상위 객체는 window 객체로 현재 브라우저 창 또는 탭을 표현하는 객체이다. 이 객체의 자식 객체들은 브라우저의 다른 기능들을 표현한다.

DOM (Document Object Model)

현재 웹페이지의 모델을 생성한다.
최상위 객체는 document 객체로 전체 문서를 표현한다. 이 객체의 자식 객체들은 문서의 다른 소요들을 표현한다.

DOM

브라우저의 렌더링 엔진은 웹 문서를 로드한 후, 파싱하여 웹 문서를 브라우저가 이해할 수 있는 구조로 구성하여 메모리에 적재하는데 이를 DOM이라 한다. 즉, 모든 요소와 요소의 attribute, 텍스트를 각각의 객체로 만들고 이들간의 관계를 표현할 수 있는 트리 구조로 구성된 것을 의미한다.

DOM API란 정적인 웹페이지에 접근하여 동적으로 변경시키기위해 필요한 프로퍼티와 메소드의 집합이다.

결론적으로 DOM은 다음 두 가지 기능을 담당한다. 🤔

  1. HTML 문서에 대한 모델 구성
    브라우저는 HTML문서를 로드한 후 해당 문서에 대한 모델을 메모리에 생성한다.
    이 때 모델은 객체의 트리로 구성되는 이것을 DOM tree라 한다.
  2. HTML 문서 내의 각 요소에 접근 / 수정
    DOM은 모델 내의 각 객체에 접근하고 수정할 수 있는 프로퍼티와 메소드를 제공한다.
    DOM이 수정되면 브라우저를 통해 사용자가 보게 될 내용 또한 변경된다.

DOM tree

DOM tree란 브라우저가 HTML 문서를 로드한 후 파싱하여 생성한 모델을 의미한다.

DOM에서 모든 요소, attribute, 텍스트는 하나의 객체이며 document 객체의 자식이다.

DOM tree의 진입점은 document 객체이며 최종점은 요소의 텍스트를 나타내는 객체이다.

🔎 DOME tree의 노드

  • Document Node
    • 트리의 최상위에 존재
    • 나머지 세 노드에 접근하기 위해선 Document Node를 통해야한다.
    • DOM tree의 entry point
  • Element Node
    • HTML 요소를 표현한다.
    • 중첩에 의한 부모-자식 관계를 가지며 이를 통해 정보를 구조화한다.
    • Attribute Node / Text Node에 접근하려면 Element Node에 우선 접근해야한다.
    • 모든 Element Node는 HTMLElement 객체를 상속한 객체로 구성된다.
  • Attribute Node
    • HTML 요소의 Attribute를 표현한다.
    • Element Node의 자식이 아닌 "일부"로 표현
    • 해당 Element Node를 찾아 접근하면 Attribute를 참조, 수정할 수 있다.
  • Text Node
    • HTML 요소의 텍스트를 표현한다.
    • Element Node의 자식
    • 자신의 자식 노드를 가질 수 없다.
    • DOM tree의 최종단
profile
🌱 😈💻 🌱

0개의 댓글