콜백(callback) : 다른함수의 전달인자로 넘겨주는 함수
document.querySelector('#link').addEventListener('click', (e) => { console.log('clicked!'); });
콜백 함수를 익명 함수로 전달하는 과정에서 또 다시 콜백 안에 함수 호출이 반복되어 코드의 들여쓰기 순준이 감당하기 힘들 정도로 깊어지는 현상
step1(function (value1) {
step2(function (value2) {
step3(function (value3) {
step4(function (value4) {
step5(function (value5) {
// Do something with value5
});
});
});
});
});
1) Promise
2) async/await
async function fn() {
let text = '하나';
//"둘"을 리턴
text = text + await new Promise((resolve, reject) => {
setTimeout(() => { resolve('둘') }, 0);
});
text += '셋';
console.log(text + '넷');
}
function samplePromise() {
return sampleFunc()
.then(data => return data)
.then(data2 => return data2)
.then(data3 => return data3)
.catch(err => console.log(err)) // 결과적으로 문제가 발생했다
}
async function sampleAsync() {
const data1 = await sampleFunc(); // 문제 발생시 data1값이 유효치 않음
const data2 = await sampleFunc2(data1);
return data2;
}
모든 식별자 (변수 이름, 함수 이름, 클래스 이름)는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효범위가 결정된다. 즉, 스코프는 식별자가 유효한 범위를 뜻한다.
MDN에 따르면 스코프란 현재 실행되는 컨텍스트를 말한다. (여기서 컨텍스트는 값과 표현식이 표현되거나 참조될 수 있음을 의미) 스코프는 또한 계층적인 구조를 가지고 있어 하위 스코프는 상위 스코프에 접근할 수 있지만 반대는 불가능하다.
console.log(a); // undefined
var a = 5;
console.log(a); // 5
console.log(a); // ReferenceError: a is not defined
let a = 5;
console.log(a); // 5
함수 선언식은 함수 전체를 호이스팅해 함수 선언 전에 함수를 사용할 수 있다.
함수 표현식은 별도의 변수에 할당하게 되는데, 변수는 선언부와 할당부를 나누어 호이스팅하며 선언부만 호이스팅한다.
//함수 선언식
foo() //"foo"
function foo() {
console.log("foo");
}
foo2() //foo2 is not defined
//함수 표현식
const foo2 = function() {
console.log("foo2");
}
클로저로 사용 : 함수가 종료돼도, 렉시컬 스코프의 index와 같은 정보를 유지한다.
콜백으로 사용 (다른 함수의 인자로 넘길 수 있음)
클로저는 함수와 함수가 선언된 어휘적 환경의 조합으로 중첩된 함수는 외부 범위(scope)에서 선언한 변수에도 접근할 수 있다.
1) 데이터를 보존하는 함수
2) 캡슐화와 정보 은닉
3) 모듈화(모듈 패턴)
변수,함수가 어디에서 사용 가능한지 알기 위해 그 변수,함수가 소스코드 내 어디에서 선언되었는지 고려하는 것을 의미한다. 중첩된 함수는 외부 scope에서 선언한 변수에도 접근할 수 있다. 스크립트 전체, 실행중인 함수, 코드블록 등은 자신만의 렉시컬 환경을 갖는다.
렉시컬 환경은 환경레코드, 외부렉시컬 환경으로 구성된다.
렉시컬 환경에서 모든 지역변수를 프로퍼티로 저장하고 있는 객체이다. this, 함수일 경우 매개변수도 포함된다.
현재 렉시컬 환경보다 더 상위의 렉시컬 환경이다. 스크립트는 최상위 렉시컬 환경이며 스크립트 내에 호출된 함수나 코드블록은 외부렉시컬 환경으로 스크립트 렉시컬 환경을 참조한다.
자바스크립트 코드가 실행되고 연산되는 범위를 나타내는 개념으로 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
자바스크립트는 실행 컨텍스트가 활성화되는 시점에 다음과 같은 현상이 발생한다.
자바스크립트 엔진은 자바스크립트 코드를 해석하고 실행하는 소프트웨어로 다음과 같은 두 가지 주요 구성 요소로 이루어져 있다.
현재 어떤 함수가 동작하고 있는지, 그 함수 내에서 어떤 함수가 동작하는지, 다음에 어떤 함수가 호출되어야 하는지 등을 제어한다.
함수 호출을 기록하고 실행 컨텍스트를 스택에 쌓고(pop) 제거하는 역할을 한다.
LIFO : 후입 선출 방식
자바스크립트는 단일 스레드 프로그래밍 언어이므로, 단일 호출 스택이 있다. 따라서 한 번에 하나의 일(Task)만 처리할 수 있다.
자바스크립트에서는 연산이나 비교 시에 자동으로 데이터 형을 변환하는 경우가 있는데 이를 암시적 변환(묵시적 변환, implicit conversion)이라고 한다. 예를 들어, 문자열과 숫자를 더할 때 숫자를 문자열로 변환하여 연산을 수행한다.
개발자가 명시적으로 데이터 형을 변환하는 경우로 자바스크립트에서 제공하는 다양한 내장 함수들을 사용하여 수행할 수 있다. 예를 들어, 문자열을 숫자로 변환하고자 할 때는 parseInt()나 parseFloat() 함수를 사용할 수 있다.
자바스크립트는 변수의 타입을 런타임에서 결정하는 동적 타입 언어(dynamic typing language)이다.
자바스크립트에서는 변수를 선언할 때 타입을 지정하지 않고, 변수에 저장되는 값에 따라 타입이 동적으로 결정된다. 예를 들어, 숫자형 변수에 문자열 값을 할당하면, 자바스크립트는 해당 변수를 문자열 타입으로 인식하게 된다.
이러한 유동성은 개발자가 데이터 형 변환과 같은 작업을 수동으로 처리할 필요가 없으므로, 코드 작성 시간이 단축되고, 코드의 유연성이 증가 하지만, 동적 타입 언어는 컴파일 시간에 타입 오류를 검출할 수 없다는 단점이 있다.
객체 지향 프로그래밍에서 상속을 구현하기 위한 중요한 개념으로 프로토타입 객체는 일반 객체와 마찬가지로 속성과 메소드를 가질 수 있다.
객체는 프로토타입 객체를 가리키는 숨겨진 링크인 [[Prototype]]을 가지는데 이 링크를 통해 객체가 가지고 있는 속성과 메소드를 찾아 사용할 수 있다. 만약 객체에서 속성이나 메소드를 찾을 수 없다면, 자바스크립트 엔진은 해당 객체의 프로토타입 객체에서 찾는다. 이러한 과정을 프로토타입 체인(prototype chain)이라고 한다.
프로토타입 체인을 이용하여 객체가 가지고 있는 속성과 메소드를 상속받을 수 있으며, 이를 통해 객체 간의 관계를 유지하면서 재사용성을 높일 수 있다.
자바스크립트의 참조 타입(객체나 배열)의 이터를 복사하는 방법
Object.assign()을 이용하면 객체 자체는 깊은 복사가 수행되지만, 2차원 이상의 객체는 얕은 복사가 수행된다. 아래 예시에서 객체는 서로 다른 주소를 참조하고 있어 깊은 복사가 이루어졌지만 내부의 객체는 같은 주소를 참조하고 있다.
let origin = {
a: 1,
b: { c: 2 }
};
let copy = Object.assign({}, origin);
copy.b.c = 3
console.log(origin === copy) // false
console.log(origin.b.c === copy.b.c) // true
스프레드 연산자 역시 배열 안에 객체로 중첩된 상태(2차원 이상의 객체)는 전개 연산자로 중첩된 곳까지 깊은 복사가 적용이 안된다.
const exampleData = [
{id: 0, name: '둘리', age: 8},
{id: 1, name: '도우너', age: 8},
{id: 2, name: '또치', age: 20},
];
const copyData = [...example];
copyData[2].name = '마이콜';
console.log(exampleData[2].name); // '마이콜'
console.log(copyData[2].name); // '마이콜'
const exampleData = [
{id: 0, name: '둘리', age: 8},
{id: 1, name: '도우너', age: 8},
{id: 2, name: '또치', age: 20},
];
const copyData = JSON.parse(JSON.stringify(exampleData));
copyData[2].name = '마이콜';
console.log(exampleData[2].name);
// '또치'
console.log(copyData[2].name);
// '마이콜'
const exampleData = [
{id: 0, name: '둘리', age: 8},
{id: 1, name: '도우너', age: 8},
{id: 2, name: '또치', age: 20},
];
const copyData = _.cloneDeep(exampleData);
copyData[2].name = '마이콜';
console.log(exampleData[2].name);
// '또치'
console.log(copyData[2].name);
// '마이콜'
변하지 않는 값을 처리하는 방법은 간단하게는 const 선언자로 상수 선언을 하는 방법이 있다. 개별 값은 이렇게 해도 되지만, 중간에 변경되면 안 되는 중요 설정 값을 담은 객체나, 원본 데이터를 담고 있는 객체 같은 경우, const 선언자로는 불변성을 유지할 수 없다.
그래서 자바스크립트는 객체에 불변성을 유지할 수 있도록 별도의 객체 메서드를 지원하고 있으며, preventExtensions < seal < freeze 순으로 불변성이 강하게 유지된다.
freeze로 불변 속성을 부여한 객체는 속성의 값을 수정할 수도 없고, 속성을 추가하거나 삭제할 수도 없다.
const apiconfig = {
name: 'API서버',
server: {
ip: '192.168.0.3',
port: '8081',
userid: 'apost',
password: 'dpdlvldkdl',
authkey: 'QK#ETRA1*AA',
}
}
Object.freeze(apiconfig)
apiconfig.name = 'API테스트서버' // freeze.js:14 Uncaught TypeError: Cannot assign to read only property 'name' of object '#<Object>'
하지만 freeze()는 strict 모드에서 정상적으로 동작하기 때문에 코드 상단에 "use strict"를 선언해야 freeze()가 동작한다. 또한 하위에 중첩된 객체가 있으면 하위 객체의 속성은 freeze가 적용되지 않는다.
따라서 앞서의 객체는 다음과 같이 수정해야 온전히 freeze()가 적용된다.
"use strict";
const apiconfig = {
name: 'API서버',
server: {
ip: '192.168.0.3',
port: '8081',
userid: 'apost',
password: 'dpdlvldkdl',
authkey: 'QK#ETRA1*AA',
}
}
Object.freeze(apiconfig)
Object.freeze(apiconfig.server)
freeze()와 유사한 기능을 하며, 중첩 객체에 대해서는 하위 객체에도 seal()을 적용해야 하는 점도 동일하나 기존에 있는 속성의 값은 자유롭게 변경할 수 있다.
seal()과 유하하나 속성 삭제가 가능하다.