window.matchMedia

window.matchMedia(mediaQueryString) 메서드는 미디어쿼리 문자열의 분석결과를 나타내는 MediaQueryList 객체를 반환한다.

객체는 media,matches,onchange 세 가지의 속성을 지닌다.

  1. media
    matchMedia()의 parameter로 넣은 MediaQueryString이다.
  2. matches
    boolean 값으로 MediaQueryString과 일치하면 true, 일치하지 않으면 false값을 지닌다.
  3. onchange
    해당 미디어 쿼리가 변화할 경우 실행될 이벤트이다.
    1
    2
    3
    let mediaQueryList = window.matchMedia(mediaQueryString);
    mediaQueryList.addListener((e) => {});
    //mediaQueryList.onchange=(e)=>{};

아래의 코드는 prefers-color-scheme속성이 dark인지 확인하는 코드이다.

1
window.matchMedia("(prefers-color-scheme:light:dark)");
1
2
3
4
5
const result = {
media: "(prefers-color-scheme: dark)",
matches: false,
onchange: null,
};

ref

MediaQueryList.addListener()
MediaQueryList
prefers-color-scheme

V8 히든클래스

자바스크립트는 동적 언어로 객체에서 속성을 즉시 추가하거나 제거할 수 있다. 그러나 이러한 동적조회는 성능 저하의 원인이다.

동적 조회

동적 언어에서 객체에 속성을 추가하거나 제거할 경우 선형탐색 후 속성에 접근하게 된다. 이럴 경우 프로퍼티에 접근할 경우 객체의 모든 속성에 대한 탐색이 필요하다.

해결방안

V8엔진은 이를 해결하기 위해 히든클래스라는 개념을 도입하였다.
자바스크립트에서 객체를 생성할 경우 새로운 히든 클래스를 생성한다.

예시

아래의 코드는 빈 객체를 생성하는 코드이다.

1
const object1 = {};

V8은 이 코드를 기반으로 새로운 히든 클래스를 만든다.
편의상 객체로 표현하였다.

1
c01 = {};

아래의 코드는 object1 객체에 속성을 추가하는 코드이다.

1
object1.name = "박성현";

아래의 코드를 실행할 경우 V8은 이전 히든클래스(c01)의 모든 프로퍼티를 상속하여 새로운 히든클래스(c02)를 만든다.

그 후 object1은 c02를 참조하고 V8은 c01을 계속해서 참조한다.

이렇게 되면 컴파일러가 프로퍼티 이름에 접근할 때 offset을 통해 접근하여 사전형 탐색을 우회할 수 있다.

1
2
c01 = {}; //V8이 참조중
c02 = { offset0: name }; //object1이 참조중

아래의 코드는 object1 객체에 또 다른 속성을 추가하는 코드이다.

1
object1.age = 20;
1
2
3
c01 = {}; //V8이 참조중
c02 = { offset0: name };
c03 = { offset0: name, offset1: age }; //object1이 참조중

V8은 이러한 히든 클래스를 재사용한다.
만약 다음과 같은 코드를 작성하였을 경우 c01을 재사용 한다.

1
object2 = {};
1
2
3
c01 = {}; //V8과 object2가 참조중
c02 = { offset0: name };
c03 = { offset0: name, offset1: age }; //object1이 참조중

하지만 다음과 같이 name,age 외의 프로퍼티를 추가하여 c03을 재사용할 수 없는 경우 다시 새로운 클래스를 만든다.
V8은 c02를 재사용하여 새로운 트랜지션 트리를 만들고 히든클래스인 c4를 만들어 추가한다.

1
object2.school = "konkuk univ";
1
2
3
4
c01 = {}; //V8과 object2가 참조중
c02 = { offset0: name };
c03 = { offset0: name, offset1: age }; //object1이 참조중
c04 = { offset0: name, offset1: school }; //object2가 참조중, c02와 연결된다

성능 개선 방안

