Code Monkey home page Code Monkey logo

tip-note's Introduction

Tip-Note

I have written the development tips and troubleshooting that I experienced during development to issue tab

This repository refers to BKJang's dev-tips repository. Thank you.🙏

tip-note's People

Contributors

seonhyungjo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

tip-note's Issues

값을 비교해보자(String, Array, Object 등등)

나도 구현할 수 있겠는데?

value-equal이라는 라이브러리가 있다.
react-router-dom내부를 살펴보다가 history라는 npm 라이브러리에서 사용을 하고 있어서 열어보니 어느 타입이든 값이 같은지 비교를 해주는 라이브러리가 있었다. 하물며 Array의 값들이 같은지 Object안에 있는 값들이 같은지를 비교를 해준다.

실제로 내부는?

실질적으로 코드의 양은 엄청나게 적다는 것을 확인했다.

function valueOf(obj) {
  return obj.valueOf ? obj.valueOf() : Object.prototype.valueOf.call(obj);
}

function valueEqual(a, b) {
  // Test for strict equality first.
  if (a === b) return true;

  // Otherwise, if either of them == null they are not equal.
  if (a == null || b == null) return false;

  if (Array.isArray(a)) {
    return (
      Array.isArray(b) &&
      a.length === b.length &&
      a.every(function(item, index) {
        return valueEqual(item, b[index]);
      })
    );
  }

  if (typeof a === 'object' || typeof b === 'object') {
    var aValue = valueOf(a);
    var bValue = valueOf(b);

    if (aValue !== a || bValue !== b) return valueEqual(aValue, bValue);

    return Object.keys(Object.assign({}, a, b)).every(function(key) {
      return valueEqual(a[key], b[key]);
    });
  }

  return false;
}

VS Code에서 Java 디버깅 해보기

VS Code에서 Java 디버깅 해보기

준비물

따라서 설치 및 셋팅하기

Visual Studio Code + Java - VS Code에서 Java 사용하기

위에 블로그를 잘 따라해서 셋팅까지는 잘했습니다.

🚨 그런데 원하는 디버그가 되지 않는다. 🚨

외부 터미널로 디버깅하기

알고리즘 테스트를 위해서 Input하는데 사용하는 Scanner, BufferedReader, StringTokenizer가 제대로 사용되지 않아서 방법을 찾음

launch.json console 수정하기

    {
      "type": "java",
      "name": "Debugging Java",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "console": "externalTerminal", // 외부 터미널로 설정하기
      "stopOnEntry": false,
      "mainClass": "",
      "args": ""
    }

해당 부분을 internalTerminal로 하게 되면 VSCode의 내부 기본설정된 터미널로 작동도 된다.

그러나 개인적으로 외부터미널이 깔끔한 것 같습니다.

xPath??

xPath를 처음으로 접하였다.
XML관련되 작업을 하게 되다가 XML xPath search라는 것을 알게 되었고, 기존의 알고 있던 사이트인 devhint 에 잘 나와있어 링트로 대체합니다.

https://devhints.io/xpath

GitHub에 사진 주소로 올리는 방법

GitHub에 사진 주소로 올리는 방법

기존의 사진은 자신이 사용하고 있던 서버에 올려서 주소를 넣거나 또는 다른 사람이 올려놓은 사진의 주소를 가져와서 넣는다.

또한 로컬의 사진을 같이 올려서 상대 주소나 절대 주소로 넣는 방법등 다양하다.

그러나

로컬 사진을 github의 주소를 사용해서 사진을 올려보자 ❗️❗️

깃헙 저장소에 사진 올리기

  1. 자신의 레포의 issue에 들어가서 new issue를 만든다.
  2. 작성하는 공간에 올리고 싶은 사진을 Drag&Drop 한다.
  3. github에 사진이 올라가면서 주소를 만들어준다.
  4. 만들어진 주소를 자신의 .md 파일에 붙여넣기 해서 사용한다.

이렇게 사용하게 되면 나의 레포에 사진이 올라가지 않아도 되며 어디서든 사진을 사용할 수 있다.

더욱 쉽게 윈도우에서 스샷을 깃헙에 올리자

윈도우 키를 사용하는 다양한 단축키가 있다. 그 중에서 윈도우키를 이용해서 원하는 화면을 클립보드에 넣을 수 있는 기능이 있다.

Win + Shift + s

해당키를 누르게 되면 화면이 블로우 처리가 되면서 마우스로 원하는 영역을 지정할 수 있다. 마우스로 원하는 영역을 드래그하게 되면 원래의 화면으로 돌아가게 되고 Ctrl + V를 누르게 되면 전에 선택한 영역이 붙여넣기가 된다.

이 기능을 이용해서 더욱 쉽게 깃헙에 사진을 올릴 수 있다.

  1. 원하는 화면을 띄운다.
  2. Win + Shift + s 를 사용해서 원하는 화면을 복사한다.
  3. 자신의 레포의 issue에 들어가서 new issue를 만든다.
  4. issue를 작성하는 영역을 클릭한 후 Ctrl + V를 누른다.
  5. github에 사진이 올라가면서 주소를 만들어준다.
  6. 만들어진 주소를 자신의 .md 파일에 붙여넣기 해서 사용한다.

이렇게 하게되면 스샷을 따로 떠서 저장을 하고 레포에 올리지 않고 스샷을 찍고 주소를 만들어 Markdown에 넣어주기만 하면 된다.

data URIs로 image 생성하기

base64 인코딩 방식으로 소스를 넣는 방법을 사용하면 크기가 작은 이미지를 손쉽게 넣을 수 있다.

Syntax

data:[<mediatype>][;base64],<data>

mediatype : image/png, image/gif 등등

예제

간단하게 구글 메인페이지에 있는 로고를 base64인코딩 방식으로 소스를 얻은 다음 직접 이미지를 그려보자

image

메인페이지에 접속을 하여 Dev Tool(F12 or Ctrl + Shift + i)를 열고 Element 탭으로 들어가게 되면 img 태그 안에 해당 로고의 이미지 주소가 보인다.

image

이미지 주소에 마우스를 올리고 오른쪽 마우스를 누르게 되면 Reveal in Source Panel이라는 것이 나오게 되고 해당 메뉴를 누르게 되면 Source 탭으로 이동하게 된다.

image

해당 로고 이미지에서 다시 오른쪽 마우스를 누르게 되면 Copy image as data URI이라고 나온 메뉴를 클릭하게 되면 base64 인코딩으로 이미지를 복사하게 된다.

image

해당 복사한 내용을 img 태그 src 속성에 단순히 붙여넣기를 해주면 이미지가 나오는 것을 확인할 수 있다.

image

함수형 연습장

목차

1.1 평가와 일급

1.2 일급 함수

1.3 고차 함수

2.1 기존과 달라진 ES6에서의 리스트 순회

2.2 Array, Set, Map을 통해 알아보는 이터러블/이터레이터 프로토콜

2.3 사용자 정의 이터러블, 그리고 이터러블/이터레이터 프로토콜 정의

2.4 전개 연산자

3.1 제너레이터와 이터레이터

3.2 odds

3.3 for...of, 전개 연산자, 구조 분해, 나머지 연산자

4.1 map

4.2 이터러블 프로토콜을 따른 map의 다형성 1

4.3 이터러블 프로토콜을 따른 map의 다형성 2

4.4 filter

4.5 reduce

4.6 reduce2

4.7 map + filtr + reduce 중첩 사용과 함수형 사고

5.1 go

5.2 pipe

5.3 go를 사용하여 읽기 좋은 코드로 만들기

5.4 go + curry를 사용하여 더 읽기 좋은 코드로 만들기

5.5 함수 조합으로 함수 만들기

7.1 range 함수와 느긋한 L.range 함수

7.2 range와 느긋한 L.range 테스트

7.3 take

7.4 제네레이터 : 이터레이터 프로토콜로 구현하는 지연평가

7.5 L.map

7.6 L.filter

7.7 range, map, filter, take, reduce 중첩사용

7.8 L.range, L.map, L.filter, take의 평가 순서

7.10 map, filter, 계열 함수들이 가지는 결합 법칙

8.1 결과를 만드는 함수 reduce, take

8.2 queryStr 함수 만들기

8.3 Array.prototype.join 보다 다형성이 높은 join함수

8.4 take, find

8.5 L.map, L.filter로 map과 filter 만들기

8.6 L.flatten, flatten

8.7 yield *, L.deepFlat

8.8 L.flatMap, flatMap

9.1 callback과 Promise

9.2 비동기를 값으로 만드는 Promise

9.3 값으로서의 Promise 활용

9.4 합성 관점에서의 Promise와 모나드

9.5 Kleisli Composition 관점에서의 Promise

9.6 go, pipe, reduce에서 비동기 제어

9.7 promise.then의 중요한 규칙

10.1 지연 평가 + Promise - L.map, map, take

10.2 Kleisli Composition - L.filter, filter, nop, take

10.3 reduce에서 nop지원

10.4 지연평가 + Promise의 효율성

10.5 지연된 함수열을 병렬적으로 평가하기 - C.reduce, C.take 1

10.6 지연된 함수열을 병렬적으로 평가하기 - C.reduce, C.take 2

10.7 즉시 병렬적으로 평가하기 - C.map, C.filter

10.8 즉시, 지연, Promise, 병렬성 조합하기

10.9 코드 간단히 정리

10.10 Node.js에서 SQL 병렬 평가로 얻은 호율

평가와 일급

  • 평가 : 코드가 계산 되어 값을 만드는 것
  • 일급
    • 값으로 다룰 수 있다.
    • 변수에 담을 수 있다.
    • 함수의 인자로 사용될 수 있다.
    • 함수의 결과로 사용될 수 있다.

일급함수

자바스크립트에서 함수는 일급이다.

  • 함수를 값으로 다뤄질 수 있다.
    • 함수를 다시 리턴할 수 있다.
  • 조합성과 추상화의 도구

고차 함수

  • 값을 함수로 다루는 함수
  • 크게 2가지의 종류가 있다.
    • 함수를 인자로 받아서 실행하는 함수

      const apply1 = f => f(1);
      const add2 = a => a + 2;
      apply1(add2); // 3

      const times = (f, n) => {
      let i = -1;
      while(++1<n) f(i);
      }

    • 함수를 만들어 리턴하는 함수( 클로저를 만들어 리턴하는 함수 )

      const addMaker = a => b => a + b;
      const add10 = addMaker(10);
      add10; //return function
      add10(10) // 20

기존과 달라진 ES6에서의 리스트 순회

  • for i++
  • for of

Array, Set, Map을 통해 알아보는 이터러블/이터레이터 프로토콜

위의 모든 것들은 for of로 순회가 가능하다.

  • Array를 통해 알아보기

    const arr = [1,2,3}
    for (const a of arr) console.log(a)

    console.log(arr[Symbol.iterator])

    let iter1 = arrSymbol.iterator;
    for (const a of iter1) console.log(a)

  • Set을 통해 알아보기

    const set = new Set([1,2,3])
    for (const a of set) console.log(a)

    console.log(set[Symbol.iterator])

    let iter1 = setSymbol.iterator;
    for (const a of iter1) console.log(a)

  • Map을 통해 알아보기

    • .keys()는 iterator를 반환한다.

    const map = new Map([['a', 1], ['b', 2], ['c', 3]])
    for (const a of map) console.log(a)

    console.log(map[Symbol.iterator])

    for (const a of map.keys()) console.log(a)
    for (const a of map.values()) console.log(a)
    for (const a of map.entries()) console.log(a)

    let iter1 = mapSymbol.iterator;
    for (const a of iter1) console.log(a)

  • 위의 Set, Map은 모두 index를 통해서 접근이 불가능하다.

  • Symbol.iterator라는 것이 있다.

이터러블/이터레이터 프로토콜

  • 이터러블 : 이터레이터를 리턴하는 [Symbol.iterator] () 를 가진 값
  • 이터레이터 { value, done } 객체를 리턴하는 next() 를 가진 값
  • 이터러블/이터레이터 프로토콜 : 이터러블을 for ...of, 전개 연산자 등과 함께 동작하도록하는 규약

사용자 정의 이터러블, 그리고 이터러블/이터레이터 프로토콜 정의

const iterable = {
	[Symbol.iterator]() {
		let i = 3;
		return {
			next() {
				return i == 0 ? { done:true } { value: i--, done:false }
			},
			[Symbol.iterator]() {
				return this // 자기자신을 반환하는 well-formed iterator
			}
		}
	}
};

let iterator = iterable[Symbol.iterator]]();
for (const a of iterator) console.log(a);

const arr2 = [1, 2, 3]
const iter2 = arr2[Symbol.iterator]();
iter2.next();
for (const a of iter2) console.log(a);
  • 중간에 멈추더라도 자신을 return 하여 well-formed iterator를 만들어 이어서 진행이 되도록 한다.

전개 연산자

const a = [1, 2]
// a[Symbol.iterator] = null; // 역시 iterator가 있기에 전개연산자가 가능하다
console.log(...a, ...[3, 4])

제너레이터와 이터레이터

제너레이터 : 이터레이터이자 이터러블을 생성하는 함수, well-formed iterator를 반환하는 함수

well-formed iterator : 자기 자신을 반환하는 iterator

자바스크립트는 어떠한 값이나 상태를 순회할 수 있는 값을 만들 수 있다.

자바스크립트는 다형성이 높다.

function *gen(){
	yield 1;
	yield 2;
	yield 3;
	yield 4;
}

let iter = gen();
console.log(iter[Symbol.iterator]() == iter]);
console.log(iter.next());

for (const a of gen()) console.log(a);

odds

// 무한히 생성하는 함수
function *infinity(i = 0){
	while(true) yield i++;
}

// iterator를 받아서 l과 동일할 경우 멈추는 로직
function *limit(l, iter){
	for( const a of iter){
		yield a;
		if(a == l) return;
	}
}

let iter3 = infinity();

function *odds(l){
	for(const a of limit(l, infinity(1))){
		if(a % 2) yield a;
		if(a == l) return;
	} 
}

let iter2 = odds(10);
console.log(iter2.next());
console.log(iter2.next());
console.log(iter2.next());
console.log(iter2.next());
console.log(iter2.next());

for (const a of odds(40)) console.log(a);

for...of, 전개 연산자, 구조 분해, 나머지 연산자

console.log(...odds(10))
console.log([...odds(10), ...odds(20)]); // 전개 연산자

const [a, b, ...rest] = odds(5); // 구조 분해, 나머지 연산자
console.log(a)
console.log(b)
console.log(rest)

map

const products = [
  { name: '반팔티', price: 15000 },
  { name: '긴팔티', price: 20000 },
  { name: '핸드폰케이스', price: 15000 },
  { name: '후드티', price: 30000 },
  { name: '바지', price: 25000 }
]

// map 함수를 직접 구현
const map = (f, iter) => {
  let res = [];
  for (const p of iter) {
    res.push(f(p));
  }
  return res
}

let names = []
for (const p of products) {
  names.push(p.name)
}
console.log(names)

console.log(map(p => p.name, products));

let prices = []
for (const p of products) {
  prices.push(p.price)
}

console.log(prices)

이터러블 프로토콜을 따른 map의 다형성 1, 2

// array 처럼 생겼지만 map 사용이 불가능하다. => 유사 배열이기 때문이다.
// Array를 상속 받지 않아서...
console.log(document.querySelectorAll('*').map(el => el.nodeName))

// 이것이 작동하는 이유는 iterable 프로토콜을 따르기 때문이다.
console.log(map(el => el.nodeName, document.querySelectorAll('*')))

console.log([1,2,3]).map(a => a + 1);

