객체
: 0개 이상(property가 없어도 객체)의 property의 집합이고 각 property는 key/value의 쌍으로 구성된다. /* 객체 literal로 객체 생성하기 */
var person = { // person은 객체
name: '홍길동', // property
age: 20 // name과 age는 property key, '홍길동'과 20은 property value
}
💡 property 작성 시 key: name 으로 쓰는 것이 권장된다.(key에 바로 콜론 : 붙이기. 띄어쓰기x)
key: name (o) , key : name (x)
크게 상관은 없지만 전자가 권장된다는 것을 알아두자.
💡 JSON과 자바스크립트 객체는 전혀 상관없다. JSON은 일반적으로 사용하는 데이터 형태의 표준.
객체를 생성하는 방법
var obj = {
'name': '홍길동',
'printName': function myPrint(){
console.log(`내 이름은 ${this.name}`); // this는 현재 사용하는 객체를 지칭하는 레퍼런스
}
};
console.log(typeof obj); //object
Property의 key
var obj = {
name: '홍길동', // key가 식별자 명명 규칙에 맞다면 quotation으로 감싸지 않아도 된다. 'name'(o), name(o)
printName: function myPrint(){ // 함수가 property의 value가 될 수 있다.
console.log(`내 이름은 ${this.name}`); // this는 현재 사용하는 객체를 지칭하는 레퍼런스
},
"!myPhone": '010-1234-5678', // key가 식별자 명명 규칙에 맞지않다면 quotation으로 감싸주어야한다.
10: 300 // key를 숫자(10)으로 써도 자동 casting해서 string('10')으로 인식한다.
};
Property의 value
💡 JavaScript에서는 함수도 객체이므로 함수를 값으로 가질 수 있다.
→ 함수의 method
(다른 언어와 다르게 자바스크립트에서는 method도 property로 취급된다. 통상적으로 method라고 부를뿐이다.)
Property의 동적 추가, 삭제
.
(dot rotation)[ ]
(bracket notation) /* property의 동적 추가, 삭제 */
var obj = {
myName: '홍길동'
}
obj.myAge = 20;
obj['myAge'] = 30;
obj[10] = 300;
//obj."!myPhone" = '010-1234-5678'; // 불가능
obj["!myPhone"] = '010-1234-5678';
console.log(obj); //{ '10': 300, myName: '홍길동', myAge: 30, '!myPhone': '010-1234-5678' }
delete obj.myAge;
console.log(obj); //{ '10': 300, myName: '홍길동', '!myPhone': '010-1234-5678' }
기타 다른 형태의 property
var obj = {
10: '문자열 10으로 인식됩니다.',
let: 'let도 keyword로 사용가능하지만 혼동되므로 권장되지 않아요.',
myName: '홍길동',
'!myName': 'naming rule에 맞지않는 경우는 따옴표로 감싸주어야 합니다.',
myName: 'key를 중복해서 쓰면 마지막에 나온 것을 씁니다.'
}
console.log(obj);
// 출력 결과
/*
{
'10': '문자열 10으로 인식됩니다.',
let: 'let도 keyword로 사용가능하지만 혼동되므로 권장되지 않아요.',
myName: 'key를 중복해서 쓰면 마지막에 나온 것을 씁니다.',
'!myName': 'naming rule에 맞지않는 경우는 따옴표로 감싸주어야 합니다.'
}
*/
console.log(obj.myAddress); //정의되지 않은 key를 쓰면 error가 아닌 undefined가 출력된다.
ES6에서 추가된 객체 literal 확장표현방식
let x = 1;
let y = 2;
// 식별자를 이용해서 property 정의하기
const obj = {x, y} // 변수의 이름을 key로 변수의 값을 value로 갖는다.
console.log(obj); //{ x: 1, y: 2 }
/* property의 value로 작성된 함수를 method로 나타내기 */
// 위 아래
// let myObj = {
// name: "홍길동",
// printName: function(){ // 얘는 원래 자바스크립트에서 말하는 method가 아님.
// console.log(this.name);
// }
// }
let myObj = {
name: "홍길동",
printName(){ // 자바스크립트라는 언어에서 얘기하는 method(축약표현으로 되어있는 형태)
console.log(this.name);
}
}
myObj.printName(); //홍길동
원시값 - immutalble(불변함)
객체 - mutable(가변함)
var obj = {name: ‘홍길동'}
유사 배열 객체(Array-like object)
Primative value를 객체처럼 쓰는 순간 내부슬롯에 값이 만들어지고 배열처럼 사용할 수 있도록 각각의 primative들이 만들어지고 여러 method가 자동적으로 상속받아서 많이 생긴다.
let myStr = ‘홍길동’;
myStr[0];
/* Primative value(원시값) vs 객체 */
let myStr = 'Hello'; // primative value
// primative value를 마치 객체(배열)처럼 사용할 수 있다.
console.log(myStr[0]); //H
console.log(myStr.length); //5
myStr[0] = 'h'; // 그림의 key 0의 값이 H에서 h로 바뀐다고 생각하면 된다.(0: H -> 0:h)
// primative value 자체의 값은 변하지 않는다.
console.log(myStr); //Hello // 그림으로 따지면 맨 상단 내부 슬롯의 Hello
console.log(myStr[0]); //H // 그림으로 따지면 맨 상단 내부 슬롯의 Hello의 H
같은 객체를 참조할 때의 내부 구조
function
: 일련의 과정을 수행하기 위한 statement를 { } (중괄호)를 이용해서 하나의 실행단위로 만들어 놓은 것
함수를 사용하는 이유
반복적인 code → 함수화
함수 정의(definition) & 호출(call, invoke)
변수는 선언한다는 표현을 하지만 함수는 정의한다고 표현한다.
function add(x, y) { // add는 함수이름, x, y는 parameter(매개변수)
return x + y; // x + y는 리턴값
}
add(3, 5); // 3, 5는 argument(인수)
// parameter와 argument 구분할 것
함수 literal
literal은 “값” 이다.
var func = function add(x, y){
return x + y;
}
// function부터 } 닫는 괄호까지가 "함수 literal"이다.
var myFunc = function add(x, y){
return x + y;
}
console.log(add(3, 5)); // ReferenceError: add is not defined
console.log(myFunc(3, 5)); //8
함수를 정의(definition)하는 방법
함수 선언문
함수 literal과 똑같이 생겼지만 변수에 저장할 수 없다!!
function add(x,y){
return x + y;
}
/* 함수 선언문 */
function foo(){ // foo라는 식별자. 내부적으로는 foo라는 이름의 변수가 생기고 함수 객체가 생성됨.
console.log('foo 함수');
}
// 객체 하나 덜렁 있는 것.. 그냥 숫자 3 하나 딱 있는거랑 똑같다.
// (1 + 2) * 3
(function bar() {
console.log('bar 함수');
})
foo();
bar(); // 사용불가능
함수 표현식
함수 이름 자체를 사용할 수 없고 식별자를 이용해서 함수를 사용할 수 있다. (그래서 보통 익명함수로 쓴다.)
var myFunc = function(x, y){
return x + y;
}
Function 생성자 함수
사용하기 굉장히 불편하므로 권장되지 않는다.
var add = new Function('x', 'y','return x+y');
‘ES6’ → 화살표함수(Arrow function)
화살표 함수에서는 this
를 사용할 수 없다.
var add = (x, y) => x + y;
함수 선언문 vs 함수 표현식 hoisting
자바스크립트 엔진에 의해서 (런타임 하기 전에) 묵시적으로 함수 이름과 똑같은 변수가 만들어진다.
/* 함수 선언문 vs 함수 표현식 hoisting */
foo(); // 호출된다.
add(); // TypeError: add is not a function
// 함수 선언문
function foo(){
console.log('foo 함수');
}
// 함수 표현식
var add = function bar(){
console.log('bar 함수');
}
함수 선언문은 hoisting의 위험이 있고 상식적으로 프로그래밍을 하는 입장에서 함수 표현식을 사용하는 것이 맞다.
💡 모든 식별자는 hoisting이 일어난다.
함수 호출(call, invoke)
함수는 함수 이름으로 호출하지 않는다. 함수에 대한 identifier(식별자)를 이용하여 호출한다.
Overloading
이 발생하지 않는다. → 인자의 개수가 달라도 호출 가능하다. “arguments라는 내부객체를 이용”하기 때문이다.
Overloading
: 같은 함수의 이름을 가지고 있지만 파라미터(매개변수) 및 리턴 타입 등의 특징이 다른 여러개의 함수를 만드는 것.
// 함수 선언문
function add(x, y){
return x + y;
}
console.log(add(2, 5));
// 인자를 순서대로 mapping하므로 x는 2로 초기화되고, y는 초기화되지 않아 undefined가 된다.
console.log(add(2)); //NaN (연산 불가)
// 인자의 개수가 달라도 함수 호출은 가능하다.
// 위에 선언한 함수 add는 내부적으로 arguments를 다 저장해둔다.
// arguments에 대해 알아보자.
function add() {
// add(2, 3, 4)를 호출하면 arguments에 [2, 3, 4]가 저장된다.
let sum = 0;
// arguments는 유사배열객체(Array-like Object)이다.
// 유사배열객체는 배열과 비슷하게 생겼지만 배열은 아니다.(배열의 특수한 method를 사용할 수 없음.)
// 모든 유사배열객체는 length라는 property를 가지고 배열처럼 index를 이용해서 access 가능. 당연히 순환 가능.
for(let i=0; i<arguments.length; i++){
sum += arguments[i];
}
return sum;
}
console.log(add(2, 3, 4)); //9
/* 함수에 return 구문이 없으면 undefined를 반환한다. */
function add() {
let sum = 0;
for(let i=0; i<arguments.length; i++){
sum += arguments[i];
}
//return sum;
}
console.log(add(2, 3, 4)); //undefined
즉시 실행 함수(IIFE; Immediately Invoked Function Expression)
함수를 선언함과 동시에 call(invoke)
/* IIFE(즉시 실행 함수) */
(function add(){
let x = 10;
let y = 20;
console.log(x + y);
}());
// 노란색 형광펜 부분을 객체(함수 literal) 하나라고 생각한다.
// 빨간생 형광펜 부분이 함수 호출부
// ( )로 감싸주지 않으면 자바스크립트 엔진이 문법 오류라고 생각하기 때문에 ( )로 감싸준다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="index1.js"></script>
<script src="index2.js"></script>
<script src="index3.js"></script>
<script src="index4.js"></script>
<script src="index5.js"></script>
</head>
<body>
소리없는 아우성!!
</body>
</html>
여기서 자바스크립트 파일들은 전역변수 공간을 공유하고 있다. 그런데 이 때 index1.js에서 정의한 변수를 index2.js에서 같은 이름으로 사용하는 동일한 변수명으로 인한 문제 등으로 충돌이 발생할 수 있다. 따라서 이런 경우에 ( )로 묶어서 즉시 실행 함수로 작성한다면 이 파일들은 전역변수 공간이 아닌 지역변수 공간을 쓰게 되고 충돌이 발생하지 않는다.중첩 함수(nested function)(또는 내부 함수(inner function))
중첩 함수(내부 함수)를 가지고 있는 바깥쪽 함수는 외부 함수(outer function)라고 한다.
/* 중첩 함수(nested function)(또는 내부 함수(inner function)) */
var x = 100; // global scope(전역변수)
var y = 200; // global scope(전역변수)
function outer(){ // outer function
let x = 0; // function level scope(지역변수)
function inner(){ // inner function
let x = 10; // function level scope(지역변수), scope가 다르기 때문에 위에 있는 x와는 다른 x이다.
console.log(y); //200
**// 자바스크립트 엔진은 처음에는 자신의 지역 scope에서 변수를 찾다가 찾는 변수가 없으면 상위 scope에서 찾는다.
// 하위 scope -> 상위 scope로 계속해서 찾아 올라간다.
// 전역변수는 가장 상위 scope이기 때문에 찾는데 오래걸린다. 그래서 전역변수를 많이 쓰면 좋지 않다는 것!
// scope chain: 모든 scope는 chain으로 연결되어 있다.**
}
}
일급 객체(first-class citizen 또는 first-class object)
💡 일급 객체가 되기위한 4가지 조건
1. 익명의 literal로 생성 가능 → 동적으로 생성이 가능
2. 객체가 변수나 자료구조에 저장 가능
3. 객체를 다른 함수의 인자로 전달 가능
4. 함수의 리턴값으로 객체를 사용
→ JavaScript의 함수는 위 조건들을 만족한다. 즉 JavaScript 함수는 “일급 객체”라고 할 수 있다.
/* 일급 객체 */
// 잘 만든 함수가 존재한다. (아래 예시의 repeat 함수)
// 그런데 이 함수의 기능을 변경(추가)하고 싶다.. -> 그 방법은?
// 1. 원래 있던 함수를 수정하자! -> 오류 발생 risk가 너무 크다.
// 2. 함수를 새로 추가해서 만들자! -> 이것도 이상해
// 3. 함수를 추상화시켜서 인자로 받아서 사용하자! -> 채택!!
function repeat(n, f){
for(var i=0; i<n; i++){
f(i);
}
}
let logAll = function(k){
console.log(k);
}
let logOdd = function(i){
if(i%2){
console.log(i);
}
}
repeat(5, logAll);
//repeat(5, logAll()); // 이거는 안된다!! ()는 함수를 호출하라는 뜻! logAll은 식별자임.
// logAll과 logOdd와 같은 함수들을 뭐라고 부를까??
// **콜백함수(Callback Function)**: 다른 함수의 인자로써 넘겨진 후 특정 이벤트에 의해 호출되는 함수.
// 그럼 콜백함수를 받아서 하나의 융합 함수를 만드는 repeat함수를 뭐라고 부를까??
// **고차함수(Higher-Ordered Function)**: 함수를 인자로 전달받거나 함수를 결과로 반환하는 함수.
// Callback function은 Higher-ordered function과 결합한다.
Scope
scope
: “식별자가 유효한 범위”
scope chain
: JavaScript Engine이 identifier를 찾을 때(identifier resolution
) 사용하는 메커니즘
💡 JavaScript Engine은 코드를 실행할 때 문맥(context)을 고려해서 실행한다.
→ 현재 실행중인 code가 어디에 존재하는 code인지와 code 주변 정보를 파악해서 실행
⇒ lexical Environment를 고려해서 실행한다. → (를 구현한 것이 바로) “execution context”
함수가 호출되었을 때
/* scope */
var x = 1; // 전역 scope의 전역변수
function foo(){
var x = 10; // 지역 scope의 지역변수
console.log(x); //10
}
function bar() {
console.log(x); //1
}
foo();
bar();
💡 전역변수를 사용하면
1. 가독성이 나빠진다.(오류의 여지가 많다.)
2. 메모리 resource를 소모한다.
3. 변수를 늦게 찾는다.(효율이 안좋다)
4. 다른 file과 변수 충돌이 생길 수 있다.
→ 꼭 필요하면 경우가 아니라면 사용을 줄이자.
내부 slot과 내부 method
객체는 Property의 집합이며 각각의 Property는 property attribute를 가진다.
JavaScript 객체는 내부 slot과 내부 method를 가진다.
[[…]]
대괄호 2개로 감싸진 형태로 나타난다.[[Prototype]]
는 obj.__Proto__
를 사용하면 내부 slot에 접근할 수 있는 방법을 제공해준다.[[Prototype]]
== __Proto__
이라고 생각하면 된다. [[Prototype]]
도 결국 객체 타입의 property다.💡 브라우저에서 직접 눈으로 확인해보자
var obj = { name: '홍길동' }; console.dir(obj) // dir로 객체의 세부 내용을 확인할 수 있다. -> 브라우저에서 확인해야 한다.
Object type의[[Prototype]]
이라는 내부슬롯을 가지고 있음을 확인할 수 있다.
Property attribute
property를 생성할 때 해당 property의 상세를 나타내는 값(기본값으로 정의된다.)
💡 Property의 상세(Property attribute)
1.[[Value]]
property의 값
2.[[Writable]]
property의 값을 수정할 수 있는지 여부(key에 대한 value를 고정시킬지 말지)
3.[[Enumerable]]
해당 property가 열거될 수 있는지 여부
4.[[Configurable]]
property attribute를 재정의 할 수 있는지 여부
위 4가지가 모두 내부슬롯[[…]]
으로 되어있다.
/* Property Attribute를 확인해보자. */
const person = {
name: 'Lee',
age: 20
};
// property attribute를 직접적으로 접근할 수 없기 때문에
// getOwnPropertyDescriptor 또는 getOwnPropertyDescriptors로 확인한다.
// property attribute를 descriptor 객체로 만들어서 가져올 수 있다.
// person이라는 객체의 name이라는 property에 대한 내용을 들고와!
console.log(Object.getOwnPropertyDescriptor(person, 'name')); // 하나의 property 가져오기
//{ **value**: 'Lee', **writable**: true, **enumerable**: true, **configurable**: true }
console.log(Object.getOwnPropertyDescriptors(person)); // 모든 property(name, age)에 대해 가져오기
// {
// name: {
// value: 'Lee',
// writable: true,
// enumerable: true,
// configurable: true
// },
// age: { value: 20, writable: true, enumerable: true, configurable: true }
// }
Property define(프로퍼티 정의하기)
/* property 정의하기 */
const person = {
age: 20
};
// person.name = '홍길동';
Object.defineProperty(person, 'name', {
value: '홍길동',
writable: false,
enumerable: false,
configurable: true
});
console.log(person); // 브라우저에서 확인해야한다.(node로 확인불가능)
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
person.name = '아이유'; // writable: false 이므로 바뀌지 않는다.
console.log(person); //홍길동
// Object.keys(): property의 key만 뽑아서 유사배열객체로 보여줌.
console.log(Object.keys(person)); // name의 enumerable: false 이므로 age만 나온다.
for(let idx in person){
console.log(idx); // property key (person의 key값들이 출력된다.)
console.log(person[idx]); // property value (person의 value들이 출력된다.)
}
📌 JavaScript의 for in과 for of 참고
자바스크립트 for in vs for of 반복문 정리