Skip to main content

109 posts tagged with "javascript"

View All Tags

· 2 min read

개요

코딩을 해준다라는 과대광고를 집어치우고 실제로 유용하게 사용할 수 있는 유스케이스들을 정리해보았다.

정규식 추출

보통 정규식을 만드는데에는 박음질하듯이 많은 테스트가 필요하다. 주로 regexr, regex101 사이트에 원하는 텍스트를 넣고 기억에 의존하면서 Negative/Positive Lookahead 를 맞추고, Negative/Positive Lookbehind 를 사용하다가 Negative Lookbehind가 프로그래밍 버전/언어별로 지원여부가 달라 찾게되는 경우가 많다.

이젠 ChatGPT에 물어본다. 아래 패키지 업데이트 내역에서 패키지명을 추출한다고 해보자.

Hello GPT.

Can you give me a regex pattern for getting to-be result?

as-is:
Orignal content


to-be:
Refined string #1
Refined string #2
...and so on.

알아서 가져다 줄 것이다.

코드로 문서 작성

내 코드의 문서가 쓰고 싶다면 코드를 넣고 README.md 를 만들어달라고 하자.

Can you write a README.md with this code?

알아서 가져다준다.

인터페이스로 Object Validation Schema 만들기

타입스크립트 인터페이스로 Joi Schema 를 만들어달라고 하자.

Raw SQL로 Query DSL 만들기

로우 쿼리를 전달하고, 쿼리 DSL, 쿼리 헬퍼 구문을 만들어달라고 하자.

Data 로 DDL 만들기

쿼리를 전달하고 위의 인터페이스 예시처럼 테이블 스키마 쿼리를 생성해달라고 하자.

참조

awesome 이 생겼다. https://github.com/f/awesome-chatgpt-prompts

· 5 min read

개요

2016년부터 정들게 쓴 hexo는 플러그인이 많고, 테마가 많아 좋았다. 2019년도부터인가.. 다른 Static Site Generator 가 많아지면서 더 이상 hexo만의 이점이 없어졌고 테마나 플러그인 개발 등 생태계 라이브러리들의 업데이트도 줄어들었다.

ejs -> njk, less -> sass -> stylus 로 테마에 스택이 의존적이였다. 결정적으로 nodejs 기반 코어라 트러블슈팅을 올려도 리소스 낭비랄까..

Docusaurus는 1~2알파 시절 algolia 검색이 제공되지 않아 swizzle 해서 local search 커뮤니티 플러그인을 사용해서 붙혀보는 삽질이 있었고 한글이 정상적으로 동작하지 않았다.

2023년에는 2버전이 런칭했고, React 기반으로 프론트엔드 스택이 일원화되었고 등등 이 작업을 진행해도 될 것 같았다.

트러블슈팅

SEO

URI

  • hexo는 front-matter 구문 안에 date: 2023-01-10 09:00:00 로 날짜를 넣어준다.
  • docusaurus는 nested folder 구조를 써야한다.
  • md 파일의 front-matter.date 를 파싱해서 URL에 /2023/01/10/title 처럼 처리해준다.

Site Verification

  • 큰 문제없이 설정에 넣어줬다.
docusaurus.config.js
{
"themeConfig": {
"metadata": [
// ? https://search.google.com/search-console
{
"name": "google-site-verification",
"content": "g"
}
]
}
}

hexo new

  • hexo new post "title" 이 커맨드를 실행해서 포스트를 만드는데, 호환할 커맨드가 필요했다.
  • 오늘날짜로 blog/2023/01/10/title.mdx 로 만들어주게 yarn cmd new title 로 생성했다.
  • URL normalize 를 위한 기능으론 import { slugize } from "hexo-util"; 를 사용하면 된다.
    • slugify는 한글도 삭제된다.