function *gen(){
  yield 1;
  yield 2;
  yield 4;
}

console.log(map(a => a + 1, gen()))

let m = new Map();
m.set('a', 10);
m.set('b', 20);

const it = m[Symbol.iterator]();
new Map(map(([k, a]) => [k, a * 2] , m))

filter

조건을 걸어서 조건에 맞는 값만 가져오는 함수

const products = [
  { name: '반팔티', price: 15000 },
  { name: '긴팔티', price: 20000 },
  { name: '핸드폰케이스', price: 15000 },
  { name: '후드티', price: 30000 },
  { name: '바지', price: 25000 }
]

const filter = (f, iter) => {
  let res = [];
  for (const a of iter) {
    if (f(a)) res.push(a);
  }
  return res
}

// let under20000 = [];
// for (const p of products) {
//   if(p.price < 20000) under20000.push(p);
// }
// console.log(...under20000);

console.log(...filter((p) => p.price < 20000, products))

// let over20000 = [];
// for (const p of products) {
//   if(p.price >= 20000) over20000.push(p);
// }
// console.log(...over20000);

console.log(...filter((p) => p.price >= 20000, products))

console.log(filter(n => n % 2, [1, 2, 3, 4]));

reduce

const nums = [1, 2, 3, 4, 5]
let total = 0;

for (const n of nums) {
  total = total + n
}
console.log(total)

// ---------------------------

const products = [
  { name: '반팔티', price: 15000 },
  { name: '긴팔티', price: 20000 },
  { name: '핸드폰케이스', price: 15000 },
  { name: '후드티', price: 30000 },
  { name: '바지', price: 25000 }
]

const reduce = (f, acc, iter) => {
  if(!iter){
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
}

const add = (a, b) => a + b

console.log(reduce(add, 0, [1, 2, 3, 4, 5])); // 15
console.log(reduce(add, [1, 2, 3, 4, 5])); // 15

console.log(reduce((total_price, product) => total_price + product.price, 0, products));

map + filtr + reduce 중첩 사용과 함수형 사고

const products = [
  { name: '반팔티', price: 15000 },
  { name: '긴팔티', price: 20000 },
  { name: '핸드폰케이스', price: 15000 },
  { name: '후드티', price: 30000 },
  { name: '바지', price: 25000 }
]

// map
const map = (f, iter) => {
  let res = [];
  for (const p of iter) {
    res.push(f(p));
  }
  return res
}

// filter
const filter = (f, iter) => {
  let res = [];
  for (const a of iter) {
    if (f(a)) res.push(a);
  }
  return res
}

// reduce
const reduce = (f, acc, iter) => {
  if(!iter){
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
}

//------------------------------------------
//------------------------------------------

const add = (a, b) => a + b;

// 가격만 뽑아내기
console.log(reduce(add, map(p => p.price, filter( p => p.price < 20000, products))));
console.log(reduce(add, filter(n => n >= 20000, map( p => p.price, products))));

go

// reduce
const reduce = (f, acc, iter) => {
  if(!iter){
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
}

// 코드를 값으로 다루는 방식을 많이 사용한다.
const go = (...args) => {
  reduce((a, f) => f(a), args)

}

go(
  0,
  a => a + 1,
  a => a + 10,
  a => a + 100,
  console.log
)// 111

pipe

// reduce
const reduce = (f, acc, iter) => {
  if(!iter){
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
}

// 코드를 값으로 다루는 방식을 많이 사용한다.
const go = (...args) => {
  reduce((a, f) => f(a), args)
}

const add = (a, b) => a + b

go(
  add(0, 1),
  a => a + 10,
  a => a + 100,
  console.log
)// 111

// go는 즉시 평가를 함수한다면, pipe는 함수를 만든다.
// 함수를 반환하는 pipe 함수
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs)

const f = pipe(
  add,
  a => a + 10,
  a => a + 100,
  console.log
)

console.log(f(0, 1))

go를 사용하여 읽기 좋은 코드로 만들기

// map
const map = (f, iter) => {
  let res = [];
  for (const p of iter) {
    res.push(f(p));
  }
  return res
}

// filter
const filter = (f, iter) => {
  let res = [];
  for (const a of iter) {
    if (f(a)) res.push(a);
  }
  return res
}

// reduce
const reduce = (f, acc, iter) => {
  if(!iter){
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
}

const go = (...args) => reduce((a, f) => f(a), args)
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs)

//---------------------------------------
const products = [
  { name: '반팔티', price: 15000 },
  { name: '긴팔티', price: 20000 },
  { name: '핸드폰케이스', price: 15000 },
  { name: '후드티', price: 30000 },
  { name: '바지', price: 25000 }
]

const add = (a, b) => a + b

go(
  products,
  products => filter(p => p.price < 20000, products),
  products => map(p => p.price, products),
  prices => reduce(add, prices),
  console.log
)

go + curry를 사용하여 더 읽기 좋은 코드로 만들기

const products = [
  { name: '반팔티', price: 15000 },
  { name: '긴팔티', price: 20000 },
  { name: '핸드폰케이스', price: 15000 },
  { name: '후드티', price: 30000 },
  { name: '바지', price: 25000 }
]

// 인자를 원하는만큼 받았을 때 평가를 해서 리턴한다.
const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

const mult = curry((a, b) => a * b); 
console.log(mult); // function
console.log(mult(1)(2));

const mult3 = mult(3);
console.log(mult3(10));
console.log(mult3(5));

// 아래와 같이 map, filter, reduce에 curry를 적용함으로써 간결하게 만들 수 있다.
// map
const map = curry((f, iter) => {
  let res = [];
  for (const p of iter) {
    res.push(f(p));
  }
  return res
})

// filter
const filter = curry((f, iter) => {
  let res = [];
  for (const a of iter) {
    if (f(a)) res.push(a);
  }
  return res
})

// reduce
const reduce = curry((f, acc, iter) => {
  if(!iter){
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
})

const go = (...args) => reduce((a, f) => f(a), args)
const add = (a, b) => a + b;

go(
  products,
  filter(p => p.price < 20000),
	map(p => p.price),
  reduce(add),
  console.log
)

함수 조합으로 함수 만들기

const total_price = pipe(
  map(p => p.price),
  reduce(add)
)

const base_total_price = predi => pipe(
  filter(predi),
  total_price
)

go(
  products,
  base_total_price(p => p.price < 20000),
  console.log
)

go(
  products,
  base_total_price(p => p.price >= 20000),
  console.log
)

range 함수와 느긋한 L.range 함수

  • range : 완전한 결과가 담긴 배열로 평가가 된다.

  • L.range: 완전한 결과가 아닌 들어올 때마다 하나씩 평가가 되어 나온다.

    const curry = f => (a, ...) => .length ? f(a, ...) : (...) => f(a, ..._);
    // reduce
    const reduce = curry((f, acc, iter) => {
    if (!iter) {
    iter = accSymbol.iterator;
    acc = iter.next().value;
    }
    for (const a of iter) {
    acc = f(acc, a);
    }
    return acc;
    })

    const add = (a, b) => a + b;
    
    const range = l => {
      let i = -1;
      let res = [];
      while (++i < l) {
        console.log(i, "range")
        res.push(i)
      }
      return res
    }
    
    var list = range(4)
    console.log(list)
    console.log(reduce(add, list))
    
    // 느긋한 L.range
    const L = {};
    L.range = function *(l) {
      let i = -1;
      while (++i < l) {
        console.log(i, "L.range")
        yield i;
      }
    }
    
    var list = L.range(4)
    console.log(list)
    console.log(reduce(add, list))
    

range와 느긋한 L.range 테스트

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
  // reduce
  const reduce = curry((f, acc, iter) => {
    if (!iter) {
      iter = acc[Symbol.iterator]();
      acc = iter.next().value;
    }
    for (const a of iter) {
      acc = f(acc, a);
    }
    return acc;
  })

  const add = (a, b) => a + b;

  const range = l => {
    let i = -1;
    let res = [];
    while (++i < l) {
      // console.log(i, "range")
      res.push(i)
    }
    return res
  }

  var list = range(4)
  console.log(list)
  console.log(reduce(add, list))

  // 느긋한 L.range
  const L = {};
  L.range = function *(l) {
    let i = -1;
    while (++i < l) {
      // console.log(i, "L.range")
      yield i;
    }
  }

  var list = L.range(4)
  console.log(list)
  console.log(reduce(add, list))

  //----------------------------------------------------------
  // 테스트 진행하기
  function test(name, time, f) {
    console.time(name);
    while(time--) f();
    console.timeEnd(name);
  }

  test('range', 10, () => reduce(add, range(10000)))
  test('L.range', 10, () => reduce(add, L.range(10000)))

take

const go = (...args) => reduce((a, f) => f(a), args)
  const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
  const reduce = curry((f, acc, iter) => {
    if (!iter) {
      iter = acc[Symbol.iterator]();
      acc = iter.next().value;
    }
    for (const a of iter) {
      acc = f(acc, a);
    }
    return acc;
  })

  const add = (a, b) => a + b;

  const range = l => {
    let i = -1;
    let res = [];
    while (++i < l) {
      // console.log(i, "range")
      res.push(i)
    }
    return res
  }

  // 느긋한 L.range
  const L = {};
  L.range = function *(l) {
    let i = -1;
    while (++i < l) {
      // console.log(i, "L.range")
      yield i;
    }
  }

  const take = curry((l, iter) => {
    let res = [];
    for (const a of iter) {
      res.push(a);
      if(res.length == l) return res;
    }

    return res
  })

  console.time('')
  // console.log(take(5, range(1000000)))
  go(
    range(10000),
    take(5),
    reduce(add),
    console.log
  )
  console.timeEnd('')
  console.time('')
  // console.log(take(5, L.range(1000000)))
  go(
    L.range(10000),
    take(5),
    reduce(add),
    console.log
  )
  console.timeEnd('')

제네레이터 : 이터레이터 프로토콜로 구현하는 지연 평가

  • 이터러블 중심 프로그래밍에서의 지연평가(Lazy Evaluation)
    • 제때 계산법
    • 느긋한 계산법
    • 제너레이터/이터레이터 프로토콜을 기반으로 구현

L.map

let L = {};
  L.map = function *(f, iter){
    for (const a of iter) {
      yield f(a);
    }
  }

  var it = L.map(a => a + 10,[1, 2, 3])
  console.log(it.next())

L.filter

const L = {};
L.filter = function *(f, iter) {
    for (const a of iter) {
      if (f(a)) yield a;
    }
  }
  
  var it = L.filter(a => a % 2, [1, 2, 3, 4])
  console.log([...it])

range, map, filter, take, reduce 중첩사용 / L.range, L.map, L.filter, take의 평가 순서

const go = (...args) => reduce((a, f) => f(a), args)
  const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

  // map
  const map = curry((f, iter) => {
    let res = [];
    iter = iter[Symbol.iterator]()
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      res.push(f(a));
    }
    return res
  })

  // filter
  const filter = curry((f, iter) => {
    let res = [];
    iter = iter[Symbol.iterator]()
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      if (f(a)) res.push(a);
    }
    return res
  })

  // reduce
  const reduce = curry((f, acc, iter) => {
    if (!iter) {
      iter = acc[Symbol.iterator]();
      acc = iter.next().value;
    }else{
      iter = iter[Symbol.iterator]()
    }

    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      acc = f(acc, a);
    }

    return acc;
  })

  const range = l => {
    let i = -1;
    let res = [];
    while (++i < l) {
      res.push(i)
    }
    return res
  }

  const take = curry((l, iter) => {
    let res = [];
    iter = iter[Symbol.iterator]()
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      res.push(a);
      if (res.length == l) return res;
    }

    return res
  })

  //----------------------------------------------

  const L = {};

  L.range = function* (l) {
    let i = -1;
    while (++i < l) {
      yield i;
    }
  }

  L.map = curry(function* (f, iter) {
    iter = iter[Symbol.iterator]()
    let cur;
    while (!(cur = iter.next()).done) {
      a = cur.value
      yield f(a);
    }
  })

  L.filter = curry(function* (f, iter) {
    iter = iter[Symbol.iterator]()
    let cur;
    while (!(cur = iter.next()).done) {
      a = cur.value
      if (f(a)) yield a;
    }
  })

  //----------------------------------------

  go(range(10),
    map(n => n + 10),
    filter(n => n % 2),
    take(2),
    console.log)

  go(L.range(10),
    L.map(n => n + 10),
    L.filter(n => n % 2),
    take(2),
    console.log)

map, filter, 계열 함수들이 가지는 결합 법칙

  • 사용하는 데이터가 무엇이든지

  • 사용하는 보조 함수가 순수 함수라면 무엇이든지

  • 아래와 같이 결합한다면 둘 다 결과가 같다.

    [[mapping, mapping][filtering, filtering][mapping, mapping]]

    [[mapping, filtering, mapping][mapping, filtering, mapping]]

결과를 만드는 함수 reduce, take

queryStr 함수 만들기

// 결과를 만드는 함수 reduce, take
  // reduce는 결과를 만들어 내는 역할
  // const queryStr = obj => go(
  //   obj,
  //   Object.entries,
  //   map(([k, v])=> `${k}=${v}`),
  //   reduce((a, b) => `${a}&${b}`)
  // );

  const queryStr = pipe(
    Object.entries,
    map(([k, v]) => `${k}=${v}`),
    reduce((a, b) => `${a}&${b}`)
  );

  console.log(queryStr({ limit: 10, offset: 10, type: 'notice' }))

Array.prototype.join 보다 다형성이 높은 join함수

L.entries = function *(obj) {
    for (const k in obj) yield [k, obj[k]]
  }

  // 배열에서만 사용되는 join이므로 iterator로 사용이 가능하도록 만듬
  const join = curry((sep = ',', iter) => reduce((a, b) =>`${a}${sep}${b}`, iter))

  const queryStr = pipe(
    L.entries,
    L.map(([k, v]) => `${k}=${v}`),
    function(a){
      console.log(a)
      return a;
    },
    join('&')
  );

  console.log(queryStr({ limit: 10, offset: 10, type: 'notice' }))

  function *b() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
  }

  console.log(join('-', b()))

take, find

const find = curry((f, iter) => go(
    iter, 
    L.filter(f),
    take(1),
    ([a]) => a
  ))

  console.log(find(u => u.age< 30)(users))

  go(
    users,
    L.map(u => u.age),
    find(n => n < 30),
    console.log
  )

L.map, L.filter로 map과 filter 만들기

L.map = curry(function* (f, iter) {
    for (const a of iter) yield f(a)
  })

  L.filter = curry(function* (f, iter) {
    for (const a of iter) {
      if (f(a)) {
        yield a
      }
    }
  })

const map = curry(pipe(
    L.map,
    take(Infinity)
  ))

  console.log(map(a => a + 10, L.range(4)))

  const filter = curry((pipe(L.filter, take(Infinity))))

  console.log(filter(a => a > 1, L.range(4)))

L.flatten, flatten

// L.flatten
  const takeAll = take(Infinity)
  const isIterable = a => a && a[Symbol.iterator];
  
  L.flatten = function *(iter){
    for (const a of iter) {
      if(isIterable(a)){
        for (const b of a) {
          yield b;
        }
      }else{
        yield a;
      }
    }
  }

  var it = L.flatten([[1,2], 3, 4, [5, 6], [7, 8, 9]])
  console.log([...it])
  console.log(take(3, L.flatten([[1,2], 3, 4, [5, 6], [7, 8, 9]])))

  // flatten
  const flatten = pipe(L.flatten, takeAll)
  console.log(flatten([[1,2], 3, 4, [5, 6], [7, 8, 9]]))

yield *, L.deepFlat