V8엔진의 히든클래스를 이용하여 성능개선을 하기 위해선 동적 프로퍼티 추가를 줄여야한다.

예를들어 반복문 안에서 객체의 동적 프로퍼티를 추가하는 대신 반복문 외부에서 프로퍼티를 만들어 사용하는 것이 좋다.

즉 기존의 히든클래스를 재사용할 떄 성능이 향상된다.

ref

V8의 히든 클래스 이야기
자바스크립트 성능의 비밀 (V8과 히든 클래스)
Fast properties in V8

js prototype!

자바스크립트는 프로토타입 기반 언어라고 불린다.
프로토타입에 대해 개념을 확실하게 하기 위해 알아보았다.

프로토타입

모든 객체들이 메소드와 속성을 상속받기 위한 템플릿으로써 프로토타입 객체(prototype object)를 가진다는 의미이다.

상속되는 속성과 메소드들은 각 객체의 생성자의 prototype라는 속성에 정의되어 있다.

프로토타입 속성

객체의 prototype 속성은 생성자가 존재하는 함수객체에만 존재한다.

인스턴스를 생성하게 되면 해당 객체의 prototype속성 내부의 멤버들을 상속받는다.

prototype속성도 하나의 객체이며 프로토타입 체인을 통해 상속하고자 하는 속성과 메소드를 담아두는 버킷이다.

생성자가 존재하는 함수객체의 인스턴스를 생성하게 되면 객체의 프로토타입은 해당 인스턴스의 __proto__로 참조하게 된다.

아래의 코드는 문자열의 프로토 타입을 확인하는 코드이다.

1
2
3
4
5
6
let str = "hihi";
//인스턴스 생성
str.__proto__;
// 인스턴스의 __proto__ 확인(String의 prototype)
str.constructor.prototype;
// 생성자의 prototype 확인(String의 prototype)

프로토타입 체인

프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메소드와 속성을 상속받을 수 있다. 이는 null을 프로토타입으로 가지는 오브젝트에서 끝난다. null은 프로토타입 체인의 종점 역할을 한다.

아래의 코드는 String()에 존재하는 메소드인 toUpperCase()를 호출하는 코드이다.

1
2
3
4
let str = "hihi";
str.toUpperCase();
//"HIHI"
// 프로토타입에 정의된 함수를 호출할 수 있다.

실행 순서는 다음과 같다.

  1. 브라우저가 str 객체가 toUpperCase 메소드를 가지고 있는지 체크한다.
  2. 없으므로 str.__proto__를 통해 객체의 프로토타입 객체(String() 생성자의 프로토타입)를 참조하여 toUpperCase()메소드가 있는지 확인 후 존재하므로 실행한다.

String() 생성자의 프로토타입인 Object에만 존재하는 valueOf() 메소드를 호출했을 경우의 코드는 다음과 같다.

1
2
3
let str = "hihi";
str.valueOf();
//hihi

실행 순서는 다음과 같다.

  1. 브라우저가 str 객체가 toUpperCase 메소드를 가지고 있는지 체크한다.
  2. 없으므로 str.__proto__를 탐색하여 str의 프로토타입 객체에 toUpperCase() 메소드가 있는지 확인한다.
  3. 존재하지 않으므로 String.prototype.__proto__Object()에서 해당 메소드를 찾는다. 존재하므로 실행한다.

아래의 코드는 존재하지 않는 메서드를 실행했을 때의 코드이다.

1
2
3
let str = "hihi";
hi.asdf();
//error

실행 순서는 다음과 같다.

  1. 브라우저가 str 객체가 toUpperCase 메소드를 가지고 있는지 체크한다.
  2. 없으므로 str.__proto__를 탐색하여 str의 프로토타입 객체에 toUpperCase() 메소드가 있는지 확인한다.
  3. 존재하지 않으므로 String.prototype.__proto__(str.__proto__.__proto) 즉 Object() 생성자에서 해당 메소드를 찾는다.
  4. 존재하지 않으므로 Object() 생성자의 프로토 타입도 확인해야 하지만 Object.protoType은 항상 체인의 끝으로 null이므로 해당 함수는 실행되지 않는다.

