배열같은 객체이자 원시 이진 데이터에 접근
Array 객체는 동적으로 길이 변화가 가능하고, 어떤 JS 값이든 가질 수 있음.
JS 엔진은 이러한 배열을 더 빠르게 하기 위해 최적화를 수행함.
이때 음성,영상 조작 기능, Websocket 등 Web App이 점점 고도화 되기에
원시 데이터에 접근해서 더 빠르게 처리하길 원했는데,
이에 등장한 것이 바로 형식화 배열, Typed Array
그렇지만 형식화 배열은 Array는 또 아니다...
더 빠른 최적화를 위해 Typed Array는 버퍼와 뷰로 분류.
원하는 크기의 메모리 공간을 할당 받고, 데이터를 저장하는 클래스
데이터를 1Byte씩 나누어 저장.
보통 이진 데이터를 담기 위한 목적이고,
// 지정한 byte만큼의 메모리공간을 차지하는 버퍼를 생성한다.
const buf1 = Buffer.alloc(bytes) // 해당 메모리 공간의 모든 값을 0으로 초기화한다
const buf2 = Buffer.allocUnsafe(bytes) // 0으로 초기화하지 않기 때문에 좀 더 빠르게 버퍼를 생성한다.
alloc을 통해 생성한 버퍼는 단지 메모리를 확보할 뿐, 메모리에 데이터 기록이 불가.
이 버퍼에 데이터를 쓰기 위해선, 해당 버퍼를 바탕으로 형식화 배열 View를 만들어야 함.
// (1)
const buf1 = Buffer.from("abc")
// (2)
const buf2 = Buffer.from([1, 100, 255])
parameter로 넘긴 지정된 값을 담는 버퍼를 생성.
딱 지정한 값이 가지는 byte만큼 메모리가 버퍼에 할당.
문자열은 넣으면 한문자당 1byte이므로, (1)은 3바이트의 메모리를
(2)는 배열이므로, 배열 길이 만큼의 바이트를 가져 3바이트를 갖는다.
cosnt buf1 = Buffer.from("abc")
console.log(buf1)
// <Buffer 61 62 63>
이걸 toString()을 통해서 쉽게 파악할 수 있다.
const buf = Buffer.from("abc");
console.log(buf.toString()); // "abc"
console.log(buf.toString("hex")); // "616263"
console.log(buf.toString("base64")); // "YWJj"
const src =
"https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png";
fetch(src)
.then((res) => res.arrayBuffer())
.then((buffer) => console.log(buffer.byteLength)); // output: 13504
이런식으로 구글 홈페이지의 로고를 fetch로 가져와 ArrayBuffer로 변환하여,
이미지 데이터의 용량 만큼 버퍼를 생성하는 것을 볼 수 있다.
fetch(src)
.then(res => res.arrayBuffer())
.then(buffer => {
const blob = new Blob([buffer]); // ArrayBuffer → Blob
const dataUrl = window.URL.createObjectURL(blob); // Blob → URL
const img = document.createElement("img");
img.src = dataUrl; // <img src="blob:...">
document.body.appendChild(img); // 화면에 이미지 붙임
});
ArrayBuffer에 담긴 이진 데이터를 Blob으로 감싸면 브라우저가 이해 가능한 파일 객체처럼 변환
이 Blob을 URL로 만들고 의 src에 넣으면 실제 이미지가 화면에 뜨게 된다!
순회가능한 자료구조를 만들기 위해 ECMA 사양에서 미리 약속한 규칙.
순회 가능한 자료구조는 배열, 문자열, 유사 배열 객체, DOM 컬렉션 등을,
for 문, for...in 문, for...of문, forEach, 스프레드, 구조분해할당 등 ...
이터러블 프로토콜을 준수한 객체가 이터러블,
이 이터러블이어야, 앞서 봤던 반복문이라고 부르는 것들로 순회가 가능하다.
const array = [1, 2, 3];
// 배열은 Array.prototype의 Symbol.iterator 메서드를 상속받는 이터러블이다.
console.log(Symbol.iterator in array); // true
// 이터러블인 배열은 for...of 문으로 순회 가능하다.
for (const item of array) {
console.log(item);
}
// 이터러블인 배열은 스프레드 문법의 대상으로 사용할 수 있다.
console.log([...array]); // [1, 2, 3]
만약 이터러블이 아니라면,
const obj = { a: 1, b: 2 };
// 일반 객체는 Symbol.iterator 메서드를 구현하거나 상속받지 않는다.
// 따라서 일반 객체는 이터러블 프로토콜을 준수하는 이터러블이 아니다.
console.log(Symbol.iterator in obj); // false
// 이터러블이 아닌 일반 객체는 for...of 문으로 순회할 수 없다.
for (const item of obj) { // → TypeError: obj is not iterable
console.log(item);
}
// 이터러블이 아닌 일반 객체는 배열 디스트럭처링 할당의 대상으로 사용할 수 없다.
const [a, b] = obj; // → TypeError: obj is not iterable
앞서 봤던 반복문등을 사용할 수 없다.
둘의 차이는 Symbol.iterator를 상속 받는지 여부.
이터러블인 배열은 상속받지만, 일반 Object는 상속받지 않는다.
이터러블의 Symbol.iterator 메서드를 호출하면, 이터레이터를 반환하는데,
이 이터레이터는 next 메서드를 갖게 된다.
// 배열은 이터러블 프로토콜을 준수한 이터러블이다.
const array = [1, 2, 3];
// Symbol.iterator 메서드는 이터레이터를 반환한다.
// 이터레이터는 next 메서드를 갖는다.
const iterator = array[Symbol.iterator]();
// next 메서드를 호출하면 이터러블을 순회하며 결과를 나타내는
// 이터레이터 리절트 객체를 반환한다.
// 이터레이터 리절트 객체는 value와 done 프로퍼티를 가진다.
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
for (변수 선언문 of 이터러블) {...}
이 for of문의 작동 원리가 앞서 봤던 이터레이터의 next 메서드를 활용하는 것!
next가 반환한 결과 객체의 value 프로퍼티 값을 계속 저 변수에 할당한다.
done 값이 true가 되는 순간 이터러블의 순회를 중단한다.
일단, 이 프로토콜이 없던 시절(~ES5)에는
배열은 for, for...in, forEach로 순회해야했고,
문자열, 유사배열 객체, DOM 컬렉션도 각각 맞는 방법으로 순회해야했음.
var arr = [1, 2, 3];
// 전통적인 for 문
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// forEach 메서드
arr.forEach(function(item) {
console.log(item);
});
var str = "hi";
// 전통적인 for 문
for (var i = 0; i < str.length; i++) {
console.log(str[i]);
}
var obj = { a: 1, b: 2 };
// for...in (객체의 key 순회)
for (var key in obj) {
console.log(key, obj[key]);
}
즉, 자료구조마다 규칙이 달라서 불편했음
따라서 ES6 부터 이터레이션 프로토콜을 도입,
그래서 배열, 문자열, Map, Set, DOM 컬렉션 등이 전부 for...of, 스프레드 문법, 디스트럭처링 등에서
똑같이 사용 가능
const arr = [1, 2, 3];
for (const item of arr) {
console.log(item); // 1, 2, 3
}
const str = "hi";
for (const ch of str) {
console.log(ch); // h, i
}
ES6 에서 도입된 제너레이터, 코드 블록의 실행을 일시 중지하고, 필요한 시점에 재개 할 수 있는 특수함수.
function* genDecFunc() {
yield 1;
}
const genExpFunc = function* () {
yield 1;
};
const obj = {
*genObjMethod() {
yield 1;
}
};
class MyClass {
*genClsMethod() {
yield 1;
}
}
정리하자면, 제너레이터는 이터레이터를 직접 만드는 번거로움 없이 쉽게 정의할 수 있는 함수.
이터레이터에서 봤던 Symbol.iterator와 Next()를 직접 구현하지 않아도
제너레이터만 정의하면 자동으로 이터레이터 프로토콜을 따르게 된다.
function* gen() {
yield 1;
yield 2;
return 3;
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: true }
console.log(g.next()); // { value: undefined, done: true }
제너레이터는 여기에 더해, return, throw 메서드를 통해서
반복을 멈추거나, 에러처리등이 가능하다.
function* genFunc() {
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.error('caught in generator:', e);
} finally {
// finally 블록이 있다면 return/throw 로 종료될 때 여기 실행됨
// console.log('finally');
}
}
const g = genFunc();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return('End!')) // { value: 'End!', done: true } ← 즉시 종료
console.log(g.next()); // { value: undefined, done: true } (이미 닫힘)
return 키워드를 통해 done 프로퍼티의 값을 true로 변경해버렸고
그 다음 next를 통해 순회하려해도 순회가 불가능.
async/await이 나오기 전에는 비동기 처리를 요런식으로 구현했다더라...
const fetch = require('node-fetch');
// 제너레이터 실행기
const async = generatorFunc => {
const generator = generatorFunc(); // ① 제너레이터 객체 생성
const onResolved = arg => {
const result = generator.next(arg); // ② yield 다음으로 진행
return result.done
? result.value // ③ 끝났으면 최종 값 반환
: result.value.then(res => onResolved(res)); // ④ 아니면 Promise 끝날 때까지 기다림
};
return onResolved(); // 실행 시작
};
앞서 본 제너레이터를 활용해서 비동기 처리를 구현하다보면 코드가 길어진다.
프로미스 기반의 async/awiat이 ES8에서 등장.
async function fetchUser() {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const user = await response.json();
console.log(user);
}
fetchUser();
await 키워드는 반드시 async 함수 내부에서 사용 되어야 한다.
async 함수가 명시적으로 프로미스를 반환하지 않더라도 암묵적으로 resolve하는 프로미스를 반환