// yield *
L.flatten = function *(iter) {
  for (const a of iter) {
    if (isIterable(a)) for (const b of a) yield b
    else yield a;
  }
};

// yield *을 활용하면 위 코드를 아래와 같이 변경할 수 있습니다. yield *iterable은 for (const val of iterable) yield val; 과 같습니다.
L.flatten = function *(iter) {
  for (const a of iter) {
    if (isIterable(a)) yield *a;
    else yield a;
  }


// L.deepFlat
// 만일 깊은 Iterable을 모두 펼치고 싶다면 아래와 같이 L.deepFlat을 구현하여 사용할 수 있습니다. L.deepFlat은 깊은 Iterable을 펼쳐줍니다.
L.deepFlat = function *f(iter) {
  for (const a of iter) {
    if (isIterable(a)) yield *f(a);
    else yield a;
  }
};
console.log([...L.deepFlat([1, [2, [3, 4], [[5]]]])]);
// [1, 2, 3, 4, 5];

L.flatMap, flatMap

// map과 flatten을 한 것이 flatMap이다.
  // JS 표준에도 있다. 

  L.flatMap = curry(pipe(
    L.map,
    L.flatten
  ))

  const flatMap = curry(pipe(L.map, flatten));

  var it = L.flatMap(a => a, [[1, 2], [5, 6], [7, 8, 9]])
  // var it = L.flatMap(map(a => a * a), [[1, 2], [5, 6], [7, 8, 9]])

  console.log([...it])
  console.log(flatMap(a => a, [[1, 2], [5, 6], [7, 8, 9]]))
  console.log(flatMap(range, [1, 2, 3]))

9.1 Callback과 Promise

function add10(a, callback){
	setTimeout(() => callback(a + 10), 1000;
};

add10(5, res => {
	add10(res, res => {
		add10(res, res => {
			console.log(res);
		};
	};
};

// add20은 프로미스를 만들어서 리턴한다.
function add20(a){
	return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
};

add20(5)
	.then(add20)
	.then(add20)
	.then(console.log);

9.2 비동기를 값으로 만드는 Promise

  • then이 단순히 중요한 것이 아니다.

  • 값을 1급으로 다룬다는 것에 의미가 있다.

  • 대기 되어지는 값을 가진다는 점에서 콜백과 다르다.

  • 콜백을 비동기를 다루는 것을 코드로만 다루고 있지만 프로미스는 리턴을 하여 값으로 다루고 있다.

    function add10(a, callback){
    setTimeout(() => callback(a + 10), 1000;
    };

    var a = add10(5, res => {
    add10(res, res => {
    add10(res, res => {
    console.log(res);
    };
    };
    };

    // add20은 프로미스를 만들어서 리턴한다.
    function add20(a){
    return new Promise(resolve => setTimeout(() => resolve(a + 20), 100));
    };

    var b = add20(5)
    .then(add20)
    .then(add20)
    .then(console.log);

    console.log(a) // undefined
    console.log(b) // Promise{}

  • 비동기 상황이 값으로써 다루어지는 상황이다.

  • 값으로 다루어진다는 것은 1급이라는 것

9.3 값으로서의 Promise 활용

  • 일급 활용

    const delay100 = a => new Promise((resolve) => setTimeout(() => resolve(a), 100));

    // 프로미스 값이 아닌 것이 들어와야 실행가능하다.
    const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);
    const add5 = a => a + 5;

    const n1 = 10
    go1(go1(n1, add5), console.log);

    var n2 = delay100(10);
    go1(go1(n2, add5), console.log);

9.4 합성 관점에서의 Promise와 모나드

  • Composition

    // f . g
    // f(g(x)) 함수 합성
    // 안전하게 함수를 합성하게 하는 모나드(하나의 도구)
    // 비동기를 안전하게 합성하도록 하는 것이 Promise이다.
    const g = a => a + 1;
    const f = a => a * a;

    console.log(f(g(1))); // 4
    console.log(f(g())); // NaN

    // 값이 어떤게 들어올지 모르는 상황에서는 어떤 식으로 만들어야 하나. => 이런 것이 모나드이다.
    Array.of(1).map(g).map(f).forEach(r => console.log(r));
    [].map(g).map(f).forEach(r => console.log(r)); // 함수 자체가 실행되지 않고 있다.

    Promise.resolve(2).then(g).then(f).then(r => console.log(r); // 비동기 상황을 안전하게 합성하기 위한 용도이다.
    Promise.resolve().then(g).then(f).then(r => console.log(r);

    new Promise(resolve => {
    setTimeout(() => resolve(2), 100);
    }).then(g).then(f).then(r => console.log(r);

9.5 Kleisli Composition 관점에서의 Promise

  • 오류가 있을 수 있는 상황을 안전하게 합성하기 위한 방법

    // f . g
    // f(g(x)) = f(g(x))
    // f(g(x)) = g(x)

    const curry = f => (a, ...) => .length ? f(a, ...) : (...) => f(a, ..._);
    // reduce
    const reduce = curry((f, acc, iter) => {
    if (!iter) {
    iter = accSymbol.iterator;
    acc = iter.next().value;
    }else{
    iter = iterSymbol.iterator
    }

      let cur;
      while (!(cur = iter.next()).done) {
        const a = cur.value;
        acc = f(acc, a);
      }
    
      return acc;
    })
    

    const go = (...args) => reduce((a, f) => f(a), args)

    const find = curry((f, iter) => go(
    iter,
    L.filter(f),
    take(1),
    ([a]) => a
    ))

    var users = [
    { id: 1, name: 'aa'},
    { id: 2, name: 'bb'},
    { id: 3, name: 'cc'}
    ];

    const getUserById = id =>
    find(u => u.id == id, users) || Promise.reject('없어요!');

    const f = ({ name }) => name;
    const g = getUserById;
    // const fg = id => f(g(id));

    // const r = fg(2);
    // console.log(r);

    // users.pop();
    // users.pop();

    // const r2 = fg(2);
    // console.log(r2);

    const fg = id => Promise.resolve(id).then(g).then(f).catch(a => a);

    fg(2).then(console.log);

9.6 go, pipe, reduce에서 비동기 제어

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

const go1 = (a, f) => a instanceof Promise ?  a.then(f) : f(a);

// reduce
const reduce = curry((f, acc, iter) => {
  if (!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }else{
    iter = iter[Symbol.iterator]()
  }

  return go1(acc, function recur(acc) {
	  let cur;
		while (!(cur = iter.next()).done) {
		  const a = cur.value;
	    acc = f(acc, a);
			if(acc instanceof Promise) return acc.then(recur);
	  }

	  return acc;
	});
})
const go = (...args) => reduce((a, f) => f(a), args)

go(Promise.resolve(1),
	a => a + 10,
	a => Promise.resolve(a + 100),
	a => Promise.reject('error~~'),
	a => a + 1000,
	a => a + 10000,
	console.log
).catch(a => console.log(a));

9.7 promise.then의 중요한 규칙

Promise.resolve(Promise.resolve(Promise.resolve(1))).then(a => {
	console.log(a);
});

new Promise(resolve => resolve(new Promise(resolve => resolve(1)))).then(console.log);

10.1 지연 평가 + Promise - L.map, map, take

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);

// reduce
const reduce = curry((f, acc, iter) => {
  if (!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  } else {
    iter = iter[Symbol.iterator]()
  }

  return go1(acc, function recur(acc) {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      acc = f(acc, a);
      if (acc instanceof Promise) return acc.then(recur);
    }

    return acc;
  });
})

const go = (...args) => reduce((a, f) => f(a), args)

const L = {}
L.map = curry(function* (f, iter) {
  for (const a of iter) {
    yield go1(a, f);
  }
})

const take = curry((l, iter) => {
  let res = [];
  iter = iter[Symbol.iterator]()
  let cur;
  return function recur() {
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      if (a instanceof Promise) return a.then(
        a => (res.push(a), res).length == l ? res : recur());
      res.push(a);
      if (res.length == l) return res;
    }
    return res
  }();
})


go([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
  L.map(a => a + 10),
  take(2),
  console.log
)
go([2,3,4],
  L.map(a => Promise.resolve(a + 10)),
  take(2),
  console.log
)

10.2 Kleisli Composition - L.filter, filter, nop, take

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);

// reduce
const reduce = curry((f, acc, iter) => {
  if (!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  } else {
    iter = iter[Symbol.iterator]()
  }

  return go1(acc, function recur(acc) {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      acc = f(acc, a);
      if (acc instanceof Promise) return acc.then(recur);
    }

    return acc;
  });
})

const go = (...args) => reduce((a, f) => f(a), args)

const L = {}

L.map = curry(function* (f, iter) {
  for (const a of iter) {
    yield go1(a, f);
  }
})

const take = curry((l, iter) => {
  let res = [];
  iter = iter[Symbol.iterator]();
  return function recur() {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      
      if (a instanceof Promise) {
        return a
        .then(ab => (res.push(ab), res).length == l ? res : recur())
        .catch(e => e == nop ? recur() : Promise.reject(e));
      }
      res.push(a);
      if (res.length == l) return res;
    }
    return res
  }();
})  

const nop = Symbol('nop');

L.filter = curry(function* (f, iter) {
  for (const a of iter) {
    const b = go1(a, f);
    if (b instanceof Promise) yield b.then(c => c ? a : Promise.reject(nop));
    else if (b) yield a
  }
})

go([1, 2, 3, 4, 5, 6],
  L.map(a => Promise.resolve(a * a)),
  L.filter(a => a % 2),
  take(2),
  console.log
);

10.3 reduce에서 nop지원

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);
const nop = Symbol('nop');

const reduceF = (acc, a, f) => 
  a instanceof Promise ? 
    a.then(a => f(acc, a), e => e == nop ? acc : Promise.reject(e)) : 
    f(acc, a);

const head = iter => go1(take(1, iter), ([h]) => h);
// reduce
const reduce = curry((f, acc, iter) => {
  if (!iter) return reduce(f, head(iter = acc[Symbol.iterator]()), iter);
  
  iter = iter[Symbol.iterator]()
  return go1(acc, function recur(acc) {
    let cur;
    while (!(cur = iter.next()).done) {
      acc = reduceF(acc, cur.value, f);
      if (acc instanceof Promise) return acc.then(recur);
    }
    return acc;
  });
})

const go = (...args) => reduce((a, f) => f(a), args)

const L = {}

L.map = curry(function* (f, iter) {
  for (const a of iter) {
    yield go1(a, f);
  }
})

const take = curry((l, iter) => {
  let res = [];
  iter = iter[Symbol.iterator]();
  return function recur() {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      
      if (a instanceof Promise) {
        return a
        .then(ab => (res.push(ab), res).length == l ? res : recur())
        .catch(e => e == nop ? recur() : Promise.reject(e));
      }
      res.push(a);
      if (res.length == l) return res;
    }
    return res
  }();
})  



L.filter = curry(function* (f, iter) {
  for (const a of iter) {
    const b = go1(a, f);
    if (b instanceof Promise) yield b.then(c => c ? a : Promise.reject(nop));
    else if (b) yield a
  }
})

const add = (a, b) => a + b

go([1, 2, 3, 4, 5, 6],
  L.map(a => Promise.resolve(a * a)),
  L.filter(a => Promise.resolve(a % 2)),
  reduce(add),
  console.log
);

10.4 지연평가 + Promise의 효율성

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);
const nop = Symbol('nop');

const reduceF = (acc, a, f) => 
  a instanceof Promise ? 
    a.then(a => f(acc, a), e => e == nop ? acc : Promise.reject(e)) : 
    f(acc, a);

const head = iter => go1(take(1, iter), ([h]) => h);
// reduce
const reduce = curry((f, acc, iter) => {
  if (!iter) return reduce(f, head(iter = acc[Symbol.iterator]()), iter);
  
  iter = iter[Symbol.iterator]()
  return go1(acc, function recur(acc) {
    let cur;
    while (!(cur = iter.next()).done) {
      acc = reduceF(acc, cur.value, f);
      if (acc instanceof Promise) return acc.then(recur);
    }
    return acc;
  });
})

const go = (...args) => reduce((a, f) => f(a), args)

const L = {}

L.map = curry(function* (f, iter) {
  for (const a of iter) {
    yield go1(a, f);
  }
})

const take = curry((l, iter) => {
  let res = [];
  iter = iter[Symbol.iterator]();
  return function recur() {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      
      if (a instanceof Promise) {
        return a
        .then(ab => (res.push(ab), res).length == l ? res : recur())
        .catch(e => e == nop ? recur() : Promise.reject(e));
      }
      res.push(a);
      if (res.length == l) return res;
    }
    return res
  }();
})  



L.filter = curry(function* (f, iter) {
  for (const a of iter) {
    const b = go1(a, f);
    if (b instanceof Promise) yield b.then(c => c ? a : Promise.reject(nop));
    else if (b) yield a
  }
})

const add = (a, b) => a + b

go([1, 2, 3, 4, 5, 6],
  L.map(a => new Promise(resolve => setTimeout(() => resolve(a * a), 1000))),
  L.filter(a => Promise.resolve(a % 2)),
  take(2),
  // reduce(add),
  console.log
);

10.5 지연된 함수열을 병렬적으로 평가하기 - C.reduce, C.take 1

10.6 지연된 함수열을 병렬적으로 평가하기 - C.reduce, C.take 2

let C = {}

  function noop() { }
  const catchNoop = arr => (arr.forEach(a => a instanceof Promise ? a.catch(noop) : a), arr);

  C.reduce = curry((f, acc, iter) => {
    const iter2 = catchNoop(iter ? [...iter] : [...acc]);
    return iter ? reduce(f, acc, [...iter2]) : reduce(f, [...iter2])
  })

  C.take = curry((l, iter) =>take(l, catchNoop([...iter])));

  const delay500 = a => new Promise(resolve => {
    console.log('hi!');
    setTimeout(() => {
      resolve(a)
    }, 1000);
  })

  console.time()
  go([1, 2, 3, 4, 5, 6, 7, 8, 9],
    L.map(a => delay500(a * a)),
    L.filter(a => delay500(a % 2)),
    L.map(a => delay500(a * a)),
    C.take(2),
    reduce(add),
    log,
    _ => console.timeEnd()
  )

fx.js

const log = console.log;

const curry = f =>
  (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

const isIterable = a => a && a[Symbol.iterator];

const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);

const reduceF = (acc, a, f) =>
  a instanceof Promise ?
    a.then(a => f(acc, a), e => e == nop ? acc : Promise.reject(e)) :
    f(acc, a);

const head = iter => go1(take(1, iter), ([h]) => h);

const reduce = curry((f, acc, iter) => {
  if (!iter) return reduce(f, head(iter = acc[Symbol.iterator]()), iter);

  iter = iter[Symbol.iterator]();
  return go1(acc, function recur(acc) {
    let cur;
    while (!(cur = iter.next()).done) {
      acc = reduceF(acc, cur.value, f);
      if (acc instanceof Promise) return acc.then(recur);
    }
    return acc;
  });
});

const go = (...args) => reduce((a, f) => f(a), args);

const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);

const take = curry((l, iter) => {
  let res = [];
  iter = iter[Symbol.iterator]();
  return function recur() {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      if (a instanceof Promise) {
        return a
          .then(a => (res.push(a), res).length == l ? res : recur())
          .catch(e => e == nop ? recur() : Promise.reject(e));
      }
      res.push(a);
      if (res.length == l) return res;
    }
    return res;
  }();
});

const takeAll = take(Infinity);

const L = {};

L.range = function* (l) {
  let i = -1;
  while (++i < l) yield i;
};

L.map = curry(function* (f, iter) {
  for (const a of iter) {
    yield go1(a, f);
  }
});