아래 코드를 통해 객체간의 프로토타입 체인을 확인해 볼 수 있다.

1
2
3
4
String.prototype.__proto__;
// Object
Object.protoType.__proto__;
// null

create()

Object.create() 메소드는 새 인스턴스를 생성한다.
정확히는 create() 메소드는 주어진 객체를 프로토타입 객체로 삼아 새로운 객체를 생성하는 함수이다.

1
2
3
let person2 = Object.create(person1);
person2.__proto__;
//person1

정리

자바스크립트는 프로토타입 기반의 언어이고
선형리스트 탐색과 유사하게 __proto__를 통해 상위 프로토타입(편의상 정의한 단어이다.)의 메소드를 탐색한다.

주의사항

위의 코드는 예시일 뿐 실제 개발환경에서 __proto__를 확인해야 할 경우 Object.getProtoTypeOf()함수를 이용해야 한다..!

ref

Object prototypes
자바스크립트 생각보다 간단했던 프로토타입
Inheritance_and_the_prototype_chain

html,head,body의 차이

html태그, head태그 그리고 body에 대해 은근 정리가 안 된 것 같아서 게시글로 정리하려고 한다.

html

<html>은 HTML 문서의 루트(최상단 요소)를 나타내며 루트 요소라고 부른다. 모든 요소들은 <html> 요소의 후손이여야 한다.

하나의 <head> 요소와 하나의 <body> 요소를 가진다.

HTML <head> 요소는 기계가 식별할 수 있는 문서 정보(메타데이터)를 담는다. 정보로는 문서가 사용할 제목, 스크립트, 스타일 시트 등이 있다.

<body> 요소와는 다르게 페이지에 표시되지 않는다.

대신 브라우저나 웹페이지를 구성하는 metadata를 포함한다.

<link>는 항상 <head>에 위치해야한다.
페이지는 HTML과 CSS를 파싱하고 나서 렌더링을 시작하므로 항상 <head>에 위치하여 빠르게 로드되어야 한다.

또한 렌더링 전에 초기화와 같은 가벼운 <script>의 경우 <head>에 넣기도 한다.

body

HTML <body> 요소는 HTML 문서의 내용을 나타낸다. 한 문서에 하나의 <body> 요소만 존재할 수 있다.

<script>는 css나 html을 조작할 수 있기 때문에 다운로드하고 실행하는 동안 DOM트리를 위한 파싱 작업을 멈춘다.(하지만 defer, async등의 속성을 사용하여 제어할 수 있다.)

또한 <script>는 무겁다.

때문에 <script>태그는 대부분의 경우 <body>태그의 맨 아래 넣는다.

느낀점

기초는 이렇지만 다크모드 깜빡임 현상을 해결하면서는 <body>의 내용을 렌더링 하기 전에 렌더링 블록 요소인 <script>를 미리 로드하고 body의 최상단에서 호출하였다.

다크모드 깜빡임 현상을 해결하며 <script><html>,<head>,<body>에 대해 더 깊게 이해하게 된 것 같다..!

ref

html mdn
head mdn
The head metadata in HTML
body mdn

왜 일반적으로 CSS태그를 …

hexo + github 블로그 다크모드 깜빡임 해결

렌더링이 시작된 후에 defer속성으로 받아온 스크립트가 실행되다보니 화면 전환중에 흰화면으로 잠시 전환되는 현상이 발생하였다.

다음과 같은 방법으로 수정하였다.

body의 기본 배경 색 없애기

먼저 7번째 줄에 있는 body의 background-color를 none으로 수정하였다.

\theme\icarus\css\include\style\base.styl

1
$body-background-color ?= none;

기존 js파일을 두 개로 나누기