archive

  • hexo의 아카이브는 Order by Created DESC 인 방면에, docusaurus는 ASC 정렬이였다.
  • 이걸 플러그인 옵션으로 제공하자라는 이슈가 있지만 닫혔고, 알아서 하라가 그 답변이였다.
  • 하나 만들어서 기존 컴포넌트 덮고 재정렬해서 연결해줬다.
docusaurus.config.js
{
"presets": [
"classic",
{
"blog": {
// 실제론 path resolving 필요
"blogArchiveComponent": "./src/component/BlogArchiveDescendingPage.tsx"
}
}
]
}

tag

  • docusaurus 태그는 tags: [tag1, tag2] 이 형식을 만족해야한다.
  • ChatGPT 에게 정규식 물어봐서 전체치환했다.

github action

  • 타겟 레포, 브랜치 세팅해준다.
  • personal access token 발행하고, 두 값 적절하게 넣어준다.
  • yarn build, yarn deploy 실행하면 알아서 잘 된다.
docusaurus.config.js
{
"organizationName": "gracefullight",
"projectName": "gracefullight.github.io",
"deploymentBranch": "main",
"trailingSlash": true
}
workflows/main.yml
env:
GIT_USER: ${{ secrets.GIT_USER }}
GIT_PASS: ${{ secrets.GIT_PASS }}

dark only

  • 어둡게, 스위치도 삭제했다.
docusaurus.config.js
{
"themeConfig": {
"colorMode": {
"defaultMode": "dark",
"disableSwitch": true
}
}
}

code block

  • 형식이 달라서 정규식 치환해주었다.
