해당 글의 내용은 poiemaweb 을 읽고 요약하여 재정리한 내용입니다.
이번에는 JavaScript lesson 18~21에 해당되는 클로저, 객체지향 프로그래밍, 빌트인 객체, 전역 객체에 대해 알아보겠습니다.
18. 클로저(Closure)
클로저란?
JavaScript 고유의 개념이 아닌 함수형 프로그래밍 언어에서 사용 되는 중요한 특성입니다.
MDN에서는 “함수와 함수가 선언 됐을 때의 렉시컬 환경과의 조합이다.” 라고 정의되어 있습니다.
- 렉시컬 환경? 유효범위가 작성된 위치에 따라 결정되는 것
function outer() {
var a = 5;
function inner() {
console.log(a);
}
return inner;
}
var func = outer();
func(); // 5
함수 outer
내부에 선언된 inner
은 외부 함수인 outer
변수 a
에 접근 가능합니다.
이는 함수 inner
가 자신이 속한 렉시컬 스코프(전역, outer 함수)를 참조할 수 있기 때문입니다.
위의 예시처럼 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부 함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있으며 이런 함수를 클로저 라고 부릅니다.
즉, 반환된 내부함수가 자신이 선언됐을 때의 환경(렉시컬 환경) 스코프를 기억하고, 자신이 선언됐을 때의 환경 밖에서 호출되어도 환경(스코프)에 접근할 수 있는 함수를 의미합니다.
클로저의 활용
자신이 생성될 때의 환경을 기억해야 하기에 메모리 차원에서 손해를 볼 수 있습니다.
클로저는 자바스크립트의 강력한 기능이기 때문에 클로저가 유용하게 사용되는 상황에 대해 살펴보겠습니다.
상태 유지
가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것 입니다.
클로저라는 기능이 없었다면 상태를 유지하기 위해 전역 변수를 사용(에러의 원인이 될 수 있음)할 수 밖에 없습니다.
이 외에도 전역 변수 억제나 정보를 은닉하는 등에 사용되기도 합니다.
자세한 예시는 원본 글 참고 부탁드립니다.
이해를 돕기 위한 참고 사이트는 더보기를 눌러주세요.
19. JS의 객체지향 프로그래밍(Object-Oriented Programming)
객체지향 프로그래밍은 현실 세계의 사물이나 개념을 추상화하여 객체라는 개념으로 표현하고, 소프트웨어를 설계하고 구축하는 데 사용되는 프로그래밍 패러다임을 의미합니다.
관계성 있는 객체들의 집합(객체들 간의 상호작용)이라는 관점으로 접근하며, 각 객체들은 별도의 역할이나 책임을 갖는 작은 독립적인 기계 or 부품으로 볼 수도 있습니다.
보다 유연하고 유지보수하기 쉬우며, 확장성 측면에서도 유리한 프로그래밍을 하도록 의도되었고, 대규모 SW 개발에 널리 사용되고 있습니다.
클래스 기반 vs 프로토타입 기반
클래스 기반 언어
클래스 기반 언어(Java, C++, C#, Python, PHP, Ruby, Object-C)는 클래스로 객체의 자료구조와 기능을 정의하고 생성자를 통해 인스턴스를 생성합니다.
- 클래스? 같은 종류의 집단에 속하는 속성(attribute)과 행위(behavior)를 정의한 것
모든 인스턴스는 오직 클래스에서 정의된 범위 내에서만 작동하며 런타임에 그 구조를 변경할 수 없습니다.
이는 정확성, 안정성, 예측성 측면에서 프로토타입 기반 언어보다 더 나은 결과를 보장합니다.
프로토타입 기반 언어
JavaScript는 멀티-패러다임 언어로 명령형, 함수형, 프로토타입 기반 객체지향 언어 입니다.
객체 리터럴
, Object() 생성자 함수
, 생성자 함수
를 통해 객체를 생성할 수 있습니다.
이미 생성된 인스턴스의 자료구조와 기능을 동적으로 변경할 수 있다는 특징이 있고, 객체 지향의 상속, 캡슐화(정보 은닉) 등의 개념은 프로토타입 체인과 클로저 등으로 구현할 수 있습니다.
ES6부터 도입된 Class
는 객체지향 프로그래밍의 개념을 지원하기 위해 등장 했으며, 객체를 생성하기 위한 템플릿으로 사용되고 있습니다.
JavaScript의 Class
는 사실 함수이고, 프로토타입 기반으로 동작하기 때문에 프로토타입에 대한 이해가 있어야 합니다.
프로토타입 체인과 메소드의 정의
모든 객체는 프로토타입이라는 다른 객체를 가리키는 내부 링크를 가지고 있으며, 직접 객체를 연결하는 것을 프로토타입 체인이라고 합니다.
function Person(name) {
this.name = name;
}
// 프로토타입 객체에 메소드 정의
Person.prototype.setName = function (name) {
this.name = name;
};
var me = new Person('Lee');
console.log(Person.prototype);
// Person { setName: [Function] }
console.log(me);
// Person { name: 'Lee' }
위의 예시에서 Person
생성자 함수의 prototype
프로퍼티가 가리키는 프로토타입 객체로 이동시킨 setName
메소드는 프로토타입 체인에 의해 모든 인스턴스가 참조할 수 있습니다.
상속(Inheritance)
JavaScript는 기본적으로 프로토타입을 통해 상속을 구현하며, 이는 프로토타입을 통해 객체가 다른 객체로 직접 상속되는 것을 의미합니다.
상속 구현 방식은 크게 두 가지로 구분할 수 있습니다.
클래스 기반의 상속 방식을 흉내 내는 의사 클래스 패턴 상속(Pseudo-classical Inheritance), 프로토타입으로 상속을 구현하는 프로토타입 패턴 상속(Prototypal Inheritance) 입니다.
이전에는 위의 방식들을 사용했지만 ES6 이후에는 class
키워드를 사용하여 상속을 쉽고 명확하게 구현할 수 있게 되었습니다.
위의 방식들로 상속을 구현하는 방법은 원본 글을 참고하길 바랍니다.
class
로 상속을 구현하는 방법은 다음과 같습니다.
// 부모 클래스 정의
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}은 소리를 낸다.`);
}
}
// 자식 클래스 정의 및 상속
class Dog extends Animal {
constructor(name) {
super(name); // 부모 클래스의 생성자 호출
}
speak() {
console.log(`${this.name} 왈왈`);
}
}
const animal = new Animal('동물');
animal.speak(); // 동물은 소리를 낸다.
const dog = new Dog('멍멍이');
dog.speak(); // 멍멍이 왈왈
상속의 기반이 될 부모 클래스를 정의한 후, extends
키워드를 사용하여 부모 클래스로부터 상속 받는 자식 클래스를 정의합니다.
자식 클래스인 Dog에서는 constructor
함수 내에서 super
키워드를 사용해서 부모 클래스의 생성자를 호출합니다.
이는 자식 클래스에서 부모 클래스의 생성자를 호출하고 인스턴스 변수를 초기화하는 데 필요합니다.
또, speak
메소드를 오버라이드(재정의)하여, 동일한 메소드 이름으로 다른 동작을 구현합니다.
자식 클래스에서 부모 클래스의 메소드를 오버라이드할 경우 해당 메소드는 자식 클래스의 인스턴스에서 호출될 때 자식 클래스에서 정의된 동작을 수행합니다.
캡슐화(Encapsulation)와 모듈 패턴(Module Pattern)
캡슐화는 외부에 공개될 필요가 없는 정보를 숨기는 것을 뜻하며 정보 은닉이라고 합니다.
JavaScript에서는 public
or private
등의 키워드를 제공하지 않지만 정보 은닉은 가능합니다.
var person = function(arg) {
var name = arg ? arg : '';
return {
getName: function() {
return name;
},
setName: function(arg) {
name = arg;
}
}
}
var me = person('Lee'); /* or var me = new person('Lee'); */
var name = me.getName();
console.log(name); // 'Lee'
me.setName('Kim');
name = me.getName();
console.log(name); // 'Kim'
person 함수는 객체를 반환하며 getName
, setName
은 클로저로서 private
변수 name에 접근할 수 있습니다.
이러한 방식을 모듈 패턴이라고 하며 캡슐화와 정보 은닉을 제공합니다.
하지만 private
멤버가 객체나 배열일 경우, 반환된 해당 멤버의 변경이 가능하기 때문에 주의해야 합니다.
이를 회피하기 위해서는 객체를 그대로 반환하지 않고 반환해야 할 객체의 정보를 새로운 객체에 담아 반환해야 합니다.
반드시 객체 전체가 그대로 반환되어야 하는 경우에는 깊은 복사(deep copy)로 복사본을 만들어 반환 합니다.
자세한 예시는 원본 글을 참고하길 바랍니다.
20. 빌트인 객체(Built-in Object)
네이티브 객체(Native objects or Built-in objects or Global objects)
ECMAScript
명세에 정의된 객체를 의미하며 애플리케이션 전역의 공통 기능을 제공합니다.
Object
, String
, Number
, Function
, Array
, RegExp
, Date
, Math
와 같은 객체 생성에 관계가 있는 함수 객체와 메소드로 구성되어 있습니다.
전역 객체(Global Object)와는 다른 의미로 사용 되고, 전역 객체는 모든 객체의 최상위 객체를 의미합니다. (window
)
Number
, Math
, Date
, String
, RegExp
, Array
에 대해서는 추후에 자세하게 다룰 예정이며, 해당 글에서는 이 외에 대한 것들을 정리하겠습니다. (업로드 후 링크 추가 예정)
Object
var o = new Object();
console.log(typeof o + ': ', o);
// String 객체 반환
// var obj = new String('String');과 동치
var obj = new Object('String');
console.log(typeof obj + ': ', obj);
console.dir(obj);
Object()
생성자 함수는 객체를 생성합니다.
인수값이 null
이거나 undefined
면 빈 객체를 반환하고, 이외의 경우 인수값에 따라 강제 형변환된 객체가 반환됩니다.
객체를 생성할 경우 특수한 상황이 아니면 객체리터럴 방식을 사용하는 것이 일반적 입니다.
var o = {};
객체에 대해서 자세하게 알고 싶다면 이전에 정리해둔 해당 글을 참고하길 바랍니다.
Function
var adder = new Function('a', 'b', 'return a + b');
adder(3, 9); // 12
JavaScript의 모든 함수는 Function
객체이며, 다른 모든 객체들처럼 new
연산자를 사용해 생성할 수 있습니다.
함수에 대한 자세한 내용 또한 이전에 정리해둔 해당 글을 참고하길 바랍니다.
Boolean
var foo = new Boolean(true); // true
var foo = new Boolean('false'); // true
var foo = new Boolean(''); // false
var foo = new Boolean(0); // false
원시 타입 boolean
을 위한 래퍼(wrapper)객체로 Boolean
생성자 함수로 Boolean
객체를 생성할 수 있습니다.
- 래퍼(wrapper)객체? 원시 타입의 값을 감싸는 객체
원시 타입 boolean
과 혼동하기 쉬우며, Boolean
객체는 true/false
를 포함하고 있는 객체입니다.
Error
try {
// foo();
throw new Error('Whoops!');
} catch (e) {
console.log(e.name + ': ' + e.message);
}
error
객체를 생성하며, error
객체의 인스턴스는 런타임 에러가 발생했을 때 throw
됩니다.
이 외 Error와 관련된 객체
EvalError, InternalError, RangeError
ReferenceError, SyntaxError, TypeError, URIError
Symbol
ECMAScript 6(Javascript 2015)에서 추가된 유일하고 변경 불가능한(immutable) 원시 타입 입니다.
Symbol
객체는 원시 타입 Symbol
값을 생성합니다.
원시 타입과 래퍼객체(Wrapper Object)
네이티브 객체는 각자의 프로퍼티와 메소드를 가집니다.
정적 프로퍼티, 메소드는 해당 인스턴스를 생성하지 않아도 사용할 수 있고, prototype
에 속해있는 메소드는 해당 prototype
을 상속 받은 인스턴스가 있어야만 사용할 수 있습니다.
하지만 원시 타입 값에 대한 표준 빌트인 객체의 메소드를 호출하면 정상적으로 작동합니다.
이는 원시 타입 값에 대해 표준 빌트인 객체의 메소드를 호출할 때, 원시 타입 값은 연관된 객체(Wrapper 객체)로 일시 변환되기 때문 입니다.
메소드 호출이 종료될 때 객체로 변환된 원시 타입 값은 다시 원시 타입 값으로 돌아갑니다.
자세한 내용은 이전에 정리해둔 해당 글을 참고하길 바랍니다.
호스트 객체
브라우저 환경에서 제공하는 window
, XmlHttpRequest
, HTMLElement
등의 DOM
노드 객체와 같이 호스트 환경에 정의된 객체를 의미합니다.
브라우저에서 동작하는 환경의 호스트 객체는 window
, BOM(Browser Object Model)
과 DOM(Document Object Model)
및 XMLHttpRequest
객체 등을 제공합니다.
전역 객체
모든 객체의 유일한 최상위 객체를 의미하고, 일반적으로 Browser-side
에서는 window
, Server-side
에서는 global
객체를 의미합니다.
BOM (Browser Object Model)
브라우저 객체 모델은 브라우저 탭 or 브라우저 창의 모델을 생성합니다.
최상위 객체는 window
객체이며, 해당 객체의 자식 객체들은 브라우저의 다른 기능들을 표현합니다.
웹 브라우저와 관련된 객체의 집합이며, 대표적인 BOM 객체는 다음과 같습니다.
window
: 브라우저 창 객체, 다른 BOM 객체의 상위 개념
screen
: 사용자 환경의 디스플레이 정보 객체
location
: 현재 페이지의 URL을 다루는 객체
navigator
: 웹 브라우저 및 브라우저 환경 정보 객체
history
: 현재의 브라우저가 접근했던 URL history
자세한 설명은 아래 더보기를 통해 참고하길 바랍니다.
- 참고 자료
BOM (Browser Object Model) 완벽 정복하기
BOM이란? BOM(Browser Object Model)이란 웹브라우저의 창이나 프래임을 추상화해서 프로그래밍적으로 제어할 수 있도록 제공하는 수단이다. BOM은 전역객체인 Window의 프로퍼티와 메소드들을 통해서 제
geniee.tistory.com
[JavaScript 요약 정리] 12. 브라우저 객체 모델(BOM)
12. 브라우저 객체 모델(BOM)1) BOM(Browser Object Model)- 웹 브라우저와 관련된 객체의 집합- 객체 모델 종류: window(최상위), location, navigator, history, screen, document- DOM(Document Object Model) 으로 통합해서 칭하
wickies.tistory.com
DOM (Document Object Model)
문서 객체 모델은 현재 웹페이지의 모델을 생성합니다.
최상위 객체는 document
객체로 전체 문서를 표현하며, 해당 객체의 자식 객체들은 문서의 다른 요소들을 표현합니다.
자세한 설명은 아래 더보기를 통해 참고하길 바랍니다.
- 참고 자료
문서 객체 모델 DOM 과 자바스크립트 JavaScriptㅣ생성 방식 및 접근 방법 - 코드스테이츠 공식 블로
웹 개발을 하는 프론트엔드 개발자라면 알고 있어야하는 자바스크립트(JavaScript)와 DOM(문서 객체 모델)란 무엇일까요?자바스크립트와 DOM(문서 객체 모델)의 관계와 차이점은 무엇인지, 자바스크
www.codestates.com
문서 객체 모델(DOM) - Web API | MDN
문서 객체 모델(DOM)은 메모리에 웹 페이지 문서 구조를 표현함으로써 스크립트 및 프로그래밍 언어와 페이지를 연결합니다. 이때 스크립트는 주로 JavaScript를 의미하나 HTML, SVG, XML 객체를 문서로
developer.mozilla.org
21. 전역 객체(Global Object)
전역 객체는 실행 컨텍스트에 컨트롤이 들어가기 이전에 생성되며 constructor
가 없기 때문에 new
연산자로 새롭게 생성할 수 없습니다. (개발자가 전역 객체 생성하는 거 불가능)
- 실행 컨텍스트? 실행할 코드에 제공할 환경 정보들을 모아 놓은 객체
전역 스코프(Global Scope)를 가지며, 자식 객체를 사용할 때 전역 객체의 작성은 생략 합니다.(window.document
에서 window
삭제)
전역 변수는 전역 객체의 프로퍼티이며, 전역 함수는 전역 객체의 메소드 입니다.
표준 빌트인 객체도 전역 객체의 자식 객체 입니다.
전역 프로퍼티(Global property)
전역 객체의 프로퍼티를 의미하며, 애플리케이션 전역에서 사용하는 값들을 나타냅니다.
전역 프로퍼티의 종류는 다음과 같습니다.
Infinity
: 양/음의 무한대를 나타내는 숫자값
NaN
: 숫자가 아님을 나타내는 숫자값
undefined
: 원시 타입 undefined를 값으로 가짐
전역 함수(Global Function)
애플리케이션 전역에서 호출할 수 있는 함수로서 전역 객체의 메소드 입니다.
eval()
var foo = eval('3 + 3');
console.log(foo) // 6
매개변수에 전달된 문자열 구문 또는 표현식을 평가하거나 실행 합니다.
보안에 취약하기 때문에 eval()
의 사용은 가급적으로 금지해야 합니다.
isFinite()
console.log(isFinite(Infinity)); // false
console.log(isFinite(NaN)); // false
console.log(isFinite('Hello')); // false
console.log(isFinite('2005/12/12')); // false
console.log(isFinite(0)); // true
console.log(isFinite(2e64)); // true
console.log(isFinite('10')); // true: '10' → 10
console.log(isFinite(null)); // true: null → 0
매개변수에 전달된 값이 유한수인지 결과를 Boolean
으로 반환 합니다.
숫자가 아닌 경우, 숫자로 변환한 후 검사를 수행 합니다.
isNaN()
isNaN(NaN) // true
isNaN(undefined) // true: undefined → NaN
isNaN({}) // true: {} → NaN
isNaN('blabla') // true: 'blabla' → NaN
isNaN(true) // false: true → 1
isNaN(null) // false: null → 0
isNaN(37) // false
// strings
isNaN('37') // false: '37' → 37
isNaN('37.37') // false: '37.37' → 37.37
isNaN('') // false: '' → 0
isNaN(' ') // false: ' ' → 0
// dates
isNaN(new Date()) // false: new Date() → Number
isNaN(new Date().toString()) // true: String → NaN
매개변수에 전달된 값이 NaN
인지 검사하여 결과를 Boolean
으로 반환 합니다.
숫자가 아닌 경우, 숫자로 변환한 후 검사를 수행 합니다.
parseFloat()
parseFloat('3.14'); // 3.14
parseFloat('10.00'); // 10
parseFloat('34 45 66'); // 34
parseFloat(' 60 '); // 60
parseFloat('40 years'); // 40
parseFloat('He was 40') // NaN
매개변수에 전달된 문자열을 부동소수점 숫자로 변환하여 반환 합니다.
문자열의 첫 숫자만 반환 되며 전후 공백은 무시되고, 첫 문자를 숫자로 변환할 수 없다면 NaN
을 반환 합니다.
parseInt()
parseInt(string, radix);
// string: 파싱 대상 문자열
// radix: 진법을 나타내는 기수(2 ~ 36, 기본값 10)
parseInt(10); // 10
parseInt('10.123'); // 10
parseInt('10', 2); // 2진수 10 → 10진수 2
parseInt('10', 8); // 8진수 10 → 10진수 8
parseInt('10', 16); // 16진수 10 → 10진수 16
매개변수에 전달된 문자열을 정수형 숫자로 반환 합니다.
반환값은 언제나 10진수이며, 문자열이 아니면 문자열로 변환한 후 숫자로 해석해서 반환 합니다.
두 번째 매개변수에는 진법을 나타내는 기수(2 ~ 36)를 지정할 수 있으며, 기수를 생략하면 10진수로 해석해 반환 합니다.
기수를 지정하면 첫 번째 매개변수에 전달된 문자열을 해당 기수의 숫자로 해석한 후, 10진수로 반환 합니다.
또, 첫 번째 매개변수에 전달된 문자열의 첫 번째 문자가 해당 기수의 숫자로 변환될 수 없다면 NaN
을 반환합니다.
공백이 있다면 첫 번째 문자열만 해석하여 반환하고 전후 공백은 무시됩니다.
encodeURI() / decodeURI()
encodeURI(URI)
// URI: 완전한 URI
decodeURI(encodedURI)
// encodedURI: 인코딩된 완전한 URI
encodeURI()
은 매개변수로 전달된 URI
를 인코딩합니다.
인코딩은 URI
의 문자들을 이스케이프 처리하는 것을 의미합니다.
이스케이프 처리
네트워크를 통해 정보를 공유할 때 어떤 시스템에서도 읽을 수 있는 ASCII Character-set로 변환하는 것
UTF-8 특수문자의 경우 1문자당 1~3byte, UTF-8 한글 표현의 경우 1문자당 3byte
이스케이프 처리 이유
URI 문법 형식 표준에 따르면 한글을 포함한 대부분의 외국어나 ASCII에 정의되지 않은 특수문자의 경우 URL에 포함할 수 없음
URL 내에서 의미를 갖고 있는 문자(%, ?, #)나 URL에 올 수 없는 문자(한글, 공백 등) or 시스템에 의해 해석될 수 있는 문자(<, >)를 이스케이프 처리해서 야기될 수 있는 문제 예방하기 위함
encodeURIComponent()
, decodeURIComponent()
도 있으며, 이는 매개변수로 전달된 URI
구성요소를 인코딩 및 디코딩 합니다.
encodeURIComponent()
는 인수를 쿼리스트링의 일부라고 간주해서 =
, ?
, &
를 인코딩 합니다.
반면 encodeURI()
는 인수를 URI
전체라고 간주하며 파라미터 구분자인 =
, ?
, &
를 인코딩하지 않습니다.
여기까지 JavaScript lesson 18~21에 해당되는 클로저, 객체지향 프로그래밍, 빌트인 객체, 전역 객체에 대해 알아보았습니다.