const nop = Symbol('nop');

L.filter = curry(function* (f, iter) {
  for (const a of iter) {
    const b = go1(a, f);
    if (b instanceof Promise) yield b.then(b => b ? a : Promise.reject(nop));
    else if (b) yield a;
  }
});

L.entries = function* (obj) {
  for (const k in obj) yield [k, obj[k]];
};

L.flatten = function* (iter) {
  for (const a of iter) {
    if (isIterable(a)) yield* a;
    else yield a;
  }
};

L.deepFlat = function* f(iter) {
  for (const a of iter) {
    if (isIterable(a)) yield* f(a);
    else yield a;
  }
};

L.flatMap = curry(pipe(L.map, L.flatten));

const map = curry(pipe(L.map, takeAll));

const filter = curry(pipe(L.filter, takeAll));

const find = curry((f, iter) => go(
  iter,
  L.filter(f),
  take(1),
  ([a]) => a));

const flatten = pipe(L.flatten, takeAll);

const flatMap = curry(pipe(L.map, flatten));

var add = (a, b) => a + b;

const range = l => {
  let i = -1;
  let res = [];
  while (++i < l) {
    res.push(i);
  }
  return res;
};

10.7 즉시 병렬적으로 평가하기 - C.map, C.filter

let C = {}

  function noop() { }
  const catchNoop = arr => (arr.forEach(a => a instanceof Promise ? a.catch(noop) : a), arr);

  C.reduce = curry((f, acc, iter) => {
    const iter2 = catchNoop(iter ? [...iter] : [...acc]);
    return iter ? reduce(f, acc, [...iter2]) : reduce(f, [...iter2])
  })

  C.take = curry((l, iter) =>take(l, catchNoop([...iter])));
  C.takeAll = C.take(Infinity);

  C.map = curry(pipe(L.map, C.takeAll));
  C.filter = curry(pipe(L.filter, C.takeAll));

  const delay500 = a => new Promise(resolve => {
    console.log('hi!');
    setTimeout(() => {
      resolve(a)
    }, 1000);
  })

  go([1, 2, 3, 4, 5, 6, 7, 8, 9],
    C.map(a => delay500(a * a)),
    log,
  )

  go([1, 2, 3, 4, 5, 6, 7, 8, 9],
    C.filter(a => delay500(a % 2)),
    log,
  )

10.8 즉시, 지연, Promise, 병렬성 조합하기

let C = {}

  function noop() { }
  const catchNoop = arr => (arr.forEach(a => a instanceof Promise ? a.catch(noop) : a), arr);

  C.reduce = curry((f, acc, iter) => {
    const iter2 = catchNoop(iter ? [...iter] : [...acc]);
    return iter ? reduce(f, acc, [...iter2]) : reduce(f, [...iter2])
  })

  C.take = curry((l, iter) =>take(l, catchNoop([...iter])));
  C.takeAll = C.take(Infinity);

  C.map = curry(pipe(L.map, C.takeAll));
  C.filter = curry(pipe(L.filter, C.takeAll));

  const delay500 = (a, name) => new Promise(resolve => {
    console.log(`${name}: ${a}`);
    setTimeout(() => {
      resolve(a)
    }, 1000);
  })

  console.time()
  // go([1, 2, 3, 4, 5],
  //   map(a => delay500(a * a, 'map 1')),
  //   filter(a => delay500(a % 2, 'filter 2')),
  //   map(a => delay500(a * a, 'map 3')),
  //   take(2),
  //   log,
  //   _ => console.timeEnd()
  // )

  // go([1, 2, 3, 4, 5],
  //   L.map(a => delay500(a * a, 'map 1')),
  //   L.filter(a => delay500(a % 2, 'filter 2')),
  //   L.map(a => delay500(a * a, 'map 3')),
  //   take(2),
  //   log,
  //   _ => console.timeEnd()
  // )

  // go([1, 2, 3, 4, 5],
  //   C.map(a => delay500(a * a, 'map 1')),
  //   L.filter(a => delay500(a % 2, 'filter 2')),
  //   L.map(a => delay500(a * a, 'map 3')),
  //   take(2),
  //   log,
  //   _ => console.timeEnd()
  // )

  // go([1, 2, 3, 4, 5],
  //   L.map(a => delay500(a * a, 'map 1')),
  //   L.filter(a => delay500(a % 2, 'filter 2')),
  //   L.map(a => delay500(a * a, 'map 3')),
  //   C.take(2),
  //   log,
  //   _ => console.timeEnd()
  // )

10.9 코드 간단히 정리

const log = console.log;

const curry = f =>
  (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

const isIterable = a => a && a[Symbol.iterator];

const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);

const reduceF = (acc, a, f) =>
  a instanceof Promise ?
    a.then(a => f(acc, a), e => e == nop ? acc : Promise.reject(e)) :
    f(acc, a);

const head = iter => go1(take(1, iter), ([h]) => h);

const reduce = curry((f, acc, iter) => {
  if (!iter) return reduce(f, head(iter = acc[Symbol.iterator]()), iter);

  iter = iter[Symbol.iterator]();
  return go1(acc, function recur(acc) {
    let cur;
    while (!(cur = iter.next()).done) {
      acc = reduceF(acc, cur.value, f);
      if (acc instanceof Promise) return acc.then(recur);
    }
    return acc;
  });
});

const go = (...args) => reduce((a, f) => f(a), args);

const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);

const take = curry((l, iter) => {
  let res = [];
  iter = iter[Symbol.iterator]();
  return function recur() {
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      if (a instanceof Promise) {
        return a
          .then(a => (res.push(a), res).length == l ? res : recur())
          .catch(e => e == nop ? recur() : Promise.reject(e));
      }
      res.push(a);
      if (res.length == l) return res;
    }
    return res;
  }();
});

const takeAll = take(Infinity);

const L = {};

L.range = function* (l) {
  let i = -1;
  while (++i < l) yield i;
};

L.map = curry(function* (f, iter) {
  for (const a of iter) {
    yield go1(a, f);
  }
});

const nop = Symbol('nop');

L.filter = curry(function* (f, iter) {
  for (const a of iter) {
    const b = go1(a, f);
    if (b instanceof Promise) yield b.then(b => b ? a : Promise.reject(nop));
    else if (b) yield a;
  }
});

L.entries = function* (obj) {
  for (const k in obj) yield [k, obj[k]];
};

L.flatten = function* (iter) {
  for (const a of iter) {
    if (isIterable(a)) yield* a;
    else yield a;
  }
};

L.deepFlat = function* f(iter) {
  for (const a of iter) {
    if (isIterable(a)) yield* f(a);
    else yield a;
  }
};

L.flatMap = curry(pipe(L.map, L.flatten));

const map = curry(pipe(L.map, takeAll));

const filter = curry(pipe(L.filter, takeAll));

const find = curry((f, iter) => go(
  iter,
  L.filter(f),
  take(1),
  ([a]) => a));

const flatten = pipe(L.flatten, takeAll);

const flatMap = curry(pipe(L.map, flatten));

var add = (a, b) => a + b;

const range = l => {
  let i = -1;
  let res = [];
  while (++i < l) {
    res.push(i);
  }
  return res;
};

const C = {};

function noop() {
}

const catchNoop = ([...arr]) =>
  (arr.forEach(a => a instanceof Promise ? a.catch(noop) : a), arr);

C.reduce = curry((f, acc, iter) => iter ?
  reduce(f, acc, catchNoop(iter)) :
  reduce(f, catchNoop(acc)));

C.take = curry((l, iter) => take(l, catchNoop(iter)));

C.takeAll = C.take(Infinity);

C.map = curry(pipe(L.map, C.takeAll));

C.filter = curry(pipe(L.filter, C.takeAll));

HTML5 태그에는 어떤 것들이 있나?(수정중)

내가 몰랐던 내용의 태그를 정리하는 시간을 가져보자.
다른 사람들은 모르지만 나는 대개 div 태그를 가장 많이 사용하며 커스텀을 한다. 그러나 이번에 HTML5를 찬찬히 보면서 이미 있는 태그를 내가 모르고 사용 하지 못하고 있다는 생각에 정리를 하여, 추후 블로그 수정에 적용을해서 HTML5에 맞는 형식을 갖추려고 한다.


<main>

문서의 주요 콘텐츠를 설정하는 태그.

  • IE 지원 불가
  • ⭐️ 한 문서에 하나의 <main> 요소만 있어야 한다.
  • main안에는 Side bar(<aside>), Navigation Link, Copyright 정보, 사이트 Logo, 검색창 같은 요소는 포함할 수 없다.

<article>

독립적으로 구분되거나 재사용 가능한 영역을 설정할 때 사용한다.

  • 예시 : (매거진 / 신문 기사, 블로그 등)
  • 일반적으로 <h1> ~ <h6> 를 포함하여 식별.
  • 작성일자와 시간을 <time> 의 datetime 속성으로 작성.

<section>

문서의 일반적인 영역을 설정한다.

  • 일반적으로 <h1> ~ <h6> 를 포함하여 식별.
  • chapter, header, footer등에서 일반적으로 사용된다.

<aside>

문서의 별도 콘텐츠를 설정할 때 사용된다.

  • 보통 광고나 기타 링크 등의 사이드바(Side bar)를 설정한다.

<dl>, <dt>,

용어(<dt>)와 정의(<dd>) 쌍들의 영역(<dl>)을 설정한다. (Description List, Definition Details, Definition Term)

  • <dl> 은 <dd><dt> 만을 포함해야 한다.
  • 키(key)/값(value) 형태를 표시할 때 유용하다.
<dl>
  <dt>Coffee</dt>
  <dd>Coffee is a brewed drink prepared from roasted coffee beans, the seeds of berries from certain Coffea species.</dd>
  <dt>Milk</dt>
  <dd>Milk is a nutrient-rich, white liquid food produced by the mammary glands of mammals. 
  </dd>
</dl>

image

<pre>

서식이 미리 지정된 텍스트를 설정한다. (Preformatted Text)

  • 텍스트의 공백과 줄바꿈을 유지하여 표시할 수 있다.
  • 기본적으로 Monospace 폰트 계열로 표시된다.
  • 아래와 같이 폰트가 바뀌게 된다.

image

<blockquote>

일반적인 인용문을 설정 (Block Quotation)

  • 기본적인 모양새는 <pre>와 비슷하다.
속성 의미
cite 인용된 정보의 URL URL

image

<b>

내가 알고 있던 b태그가 아니라서 추가

문체가 다른 글자의 범위를 설정.(Bring Attention)

  • 특별한 의미를 가지지 않음.
  • 읽기 흐름에 도움을 주는 용도로 사용.
  • 다른 태그가 적합하지 않은 경우 마지막 수단으로 사용.
  • 기본적으로 글자가 두껍게(Bold) 표시됨.

<mark>

사용자의 관심을 끌기 위해 하이라이팅할 때 사용.(Mark Text, 형광펜을 사용하여 관심있는 부분을 표시하는 것과 같은 의미)

  • 기본적으로 형광펜을 사용한 것처럼 글자 배경이 노란색(Yellow)으로 표시됨.

<em>

단순한 의미 강조를 표시.(Emphasis)

  • 중첩이 가능.
  • 중첩될수록 강조 의미가 강해짐.
  • 정보통신보조기기 등에서 구두 강조로 발음됨.
  • 기본적으로 이탤릭체(Italic type)로 표시됨.

<strong>

의미의 중요성을 나타내기 위해 사용.(Strong Importance)

  • 기본적으로 글자가 두껍게(Bold) 표시됨.

<i>

<em><strong> <mark> <cite> <dfn> 등에서 표현할 수 있는 적합한 의미가 아닌 경우 사용.(평범한 글자와 구분(아이콘이나 특수기호 같은)하기 위해 사용)

  • 기본적으로 이탤릭체(Italic type)로 표시됨.

<dfn>

용어를 정의할 때 사용.(Definition)

<cite>

창작물에 대한 참조를 설정.(책, 논문, 영화, TV 프로그램, 노래, 게임 등의 제목)

  • 기본적으로 이탤릭체(Italic type)로 표시됨.

<q>

짦은 인용문을 설정.(Inline Quotation)

  • 긴 인용문을 설정할 경우 **<blockquote>**를 사용.

Untitled

<u>

밑줄이 있는 글자를 설정.(Underline)

  • 순수하게 꾸미는 용도의 요소로 사용.
  • **<a>**와 헷갈릴 수 있는 위치에서 사용하지 않도록 주의.
  • **<span style="text-decoration: underline;">**을 활용할 수 있을 경우에는 사용을 권장하지 않음

<code>

컴퓨터 코드 범위를 설정.(Inline Code)

<code>document.getElementById('id-value')</code> is a piece of computer code.

  • 기본적으로 고정폭(Monospace) 글꼴 계열로 표시됨.

<kbd>

텍스트 입력 장치(키보드)에서 사용자 입력을 나타내는 텍스트 범위를 설정.(Keyboard Input)

<sup>, <sub>