기존 js파일의 경우 init 함수에서 버튼 설정과 다크모드 로드를 동시에 해주었는데,
다크모드 로드는 렌더링 전에 실행되어야 하고 버튼 설정은 렌더링 후에(최소 DOM트리 생성 이후) 실행되어야 한다.
때문에 파일을 두 개로 나누었다.

darkmode.js엔 chinsun9님의 게시글을 읽고 matchMedia를 읽어서 실행하는 부분을 추가하였다.
(너무 친절하게 답변해주셨다..!)

localstorage와 matchMedia를 이용하여 다크모드를 로드하는 함수이다.
\themes\icarus\source\js\darkmode.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function init() {
const darkScheme = window.matchMedia("(prefers-color-scheme:dark)");
const theme = localStorage.getItem("theme");
if (theme === "dark") {
document.body.classList.add("dark");
return;
}
if (theme === "light") {
return;
}

if (darkScheme) document.body.classList.add("dark");

localStorage.setItem("theme", darkScheme ? "dark" : "light");
}

버튼 관련 설정을 하는 js 파일이다.
hexo\themes\icarus\source\js\darkmodebutton.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const darkModeButton = document.querySelector("#changeModeButton");
const darkScheme = window.matchMedia("(prefers-color-scheme:dark)");

darkScheme.addEventListener("change", (e) => {
console.log("hidark");
const newColorScheme = e.matches ? "dark" : "light";
localStorage.setItem("theme", newColorScheme);

if (newColorScheme === "dark") {
document.body.classList.add("dark");
btn.innerText = "🌙";
return;
}
document.body.classList.remove("dark");
btn.innerText = "🌞";
});

function toggleTheme() {
console.log("hibutton");
document.body.classList.toggle("dark");
darkModeButton.innerText = document.body.classList.contains("dark")
? "🌙"
: "🌞";
localStorage.setItem(
"theme",
document.body.classList.contains("dark") ? "dark" : "light"
);
}

function initButton() {
console.log("initButton");
const data = localStorage.getItem("theme");
if (data == "dark") {
darkModeButton.innerText = "🌙";
return;
}
darkModeButton.innerText = "🌞";
}

initButton();
darkModeButton.addEventListener("click", toggleTheme);

작성한 js파일의 위치 지정해주기

  1. darkmodebutton.js
    버튼은 렌더링후에(최소 DOM트리 생성 이후) 실행되어야 하므로 아래부분에 추가해주었다.
    \themes\icarus\layout\common\scripts.jsx
    56번쨰 줄에 darkmodebutton.js추가

    1
    2
    <script src={url_for("/js/main.js")} defer></script>
    <script src={url_for("/js/darkmodebutton.js")} defer></script>
  2. darkmode.js
    body 최상단에서 실행하기위해 head태그에서 js파일을 로드했다.
    적다보니 preload속성을 추가해도 좋을 것 같다.

    hexo\themes\icarus\layout\common\head.jsx

    1
    <script src={url_for("/js/darkmode.js")}></script>
  3. darkmode.js의 init 함수
    다크모드를 로드하는 건 렌더링 이전에 실행되어야한다.
    때문에 body태그 최상단에 rendering block 요소로 defer나 async 속성을 지정하지 않고 넣어주었다.
    hexo\themes\icarus\layout\layout.jsx

    1
    2
    3
    4
    <html lang={language ? language.substr(0, 2) : ""}>
    <Head site={site} config={config} helper={helper} page={page} />
    <body class={`is-${columnCount}-column`}>
    <script>init();</script>

ref

[A1] 웹뷰에서 다크모드 상속받기: 일관성있는 사용자 경험을 위하여
blog fix 다크 모드 화면 깜박임 제거

github pull request시 주의사항

주의사항

같은 단위의 변경사항을 커밋하고 단위마다 pull request를 보내자.

해결방안

브랜치를 신규로 생성하고 특정 commit 리버전을 그 브랜치로 merge한 후 그 브랜치를 커밋, pull request하여 수습(?)한다.

