1. 표준 빌트인 객체 - ECMAScript 사양에 정의된 객체를 말하며 애플리케이션 전역의 공통 기능을 제공한다. 2. 호스트 객체 - ECMAScript 사양에 정의되어있지 않지만, 자바스크립트 실행 환경에서 추가로 제공하는 객체를 말한다. 3. 사용자 정의 객체 - 표준 빌트인 객체와 호스트 객체처럼 기본 제공되는 객체가 아닌 사용자가 직접 정의된 객체를 말한다.
표준 빌트인 객체
- Object, Number, Boolean, Symbol, Data, Math, RegExp, Array, Map/Set, WeakMap/WeakSet, Function, Promise, Reflect, Proxy, JSON, Error 등 40여 개의 표준 빌트인 객체를 제공
- Math, Reflect, JSON(정적 메서드만 제공)을 제외한 표준 빌트인 객체는 모두 인스턴스를 생성할 수 있는 생성자 함수 객체(프로토타입 메서드와 정적 메서드를 제공)다.
const strObj = new String('Lee');
//string 생성자 함수를 통해 생성한 strObj 객체의 프로토타입은 String.prototype이다.
console.log(Object.getPrototypeOf(strObj) === String.prototype); //true
- 표준 빌트인 객체의 prototype 프로퍼티에 바인딩 된 객체(ex. String.prototype)는 다양한 기능의 빌트인 프로토타입 메서드를 제공한다.
- 그리고 표준 빌트인 객체는 인스턴스 없이도 호출 가능한 빌트인 정적 메서드를 제공한다.
3) 전역 객체는 Object, String, Number, Boolean, Function, Array, RegExp, Date, Math, Promise 같은 모든 표준 빌트인 객체를 프로퍼티로 가지고 있다.
4) 자바스크립트 실행환경에 따라 추가적으로 프로퍼티와 메서드를 갖는다. 브라우저 환경에서는 DOM, BOM, Canvas, XMLHttpRequest, fetch, requestAnimationFrame, SVG, Web Storage, Web Component, Web Worker와 같은 클라이언트 사이드 Web API를 호스트 객체로 제공하고, Node.js 환경에서는 Node.js 고유의 API를 호스트 객체로 제공한다.
5) var 키워드로 선언한 전역 변수와 선언하지 않은 변수에 값을 할당한 암묵적 전역, 그리고 전역 함수는 전역 객체의 프로퍼티가 된다.
//var 키워드로 선언한 전역 변수
var foo = 1;
console.log(window.foo); // 1
//선언하지 않은 변수에 값을 암묵적 전역, bar은 전역 변수가 아니라 전역 객체의 프로퍼티
bar = 2;
console.log(window.bar); //2
//전역 함수
function baz() { return 3; }
console.log(window.baz()); //3
6) let이나 const로 선언한 전역 변수는 전역 객체의 프로퍼티가 아니다. let이나 const 키워드로 선언한 전역 변수는 보이지 않는 개념적인 블록(전역 렉시컬 환경의 선언적 환경 레코드) 내에 존재하게 된다.
- eval함수를 통해 사용자로부터 입력받은 콘텐츠를 실행하는 것은 보안에 매우 취약하다.
- 그리고 eval함수를 통해 실행되는 코드는 js엔진에 의해 최적화가 수행되지 않아서 일반적인 코드 실행에 비해 처리 속도가 느림
isFinite
isNaN
parseFloat
parseInt
encodeURI / decodeURI
- 쿼리 스트링 구분자로 사용되는 =, ?, &는 인코딩하지 않음
encodeURIComponent / decodeURIComponent
- 쿼리 스트링 구분자로 사용되는 =, ?, &는 인코딩함.
암묵적 전역
var x = 10; //전역 변수
function foo() {
//선언하지 않은 식별자에 값을 할당
y = 20; //window.y = 20;
}
//선언하지 않은 식별자 y를 전역에서 참조가능
console.log(x+y); //30
- 선언하지 않은 식별자 y는 마치 선언된 전역 변수처럼 동작한다.
- 이는 선언하지 않은 식별자에 값을 할당하면 전역 객체의 프로퍼티가 되기 때문임. (y는 전역객체에 프로퍼티를 동적 생성해서 마치 전 역 변수처럼 동작함)
- y는 변수 선언 없이 단지 전역 객체의 프로퍼티로 추가되었을 뿐이므로 y는 변수가 아니다. (변수 호이스팅이 발생하지 않는다.)
//전역 변수 x는 호이스팅이 발생함
console.log(x); //undefined
//전역 변수가 아니라 단지 전역 객체의 프로퍼티인 y는 호이스팅이 발생하지 않는다.
console.log(y); //ReferenceError: y is not defined
var x = 10; //전역 변수
function foo() {
//선언하지 않은 식별자에 값 할당
y = 20; // window.y = 20;
}
foo();
console.log(x+y); //30
ES5부터 strict mode가 추가되었다. - strict mode는 자바스크립트 언어의 문법을 좀 더 엄격히 적용하여 오류를 발생시킬 가능성을 높임. - 자바스크립트 엔진의 최적화 작업에 문제를 일으킬 수 있는 코드에 대한 명시적인 에러를 발생시킴. - ESLint 같은 린트 도구를 사용해 유사한 효과를 얻을 수 있다.
*ESLint : - 정적 분석 기능을 통해 소스코드를 실행하기 전에 소스코드를 스캔하여 문법적 오류만이 아니라 잠재적 오류까지 찾아내고 오류의 원인을 리포팅해주는 도구
Strict mode의 적용
- 전역의 선두나 함수 몸체의 선두에 추가한다.
'use strict';
function foo() {
x = 10; //ReferenceError: x is not defined
}
foo();
전역에 strict mode를 적용하는 것은 피하자
- 전역에 적용한 strict mode는 스크립트 단위로 적용함. (해당 스크립트에 한정되어 적용)
- strict mode 스크립트와 non-strict mode 스크립트를 혼용하는 것은 오류를 발생시킬 수 있다.
- 특히, 외부 서드파티 라이브러리를 사용하는 경우 라이브러리가 non-strict mode인 경우도 있어서 전역에 strict mode를 적용하는 것은 바람직하지 않다.
- 이러한 경우, 즉시 실행 함수로 스크립트 전체를 감싸서 스코프를 구분하고, 즉시 실행 함수의 선두에 strict mode를 적용한다.
(function () {
//즉시 실행 함수 선두에 적용
'use strict';
//Do something..
}());
함수 단위로 strict mode를 적용하는 것도 피하자
- 위와 같은 이유로 strict mode는 즉시 실행 함수로 감싼 스크립트 단위로 적용하는 것이 바람직함.
strict mode가 발생시키는 에러 예시
- 암묵적 전역
- 변수, 함수, 매개변수의 삭제
- 매개변수 이름의 중복
- with 문의 사용
strict mode 적용에 의한 변화
일반함수의 this
- strict mode에서 일반 함수로서 호출하면 this에 undefined가 바인딩된다.
- 생성자 함수가 아닌 일반 함수 내부에서는 this를 사용할 필요가 없기 때문이다. (에러발생x)
(function() {
'use strict';
function foo() {
console.log(this); //undefined
}
foo();
function Foo() {
console.log(this); //Foo
}
new Foo();
}());
- Person 생성자 함수에 의해 생성된 me 객체는 Object.prototype 메서드인 hasOwnProperty로 호출할 수 있는데, 이것은 me 객체가 Person.prototype뿐만 아니라 Object.prototype도 상속받았다는 것을 의미함.
프로토타입 체인 - JS가 객체지향 프로그래밍의 상속을 구현하는 메커니즘 - 자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때, 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다.
- Object.prototype을프로토타입 체인의 종점이라 함.!
프로토타입 체인은상속과 프로퍼티 검색을 위한 메커니즘 vs. 스코프 체인은식별자 검색을 위한 메커니즘 (자바스크립트 엔진은 함수의 중첩 관계로 이루어진 스코프의 계층적 구조에서 식별자를 검색함.)
me.hasOwnProperty('name');
1. 스코프 체인에서 me 식별자를 검색한다. 2. me 식별자는 전역에서 선언되었기 때문에 전역 스코프에서 검색된다. 3. me 식별자를 검색한 다음, me 객체의 프로토타입 체인에서 hasOwnProperty 메서드를 검색한다. => 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용함
오버라이딩과 프로퍼티 섀도잉
- 프로토타입이 소유한 프로퍼티를 프로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티라고 함.
- 인스턴스 메서드가 프로토타입 메서드를 오버라이딩하여,,
- 상속 관계에 의해 프로퍼티가 가려지는 현상을 프로퍼티 섀도잉이라고 한다.
- 오버라이딩 : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
- 하위 객체를 통해 프로토타입의 프로퍼티를 변경/삭제하는 것은 불가능한다. (get 허용, set 불가)
- 프로토타입에 직접 접근하면 가능.
프로토타입의 교체
- 프로토타입은 임의의 다른 객체로 변경할 수 있다.
생성자 함수에 의한 프로토타입의 교체
const Person = (function () {
function Person(name) {
this.name = name;
}
//1) 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체하기
Person.prototype = {
sayHello() {
console.log(`Hi My name is ${this.name}`);
}
};
return Person;
}());
const me = new Person('Lee');
//프로토타입을 교체하면 constructor프로퍼티와 생성자 함수 간 연결이 파괴됨.
console.log(me.constructor === Person); //false
//프로퍼티 체인에 따라 Object.prototype의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); //true
- 프로토타입은 생성자의 prototype 프로퍼티 뿐만 아니라 인스턴스의 __proto__ 접근자 프로퍼티를 통해 접근할 수 있다.
- 따라서 인스턴스의 접근자 프로퍼티나 Object.setPrototypeOf 메서드를 통해 프로토타입을 교체할 수 있다.
생성자 함수의 prototype 프로퍼티에 다른 임의의 객체를 바인딩하는 것은 미래에 생성할 인스턴스의 프로토타입을 교체하는 것이고, __prototype__ 접근자 프로퍼티를 통해 프로토타입을 교체하는 것은 이미 생성된 객체의 프로토타입을 교체하는 것이다.
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
//프로토타입으로 교체할 객체
const parent = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
//1) me 객체의 프로토타입을 parent 객체로 교체하기
Object.setPrototypeOf(me, parent);
//== me.__proto__ = parent;
me.sayHello();
//프로토타입을 교체하면 constructor프로퍼티와 생성자 함수 간 연결이 파괴됨.
console.log(me.constructor === Person); //false
//프로퍼티 체인에 따라 Object.prototype의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); //true
- 인스턴스에 의한 프로토타입 교체 : Person 생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리키지 않는다.
- (근데 생성자 함수에 의한 프로토타입 교체는 ok)
- 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하고, 생성자 함수의 prototype 프로퍼티를 재설정해서 파괴된 생성자 함수와 프로토타입 간의 연결을 되살려볼 수 있다.
-> (프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 꽤나 번거로움 ;;)
-따라서프로토타입은직접교체하지 않는 것이 좋다~~~~~ 상속관계를 인위적으로 설정할 때는 이후 나오는직접상속이 더 안전하다.
Instanceof 연산자
-이항 연산자로서좌변에는 객체를 가리키는 식별자,우변에는 생성자 함수를 가리키는 식별자 (함수가 아니면 TypeError 발생)
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
//Person.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가
console.log(me instanceof Person); //true
//Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가
console.log(me instanceof Object); //true
- 프로토타입 교체를 통해 instanceof 연산자의 동작 과정 알아보기
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
const parent = {};
Object.setPrototypeOf(me, parent);
//Person의 생성자 함수와 parent 객체는 연결되어있지 않다.
console.log(Person.prototype === parent); // false
console.log(parent.constructor === Person); // false (Person 말고 Object가 ok일듯)
//Person.prototype이 me 객체의 프로토타입 체인 상에 존재하지 않아서 false로 평가되는거임.
console.log(me instanceof Person); //false
console.log(me instanceof Object); //true
- parent 객체를 Person 생성자 함수의 prototype 프로퍼티에 바인딩하면, me instanceof Person은 true..
for ... in문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true인 프로퍼티를 순회하며 열겨한다.
-배열에는 for ... in 문을 사용하지 않고 일반적인 for문이나for ... of 문(키가 아닌 값 할당) 또는Array.prototype.forEach메서드 사용을 권장
- 사실 배열도 객체이므로 프로퍼티와 상속받은 프로퍼티가 포함될 수 있음.
Object.keys/valules/entries 메서드
- for ... in문은 객체 자신의 고유 프로퍼티 뿐만 아니라 상속받은 프로퍼티도 열거하기 때문에 Object.prototype.hasOwnProperty 메서드로 객체 자신의 프로퍼티인지 확인하는 추가 절차가 필요하다.
-객체 자신의 프로퍼티만 열거하기 위해서는 Object.keys/valules/entries 메서드를 사용하는 것을 권장한다.
const person = {
name: 'Lee',
address: 'Seoul',
__proto__: {age : 20}
};
//객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환
console.log(Object.keys(person)); ["name", "address"]
//ES8에서 도입된 Object.values 메서드
//객체 자신의 열거 가능한 프로퍼티 값을 배열로 반환
console.log(Object.values(person)); // ["Lee", "Seoul"]
//ES8에서 도입
//객체 자신의 열거 가능한 프로퍼티 키와 값의 쌍의 배열을 배열에 담아 반환
console.log(Object.entries(person)); // [ ["name", "Lee"], ["address", "Seoul"] ]
Object.entries(person).forEach(([key, value]) => console.log(key, value));
/*
name Lee
address Seoul
*/
자바스크립트는 명령형, 함수형, 프로토타입 기반 객체지향 프로그래밍을 지원하는 멀티 패러다임 프로그래밍 언어
객체지향 프로그래밍
- 자바스크립트는 객체 기반의 프로그래밍 언어이며 자바스크립트를 이루고 있는 거의 "모든 것"이 객체다.
- 객체는 상태 데이터와 동작을 하나의 논리적인 단위로 묶은 복합적인 자료구조라 할 수 있음.
- 이때 객체 상태 데이터를 프로퍼티, 동작을 메서드라고 부름.
상속과 프로토타입
- 자바스크립트는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 피한다.
function Circle(radius) {
this.radius = radius;
this.getArea = function () {
return Math.PI * this.radius ** 2;
};
}
const circle1 = new Circle(1);
const circle2 = new Circle(2);
//getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
//getArea 메서드는 하나만 생성해서 모든 인스턴스가 사용하는 것이 바람직함.
console.log(circle1.getArea === circle2.getArea); //false
console.log(circle1.getArea());
console.log(circle2.getArea());
- 위 생성자 함수는 문제가 있음.
- Circle 생성자 함수는 인스턴스를 생성할 때마다 getArea 메서드를 중복 생성하고 모든 인스턴스가 중복 소유한다.
- 메모리를 불필요하게 낭비하고, 퍼포먼스에도 악영향을 준다.
자바스크립트는 프로토타입을 기반으로 상속을 구현한다. - 상속을 통해 불필요한 중복을 제거해보자!
//생성자 함수
function Circle(radius) {
this.radius = radius;
}
// Circle 생성자 함수가 생성한 모든 인스턴스가 getArea 메서드를
// 공유해서 사용할 수 있도록 프로토타입에 추가함.
// 프로토타입은 Circle 생성자 함수의 prototype 프로퍼티에 바인딩되어 있다.
Circle.prototype.getArea = function() {
return Math.PI * this.radius ** 2;
};
const circle1 = new Circle(1);
const circle2 = new Circle(2);
//부모 객체의 역할을 하는 프로토타입 Circle.prototype으로부터 getArea 메서드를 상속받는다.
console.log(circle1.getArea === circle2.getArea); //true
- 생성자 함수가 생성할 모든 인스턴스가 공통적으로 사용할 프로퍼티나 메서드를 프로토타입에 미리 구현해두면
생성자 함수가 생성할 모든 인스턴스는 별도의 구현 없이 상위(부모) 객체인 프로토타입의 자산을 공유하여 사용할 수 있다.
프로토타입 객체
- 프로토타입 객체는 객체 간 상속을 구현하기 위해 사용한다.
- 프로토토입을 상속받은 하위(자식) 객체는 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 사용할 수 있다.
- 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가지고, 이 내부 슬롯의 값은 프로토타입의 참조다.
- 객체 생성 방식에 의해 [[Prototype]]에 저장되는 프로토타입이 결정된다.
- 객체 리터럴에 의해 생성된 객체의 프로토타입 : Object.Prototype
- 생성자 함수에 의해 생성된 객체의 프로토타입 : 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체
모든 객체는 하나의 프로토타입을 갖는다. 모든 프로토타입은 생성자 함수와 연결되어 있다. - 즉, 객체와 프로토타입과 생성자 함수는 서로 연결되어 있다.
1) __proto__ 접근자 프로퍼티
- 모든 객체는 __proto__ 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 [[Protoype]] 내부 슬롯에 간접적으로 접근할 수 있다.
- 접근자 프로퍼티는 자체적으로 값([[Value]] 프로퍼티 어트리뷰트)을 가지지 않고, 접근자 함수인 ([[Get]], [[Set]] 프로퍼티 어트리뷰트로 구성된 프로퍼티다.)