위 첨자(<sup>)와 아래 첨자(**<sub>**를 설정.(Superscripted text, Subscript text)

<time>

날짜나 시간을 나타내기 위한 목적으로 사용.

Untitled

  • IE 지원 불가

<del>

삭제된(변경된) 텍스트의 범위를 지정.

Untitled

<ins>

새로 추가된(변경된) 텍스트의 범위를 지정.

Untitled

<figure>, <figcaption>

<figure> 는 이미지나 삽화, 도표 등의 영역을 설정. <figcaption> 는 <figure> 에 포함되어 이미지나 삽화 등의 설명을 표시.(Figure Caption)

<progress>

작업의 완료 진행률을 표시.

Untitled

accesskey

요소의 키보드 단축키 힌트를 제공.

  • 다음의 이유 등으로 일반 목적의 웹사이트에서는 사용을 권장하지 않음.
    • 브라우저 키보드 단축키 혹은 보조기기의 기능과 충돌
    • 특정 키보드의 존재하지 않는 키 사용
    • 숫자 같은 논리적인 관계가 없는 키 지정
    • **accesskey**의 존재를 모르는 사용자의 실수

스크롤을 조금 더 쾌적하게

스크롤을 조금 더 쾌적하게

JavaScript에서 addEventListener()를 사용해서 등록된 이벤트는 컴포지터 스레드가 받는다. 이 이벤트가 들어오게 되면 컴포지터 스레드는 메인 스레드에 이벤트를 넘기고 렌더링 파이프라인에 따라 layout, paint를 거쳐 layout tree가 만들어지는 것을 기다린다.

{ passive:true }라는 것은 이벤트를 받는 컴포지터 스레드에 해당 이벤트가 메인스레드의 처리를 기다리지 않고 바로 파이프라인 단계 중 마지막인 Composite를 수행해도 된다는 것을 미리 알려주는 것이다.

이 속성을 사용하게 되면 원래의 동작대로 메인 스레드에 넘기기는 하지만, 처리를 기다리지 않고 바로 Composite를 수행한다. 즉 스크롤 이벤트를 받아 새프레임믈 바로 합성할 수 있다는 의미이고, 결국 스크롤 성능이 향상된다.

주의사항

스크롤 성능 향상을 위해 { passive:true } 속성을 사용할 경우 e.preventDefault()를 사용해서는 안된다.

참고

focus()가 가능한 Element면 focus()가 가능하다?

모든 Element가 focus()가 가능한가?

MDN에 따르면 포커스가 가능하면 사용이 가능하다고 되어있다.

?????

무슨말인가 했더니 모든 Element가 focus()를 사용할 수 있는 것은 아니라는 것이다.

그렇다면 어떻게 해야하나?

focus를 주고 싶은데 화면상에 보여야 하는데 어떻게 해야하는가?
focus()와 비슷한 기능을 하는 것이 존재한다.

scrollIntoView()라는 것으로 해당 Element를 View안쪽으로 보이게 해주는 Method이다.
인자로는 true || false가 가능하며 true는 화면상 위로 false는 아래로 보이도록 해준다.

React Hook 예제로 자세히 살펴보기

const Hook = (function() {
  let hooks = []
  let currentHook = 0 // Hook 배열과 반복자(iterator)!
  return {
    render(Component) {
      const Comp = Component() // 이펙트들이 실행된다.
      console.log('Check 2')
      Comp.render()
      currentHook = 0 // 다음 렌더를 위해 초기화
      return Comp
    },
    useEffect(callback, depArray) {
      const hasNoDeps = !depArray
      const deps = hooks[currentHook] // type: array | undefined
      const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true
      console.log('Check useEffect', currentHook, hasNoDeps, hasChangedDeps)
      if (hasNoDeps || hasChangedDeps) {
        callback()
        hooks[currentHook] = depArray
      }
      currentHook++ // 이 Hook에 대한 작업 완료
    },
    useState(initialValue) {
      console.log('Check useState', hooks[currentHook] || initialValue)
      hooks[currentHook] = hooks[currentHook] || initialValue // type: any
      const setStateHookIndex = currentHook // setState의 클로저를 위해!
      const setState = (newState) => {
        hooks[setStateHookIndex] = newState
      }
      return [hooks[currentHook++], setState]
    },
  }
})()

function Counter() {
  const [count, setCount] = Hook.useState(0)
  const [text, setText] = Hook.useState('foo') // 두번 째 상태 Hook!
  console.log('Check Create Component')
  Hook.useEffect(() => {
    console.log('Check Inner useEffect', count, text)
  }, [count, text])
  return {
    click: () => setCount(count + 1),
    type: (txt) => setText(txt),
    noop: () => setCount(count),
    render: () => console.log('render', { count, text }),
  }
}
let App
App = Hook.render(Counter)
// App.click()
// App.type('bar')
App.noop()
App = Hook.render(Counter)

// App = Hook.render(Counter)
// App.type('bar')
// App = Hook.render(Counter)
// App.noop()
// App = Hook.render(Counter)
// App.click()
// App = Hook.render(Counter)

// Hook은 데이터가 변경이 되면 render가 새로 돌면서 기존의 만들어진 것을 버리고 새로운 Counter를 만든다.
// 만드는 과정에서 이전의 값을 보관하고 있기 위해서 Hook 객체 내부적으로 데이터를 가지고 있는다.
// useState, useEffect는 데이터 변경감지고 render가 돌게 되면서 새로이 만든다.
// 새로 만드는 과정에서 useEffect는 기존의 가지고 있던 데이터와 새로 들어온 데이터 하나하나를 비교해서 Callback()을 실행한다.

requestIdleCallback 어디까지 아니?

window.requestIdleCallback()

이제는 많은 분들이 알고있으며, 사용하는 곳도 많은 이 requestIdleCallback에 대해서 심화적으로 작성을 하려고 합니다.

window.requestIdleCallback(callback[, options])

흔히 requestIdleCallback 함수는 parameter로 callback function과 options를 넣는다.

모르는 분들을 기준으로 작성을 하자면, options값으로는 timeout을 넣을 수 있다. idleCallback이 너무 오래걸리는 경우가 발생하는 경우를 방지하기 위해서 사용한다.

Example

window.requestIdleCallback(callback function, { timeout: 10000 })

여기에 더 나아가 callback function의 args로 넘어가는 값이 있다.!!!

바로 IdleDeadline이다.

IdleDeadline

IdleDeadline는 Property 1개, Method 1개를 가진다.

Property

IdleDeadline.didTimeout

위에서 언급한 options 값의 timeout이 만료되었음에도 불구하고 실행중이라면, 값이 true가 되는 Boolean 값이다.

Example

function (deadline) {
  try {
    if (deadline.didTimeout) {
      throw new Error('IdleCallback timeout')
    }
  }
}

Method

IdleDeadline.timeRemaining()

options 값의 timeout을 기준으로 현재 유휴 시간에 남아있는 밀리초 수를 예측하는 부동소수점 값이 반환된다.

Example

function (deadline) {
  try {
    if (deadline..timeRemaining() <=0 ) {
      throw new Error('no remaining time')
    }
  }
}

깃헙에는 많은 라이센스가 있는데 그게 뭐에요?

Open Source Licenses

오픈 소스 라이선스는 오픈 소스 정의를 준수하는 라이선스이다. 요약하면 소프트웨어를 자유롭게 사용, 수정 및 공유 할 수 있다.

우리가 많이 보는 라이센스들

  • Apache License 2.0
  • BSD 3-Clause "New" or "Revised" license
  • BSD 2-Clause "Simplified" or "FreeBSD" license
  • GNU General Public License (GPL)
  • GNU Library or "Lesser" General Public License (LGPL)
  • MIT license
  • Mozilla Public License 2.0
  • Common Development and Distribution License
  • Eclipse Public License

아래의 사이트를 들어가게 되면 해당 라이센스에 대한 정보와 작성법이 있습니다.

Reference

[지연평가 Series-1] Iterable, Iterator에 대해서 간단하게 알아보기

Iteration

Iteration 프로토콜에는 두 가지의 프로토콜이 있다.

  1. Iterable 프로토콜
  2. Iterator 프로토콜

ES6(ES2015)에 추가된 두 가지는 프로토콜 즉, 규약이다.

Iterable

Iterable 프로토콜은 반복 가능한 객체를 나타내는 프로토콜이다.

우리가 많이 사용하는 for...of , spread, 'distructing` 등에서 반복되는 행동을 정의하는 객체를 반복 가능 하다고 한다.

반복 가능한 객체로는 내장 객체인 Array, Map, Set, String 등이 있다. 이러한 객체들은 [Symbol.iterator]라는 키를 가진다.

<규칙>

  1. 순회할 수 있는 데이터를 가지고 있어야 한다.
  2. Symbol.iterator 를 메서드로 가지고 있어야 한다.
  3. Symbol.iterator 메서드는 iterator 객체를 반환해야 한다.
  4. iterator 객체는 반드시 next라고 하는 메서드를 가져야 한다.
  5. next 에는 데이터에 접근할 수 있어야 한다.
  6. iterator 객체인 iteratorObjiteratorObj.next()하면 {value : data }, done : false} 형식으로 추출되며 전부 순회했을 경우 { done : false }가 반환되도록 한다.

위와 같은 규칙을 만족하는 것을 iterable이라고 하며, 반환된 객체를 iterator라고 한다.

앞서 언급했듯이 프로토콜이다. 일정한 형태만 갖춰진다면 우리도 쉽게 만들 수 있다.

Iterable한 객체를 만들기 위해서는 [Symbol.iterator] 라는 키에 next 라는 메소드를 가지는 객체를 반환하는 함수를 할당하면 된다.

함수의 반환값은 value 프로퍼티를 가지는 객체이다.

    const fibonacci = {
      [Symbol.iterator]() {
        let [pre, cur] = [0, 1];
        const max = 10;
    
        return {
          next() {
            [pre, cur] = [cur, pre + cur];
            return {
              value: cur,
              done: cur >= max
            };
          }
        };
      }
    };

Iterator

Iterator 프로토콜은 반복 가능한 객체의 값을 시퀀스대로 처리하는 프로토콜이다.

  1. 컬렉션 내의 항목에 대해 한 번에 하나씩 접근하면서 현재의 위치를 추적하는 방법을 알고 있는 객체
  2. 반복 가능 인터페이스에 의해 반환되는 객체

Iterator 객체는 next 메소드를 통해 다음 시퀀스를 진행하게 된다. 이 메소드는 value와 done을 가진 객체를 리턴한다.

    function createIterator(items) {
      let i = 0;
      return {
          next: function() {
          const done = (i >= items.length);
          const value = !done ? items[i++] : undefined;
          return {
              done: done,
            value: value
          };
        }
      };
    }

Reference

간단하게 skeleton UI 따라해보기

페이스북이나, 유튜브같은 서비스에서는 사진이나 영상이기에 불러오는데 시간이 많이 걸리게 된다.
이럴때 Skeleton UI를 사용하게 되면 사용자는 로딩 중이라는 것을명확하게 알 수 있다.

skeleton.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="./skeleton.css">
    <title>Document</title>
</head>

<body>
    <div class="container">
        <div class="bar">
            <div class="indicator"></div>
        </div>
        <div class="wrapper">
            <div class="skeleton"></div>
        </div>
    </div>
</body>
<script>
    setTimeout(() => {
        const parentDiv = document.getElementsByClassName("skeleton")[0];
        const barDiv = document.getElementsByClassName("bar")[0]
        const childDiv = document.createElement("div");
        childDiv.style.width = "100vw";
        childDiv.style.height = "172px";
        childDiv.style.backgroundImage = 'url("https://source.unsplash.com/random")';
        childDiv.style.backgroundSize = "cover";

        parentDiv.insertAdjacentElement("beforeend", childDiv);
        barDiv.style.display = "none";
    }, 3000);
</script>

</html>

skeleton.css

body{
    margin: 0px;
    padding: 16px;
}