git cherry-pick을 이용할 수 있다.

ref

git 명령어 : git cherry-pick
github 원하는 commit만 pull request 보내기

Please commit your changes or stash them before you merge에러 해결하기

pull을 하였을 때 다음과 같은 에러가 발생하였다.

에러

1
2
3
4
5
6
7
8
$ git pull origin sunghyeon
From https://github.com/pshdev1030/NCRBQDTJY
* branch sunghyeon -> FETCH_HEAD
error: Your local changes to the following files would be overwritten by merge:
Programmers/Sunghyeon/다리를 지나는 트럭/README.md
Programmers/Sunghyeon/다리를 지나는 트럭/다리를 지나는 트럭.py
Please commit your changes or stash them before you merge.
Aborting

발생 이유

pull 받아오기 전에 수정해서 발생한 에러이다

내가 수정한 변경사항을 commit하거나
내가 수정한 변경사항을 merge하기 전에 stash에 임시 저장해두라는 말이다.

내 해결방안

나는 commit후에 pull을 통해 해당 에러를 해결하였다.

다른 해결방안

1
2
3
4
5
6
7
1. git stash //stash로 변경사항을 옮김
2. git pull origin 브랜치명 // 원격저장소에서 변경사항을 받아옴
3. git stash pop //내 변경사항과 원격저장소에서 pull 받은 소스가 merge됨
4. 소스 수정
5. git add .
6. git commit -m "커밋 메시지"
7. git push origin 브랜치명

ref

git stash
git stash

body vs query vs params

express를 이용해 API를 호출하기 위한 세 가지 방식이 있다.

1. req.params

url이 https://pshdev1030.github.io/:id 일 경우
id를 req.params.id로 접근할 수 있다.

req.query

1
2
3
{
"id": "pshdev1030"
}

2. req.query

url이 https://pshdev1030.github.io/user?id=pshdev1030일 경우

req.query로 접근할 수 있다.

req.query

1
2
3
{
"id": "pshdev1030"
}

3. req.body

url을 이용하지 않고 요청에 json객체를 담아서 전송받아 사용한다.
req.body를 통해 접근할 수 있다.

in react

1
axios(endpoint, { id: "pshdev1030" });

req.body

1
2
3
{
"id": "pshdev1030"
}

nextjs + mongo 연동하기

nextjs+ mongoDB 연동을 해보았다.

.env 설정

루트 디렉토리에 .env 파일을 만들어준다.
.gitignore가 존재할 경우 .env도 추가해주어야 한다.

.env

1
MONGODB_URL=mongodb+srv://<사용자명>:<사용자 비밀번호>@cluster0.gvztc.mongodb.net/<DB이름>?retryWrites=true&w=majority

mongooose 설치

nodejs와 연동하기 위해 mongoose 모듈을 설치해준다.

1
npm i mongoose

db 연결하기

캐시를 통해 핫리로드가 가능하게 하고 데이터베이스를 불러온다.

공식문서에서 가져왔다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import mongoose from "mongoose";

const MONGODB_URI = process.env.MONGODB_URI;

if (!MONGODB_URI) {
throw new Error(
"Please define the MONGODB_URI environment variable inside .env.local"
);
}

/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections growing exponentially
* during API Route usage.
*/
let cached = global.mongoose;

if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}

async function dbConnect() {
if (cached.conn) {
return cached.conn;
}

if (!cached.promise) {
const opts = {
bufferCommands: false,
};

cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
return mongoose;
});
}
cached.conn = await cached.promise;
return cached.conn;
}

export default dbConnect;

스키마 정의하고 모델 반환하기

스키마를 정의해주고 모델을 반환한다.
db에 해당 스키마가 존재하지 않을 경우 생성하고 모델을 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import mongoose from "mongoose";