# hexo
\`\`\`language title

# docusaurus
\`\`\`language title="title"

comment

  • 코멘트 기능은 질답할 시간이 없다.
  • 과감하게 삭제하려했으나 커스터마이징해서 gitalk을 연동했다.
  • algolia가 오픈소스 개발문서가 아니여서그런지 무료 인덱싱을 요청했으나 티켓 상태가 변경되지 않았다.
  • @easyops-cn/docusaurus-search-local를 사용했는데, 이건 모바일 지원이 안 된다.
docusaurus.config.js
{
themes: [
"@easyops-cn/docusaurus-search-local",
/** @type {import("@easyops-cn/docusaurus-search-local").PluginOptions} */
({
indexDocs: false,
blogRouteBasePath: "/",
hashed: true,
language: ["en", "ko"],
}),
],
}
  • 1주정도 지났을까 algolia 에서 회신이 왔고, 인덱싱도 성공적으로 완료되었다.

결과

  • 만족스럽다.
  • 사내 문서로 쓰기엔 typesense 를 먼저 구축해야할 것 같아서 쉽진 않을듯..
  • 모바일로 볼 필요가 없으면 위의 플러그인으로도 만족스러울 것 같다.

· 4 min read

개요

  • nextjs 13버전에서 app 디렉토리가 생기면서 client/server 컴포넌트를 쉽게 사용할 수 있는 환경이 되었다.
  • getStaticProps, getServerSideProps 와 같은 메소드가 사라졌고, 양쪽이 fetch 로 통합이 되었다.
  • 페이지별 상태 초기화를 위한 HOC 중첩을 가져가지 않아도 될 것 같았다.

App

Node.js + React DOM Renderer

app 내의 모든 로직은 Node.js 이다. 따라서 이런 로직이 가능하다.

app/page.tsx
import { hostname } from "os";

export default function Main() {
return <div>{hostname()}</div>;
}

이벤트를 바인딩할 수 없다.

app/page.tsx
import type { SyntheticEvent } from "react";

export default function Main() {
const handleSubmit = (event: SyntheticEvent<HTMLFormElement>) => {
console.log("submitting");
};

return (
<form onSubmit={handleSubmit}>
<button type="submit">submit</button>
</form>
);
}
Error: Event handlers cannot be passed to Client Component props.
<form onSubmit="{function}" children="...">
^^^^^^^^^^ If you need interactivity, consider converting part of this to a
Client Component.
</form>

Design system

대부분의 디자인 시스템 라이브러리는 ThemeProvider 로 테마 상태를 공유하고 Baseline StyleSheet를 전역에 넣어준다. 스타일시트를 위해 RootStyleRegistry 란 HOC 만들어 Baseline StyleSheet 를 동적으로 넣어주면 초기화는 가능하지만,

문제는 Server Component 내에서 use 을 사용할 수 없으니 ThemeProvider 로 기능동작이 불가능하다. 어찌저찌 'use-client' directive 로 Client Component 로 설정한다고 하여도 Provider 로 인해 하위 모든 컴포넌트가 Client Component 로 동작해야할 것이다.

그래서 문서에서 다음과 같이 표기가 된 것으로 보인다.

If you want to style Server Components, we recommend using CSS Modules or other solutions that output CSS files, like PostCSS or Tailwind CSS.

결론

  • 아직 app 폴더를 사용하기엔 이르다.
  • vercel/app-playground에 충분한 예시가 갖춰지고, 생태계가 React Server Component 를 충분히 지원할 때까지는 프로덕션에서 사용은 불가능하다고 보인다.

사견

개인적으로는 왜 이렇게 많이 클라이언트에서 렌더링해야해? 그냥 필요한 영역만 클라이언트에서 그려주면 되잖아? 라는 접근방식은 디버깅 관점에서 마음에 들진 않는다. 웹을 하나의 Client 관점에서 본다면, 설치의 유무만 다를 뿐 앱에서 서버에서 렌더링된 HTML을 받고 일정부분만을 Server Driven UI 로 가는거와 마찬가지다. 복잡도가 크게 증가한다.

이러한 시도는 이미 Dotnet, Laravel Livewire 등 다른 언어에서 많이 진행되어왔고, Remix 에서는 이미 잘 동작 중이다. 단지 React 를 서버에서 쓰기 위해 다시 MVC 시절로 회귀하려는지 모르겠다.

· One min read

개요

벤치마킹, 툴 테스팅, 엑셀 스트리밍, 맵 리듀스 등 여러가지 테스트를 위해서 빈 레파지토리를 시작해야하는 경우가 많다. 복사해서 사용하기 위해 기록해두자.

CMD

yarn init -2
yarn add -D typescript ts-node @types/node
yarn tsc --init

touch src/index.ts
code .

# package.json
"start": "ts-node src/index.ts"

협업 여부에 따라 commitlint, eslint, yarn plugins 등등..

패키지

· 7 min read

A1: Do more, with less

UI 개발의 일반적인 문제

화면마다 매번 새로 디자인 되는 컴포넌트: UI의 파편화로 생산성 감소 새로 구현하는게 맘편해요...

디자인 일관성 다운 -> 중복코드 파편화 -> 유지보수가 어려운 UI 코드 무한루프

디자인시스템 구성요소

  • 토큰: 컬러, 타이포그래피, 사이즈, 여백, 트랜지션…
  • 컴포넌트: 토큰을 가지고 컴포넌트의 생김새 정의
    • Primary, fill, normal, disabled…
  • 패턴: Danger fill > 한 화면에 하나만 사용, 경고용도로 사용

스펙을 가지고 스토리북 생성

디자인 시스템 가이드 정의 > 디자인과 개발에 동일한 컴포넌트 구현 및 사용 > 커뮤니케이션 비용 다운, 효율성 일관성, 퀄리티 증가

100명 이상 소통하기에 다른 문제가 생겼다.

  • 코드를 디자인에 일치시키는 어려움 (14:25)
  • 비주얼 중심의 디자이너 언어 <-> 기능 중심의 개발자 언어
    • Blue: Primary
    • Red: Danger
  • 엄격함과 유연함 사이에 절충이 필요했다.

스케치로 만들고 직접 리액트 컴포넌트를 수동으로 만들었다.

  • 휴먼 에러가 생기고, 스케치컴포넌트와 리액트컴포넌트를 서로 비교해야했다.
  • 디자인과 코드 사이에 의존 관계를 만들어야한다.
  • 기존 디자인툴은 모든 프롭의 조합을 그려놓아야한다. (스티커 붙히기) 파워포인트로 디자인만들기 급..
    • 디자인 툴에서도 코드를 사용할 수 있으면 좋을텐데.. > 프레이머로 전환

프레이머

  • 모든 요소가 리액트 컴포넌트로 이루어짐 22:17분
  • 디자이너가 프롭을 정의하고 입력할 수 있음
  • 컨셉 검증을 위해 리액트로 작성된 라이브러리를 프레이머로 옮김
  • 리액트 컴포넌트 수정시 디자인 컴포넌트도 수정되어 코드 동기화가 완료됨

언어가 다른것

  • 제플린, 피그마에서 디자인한건지 디자인시스템컴포넌트인지를 확인하려면 일일히 눌러봐야함
    • 토스에서는 손가락 이모지를 앞에 넣어서 명시적으로 이름을 바꿧음
      • button-1. fill-medium-primary (style, size, color)
    • 박스에 버튼이 있는경우, 알 수가 없음.. 박스에 들어가는 버튼인지 버튼만을 넣는것인지... (29:49)

왜 디자인을 다시 개발해야하는가? 프레이머로 커스텀 핸드오프 기능 구현

  • 클릭시 전체 자식 노드의 프롭 정보를 노출시킴 (32:09)
  • 개발자와 디자이너의 언어도 일치시킴

DST (Design Syntax Tree)

  • 리액트 노드의 형태를 모방해서 기계가 파싱가능한 트리구조로 추상화 (33:41)
  • DST Element (34:22) -> DST React Renderer (35:20) (35:38)
{
"elementType": "AmountTop",
"properties": {
"title": "",
"subtitle": "",
"button": {
"elementType": "button",
"properties": {
"Title": "",
"Type": "",
"Style": "fill"
}
}
}
}

디자인을 바로 코드로 옮기는 목표 달성

엄격함과 유연함

  • 디자인 시스템에 컴포넌트가 있는줄 몰랐어요
  • 구멍인 줄은 알았는데 자유롭게 쓰고 싶었어요
  • 컴포넌트를 쓰긴 했는데 패턴을 어긴지 몰랐어요 등등..
  • 커스텀 컴포넌트 계속 만들면 디자인시스템 도입 이전하고 똑같음
  • 리스트 컴포넌트 스펙 참조 (41:21)
    • 디자이너가 컴포넌트 목록을 잘 몰라서 새로 만드는 경우
    • 가이드가 힘들었음 (43:26)
    • 디자인 시스템 관리자가 매번 패턴 점검 필요

디자인 린터 (디자인 시스템 커버리지 계산기)

  • 권장 패턴이 아닌 경우 알림
  • DST 를 통해 디자인 분석
  • 크롬 확장프로그램으로
    • 프레이머 웹사이트의 html 에서 Dst 정보 파싱
    • DST 에서 컴포넌트 관계 분석
    • 권장 패턴에 맞게 사용중인지 계산 (권장 패턴 요소 개수 / 전체 요소 개수) (44:50)
    • 학습비용과 전파비용을 극복함

React + DST (46:20)

  • DST 의 스펙만 확장시키면 됨
  • Server driven UI 나 다른 언어 컴포넌트로 전환도 가능해보임.

A6: swc

  • 많은 벤더가 전환 중

개인적인 의견: 엄청 빨라짐, 아직 롤업 플러그인 지원은 미비, 모든 Node 생태계가 러스트로 이동 중, swcpack 프로덕션 기대

B1: recoil

개인적인 의견: 인포그래픽 아름다워서 공유용으로 적합.

· 4 min read

개요

  • docker nodejs base image 에 alpine3.13 에 대한 이미지가 있어서 릴리즈노트를 확인했다.
  • Node.js (LTS) is compiled with -O2 instead of -Os which noticeably improves performance. 란 구문이 눈에 띄었다.
  • noticeably improves 란 추상적인 표현은 호기심을 자극하기에 충분했다.

히스토리

  • 처음에 빌드 플래그 변경을 제안한 사람에 따르면, 빌드 플래그 수정으로 15% speedup 이 있을 것이라고 하였다.
  • 이에 몇몇 알파인 패키지들이 O2 로 전환되었고, Node.js도 2020-12-19 커밋에 포함되었다.
  • 여기엔 아래와 같은 코멘트가 달려있고, v8/web-tooling-benchmark를 사용한 것으로 보였다.
# Compiling with O2 instead of Os increases binary size by ~10%
# (53.1 MiB -> 58.6 MiB), but also increases performance by ~20%
# according to v8/web-tooling-benchmark

테스트1

  • 빌드 플래그의 변경으로 nodejs 유저가 베이스 이미지의 버전을 하나 올려주는 것만으로 어플리케이션의 성능을 20% 까지 올릴 수 있다는 이야기로 보였다.
  • fastify/benchmarks 레파지토리의 Express.js 를 아래처럼 도커라이징했다.

node:lts-alpine3.12

# 테스트 당시 LTS === 14
FROM node:lts-alpine3.12

RUN mkdir -p /usr/src/app \
&& chown node:node -R /usr/src/app

WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./

USER node
RUN npm install && npm cache clean --force

COPY --chown=node:node . .
EXPOSE 3000
CMD [ "node", "server.js" ]

node:lts-alpine3.13

# 테스트 당시 LTS === 14
FROM node:lts-alpine3.13

RUN mkdir -p /usr/src/app \
&& chown node:node -R /usr/src/app

WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./

USER node
RUN npm install && npm cache clean --force

COPY --chown=node:node . .
EXPOSE 3000
CMD [ "node", "server.js" ]

결과1

컨테이너는 테스트되는 이미지만을 띄웠고, docker 4CPUs, 6G RAM 에서 실행했다.

autocannon -c 100 -d 40 -p 10 localhost:3000

부푼 기대만큼 결과가 같게 나왔으면 좋겠지만 결과 추가를 못할만큼 Latency, Req/Sec, Bytes/Sec 모든 수치에서 엎치락 뒤치락하는 모습을 보였다.

테스트2

  • noticeably improves 는 CPU intensive Task 를 비교해봐야하는 것일까란 의문만 남았다.
  • node official image 를 확인해보니 nodejs.org의 배포본을 풀어 사용하는 것으로 보였다.
  • 따라서 이미지를 alpine 패키지를 사용할 수 있게 재구성하고 테스트했다.

alpine:3.13

FROM alpine:3.13

RUN apk add --update nodejs npm
RUN addgroup -g 1000 node \
&& adduser -u 1000 -G node -s /bin/sh -D node \
&& mkdir -p /usr/src/app \
&& chown node:node -R /usr/src/app

WORKDIR /usr/src/app
COPY --chown=node:node package*.json ./

USER node
RUN npm install && npm cache clean --force

COPY --chown=node:node . .
EXPOSE 3000
CMD [ "node", "server.js" ]

결과2

  • 더 느렸다.

결론

  • noticeably improves, ~20% 란 문구로 인해 fastify/benchmarks 로직으로 도커라이징하여 테스트해보았으나 비슷한 퍼포먼스를 보여주었다.
  • 위의 수치를 검증할 수 있는 테스트베드가 있다면 돌려보고 싶다. (링크 있으시면 공유부탁드립니다.)

참조

· One min read
  • yarn set version berry
  • rm -rf node_modules package-lock.json
  • yarn
  • .gitignore 추가
  • package.scripts 에서 다른 스크립트를 실행하는 경우, yarn 을 붙혀서 실행
  • .husky 변경
    • package.scripts "postinstall": "husky install"
    • 실행 훅 스크립트를 npx --no-install 에서 yarn 으로 변경
  • Dockerfile 변경 (node 기본이미지에 yarn은 베이스로 설치되어 있음)
    • yarn build
    • entrypoint 커맨드
  • 의존관계 패키지 추가 설치
  • 테스트

· 5 min read

scss alias

// ~는 노드 모듈 처리된다.
@import "~@material";

// @styles 로 사용하고 싶을 경우가 있다.
@import "@styles/mystyle";
  • 두 번째의 경우는 scss 시트를 처리해주면서 (plugin postcss) Error: Can't find stylesheet to import. 와 같은 에러를 뱉는다.
  • 웹팩의 경우 resolve.alias 가 알아서 처리를 해주지만, rollup-plugin-postcss 의 sass-loader 에서 importer 를 추가해줘야할 것 같았다.

sass-loader

  • 이 플러그인의 sass-loader 는 dart-sass 의 importer 를 확장해서 쉽게 추가가 가능해보였다.
  • 그러나 아무리 확장처리를 해도 importer 에서 해당 경로가 리졸브 되지 않았고 관련 이슈가 3개, PR이 2개 등록된 것을 확인할 수 있다.
  • 이 걸 해결하려면 postcss.use.sass 사용시에 importer 를 override 해주는 custom sass-loader 를 직접 만들어야한다.

custom-sass-loader

import { createRequire } from "module";
import path from "path";
import { promisify } from "util";

import resolve from "resolve";
import PQueue from "p-queue";

function loadModule(moduleId) {
try {
return require(moduleId);
} catch {
// Ignore error
}

try {
return createRequire(path.resolve(process.cwd(), "noop.js"))(moduleId);
} catch {
// Ignore error
}
}

// This queue makes sure node-sass leaves one thread available for executing fs tasks
// See: https://github.com/sass/node-sass/issues/857
const threadPoolSize = process.env.UV_THREADPOOL_SIZE || 4;
const workQueue = new PQueue({ concurrency: threadPoolSize - 1 });

const moduleRe = /^~([a-z\d]|@).+/i;

const getUrlOfPartial = (url) => {
const parsedUrl = path.parse(url);
return `${parsedUrl.dir}${path.sep}_${parsedUrl.base}`;
};

const resolvePromise = promisify(resolve);

// List of supported SASS modules in the order of preference
const sassModuleIds = ["node-sass", "sass"];

export default {
name: "sass",
test: /\.(sass|scss)$/,
process({ code }) {
return new Promise((resolve, reject) => {
const sass = loadSassOrThrow();
const render = promisify(sass.render.bind(sass));
const data = this.options.data || "";
workQueue.add(() =>
render({
...this.options,
file: this.id,
data: data + code,
indentedSyntax: /\.sass$/.test(this.id),
sourceMap: this.sourceMap,
importer: [
(url, importer, done) => {
if (!moduleRe.test(url)) {
// 이부분에 alias importer 가 추가되어야한다.
if (/^@styles/.test(url)) {
return done({
file: url.replace(
/^@styles/,
path.resolve(__dirname, "./src/styles")
),
});
}
return done({ file: url });
}
const moduleUrl = url.slice(1);
const partialUrl = getUrlOfPartial(moduleUrl);

const options = {
basedir: path.dirname(importer),
extensions: [".scss", ".sass", ".css"],
};
const finishImport = (id) => {
done({
// Do not add `.css` extension in order to inline the file
file: id.endsWith(".css") ? id.replace(/\.css$/, "") : id,
});
};

const next = () => {
// Catch all resolving errors, return the original file and pass responsibility back to other custom importers
done({ file: url });
};

// Give precedence to importing a partial
resolvePromise(partialUrl, options)
.then(finishImport)
.catch((error) => {
if (
error.code === "MODULE_NOT_FOUND" ||
error.code === "ENOENT"
) {
resolvePromise(moduleUrl, options)
.then(finishImport)
.catch(next);
} else {
next();
}
});
},
].concat(this.options.importer || []),
})
.then((result) => {
for (const file of result.stats.includedFiles) {
this.dependencies.add(file);
}

resolve({
code: result.css.toString(),
map: result.map && result.map.toString(),
});
})
.catch(reject)
);
});
},
};

// 이하 생략...

rollup.config.js

위의 커스터마이징된 sass-loader 를 use: ['sass'] 대신 등록해준다.

postcss({
// 추가
loaders: [customSassLoader],
// 제거
// use: ['sass']
});

결론

  • rollup-plugin-postcss 는 이슈가 있어 확장을 직접해야하고 이는 유지보수 포인트로 다가올 수 있다.
  • postcss-import 는 alias 기능이 없어 사용이 불가능하다.
  • postcss-import-alias 등등의 alias 를 추가한 라이브러리도 불가능한데, 이는 postcssLoader 가 먼저 등록 되는데 의외의 결과이다. rollup-plugin-postcss 를 직접 로컬에서 빌드해서 해당 지점을 확인해볼 수 있겠지만 PR 을 받지 않으므로 의미도 없다.
  • rollup-plugin-scss를 사용하고 postcss 를 processor 로 줘서 반대로 처리할 수 있어보이는데, 빌드 파이프라인을 다 리팩토링을 해야하므로 나중에 도전해보는걸로 하자.

· 5 min read
  • prototype 은 constructor 와 상속을 이해하기 위해 필요하다.
  • 여러 코드 예시를 가지고 이걸 설명하려고 하는 포스팅이 많은데 이것도 정작 중요한 스펙에 대한 내용이 다 빠져있다.

Prototype

다른 객체에 공유 프로퍼티를 제공하는 객체

  • NOTE: 생성자가 객체를 만들 때 그 객체는 암묵적으로 생성자의 prototype 속성을 참조한다.
  • 생성자의 prototype 프로퍼티는 constructor.prototype 에 의해 참조될 수 있고 객체의 프로토타입에 추가된 프로퍼티는 상속을 통해 프로토타입을 공유하는 모든 객체에 공유된다.
  • Object.create 함수를 사용하여 명시적으로 지정된 프로토타입으로 객체를 만들 수 있다.

new 실행 단계

ECMAScript의 function 객체 F에 대한 내부 메소드 [[Construct]] 가 argumentsList 와 newTarget 파라미터와 함께 호출된다. argumentsList 는 빈 List 가 가능하다.

  1. Assert: F는 ECMAScript function object이다.
  2. Assert: newTarget은 Object이다.
  3. Let callerContext를 현재 execution context로 설정한다.
  4. kind는 F.[[ConstructorKind]]로 설정한다.
  5. if kind가 base라면
    • a. thisArgument를 newTarget의 prototype을 가지는 새로운 객체로 설정한다.
  6. calleeContext는 PrepareForOrdinaryCall(F, newTarget)로 설정한다.
  7. Assert: calleeContext는 이제 현재 실행 중인 execution context이다.
  8. If kind가 base라면, OrdinaryCallBindThis(F, calleeContext, thisArgument)로 this 바인딩을 한다.
  9. constructorEnv를 calleeContext의 LexicalEnvironment로 설정한다.
  10. envRec를 constructorEnv의 EnvironmentRecord로 설정한다.
  11. 함수 F를 argumentList로 실행한 결과를 result로 설정한다.
  12. calleeContext를 execution context stack에서 삭제하고 callerContext를 현재 execution context로 복구한다.
  13. If result.[[Type]]이 return이면
    • a. result.[[Value]]가 객체이면 result.[[Value]]를 반환한다.
    • b. If kind가 base이면 thisArgument를 반환한다.
    • c. If result.[[Value]] 가 undefined 가 아니라면 TypeError를 발생시킨다.
  14. 아니라면, result를 반환한다.
  15. EnviromnetRecord의 this를 반환한다.

5.a

  • 여기서 this를 newTarget의 prototype을 가지는 객체로 생성한다.
  • 이 사양으로 프로토타입 체인이 실행된다.

8

  • 위에서 생성된 객체를 this로 설정한다.

11

  • argumentsList를 파라미터로 넘긴 생성자 함수 F로 새로운 객체를 초기화한다.

15

  • 초기화된 this를 함수 호출의 결과로 반환한다.
  • 이 사양으로 new 키워드로 실행시 return this; 구문이 없는데도 인스턴스가 반환된다.

기타 특성

  • 함수 객체의 prototype은 Object.prototype에 대한 delegation link와 constructor 속성을 가진 객체의 참조를 가진다.
  • constructor 속성은 함수 객체에 대한 역참조를 가지고 있다. (재귀적)
  • 함수는 Function.prototype 에 대한 델리게이션 링크를 가지고 있어 apply와 call을 상속받는다.

__proto__ 와 prototype 의 차이

  • __proto__ 는 내부 슬롯인 [[Prototype]]에 접근하는 접근자 프로퍼티다.
  • 함수 객체만이 가지고 있는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다.
    • 따라서 화살표 함수나 메소드 축약 문법으로 생성한 함수는 프로토타입을 가지지 않는다.

참조

· 3 min read
  • this 는 함수나 스코프 기반으로 결정되는 것이 아니라 호출 방법에 따라 변경된다.
  • 여러 코드 예시를 가지고 이걸 설명하려고 하는 포스팅이 많은데 이것도 정작 중요한 스펙에 대한 내용이 다 빠져있다.
  • this 가 어떻게 동작하나요? 에 대한 대답은 하나다. ECMA OrdinaryCallBindThis 사양대로 동작합니다.

OrdinaryCallBindThis

OrdinaryCallBindThis가 호출되었을 때 함수 F와 실행 컨텍스트 calleeContext 그리고 ECMAScript 값인 thisArgument 를 활용하여 다음 단계를 수행한다.

  1. thisMode 를 F.[[thisMode]] 으로 설정한다.
  2. if thisMode가 lexical 이면 undefined 를 반환한다.
  3. calleeRealm을 F.[[Realm]] 으로 설정한다.
  4. localEnv를 calleeContenxt 의 LexicalEnvironment 로 설정한다.
  5. if thisMode 가 "strict" 라면 thisValue 는 thisArgument 로 설정한다.
  6. else
    • a. if thisArgument 가 undefined 또는 null 이면
      • i. globalEnv를 calleeRealm.[[GlobalEnv]] 로 설정한다.
      • ii. globalEnvRec는 globalEnv 의 EnvironmentRecord 로 설정한다.
      • iii. Assert: globalEnvRec 는 global EnvironmentRecord 이다.
      • iv. thisValue 를 GlobalEnvRec.[[GlobalThisValue]] 로 설정한다.
    • b. else
      • i. thisValue 를 thisArgument 를 객체로 변환 (ToObject) 하여 설정한다.
      • ii. NOTE: ToObject는 calleeRealm을 사용하여 래퍼 객체를 생성한다.
  7. envRec 를 localEnv 의 EnviromentRecord 로 설정한다.
  8. Assert: envRec 는 function EnviromentRecord 이다.
  9. Assert: envRec.[[ThisBindingStatus]]가 초기화되지 않았으므로 10번은 abrupt completion 을 반환하지 않는다.
  10. envRec.BindThisValue(thisValue) 를 반환한다.
  • 볼드체로 표시한 부분을 잘 보자.

5

  • 이 사양으로 인해 use strict 사용시에 동작이 변경된다.

6.a.iv

  • 이 사양으로 인해 thiswindow로 선언된다.

6.b.i

  • 이 사양으로 인해 this에 값을 넘길 경우 객체로 변경된다.

참조