.container {
    width: 100%;
    height: 100vh;
    position: relative;
    overflow: hidden;
  }
  
  .wrapper {
    width: 100%;
    height: 100%;
    display: flex;
  }
  
  .skeleton:empty {
    width: 100%;
    height: 100%;
    background-image: 
      linear-gradient(#f1f3f5 172px, transparent 0),
      linear-gradient(#f1f3f5 16px, transparent 0),
      linear-gradient(#f1f3f5 10px, transparent 0),
      linear-gradient(#f1f3f5 10px, transparent 0),
      linear-gradient(#f1f3f5 50px, transparent 0),
      linear-gradient(#f1f3f5 11px, transparent 0),
      linear-gradient(#f1f3f5 8px, transparent 0),
      linear-gradient(#f1f3f5 8px, transparent 0),
      linear-gradient(#f1f3f5 14px, transparent 0),
      linear-gradient(#f1f3f5 8px, transparent 0);
    background-repeat: repeat-y;
    background-size: 
        100% 304px, 
        100% 304px, 
        200px 304px, 
        300px 304px, 
        50px 304px,
        40px 304px, 
        100px 304px, 
        100px 304px, 
        112px 304px, 
        89px 304px;
    background-position: 
        0px 0px, 
        0px 188px, 
        0px 214px, 
        210px 214px, 
        0px 244px,
        56px 244px, 
        56px 264px,
        56px 279px, 
        231px 244px, 
        254px 264px;
  }
  
  .bar {
    width: 100%;
    position: absolute;
    animation-name: skeleton;
    animation-duration: 2s;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in;
  }
  
  .indicator {
    width: 0;
    box-shadow: 0 0 60px 75px white;
  }
  
  .bar,
  .indicator {
    height: 100vh;
  }
  
  @keyframes skeleton {
    0% {
      transform: translateX(0);
      opacity: 0;
    }
  
    20% {
      opacity: 0.25;
    }
  
    50% {
      opacity: 1;
    }
  
    80% {
      opacity: 0.5;
    }
  
    100% {
      transform: translateX(100%);
      opacity: 0;
    }
  }

시연

skeleton-ui

z-index의 최대값은??

표로 확인하기

ROWSER MAX Z-INDEX VALUE WHEN EXCEEDED, VALUE CHANGES TO:
Internet Explorer 6 2147483647 2147483647
Firefox 0 - 2 2147483647 element disappears
Firefox 3 2147483647 0
Firefox 4+ 2147483647 2147483647
Safari 0 - 3 16777271 16777271
Safari 4+ 2147483647 2147483647
Internet Explorer 6+ 2147483647 2147483647
Chrome 29+ 2147483647 2147483647
Opera 9+ 2147483647 2147483647

결론

그냥 2147483647

Chrome 75 Update

1. CSS Filter Property

GPU를 사용하도록 하는 Flag로도 사용이 되는 filter 속성이 개발자도구에서 바로바로 반영이 되는 모습을 볼 수 있다.

참고 : Layer Model

image

image

2. Command Menu로 Storage 지우기

Service workers, localStorage, sessionStorage, IndexedDB, Web SQL, Cookies, Cache, and Application Cache를 Command Menu(Ctrl + Shift + P)로 한번에 지울 수 있는 기능이 추가되었다. 모든 것을 한번에 지우지 않고 별개로 지우고 싶다면 Application > Clear Storage 에 가서지우면 된다.

3. IndexedDB databases를 한곳에서

기존의 <iframe> 내부에서 IndexedDB를 사용하고 있다하더라도 main origin의 내용만 볼 수 있었으나, 이제는 모든 origin의 내용을 볼 수 있다.

4. resource's uncompressed size

Network Tab에서 해당 resource에 Hover를 하게 되면서 압축되지 않은 크기도 보여준다.

5. Inline breakpoints

개인적으로 제일 맘에 드는 내용이다. 기존에

document.querySelector('#dante').addEventListener('click', logWarning);

위와 같은 inline-code가 있으면 breakpoint를 1개로 잡을 수 밖에 없었다.

image

이제는 각각의 실행단위로 주어진다.

image

6. IndexedDB and Cache resource counts

IndexedDB과Cache의 자원의 수를 표시해주는 기능이 추가 되었다.

image

Reference

-New in Chrome 75

만드는건 어려운데 사용하면 좋은 정규식(Sample)

정규식이 잘 사용하면 정말정말 끝도없이 편하지만 진입장벽이 생각보다 존재한다. 이번 기회에 공부를 해보자

규칙 구현 설명
모든 공백 찾기 /\s/g 띄어쓰기, 탭, 줄바꿈 감지
휴대전화번호 01[016789]\D?\d{3,4}\D?\d{4} 010-1234-5678, 01012345678, 010.1234.5678
이메일 체크 /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$ [email protected]
URL https?://(\w*:\w*@)?[-\w.]+(:\d+)?(/([\w/_.]*(\?\S+)?)?)? https://regexr.com/
// /**/주석 찾기 \/\/.* // 주석을 찾자
마스터 카드 5[1-5]\d{14} MasterCard: 5212345678901234
비자 카드 4\d{12}(\d{3})? Visa 1:4123456789012

Reference

[지연평가 Series-2] Generator에 대해서 간단하게 알아보기

지난 글 Iterable, Iterator에 대해서 간단하게 알아보기에 이어서 제너레이터에 대해서 알아보자

Generator

MDN에 따르면

Generator 객체는 generator function으로부터 반환된 값이며 Iterable과 Iterator 프로토콜을 준수한다.

기본적으로 아래와 같은 특징을 가진다.

  • 제너레이터 : Iterator가 자기자신을 리턴하는 Iterable 객체 === Well-formed iterable
  • 자바스크립트는 다형성이 높아서 어떠한 값이나 상태를 순회할 수 있는 값을 만들 수 있다.

쉽게 제너레이터는 Iterator를 반환하며 그 반환한 Iterator는 자신 자신의 상태를 기억하고 있다.

function *gen(){
  yield 1;
  yield 2;
  yield 3;
  yield 4;
}

let iter = gen();
console.log(iter.next()); // 1
console.log(iter[Symbol.iterator]() === iter); // true

for (const a of iter) console.log(a); // 2 3 4

image

위의 예제에서 보이는 것이 제너레이터의 특징을 모두 보여주고 있다.

제너레이터 함수는 크게 2가지를 알면 된다. *yield이다. *은 제너레이터 함수라는 것을 명시적으로 표시하는 것이며, yield는 iterator의 next() 메서드를 실행했을 때 보여줄 값이며, 해당 위치에서 멈추게 된다.

먼저 let iter = gen(); 제너레이터 함수를 통해서 하나의 제너레이터 객체를 반환받았다.

image

반환 받은 제너레이터 객체는 iterator, iterable 프로토콜을 따르고 있어 next() 메서드를 통해서 다음 값을 가져올 수 있다.

그래서 다음 줄인 console.log(iter.next());를 실행하게 되면 결과로 아래와 같은 value, done이 나오게 된다.

image

다음 줄로 넘어가서 console.log(iter[Symbol.iterator]() === iter);를 보게 되면 위에서 설명한 특징 중 하나인 Well-formed iterable 라는 것을 명시적으로 보여주고 있다.

마지막으로 iter는 당연하게 Iterator임으로 for...of로 순환이 가능하다. 그런데 추가적으로 제네레이터 객체는 Well-formed iterable로 자기 자신의 상태를 기억하고 있어서 1부터가 아닌 2부터 출력하는 모습을 볼 수 있다.

응용해보기

// 무한히 생성하는 함수
function *infinity(i = 0){
  while(true) yield i++;
}

// iterator를 받아서 l과 동일할 경우 멈추는 로직
function *limit(l, iter){
  for( const a of iter){
    yield a;
    if(a == l) return;
  }
}

function *odds(l){
  for(const a of limit(l, infinity(1))){
     if(a % 2) yield a;
     if(a == l) return;
  } 
}

let iter2 = odds(10);
console.log(iter2.next());
console.log(iter2.next());
console.log(iter2.next());
console.log(iter2.next());
console.log(iter2.next());

for (const a of odds(40)) console.log(a);

image


Reference

Dead Lock(데드락)

오랜만에 생각이 나서 찾아보니 잘 정리를 해주신 분의 글을 토대로 정리를 하였습니다.


Deadlock

  • 영원히 오지 않는 그 분을 기다리는 것(응답없음 상태 같은 것이라고 생각하면 된다.)

  • 다시 돌아가지 않고 멈춰버린 상태

  • 예를 들어

    • A라는 프로세스는 B가 가진 자원이 있어야 동작이 가능하다.
      그래서 B작업이 끝나서 자원을 넘겨주기를 기다리고 있다.
    • B라는 프로세스 역시 A가 가진 자원이 있어야 하기 때문에 A작업이 끝나기만 기다리는 상태
    • 이로써 무한 교착 상태가 탄생하게 된다.

일반적으로 데드락(Deadlock)이라는 상태는 이유없이 생기지 않는다.

흔히 아래의 4가지 조건을 모두 만족하면 데드락이 발생한다고 한다.

  • 상호배제(Mutual exclusion) : 한 번에 오직 1개의 프로세스만이 자원에 접근할 수 있다.
  • 점유 및 대기(Hold and wait) : 최소한 한개의 자원을 가진 프로세스가 다른 프로세스 소유의 자원을 추가로 얻기 위해 기다린다.
  • 비선점(No preemption) : 해당 작업이 완료되기 전까지는 자원이 반환되지 않는 것
  • 환형대기(Circular wait) : 연쇄적(꼬리에 꼬리를 무는, 뫼비우스)으로 자원을 기다리는 형태

데드락 해결 방법

위에서 언급했듯이 4가지의 조건이 모두 충족해야 발생한다. 그렇다면 1개라도 만족을 못하면 해결할 수 있다고 보면 된다.

1). Ostrich Algorithm

  • 시스템을 재시작한다.

  • (장) 가장 빠르고 비용이 적게든다

  • (단) 데드락의 발생 빈도가 높아질수록 효율이 극도로 나빠진다.

2) Deadlock Prevention

  • 데드락의 발생 조건을 막아 데드락이 일어나지 않게 한다.

  • 상호배제 : 동시에 여러 프로세스의 접근을 가능하게 한다. (현실적으로 불가능)

  • 점유 및 대기 : 한번에 모든 자원을 가지거나 아무것도 가지지 못하게 한다 (hold 방지)
    실행 전에 자원을 미리 할당해 버린다 (wait 방지)
    자원 활용도가 떨어지며, starvation이 발생할 수 있다.

  • 비선점 : 한개의 작업이라도 실패하면 모든 자원을 release 한다.

  • 환형대기 : 자원 형태에 따라 순서를 매겨 프로세스가 리소스를 요청하면 해당 순서에 따라 자원을 할당한다.

3) Deadlock Avoidance

  • Dijkstra의 banker's Algorithm을 이용
  • 각 프로세스에 자원을 할당하려 교착상태가 발생하지 않고 모든 프로세스가 완료될 수 있는 상태를 안정상태,
    교착상태가 발생할 수 있는 상태를 불안정 상태라 한다.
  • 자원의 양과 사용자(프로세스) 수 가 일정해야 한다.
  • 각각의 프로세스가 OS에게 자신이 필요로하는 최대의 자원을 알려준다.

OS는 오직 safe한 상태를 가지는 프로세스에게만 자원을 할당한다.

  • safe state : 받은 자원을 통해 무사히 작업을 수행하고 반납할 수 있는 상태.
  • 지나치게 보수적이라서 safe state를 위해 병렬성을 감소시킨다.
  • 또한, 프로세스가 알려줬던 요구량보다 더 많은 요구량이 발생할 수 있다.

 4) Deadlock Detection

  • 데드락 상태가 되는 것은 허락하지만, wait-for graph 또는 detection algorithm 을 통해 데드락 유발 프로세스를 찾아낸다.

5) Deadlock Recovery

  • 매 time slice 마다 프로세스를 제거함과 동시에 회수해 더 이상 데드락이 발생하지 않을 때 까지 한다.

또는 체크 포인트를 만들어 데드락이 발생하면 체크 포인트 지점으로 롤백한다.

  • rollback시 starvation이 발생할 가능성이 높다( 무한 rollback이 발생할 수 있다.)

JavaScript에서 map을 더더욱 잘 알고 사용하기

요즘 사람들이 많이 사용하는 함수에는 map, filter, 'reduce`가 있다. 이러한 함수를 사용할 때는 주의를 해야한다.

['1', '2', '3', '4'].map(parseInt)

위와 같은 예제가 있다고 하자 그렇다면 사람들은 [1, 2, 3, 4]가 나올 것이라고 생각하며, 이렇게 나오길 바랄 것이다. 그러나 실제로는 [1, NaN, NaN, NaN]이 나오게 된다.

Why

parseInt는 기본적으로 2개의 인자를 받는다. MDN에 나와있는 문법을 보게 되면

parseInt(string, radix);

2개의 인자를 받으면서 2번째 인자는 진수를 받고있다.
이에 map이라는 함수를 살펴보게 되면

arr.map(callback(currentValue[, index[, array]])[, thisArg])

위와 같은 문법을 가진다. 요즘 사람들이 많이 사용하는 함수에는 map, filter, 'reduce`가 있다. 이러한 함수를 사용할 때는 주의를 해야한다.

['1', '2', '3', '4'].map(parseInt)

위와 같은 예제가 있다고 하자 그렇다면 사람들은 [1, 2, 3, 4]가 나올 것이라고 생각하며, 이렇게 나오길 바랄 것이다. 그러나 실제로는 [1, NaN, NaN, NaN]이 나오게 된다.

Why

parseInt는 기본적으로 2개의 인자를 받는다. MDN에 나와있는 문법을 보게 되면

parseInt(string, radix);

2개의 인자를 받으면서 2번째 인자는 진수를 받고있다.
이에 map이라는 함수를 살펴보게 되면

arr.map(callback(currentValue[, index[, array]])[, thisArg])

위와 같은 문법을 가진다. 위의 인자를 살펴보면 첫 번재인자로 콜백함수를 받고 콜백함수에는 순서대로 현재 value, index, array가 들어가게 된다.

이렇게 되니 map안에 단순하게 parseInt라는 함수를 넣게 되면 parseInt는 두 번째인자로 map의 index를 받게 되어 결과값이 다르게 나오게 되는 것이다.

라이브러리에서 많이 사용하는 unique key를 만드는 방법

개발을 하다보면 유니크한 키가 필요하다. 그런데 어떻게 작성해야 좋은 유니크 키가 될까? 라고 고민을 할 수 있다. 그래서 아래와 같이 일반적으로 라이브러리를 만드시는 분들이 많이 사용하는 유니크 만드는 방법을 가져왔습니다.

잡다한 설명

Math의 랜덤함수를 가져와서 toString(radix) 함수에 기수를 넣어 변환을 하면 0.65lni4m484i 와 같은 모양새를 가지게 된다. 여기서 .을 기준으로 뒤로 사용하되 자신이 원하는 길이만큼의 글자를 잘라서 사용할 수 있다.

생각보다 간단한 유니크키를 만드는 방법이다.

Math.random()
      .toString(36)
      .substr(2, keyLength)

우아하게 console 사용하기!!

우리가 제일 많이? 사용하는 API 중 하나는 console.log라고 생각한다.

따지고 보면 console.log 라는 것은 console이라는 것에 log를 찍은다는 것인데 그렇다면 다른 console 하위 메서드가 있는 것이 아닌가?

많은 사람들이 이미 알고 있을 부분이지만 정리를 해보았다.


console.log()

제일 많이 사용하고 기본적인 로그를 찍어주는 기능이다.

로그를 찍어주는 기능은 모두가 알고있다는 가정하에 %c를 사용해보자

console.log("%cSeonHyung Jo", "background: #3F51B5; color:#FFF; padding:5px; border-radius: 10px;line-height: 15px;")

image

console.assert()

기본적으로 2개의 인자를 받으며, 첫번째 인자가 false인 경우 메시지와 스택을 보여준다.

image

console.clear()

콘솔을 깨끗하게 지운다.

image

console.dir()

console.log를 사용하여 데이터를 보려고 할 때 나오는 형태와 비슷하게 보여주는 API이다.

image

console.dirxml()

객체를 XML/HTML 엘리먼트 형태로 나타내며, 아닐 경우 JS 객체로 표시한다.

image

console.info(), console.warn(), console.error()

console를 찍어주는 3대장이라고 할 수 있다. 우리가 브라우저 Dev Tool에서 보이는 색이나 Level을 다르게 찍어줄 수 있다.

image

크롬 개발자도구에서 Level 선택창

image

console.memory

현재 메모리를 볼 수 있다.

image

console.table()

개인적으로 마음에 드는 API로 데이터를 표형태로 보여준다.

image

메모리와 테이블을 합쳐서 볼 수도 있다.

image

console.trace()

이름 그대로 stact trace를 출력해줍니다. 디버깅을 할 때 유용합니다.

image

console.group(), console.groupCollapsed(), console.groupEnd()

그룹으로 감싸는 역할로 예제는 MDN에서 약간만 수정했습니다.

image

Masonry Layout(CSS Grid + Intersection Observer + insertAdjacentElement) == Pinterest Layout

오늘은 몇시간만에 겨우 만든 Masonry Layout를 소개하려고 합니다.
흔히 사람들이 핀터레스트 레이아웃이라고 알고 있는 것으로 스크롤이 끝에 닿으면 자동으로 사진들이 더 보여주는 기능입니다.

하단에 보이는 노란색 Div에 Intersection Observer를 걸어서 무한스크롤을 구현했으며 Grid Cell을 추가하는데 성능을 고려하여 insertAdjacentElement()를 사용하였습니다.

무엇보다 CSS3 Grid Layout을 사용하여 반응형에도 좋으며 구현하는데 도움이 많이 되었습니다.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="./index.css">
    <script src="./index.js"></script>
    <title>Document</title>
</head>

<body>
    <div id="gridWrapper"></div>
</body>
<script>
    const INIT_COLUMN_SIZE = 5; // Grid Template Size
    const INIT_ROW_SIZE = 20; // Grid Template Size
    const MAX_CELL_COLUMN_SIZE = 3; // Grid Cell Size
    const MAX_CELL_ROW_SIZE = 10; // Grid Cell Size
    const ADD_CELL_SIZE = 20; // Create Cell Count

    // Init currentRowNumList
    const currentRowNumList = Array(INIT_COLUMN_SIZE).fill(1);

    // Create Wrapper Div
    const gridWrapper = document.querySelector("#gridWrapper");

    // Create masonry Div
    const masonry = document.createElement("div");
    masonry.id = "masonry";
    masonry.style.gridTemplateColumns = `repeat(${INIT_COLUMN_SIZE}, 1fr)`;
    masonry.style.gridTemplateRows = `repeat(${INIT_ROW_SIZE}, 1fr)`;

    // Create scrollPoint Div
    const scrollPoint = document.createElement("div");
    scrollPoint.id = "scrollPoint";

    gridWrapper.insertAdjacentElement("afterbegin", masonry);
    gridWrapper.insertAdjacentElement("afterend", scrollPoint);

    // Make intersectionObserver to create add new grid cells
    const intersectionObserver = new IntersectionObserver(function (entries) {
        Promise.all(createGridCell(ADD_CELL_SIZE)).then(() => {
            console.log("Finish");
        })
    });

    // start observing
    intersectionObserver.observe(document.querySelector("#scrollPoint"));
</script>

</html>

index.js

let standardColumnRect = 1;
let standardRowRect = 1;

const getRandomNum = maxNum => {
  return Math.ceil(Math.random() * maxNum);
};

const addGridAction = () =>
  new Promise(resolve => {
    let maxColSize = 1;

    for (let i = standardColumnRect; i < INIT_COLUMN_SIZE; i++) {
      if (currentRowNumList[i] > standardRowRect) {
        break;
      }
      maxColSize += 1;
    }

    const columnSizeCondition =
      maxColSize >= MAX_CELL_COLUMN_SIZE ? MAX_CELL_COLUMN_SIZE : maxColSize;
    const columnSize = getRandomNum(columnSizeCondition);
    const rowSize = Math.ceil(Math.random() * MAX_CELL_ROW_SIZE + 1);

    // Random Picture from https://source.unsplash.com/random
    const newDiv = document.createElement("div");
    newDiv.style.gridColumn = `${standardColumnRect} / ${standardColumnRect +
      columnSize}`;
    newDiv.style.gridRow = `${standardRowRect} / ${standardRowRect + rowSize}`;
    newDiv.style.backgroundImage = "url('https://source.unsplash.com/random')";
    newDiv.style.backgroundSize = "cover";
    newDiv.style.backgroundRepeat = "no-repeat";
    newDiv.style.border = "1px solid black";

    masonry.insertAdjacentElement("beforeend", newDiv);

    for (let i = standardColumnRect; i < standardColumnRect + columnSize; i++) {
      currentRowNumList[i - 1] += rowSize;
    }

    // Grid 우측 영역을 넘어가면 1로 초기화
    standardRowRect = Math.min(...currentRowNumList);
    standardColumnRect = currentRowNumList.indexOf(standardRowRect) + 1;

    resolve();
  });

const createGridCell = cellCount => {
  const promiseList = [];
  
  for (let i = 0; i < ADD_CELL_SIZE; i++) {
    promiseList.push(addGridAction());
  }

  return promiseList;
};

index.css

body{
    margin: 0px;
    padding: 10px;
}

#masonry {
    display: grid;
    border: 3px solid black;
    gap: 20px;

    min-height: 120vh;
    margin: 0px;
 }

 #scrollPoint{
     width: 100px;
     height: 100px;
     border: 1px solid yellow;
     background-color: yellow;
 }

시연

Masonry

CEF Version은 어떤 규칙으로 만드나?

JCEF를 빌드하기 위해서는 JCEF에 필요한 CEF의 버전과 Chromium 버전을 알아야 한다.

흔히 우리가 말하는 Chrome 버전이 76이 나왔다. 75가 나왔다고 하는 Major한 버전은 Chromium의 Major 버전에서 왔다고 생각하면 된다.

CEF버전은 Chromium 버전과 다르게 이어 나가고 있었으나 2019년 2월 Chromium 73버전이 나오면서 CEF에서도 Chromium의 Major 버전을 동일하게 가져가고 있다.

CEF 버전 확인하기

73 버전 이전에는 위의 사이트와 같이 3으로 시작하는 버전을 사용했다면, 현재는 다르게 가져가고 있다. (설명은 73이후 버전에 대한 설명으로 한다.)

X.Y.Z+gHHHHHHH+chromium-A.B.C.D

현재 CEF의 버전은 X.Y.Z+gHHHHHHH+chromium-A.B.C.D 형태를 가지고 있다.

ex) 75.0.13+g2c92fcd+chromium-75.0.3770.100

  • "X"는 Chromium Major 버전이다. (e.g. 75).
  • "Y"는 release branch가 만들어지면 0부터 시작하고, CEF C / C ++ API가 변경될 때 올라가는 숫자이다. (include / cef_api_hash.h 파일의 갱신에 의해 결정된다.)
  • "Z"는 release branch가 만들어지면, 0부터 시작하고 각 커밋이 변경될 때 숫자가 올라가며, "Y"가 변경되면 0으로 바뀌게 된다.
  • "gHHHHHHH"는 Git 커밋 해시의 7자 약어이다. 이를 통해 Git에서 관련 커밋 히스토리를 쉽게 찾을 수 있다.
  • "A.B.C.D"는 Chromium 버전이다.(e.g. 75.0.3770.100).

위와 같은 버전 명세는 Semantic Versioning 2.0 표준을 따른다.

👷 1급 시민(first class citizen)

자바스크립트의 함수는 1급 객체이다

함수형을 배우기 전에 순수함수에 대해 알아보기 전에 우리는 이론적으로 나아가 1급 시민이라는 것을 알아보고 1급 객체, 1급 함수까지에 대해서 알아볼 필요성이 있다.

1급 시민

Christopher Stracheysms는 수업에 사용한 노트에서 1급 시민(first class citizen)과 2급 시민(second class citizen)이 소개가 되었다.

1급 시민의 조건

  • 변수에 담을 수 있다.
  • 인자로 전달할 수 있다.
  • 반환값으로 전달할 수 있다.

위의 3가지의 조건으로 보자면 대부분의 프로그래밍 언어에서 숫자나 문자같은 것들은 모두 1급 시민 조건을 충족한다.

1급객체(first class object)

1급 객체라는 것은 특정 언어에서 객체를 1급 시민으로써 취급한다는 것이다.
대부분 위의 조건을 모두 충족한다.

1급 함수(first class function)

1급 객체 뿐만 아니라 1급 함수도 존재한다.
함수를 1급 시민으로 취급하는 것이다. 몇몇 학자들은 1급 시민의 조건 + 다음의 조건을 충족해야 1급 함수라고 할 수 있다고 한다.

  • 런타임 생성이 가능하다.
  • 익명으로 생성이 가능하다

위의 조건으로 본다면 C의 함수는 1급 함수로 볼 수 없다.

JavaScript의 함수는 1급 함수? 1급 객체?

JavaScript에서는 객체를 1급 시민으로 취급한다. 그리고 JavaScript의 함수도 객체로서 관리되므로 1급 객체라고 볼 수 있다. 동시에 JavaScript의 함수는 1급 함수의 추가 조건도 만족한다.(런타임 + 익명)

1급 객체인 동시에 1급 함수이지만, 보통 1급 객체로 기술하고 있다. 이는 함수가 JavaScript에서 함수는 객체이기 때문일 것 같다.

JavaScript에서 함수가 1급 객체인 것이 중요한 이유

함수가 1급 객체라는 사실은 겉으로 봤을 땐 그리 특별하지 않다.

가장 중요한 장점은 바로 고차 함수(high order function)가 가능하다는 점이다. JavaScript의 each, filter, map, sort 등의 함수들은 엄청나게 편안함을 가져다 주고 있다.

반면, Java 7의 Collections.sort 함수 같은 경우도 비교를 위해 인자를 하나 넘겨주게 되는데, 이것은 함수가 아니라 Comparator interface 타입의 인스턴스(instance)이다. 함수 하나를 넘겨주기 위해서 새로운 클래스를 만들고 그것의 인스턴스까지 생성해야 하는 것이다.

물론Java 8에서는 람다(lambda)가 지원되면서 간편해졌다.

1급 객체가 JavaScript의 클로져(closure) 와 만나면 또 하나의 장점이 생긴다. JavaScript의 함수는 생성될 당시의 Lexical Environment를 기억하게 되는데, 함수를 주고받게 되면 이 Lexical Environment도 함께 전달된다. 이것을 이용해서 커링(currying)메모이제이션(memoization) 이 가능해진다.


Reference

Animating Append List Items - CSS Animation Practise

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<style>
li {
  list-style: none;
  background: #d1703c;
  color: #fff;
  height: 0;
  line-height: 2em;
  margin: 0;
  padding: 0 0.5em;
  overflow: hidden;
  width: 10em;
}

li.show {
  height: 2em;
  margin: 2px 0;
}

.fade li {
  transition: all 0.4s ease-out;
  opacity: 0;
  height: 2em;
}
.fade li.show {
  opacity: 1;
}

.slide-fade li {
  transition: all 0.4s ease-out;
  opacity: 0;
}
.slide-fade li.show {
  opacity: 1;
}

.swing {
  perspective: 300px;
}

.swing li {
  /* opacity: 0;
  transform: rotateX(-90deg);
  transition: all 0.5s cubic-bezier(.36,-0.64,.34,1.76); */

  opacity: 0;
  transform: rotateY(-90deg);
  transition: all 0.5s cubic-bezier(.36,-0.64,.34,1.76);
}

.swing li.show {
  opacity: 1;
  transform: none;
  transition: all 0.5s cubic-bezier(.36,-0.64,.34,1.76);
}
</style>

<body>
  <ul id="list" class="swing">
    <li class="show">List item</li>
    <li class="show">List item</li>
  </ul>
  <button id="add-to-list">Add a list item</button>
</body>

<script>
  /*
   * Add items to a list - from cssanimation.rocks/list-items/
   */
  document.getElementById('add-to-list').onclick = function () {
    const list = document.getElementById('list');
    const newLI = document.createElement('li');
    
    newLI.innerHTML = 'A new item';

    list.appendChild(newLI);
    
    setTimeout(function () {
      newLI.className = newLI.className + " show";
    }, 10);
  }
</script>

</html>

Github도 다크다크하게 쓰자

워낙 다크테마를 좋아하여 Chrome Dev-Tool은 new moon theme를 사용하며 VSCode 역시 어둡게 사용하고 있다.

그런데 이번에는 GitHub Dark Theme를 찾게 되었다. 해당 Extension을 적용하게 되면 아래와 같이 너무나 이쁜 다크다크로 보인다.

image

다같이 다크다크하게 사용해보시죠. 설치경로는 여기를 누르면 됩니다.

Window에서 Hyper.js로 Mac iTerm 분위기 느껴보기

Window에서 Hyper.js로 Mac iTerm 분위기 느껴보기

요즘 대세인 CEF를 통해서 웹이 브라우저 밖에서도 자유롭게 실행이 되고 사용할 수 있게 되었다.
현재 웹개발을 하는데 많이 사용되는 vscode 역시 CEF를 사용하고 있다고 생각하면 된다.

그 중 현재 보려는 Hyper.js라고 하면 이름부터 JS스럽다. 바로 이 녀석을 실행해서 디버그 도구를 열게 되면 크롬하고 똑같은 디버그 도구가 나온다. 바로 CEF라는 증거이다.

이제 간단하게 Hyper.js에 들어가서 자신의 운영체제 맞는 파일을 다운받아서 실행한다.

hyper.js

자신만의 커스텀 설정

hyper.js가 좋은 점은 테마도 다양하는 것과 플러그인 역시 많다는 것이다.

공식 홈페이지에도 테마와 플러그인 설명이 있지만 모든 내용이 나오지는 않는다. 아래의 주소를 들어가게 되면 오히려 많은 내용을 볼 수 있다.

테마, 플러그인 쇼핑하기

개인적으로 설명도 가능하니 색상과 기능을 넣어보는 것도 좋은 경험이 될 것 같다.

설명이 귀찮으신 분들을 위해 제 설명을 올려놨습니다.

설정 훑어보기

아스키 코드 깔끔하게 가져다 사용하기

Character Entity Name Entity Number Description
    &#32; Space
!   &#33; Exclamation mark
"   &#34; Quotation mark
#   &#35; Number sign
$   &#36; Dollar sign
%   &#37; Percent sign
& &amp; &#38; Ampersand
'   &#39; Apostrophe
(   &#40; Opening/Left Parenthesis
)   &#41; Closing/Right Parenthesis
*   &#42; Asterisk
+   &#43; Plus sign
,   &#44; Comma
-   &#45; Hyphen
.   &#46; Period
/   &#47; Slash
0   &#48; Digit 0
1   &#49; Digit 1
2   &#50; Digit 2
3   &#51; Digit 3
4   &#52; Digit 4
5   &#53; Digit 5
6   &#54; Digit 6
7   &#55; Digit 7
8   &#56; Digit 8
9   &#57; Digit 9
:   &#58; Colon
;   &#59; Semicolon
< &lt; &#60; Less-than
=   &#61; Equals sign
> &gt; &#62; Greater than
?   &#63; Question mark
@   &#64; At sign
A   &#65; Uppercase A
B   &#66; Uppercase B
C   &#67; Uppercase C
D   &#68; Uppercase D
E   &#69; Uppercase E
F   &#70; Uppercase F
G   &#71; Uppercase G
H   &#72; Uppercase H
I   &#73; Uppercase I
J   &#74; Uppercase J
K   &#75; Uppercase K
L   &#76; Uppercase L
M   &#77; Uppercase M
N   &#78; Uppercase N
O   &#79; Uppercase O
P   &#80; Uppercase P
Q   &#81; Uppercase Q
R   &#82; Uppercase R
S   &#83; Uppercase S
T   &#84; Uppercase T
U   &#85; Uppercase U
V   &#86; Uppercase V
W   &#87; Uppercase W
X   &#88; Uppercase X
Y   &#89; Uppercase Y
Z   &#90; Uppercase Z
[   &#91; Opening/Left square bracket
\   &#92; Backslash
]   &#93; Closing/Right square bracket
^   &#94; Caret
_   &#95; Underscore
`   &#96; Grave accent
a   &#97; Lowercase a
b   &#98; Lowercase b
c   &#99; Lowercase c
d   &#100; Lowercase d
e   &#101; Lowercase e
f   &#102; Lowercase f
g   &#103; Lowercase g
h   &#104; Lowercase h
i   &#105; Lowercase i
j   &#106; Lowercase j
k   &#107; Lowercase k
l   &#108; Lowercase l
m   &#109; Lowercase m
n   &#110; Lowercase n
o   &#111; Lowercase o
p   &#112; Lowercase p
q   &#113; Lowercase q
r   &#114; Lowercase r
s   &#115; Lowercase s
t   &#116; Lowercase t
u   &#117; Lowercase u
v   &#118; Lowercase v
w   &#119; Lowercase w
x   &#120; Lowercase x
y   &#121; Lowercase y
z   &#122; Lowercase z
{   &#123; Opening/Left curly brace
|   &#124; Vertical bar
}   &#125; Closing/Right curly brace
~   &#126; Tilde

출처

SVG의 알 수없는 0.5의 차이

SVG의 알 수없는 0.5의 차이

SVG나 Canvas를 사용해 보면 어느 순간 알게 되는 사실이 하나 있다.
SVG, Canvas는 기본적으로 Vector이다. 그래서 일반적으로 우리가 사용하는 Pixel과는 그려지는 모양새가 다르다.

image

위의 사진에서 한 칸의 사각형을 하나의 픽셀이라고 생각을 해보면, border가 1px선을 단순하게 그렸다고 한다면 3칸의 사각형을 차지하게 될 것이다.

다시 위의 사진을 SVG 또는 Canvas로 X가 (1, 0)에서 (1, 3)까지 1px 선을 그렸다고 한다면 x = 1을 기준으로 양측 0.5만큼은 진하게 칠해지며, 이후 양측 0.5만큼은 연하게 칠해지는 것이 보인다.

즉 SVG 또는 Canvas로 그리게 되면 6칸을 차지하게 되는 것이다.

이는 위에서 말했듯이 SVG, Canvas가 Vector이기 때문이다.

그렇다면 어떻게 그려야 픽셀과 동일하게 보이나?

image

단순하게 좌표를 0.5씩 이동시키는 것이다. 그렇게 되면 위의 사진처럼 좌표는 픽셀의 사이에 위치하게 되고 SVG, Canvas는 좌표를 기준으로 양측 0.5만큼 칠하므로 픽셀 영역을 모두 칠하게 된다.

Reference

Lodash에서도 사용되는 비트마스킹

기존에 작성했던 글 BitWise Operator에서 언급된 비트마스킹을 직접사용하는 곳을 찾았다.

사람들이 많이 사용하는 라이브러리 중 하나인 Lodash에서 사용되고 있었다.

/** Used to compose bitmasks for cloning. */
const CLONE_DEEP_FLAG = 1
const CLONE_FLAT_FLAG = 2
const CLONE_SYMBOLS_FLAG = 4
function baseClone(value, bitmask, customizer, key, object, stack) {
  let result
  const isDeep = bitmask & CLONE_DEEP_FLAG
  const isFlat = bitmask & CLONE_FLAT_FLAG
  const isFull = bitmask & CLONE_SYMBOLS_FLAG

 ...

baseClone의 일부를 가져왔다. 그 중 이름이 bitmask라고 적혀있는 부분이 비트마스킹을 사용한 부분이다.

비트마스킹에 대해서 궁금하시면 아래 글을 참고하세요


Reference

Window 7 NSIS Shortcut 삭제 안되는 문제

윈도우 7에서 시작메뉴 ShortCut 등록과 삭제에 대해서 안되는 이슈가 있어서 찾아서 올렸습니다.

결국 1줄씩 추가를 해주면 된다.

# 설치
  SetShellVarContext all
  CreateDirectory "$SMPROGRAMS\메뉴명"
  CreateShortCut "$SMPROGRAMS\프로그램명.lnk" "$INSTDIR\ExeProg.exe" ...
# 삭제
  SetShellVarContext all
  Delete "$SMPROGRAMS\메뉴명\*.*"
  RMDir "$SMPROGRAMS\Green E-Diet Agent"

참고 참고 사이트로 이동하기

간단하게 Shadow DOM 사용해보기

도입

Iframe 를 사용해서 해당 주소 화면을 가져와서 Main화면에 그려주어야 한다. 그렇다면 당연하게 해당 frame에서 window.parent.postmessage()를 사용해서 메시지로 화면을 넘긴 후 Main에 innerHTML를 해주면 된다.

그런데 한가지 문제가 있다. 여러 개의 프레임에서 가져올 때 해당 화면의 스타일도 가져와서 적용시켜야하는 것이다. 그렇다면 제일 큰 문제는 같은 이름의 id, class, tag에 걸려있는 style이 제대로 적용이 되지 않는 이슈가 발생한다.

이에 별개의 공간을 가지는 Shadow-Root를 사용해서 별도의 공간을 할당하고 안에 화면을 그리고 스타일을 적용하여 내부에서만 스타일이 적용되고 외부로 새어 나오지않는 방법을 채택하였다.

예제

index.html

<!DOCTYPE html>
<html lang='en'>

<head>
  <meta charset='UTF-8'>
  <meta name='viewport' content='width=device-width, initial-scale=1.0'>
  <meta http-equiv='X-UA-Compatible' content='ie=edge'>
  <title>Main</title>
</head>

<body>
  <div style='width: 200px; height: 200px; border: 1px solid black'></div>
</body>

<script>
  const divElement = document.getElementsByTagName('div')[0]
  const shadow = divElement.attachShadow({ mode: 'open' }); // open 모드로 생성 버전이 올라가면서 createShadowRoot를 사용하지 않고 이 한줄로 사용이 가능하다.
  const html = document.createElement('html');
  const link = document.createElement('link');
  const styles = document.createElement('style');

  link.setAttribute('rel', 'stylesheet');
  link.setAttribute('type', 'text/css');
  link.setAttribute('href', './index.css');

  html.innerHTML = `
    <!DOCTYPE html>
    <html lang='en'>
    <head>
        <meta charset='UTF-8'>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
        <meta http-equiv='X-UA-Compatible' content='ie=edge'>
        <title>shadow-root</title>
    </head>
    <body id='sdBody'>
        <a><span></span>button</a>
    </body>
    </html>
  `;

  styles.textContent = `
    a, span {
      vertical-align: top;
      display: inline-block;
      box-sizing: border-box;
    }

    a {
        height: 20px;
        padding: 1px 8px 1px 6px;
        background-color: #1b95e0;
        color: #fff;
        border-radius: 3px;
        font-weight: 500;
        font-size: 11px;
        font-family:'Helvetica Neue', Arial, sans-serif;
        line-height: 18px;
        text-decoration: none;   
    }
    
    a:hover {  
      background-color: #0c7abf; 
      cursor: pointer;
    }

    span {
      position: relative;
      top: 2px;
      width: 14px;
      height: 14px;
      margin-right: 3px;
      background: transparent 0 0 no-repeat;
      background-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2072%2072%22%3E%3Cpath%20fill%3D%22none%22%20d%3D%22M0%200h72v72H0z%22%2F%3E%3Cpath%20class%3D%22icon%22%20fill%3D%22%23fff%22%20d%3D%22M68.812%2015.14c-2.348%201.04-4.87%201.744-7.52%202.06%202.704-1.62%204.78-4.186%205.757-7.243-2.53%201.5-5.33%202.592-8.314%203.176C56.35%2010.59%2052.948%209%2049.182%209c-7.23%200-13.092%205.86-13.092%2013.093%200%201.026.118%202.02.338%202.98C25.543%2024.527%2015.9%2019.318%209.44%2011.396c-1.125%201.936-1.77%204.184-1.77%206.58%200%204.543%202.312%208.552%205.824%2010.9-2.146-.07-4.165-.658-5.93-1.64-.002.056-.002.11-.002.163%200%206.345%204.513%2011.638%2010.504%2012.84-1.1.298-2.256.457-3.45.457-.845%200-1.666-.078-2.464-.23%201.667%205.2%206.5%208.985%2012.23%209.09-4.482%203.51-10.13%205.605-16.26%205.605-1.055%200-2.096-.06-3.122-.184%205.794%203.717%2012.676%205.882%2020.067%205.882%2024.083%200%2037.25-19.95%2037.25-37.25%200-.565-.013-1.133-.038-1.693%202.558-1.847%204.778-4.15%206.532-6.774z%22%2F%3E%3C%2Fsvg%3E);
    }
  `;

  shadow.appendChild(html);
  shadow.querySelector('head').appendChild(link);
  shadow.appendChild(styles);
</script>
</html>

index.css

html, body{
  padding: 0px;
  margin: 0px;
  width: 100%;
  height: 100%;
}

body{
  background-color: blue
}

내 브라우저에도 눈이 내리네

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" type="text/css" href="style.css">
    <title>Make it Snow.</title>
</head>
<body style="background: black;">
    <div class='container'>
        <canvas id='canvas'></canvas>
        <section class='section'>
            <div class='text'>
                <p>Make it Snow.</p>
            </div>
        </section>
    </div>
    <script type='text/javascript' src="index.js"></script>
</body>
</html>

index.js

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var particlesOnScreen = 200;
var particlesArray = [];
var w,h;

w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;

function random(min, max) {
    return min + Math.random() * (max - min + 1);
};

function createSnowFlakes() {
    for(var i = 0; i < particlesOnScreen; i++){
        particlesArray.push({
            x: Math.random() * w,  
            y: Math.random() * h,  
            opacity: Math.random(),  
            speedX: random(-1, 3),  
            speedY: random(1, 4),    
            radius: random(0.1, 5),
        })
    }
};

function drawSnowFlakes(){
    for(var i = 0; i < particlesArray.length; i++){    
        var gradient = ctx.createRadialGradient(  
            particlesArray[i].x,   
            particlesArray[i].y,   
            0,                     
            particlesArray[i].x,   
            particlesArray[i].y,   
            particlesArray[i].radius  
            );

            gradient.addColorStop(0, "rgba(255, 255, 255," + particlesArray[i].opacity + ")");  // white
            gradient.addColorStop(.8, "rgba(210, 236, 242," + particlesArray[i].opacity + ")");  // bluish
            gradient.addColorStop(1, "rgba(237, 247, 249," + particlesArray[i].opacity + ")");   // lighter bluish
          
            ctx.beginPath(); 
            ctx.arc(
            particlesArray[i].x,  
            particlesArray[i].y,  
            particlesArray[i].radius,  
            0,                         
            Math.PI*2,                 
            false                     
            );

        ctx.fillStyle = gradient;   
        ctx.fill();                 
    }
};

function moveSnowFlakes(){
    for (var i = 0; i < particlesArray.length; i++) {
        particlesArray[i].x += particlesArray[i].speedX;     
        particlesArray[i].y += particlesArray[i].speedY;     

        if (particlesArray[i].y > h) {                                                                               
            particlesArray[i].x = Math.random() * w * 1.5;
            particlesArray[i].y = 0;
        }
    }
};

function updateSnowFall  () {
    ctx.clearRect(0, 0, w, h);
    drawSnowFlakes();
    moveSnowFlakes();

    window.requestAnimationFrame(updateSnowFall);
};

window.requestAnimationFrame(updateSnowFall);
// setInterval(updateSnowFall, 1000/60);
createSnowFlakes();


function clientResize(ev){
  w = canvas.width = window.innerWidth;
  h = canvas.height = window.innerHeight;
};

window.addEventListener("resize", clientResize);

snow

간단하게 Long Press 구현해보기

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <button>Button</button>    
</body>
<script>
    let pressTimer = null
    const start = (e) => {
      if (e.type === 'click' && e.button !== 0) {
        return
      }

      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          handler(e)
        }, 500)
      }
    }

    let cancel = (e) => {
      if (pressTimer !== null) {
        clearTimeout(pressTimer)
        pressTimer = null
      }
    }

    const handler = (e) => {
      console.log(e)
    }

    const button = document.getElementsByTagName('button')[0]
    button.addEventListener("mousedown", start)
    button.addEventListener('mousedown', start)
    button.addEventListener('touchstart', start)

    button.addEventListener('click', cancel)
    button.addEventListener('mouseout', cancel)
    button.addEventListener('touchend', cancel)
    button.addEventListener('touchcancel', cancel)
</script>
</html>

String으로 된 "true", "false" Boolean 타입으로 convert하기

String으로 된 "true", "false" Boolean 타입으로 convert하기

생각해보면 별거 아니라고 생각하실 수 있지만 더더욱 간단한 방법과 다양한 방법을 찾아보았습니다.

정규식을 사용해서 변환하기

const stringValue = "true"; 
const boolValue = (/true/i).test(stringValue) 

정규식으로 true를 찾아서 표현을 하며 이렇게 하면 나머지는 다 false로 나오게 될 것이다.

동등 연산자(==) 사용하기

const stringValue = "true"
const boolValue = (stringValue =="true")

동등연산자는 흔히 생각할 수 있는 방법입니다. String의 값이 같다면 true를 반환합니다.

JSON.parse를 사용하는 방법

const stringValue = "true"
const boolValue = JSON.parse(stringValue)

개인적으로 제가 딱 원하던 방법입니다.

삼항 비교

const stringValue = "true";
const boolValue = stringValue.toLowerCase() == 'true' ? true : false;   //returns true

변수를 delete 할 수 없나요?

MDN에 따르면 delete 연산자는 객체의 속성을 제거한다고 한다. 아무리 보아도 객체의 속성이라는 말이 눈에 띈다.

일반적으로 우리가 생각하는 것과는 다르게 delete 는 메모리 해제에 관하여 직접적으로 하는 것이 없다. 메모리 관리는 breaking references를 통하여 간접적으로일어난다고 한다.(이에 관련해서는 추후에 찾아보자)

delete 연산자를 사용하여 삭제를 하면 true를 반환하며, 실패한 경우 false를 반환한다.

  • 존재하지 않는 속성인 경우 true
  • 객체 프로토타입 체인에 같은 이름의 속성이 있다면 삭제후에 프로토타입체인을 통해서 프로퍼티 사용이 가능하다.
  • var 선언된 어떠한 프로터퍼티라도 global, function 스코프에서 삭제가 불가능하다.
  • let const는 어떠한 스코프에서도 불가능하다.
  • Non-configurable 속성은 삭제 할 수 없다.

그런데 예외적으로 찾은 것이 있는데, 변수 선언시 var를 사용하지 않고 글로벌에 바로 넣게 되면 삭제가 된다.

a = "a"
delete a
console.log(a) // undefinded

이는 var를 사용하지 않고 선언을 하게 되면 window(global)의 속성으로 들어가기 때문에 delete연산자로 삭제가 가능하게 된다. 다르게 말하면 configurable 속성이 true인 것이다.

직접 속성을 확인하게 되면

Object.getOwnPropertyDescriptor(window, "a")

image

아래와 비교해보면 확실히 var를 사용해서 선언한 값과는 다른 속성을 가지게 된다.

image

내가 업로드한 파일 내가 다운받기

<!DOCTYPE html>
<html>
<head></head>
<body>
  <input type="file" id="filebtn" value="upload" />
  <input type="button" id="btn" value="Download" />
</body>
<script>
  function download(file) {
    const name = file.name;
    const blob = new Blob([file]);
    const url = URL.createObjectURL(blob);
    const element = document.createElement('a');

    element.setAttribute('href', url);
    element.setAttribute('download', name);

    document.body.appendChild(element);
    
    element.click();

    document.body.removeChild(element);
  }

  document.getElementById("btn").addEventListener("click", function () {
    const file = document.getElementById("filebtn").files
    download(file[0]);
  })
</script>

</html>

당신이 아는 body: { height: 100%: width: 100% }는 어디까지 인가요?

width, height가 100%일 때 어디까지가 100%를 100% 확신하시는 분은 안 보셔도 됩니다.

우리가 아는 body 100%

흔히 100%로면 우리가 눈에 보이는 모든 곳을 커버하는 게 100%라고 생각합니다. 그런데 실제로 100%는 생각보다 작습니다.

실제로 HTML에서 그리는 Body는 Viewport를 기준으로 100%입니다. 즉 쉽게 말을 하면 현재 페이지를 연 브라우저의 실제 화면 사이즈의 % 입니다.

예를 들어 Naver에 들어가서 F12로 개발자 도구를 열고 Elements에서 body를 누르게 되면 꽉 차게 보입니다. 그러나 화면에서 스크롤을 하고 다시 개발자도구 Elements에서 body 마우스를 올리게 되면 우리가 생각한 Body의 사이즈가 아니라는 것을 알게 됩니다. 확인을 해보면 Naver에서는 height : 100%를 해준 것으로 보입니다.

Body가 꽉 차게 하고 싶다...

그렇다면 구글은 어떻게 처리를 하나? 구글에서 검색하게 되면 스크롤이 생기고 위에서 했던 방법처럼 개발자도구를 열고 Elements에서 Body에 마우스를 올리게 되면, 구글은 실제로 body가 스크롤이 있어도 영역이 스크를 아래까지 잘 잡히는 것을 볼 수 있습니다.

이는 구글에서는 body에 height: 100%를 주지 않아서입니다. 위에서 말했던 것처럼 Body의 % 는 Viewport를 기준으로 계산을 해서 PX이 나오기 때문에 생각과 다른 것입니다.

결국

구글처럼 100%를 주지 않게 되면 body 내부 Elements들이 들어가면서 body 사이즈가 같이 늘어나게 되면서 body 영역이 scroll height까지 넓어지게 됩니다.

만약?

만약에 Naver에서 body에 mouseenter를 걸고(걸면 안 되지만) 스크롤을 최하위로 내린 상태에서 화면에 마우스를 올려놓아도 mouseenter 이벤트는 작동하지 않을 것이다.

Go로 WebAssembly 경험해보기

웹어셈블리는 날이 갈수록 점차 인기가 상승하고 있다.

더욱이 구글에서 웹에서 돌아가는 고사양 게임, 웹에서 돌아가는 포토샵을 보여주게 되면서 더이상 웹은 단순한 서핑을 위한 용도가 아니게 되면서 웹어셈블리에 대한 관심도 증가하고 있다.

기존의 웹어셉블리 파일, wasm 확장자를 가진 파일을 만들기 위해서 asm.js와 같은 Javascript로 작성하게 하는 방법과 C, C++, Rust와 같은 저수준의 언어로 작성을 하고 컴파일하는 방법이 있었다.

그런데 Go에서 1.11 버전에서 실험적으로 WebAssebly를 지원하게되고, 1.12로 오게 되면서 더욱 좋아졌다고 한다. 이에 나중을 위해서 한번 경험을 해보자!!

참고자료 Github wiki- go

준비물 : 당연히 go를 다운받아주세요(최신으로)

1. main.go

package main

import "fmt"

func main() {
	fmt.Println("Hello, WebAssembly!")
}

위와 같이 간단하게 텍스트를 출력하는 로직을 작성한다.

2. Build

$ GOOS=js GOARCH=wasm go build -o main.wasm

간단하게 변수를 넣어주고 .wasm 확장자로 Build를 해준다.

3. 자바스크립트 실행을 도와주는 파일 가져오기

Go에서는 웹어셈블리 파일을 실행하는데 도와주는 파일이 있다. 그 파일을 가져와서 index.html 파일안에서 실행하자.

cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
<html>
	<head>
		<meta charset="utf-8">
		<script src="wasm_exec.js"></script>
		<script>
			const go = new Go();
			WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
				go.run(result.instance);
			});
		</script>
	</head>
	<body></body>
</html>

4 서버를 띄우자

# install goexec: go get -u github.com/shurcooL/goexec
$ goexec 'http.ListenAndServe(":8080", http.FileServer(http.Dir(".")))'

먼저 해당 goexec를 설치해주며, 해당 서버를 실행시키고 브라우저에서 개발자도구로 확인한다.

간단한 성능 테스트

image

위와 같이 100만번 for문을 돌리는 로직을 만들어서 .wasm 확장자로 만들었다.

image

위와 같이 웹어셈블리코드만 측정하게 되면,

image

13ms 정도가 나오게 된다.

[추가] Rust로 웹어셈블리 만들기

go가 아닌 rust로 만들어서 테스트를 추가로 진행해보았다.

rust

image

이상하게 rust로 만든 내용은 0.3ms로 엄청 짧은 시간이 걸리는 것으로 확인이 된다.

image

비교를 위해서 JS를 작성하였으며, 똑같이 100만번을 돌리고 있다.

image

22ms로 웹어셈블리로 돌리는 것과 확연히 차이가 나는 것을 볼 수 있다.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.