const ProductSchema = new mongoose.Schema(
{
title: {
type: String,
required: true,
maxlength: 60,
},
desc: {
type: String,
required: true,
maxlength: 200,
},
img: {
type: String,
required: true,
},
prices: {
type: [Number],
required: true,
},
extraOptions: {
type: [
{
text: { type: String, required: true },
price: { type: Number, required: true },
},
],
},
},
{ timestamps: true }
);

export default mongoose.models.Product ||
mongoose.model("Product", ProductSchema);

사용하기

next.js에서는 api를 만드는 기능을 제공한다.
express와 비슷하다. RESTful한 API도 생성이 가능하다.

1
2
3
4
5
/pages/api/product/index.js 주소/api/product로 접근 가능

/pages/api/product/[id].js 주소/api/product/asdf로 {id:asdf}에 접근 가능

/pages/api/product/[...data].js 주소/api/user/의 하위 경로들로 전부 접근 가능

아래 코드는 주소/api/product를 통해 접근 가능하다.

/pages/api/products/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import dbConnect from "../../../util/mongo";
import Product from "../../../models/Product";
export default async function handler(req, res) {
const { method } = req;

dbConnect();

if (method === "GET") {
try {
const products = await Product.find();
res.status(200).json(products);
} catch (err) {
res.status(500, json(err));
}
}

if (method === "POST") {
try {
const product = await Product.create(req.body);
res.status(201).json(product);
} catch (err) {
res.status(500).json(err);
}
}
}

hexo + blog runMicroTasks 에러 해결하기

hexo generate중에 에러가 발생했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
FATAL {
err: Error: ** Processing: C:\Users\SDPark-pc\AppData\Local\Temp\f657ac55-7bfc-474b-8bd4-783a7a740fb7
1024x1024 pixels, 4x8 bits/pixel, RGB+alpha
Input IDAT size = 16536 bytes
Input file size = 16593 bytes

Trying:
zc = 9 zm = 8 zs = 0 f = 0 IDAT size = 16536
zc = 9 zm = 8 zs = 3 f = 0 0
at C:\Users\SDPark-pc\Desktop\REACT\blog\hexo\node_modules\exec-buffer\node_modules\execa\index.js:231:11
at runMicrotasks (<anonymous>)
at processTicksAndRejections (internal/process/task_queues.js:93:5) {
code: 3221225477,
killed: false,
stdout: '',
stderr: '** Processing: C:\\Users\\SDPark-pc\\AppData\\Local\\Temp\\f657ac55-7bfc-474b-8bd4-783a7a740fb7\r\n' +
'1024x1024 pixels, 4x8 bits/pixel, RGB+alpha\r\n' +
'Input IDAT size = 16536 bytes\r\n' +
'Input file size = 16593 bytes\r\n' +
'\r\n' +
'Trying:\r\n' +
' zc = 9 zm = 8 zs = 0 f = 0\t\tIDAT size = 16536\r\n' +
' zc = 9 zm = 8 zs = 1 f = 0\r zc = 1 zm = 8 zs = 2 f = 0\r zc = 9 zm = 8 zs = 3 f = 0',
failed: true,
signal: null,
cmd: 'C:\\Users\\SDPark-pc\\Desktop\\REACT\\blog\\hexo\\node_modules\\hexo-filter-cleanup\\node_modules\\optipng-bin\\vendor\\optipng.exe -strip all -clobber -fix -o 2 -out C:\\Users\\SDPark-pc\\AppData\\Local\\Temp\\044b15b2-8a65-4748-97c3-ad4280ef2c14 C:\\Users\\SDPark-pc\\AppData\\Local\\Temp\\f657ac55-7bfc-474b-8bd4-783a7a740fb7',
timedOut: false
}
} Something's wrong. Maybe you can find the solution here: %s https://hexo.io/docs/troubleshooting.html

hfc에서 발생한 에런지 hexo에서 발생한 에러인지 모르지만 해결하였다.

다음과 같이 execa라는 모듈을 재설치 해주었다.

1
2
npm rm execa
npm i execa