Javascript 내에서 callback를 더 효율적으로 사용하기 위해 promise가 등장하였고 promise를 더 효율적으로 사용하기 위해 es8에서 async/await 이 등장하였습니다.

 

promise와 async/await이 브라우저에서 어떻게 구현되는지 동작의 차이를 알게되었고 이를 글로 적어보려 합니다.

promise, async/await은 둘다 promise를 return하거나 throw하므로 micro task queue에서 다루어집니다.

callback queue에는 3종류가 있습니다.
macro task queue, micro task queue, animationFrame
우선순위는 micro task queue > animationFrame > macro task queue 순입니다.

macro task queue에 있는 것이 비워지기 전에 항상 micro task queue, animationFrame을 전부 실행시킵니다.

micro task queue 전부 실행
animation Frame 전부 실행
macro task queue 하나 실행

micro task queue 전부 실행
animation Frame 전부 실행
macro task queue 하나 실행

위와 같이 실행됩니다.
이렇게 동작하는 이유는 micro task queue, animation frame을 항상 동일한 상황에서 실행 시키기 위함이라고 합니다.

 

promise의 예시입니다

console.log('start')

setTimeout(() => console.log('setTimeout'),0)

Promise.resolve('Promise').then(res => console.log(res))

console.log('End')

promise의 resolve는 바로 micro task queue에 함수를 추가시킵니다.

실행결과는 아래와 같습니다.

start
end
promise
setTimeout

 

async/await을 활용 했을때의 예시입니다.

const myPromise = () => {
    return new Promise((resolve, reject) => {
        resolve('hihi')
    })
}
// const myPromise = () => Promise.resolve('hihi')

const myFunc = async () => {
    console.log("Inner function")
    console.log(await myPromise())
    console.log("goOut function")
}

console.log("Before Function")
myFunc()
console.log("After Function")

위의 출력 결과는 아래와 같습니다.

Before Function
Inner function
After Function
hihi
goOut function

결론 => async 함수에서 await을 만나면 해당 함수는 pause 상태가 된채로 micro task queue로 이동되어집니다. 이후 콜스택이 비어지길 기다렸다가 micro task queue에서 실행이 됩니다.

그래서 위의 실행결과에서 After Function이 먼저 실행되고 hihi, goOut function이 실행된것입니다.

 

 

위의 동작으로 인해 async내에서 await끼리의 순서는 보장됨이 확인 되었습니다.

async 내에서 await을 만나면 pause된 상태로 micro task queue로 이동되어 지고 실행되고 

await을 또 만나면 다시 pause된 상태로 micro task queue로 이동되어지면서 실행 되기 때문입니다.

 

나중에는 promise -> generator -> async/await을 알아보겠습니다.

 

 

 

 

출처: https://towardsdev.com/event-loop-in-javascript-672c07618dc9

 

The JavaScript Event Loop: Explained

Call Stack, Web APIs, Event Queue, Micro-tasks, and Macro-tasks

towardsdev.com

https://velog.io/@chojs28/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EC%9D%98-%EC%9B%90%EB%A6%AC-Event-Loop-Task-Queue

 

자바스크립트 동작 원리와 비동기 처리의 원리 (Event Loop, Task Queue)

자바스크립트 엔진은 대표적으로 Google V8 엔진이 있다. Chrome과 Node.js 에서 사용하고 있다.자바스크립트 엔진은 가능한 빠르게 최적화된 코드를 생성하기 위해 전통적으로는 인터프리터 방식으

velog.io

https://velog.io/@titu/JavaScript-Task-Queue%EB%A7%90%EA%B3%A0-%EB%8B%A4%EB%A5%B8-%ED%81%90%EA%B0%80-%EB%8D%94-%EC%9E%88%EB%8B%A4%EA%B3%A0-MicroTask-Queue-Animation-Frames-Render-Queue

 

[JavaScript] Task Queue말고 다른 큐가 더 있다고? (MicroTask Queue, Animation Frames)

자바스크립트에서 비동기 함수가 동작하는 원리에 대해서 공부했다면, Task Queue에 대해 들어보았을 것이다. Task Queue는 Web API가 수행한 비동기 함수를 넘겨받아 Event Loop가 해당 함수를 Call Stack에

velog.io

 

728x90

'Frontend > Javascript' 카테고리의 다른 글

Javascript async/await, Promise 실수 포인트 🗝  (0) 2022.04.19
Javascript 콜백 📞  (0) 2022.04.19
Javascript Prototype 🤢  (1) 2022.04.15
React-query 🌱  (1) 2022.04.09
Javascript 실행 컨텍스트(Execution Context)  (1) 2022.04.03

프로토타입(prototype)이란 한글로 원래의 형태 또는 전형적인 예라는 뜻을 가지고 있습니다.

javascript는 프로토타입 기반의 언어입니다.

그래서 javascript에서 프로토타입 개념을 이해하는 것은 매우 중요합니다.

 

클래스 기반언어 에서는 클래스를 먼저 선언하고 새로운 객체(인스턴스)를 생성합니다.

하지만 javascript에서는 프로토타입(prototpe)을 원형으로 삼고 이를 이용하여 객체를 생성합니다.

 


var jade = { 
  name: 'jade'
}

jade.toString() // '[object Object]'


function Person(name) {
  this.name = name
}

var wade = new Person('wade')

wade.name // 'wade'
wade.hasOwnProperty('name') // true

 

위의 코드는 javascript에서 객체를 선언한 예시입니다.

하나는 객체 리터럴 방식을 사용하였고 하나는 생성자 함수를 사용하였습니다.

jade 변수와 wade 변수에서는 각각 toString, hasOwnProperty 메서드를 선언해주지 않았습니다.

하지만 사용 결과는 에러를 반환하지 않고 정상적인 결과를 반환하였습니다. 

왜 이런 동작이 가능한 걸까요??

javascript는 프로토타입 기반의 언어이고 프로토타입 체인이 이루어져 해당 메소드들을 사용할 수 있었기 때문입니다.

 

javascript에서 변수는 호출된 프로퍼티가 존재하지 않으면 자신의 프로토타입 객체에 접근하여 해당 메서드가 있는지 탐색합니다.

있으면 사용, 없으면 다음 프로토타입 객체에 접근하여 메서드를 탐색 및 사용하게 됩니다. 

이것을 프로토타입 체인이라 합니다.

여기서 나오는 프로토타입 객체는 자신의 원형이 되는 객체, 부모 객체라 이해하였습니다.

 


위 그림을 이해하면 프로토타입의 동작 흐름을 이해하는데 큰 도움이 될 수 있습니다.

위 그림과 아래의 코드를 같이 살펴보겠습니다.

function Person(name) {
    this.name = name
}

var logan = new Person('logan')

console.log(Person.prototype)  // { constructor: f }
console.log(Person.prototype.constructor) // f Person
console.log(logan.constructor) // f Person

위의 코드를 더 위의 그림에 도식화한 내용입니다.

 

이제 상세히 설명하겠습니다.

Person은 생성자 함수 입니다(javascript에서는 보통 파스칼 케이스로 선언)

logan은 new Person을 통해 생성된 객체 입니다.

Person 생성자 함수에서 prototype 속성을 통해 Person의 원형 객체에 접근할 수 있습니다.

logan에서는 __proto__를 통해 자신의 원형객체에 접근할 수 있습니다.

constructor는 새롭게 생성된 인스턴스에서 자신의 생성자 함수에 접근할 수 있는 수단입니다.

 

아직 한가지 동작은 설명이 되지 않았습니다.

logan.constructor의 동작입니다.

logan에는 constructor 속성이 존재하지 않습니다. 따라서 동작을 하지 않아야만 할것 같습니다.

하지만 동작합니다. 

logan.__proto__.constructor이 호출되었기 때문입니다. 이때 프로토타입 체인이 등장하게 됩니다.

 

우선 logan 객체에서 constructor가 존재하는지 찾습니다.

없는 것을 확인했고 __proto__를 통해 logan의 프로토타입에 접근하여 constructor를 찾았고 이를 호출했습니다.

 

아래의 코드를 보겠습니다.

function Person(name){
  this.name = name
}

var logan = new Person('logan')

console.log(logan) // { name: 'logan' }

console.log(logan.hasOwnProperty('name')) // true
console.log(logan.hasOwnProperty('constructor')) // false

console.log(logan.__proto__.hasOwnProperty('constructor')) // true
console.log(Object.getPrototypeOf(logan).hasOwnProperty('constructor')) // true

console.log(logan.__proto__.__proto__.hasOwnProperty('hasOwnProperty')) // true
console.log(
  Object.getPrototypeOf(
    Object.getPrototypeOf(
      logan
    )
  ).hasOwnProperty('constructor')
) // true

logan 객체에는 hasOwnProperty 메소드가 없습니다.

하지만 위의 코드는 정상적으로 동작합니다. 

왜 그런것일까요?? 

hasOwnProperty가 호출되면 logan의 프로토타입들을 프로토타입 체인을 통해 계속 접근하였고

최상위 프로토타입인 Object에 hasOwnProperty가 존재하여 이를 호출하여 사용한 것이기 때문입니다.

아래의 그림에 조금의 설명이 있습니다.

지금 당장 이해하지 않아도 됩니다.

__proto__는 객체 자신의 프로토타입 객체(부모 객체)를 찾아가는 메서드 입니다.

__proto__를 호출하게 되면 내부적으로 Object.getPrototypeOf가 호출되는 것으로 알고 있습니다.

이를 이용해 프로토타입 체인이 일어나게 됩니다.

프로퍼티가 호출되면 자신에게 존재하지 않으면 상위 프로토타입에서 해당 프로퍼티를 호출합니다.

 

위의 코드에서 Object.hasOwnProperty가 logan.hasOwnProperty가 호출된 사례 입니다.


이 왼쪽트리가 갑자기 등장해서 이해가 안되었을 수도 있습니다.

javascript에서 모든 Wrappe 함수의 결과물은 객체 입니다.

Function도 객체, String도 객체, Number도 객체, Boolean도 객체 입니다.

 


이런 프로토타입의 속성을 이용해서 클래스 기반에서 동작하는 상속을 구현할 수 있습니다.

function Person(name) {
  this.name = name
  this.getName = function() {
    console.log(this.name)
  }
}

var logan = new Person('logan')
logan.getName()

이런식으로 메소드를 생성자 함수 내에서 정의해주면 새로 생성된 객체는 getName 메소드를 호출하게 됩니다.

이 생성자 함수를 이용하여 무수히 많은 객체를 생성한다고 가정하겠습니다.

그러면 객체마다 getName이라는 메서드에 메모리 영역을 할당해야 합니다.

이러한 메모리 영역을 프로토타입을 이용하면 세이브 할 수 있습니다

 

function Person(name) {
  this.name = name
}

Person.prototype.getName = function () {
  console.log(this.name)
}

var logan = new Person('logan')
logan.getName()

 

위와 같이 프로토타입 체인을 이용하면 Person 생성자 함수가 무수히 많은 객체를 생성해도 

생성된 객체 내에 getName 메소드가 존재하지 않으므로 __proto__를 통해 getName메소드에 존재합니다.

그러면 메모리 영역을 객체마다 할당하지 않아도 같은 결과를 가져올 수 있습니다.

 


 

프로토 타입 체인 -> 프로퍼티를 가져오기 위해 자신의 프로토타입에 프로퍼티가 있는지 찾기위한 과정

자식이 차키가 없으면 부모의 차키를 찾고 그 다음엔 조부모의 차키를 찾는다.

차키를 찾지 못하면 그 차는 사용할수가 없다.

 

스코프 체인 -> 실행컨텍스트에서 변수를 찾기 위해 스코프체인 프로퍼티를 참조하여 변수를 찾기위한 과정

 

 


 

출처: https://poiemaweb.com/js-prototype

 

Prototype | PoiemaWeb

자바스크립트의 모든 객체는 자신의 부모 역할을 하는 객체와 연결되어 있다. 그리고 이것은 마치 객체 지향의 상속 개념과 같이 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있게

poiemaweb.com

https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85

 

프로토타입 - 위키백과, 우리 모두의 백과사전

일련의 프로토타입을 통하여 최종 디자인이 만들어진다. 프로토타입(prototype)은 원래의 형태 또는 전형적인 예, 기초 또는 표준이다. 시제품이 나오기 전의 제품의 원형으로 개발검증과 양산 검

ko.wikipedia.org

* 코어자바스크립트 책 6장 프로토타입

728x90

'Frontend > Javascript' 카테고리의 다른 글

Javascript 콜백 📞  (0) 2022.04.19
Javascript Promise, async/await MicroTask queue 동작차이 🖇  (1) 2022.04.15
React-query 🌱  (1) 2022.04.09
Javascript 실행 컨텍스트(Execution Context)  (1) 2022.04.03
npm, yarn  (0) 2022.04.03

react-query는 서버의 상태를 다루기에 정말 좋은 라이브러리라고 생각합니다.

react-query의 공식 홈페이지에서도 서버의 상태를 위한 라이브러리로 설명되어있습니다.

* React Query는 서버 상태 라이브러리입니다. 서버와 클라이언트의 비동기적인 동작을 관리하기에 적합합니다.
* Redux, MobX, Zustand, etc 는 클라이언트 상태 라이브러리 입니다. 비동기 데이터를 저장할수는 있지만 React  Query에 비해서는 비효율적입니다.

이러한 점을 염두에 두고 React Query는 클라이언트 상태에 위치한 캐시데이터를 관리하기 위한 boiler plate코드와 관련 로직들을 코드 몇 줄 만으로 대체한다는 것입니다!

 

Redux에서 서버의 api요청 응답을 저장해두고 있을때는 아래의 사항들이 걸렸습니다.

1. 너무 많은 boiler plate 코드

2. 언제 응답되었는지를 알수 없는 데이터 로드 시기

3. 전역 클라이언트 상태와 같이 관리되어지는 서버 상태

 

그래서 React Query를 도입했었고 axios, fetch등과 같이 비동기 요청을 보내주는 도구들과 함께 효율적으로 사용했었습니다.

아래에서는 간단한 기본 개념과 쓰임을 알아보려 합니다.

 


먼저 stale에 대하여 알아보려 합니다.

브라우저에서는 header의 cache-control를 통해 네트워크 요청의 캐시를 관리합니다.

캐시를 이용하는 목적은 동일한 요청이라면 서버로의 요청이 아니라 메모리에서 가져와 더 빠른시간내에 응답을 하기 위해서 사용합니다.

cache-control의 값에는 max-age, no store, no cache 등이 들어갈 수 있지만 stale-while-revalidate라는 값 또한 들어갈 수 있습니다. (stale은 신선하지 않은 이라는 뜻입니다.)

 

예시를 들어보겠습니다. cache-control: "max-age= 60, stale-while-revalidate= 60" 으로 응답헤더에 포함되어 있다면

1~60초에 요청이 들어오면 캐시는 신선하므로 바로 가져가 사용합니다.

61~120초 요청에 들어오면 캐시는 stale 상태 신선하지 않은 상태가 됩니다. 그래서 우선 캐시된 값을 내보내고 서버로 값을 refetch, 즉 fresh 한 상태로 바꾸기 위해 요청을 보내고 응답을 받아오면 캐시된 값을 교체해 줍니다.

 

react-query에서 기본 stale time은 0입니다. cache-time의 default는 5분 입니다. 

이 설정을 바꾸고 싶다면 개별 요청에서만 옵션을 변경해주어 보내주거나 queryClient에서 설정해주면 모든 요청에 기본 요청으로 설정됩니다.

 

stale 쿼리들은 아래와 같은 상황에서는 백그라운드 상태에서 자동으로 요청됩니다.

  • New instances of the query mount(잘 모르겠습니다...)
  • window가 다시 focus 되었을때 
  • network가 재연결 되었을때
  • query가 설정한 retch 간격에 따라

 

그외 기본 사항들

  • inactive 상태인 쿼리들은 5분 이후에 garbage collect 됩니다.
  • 요청에 실패할 경우 3번 까지 요청을 보내고 모두 실패하였을시 화면에 표시합니다. 이때 요청간 delay를 설정할 수 있습니다.
  • 기본적으로 Json 형식의 쿼리 결과는 데이터가 실제로 변경되었는지 여부를 감지하기 위해 구조적으로 공유되어 집니다. 실제로 변경되지 않은 경우 데이터 참조는 변경되지 않은 상태로 유지되어 useMemo및 useCallback과 관련하여 값 안정화에 더 도움이 됩니다.

 


 

QueryClient Provider

import { QueryClient, QueryClientProvider } from 'react-query';

const queryClient = new QueryClient();

function App(){
  return (
    <QueryClientProvider client={queryClient}>
      <Component />
    </QueryClientProvider>
  )
}

useQuery

import {useQuery} from 'react-query';

const PeopleWithFetch = () => {
  const { isLoading, error, data } =  useQuery(
    ["todo", 1], 
    async () => await fetch("https://jsonplaceholder.typicode.com/todos/1").then(r => r.json()),
  )

  if (isLoading) return (<>"is Loading"</>)

  if (error) return (<>"somethin wrong"</>)

  return (
    <div>
      {data.title}
    </div>
  )
}

export default PeopleWithFetch;

 

isLoading, error와 같이 따로 설정이 필요하게 됩니다.

이러한 경우 Suspense, ErrorBoundary의 사용을 검토해 볼 수 있습니다.

만약 useQuery를 복수개 요청하였고 모든 데이터의 유효성이 검증되어야 한다면 코드는 복잡해질수 있습니다.

Suspense를 사용하여 loading 상태를 대체할 수 있고 ErrorBoundary를 사용하여 error 상태를 대체할 수 있습니다.

 

react-query에서는 errorResetBoundary를 제공해주고 있습니다.

react-error-boundary 라이브러리와 같이 사용하면 errorBoundary  역할을 할수 있습니다.

 

저는 typescript에서 react 18, react-dom18을 사용하여 suspense, react-query를 사용하려 하였으나 아직 @types/react-query내부에서 참조하고 있는 react가 17을 기반으로 하고 있어 그런지 typescript 적용에는 실패하였습니다.
추후에 react js 에서 시도해보고 안되면 다른 방법을 찾아볼 예정입니다.

 

복수의 useQuery 사용

한 컴포넌트에서 여러 요청이 복수로 parallel하게 수행되어져야 할 경우 아래와 같이 작성할 수 있습니다.

 function App () {
   // The following queries will execute in parallel
   const usersQuery = useQuery('users', fetchUsers)
   const teamsQuery = useQuery('teams', fetchTeams)
   const projectsQuery = useQuery('projects', fetchProjects)
   ...
 }

 

parallel하게 수행되어 져야 하는 요청이 복수개일 경우 useQueries의 사용을 고려할 수 있습니다.

useQueries는 쿼리키, 쿼리함수를 복수개를 받을 수 있습니다.

 function App({ users }) {
   const userQueries = useQueries(
     users.map(user => {
       return {
         queryKey: ['user', user.id],
         queryFn: () => fetchUserById(user.id),
       }
     })
   )
 }

 

 

Dependent Query 사용시

a요청이 수행되어 지고 a요청의 결과를 가지고 b 요청이 수행되어 져야 할 경우를 의존적 쿼리라 칭하겠습니다.

이런 의존적 쿼리가 필요할 경우 enabled 옵션을 사용하면 편리합니다.

// Get the user
 const { data: user } = useQuery(['user', email], getUserByEmail)
 
 const userId = user?.id
 
 // Then get the user's projects
 const { isIdle, data: projects } = useQuery(
   ['projects', userId],
   getProjectsByUser,
   {
     // The query will not execute until the userId exists
     enabled: !!userId,
   }
 )
 
 // isIdle will be `true` until `enabled` is true and the query begins to fetch.
 // It will then go to the `isLoading` stage and hopefully the `isSuccess` stage :)

이 외에 useInfiniteQuery등과 옵션중 keepPreviousData등 편리한 옵션이 많아서 매우 유용합니다!!


useMutation

get 요청을 보낼때는 useQuery를 사용합니다.

post, delete, patch, put 과 같은 요청을 이용할때는 useMutation 훅을 사용합니다.

useQuery에 비해 적은 옵션을 사용합니다.

 


DevTool

 

ReactQuery에서는 DevTool을 제공합니다. 

키와 데이터 값을 같이 볼수 있고

fresh, fetching, stale 등 데이터의 상태를 눈으로 볼수 있고 

refetch, invalidate, reset, remove 등의 동작을 수행할 수 있습니다.

 

데이터의 상태를 다룰수 있어 편리한 도구 입니다.

 

 

 

 

출처 : https://react-query.tanstack.com/guides/does-this-replace-client-state

 

Does React Query replace Redux, MobX or other global state managers?

With those points in mind, the short answer is that React Query replaces the boilerplate code and related wiring used to manage cache data in your client-state and replaces it with just a few lines of code. For a vast majority of applications, the truly gl

react-query.tanstack.com

https://web.dev/i18n/ko/stale-while-revalidate/

 

stale-while-revalidate로 최신 상태 유지

stale-while-revalidate는 개발자가 캐시된 콘텐츠를 즉시 로드하는 즉시성과 캐시된 콘텐츠에 대한 업데이트가 향후에 사용되도록 보장하는 최신성 간의 균형을 유지하는 데 도움이 됩니다.

web.dev

https://www.stevy.dev/react-state-management-guide/

 

728x90

실행 컨텍스트(Execution Context)는 실행 가능한 코드가 실행되기 위해 필요한 환경을 뜻합니다

(실행할 코드에 제공할 환경정보를 모아놓은 객체)

 

실행 가능한 코드는 크게 3개의 종류가 있습니다.

  • 전역 코드
  • 함수 코드
  • eval 코드(eval은 사용하지 않아야 합니다.)

 

일반적으로 실행이 가능한 코드는 전역코드와 함수코드 입니다.

 

자바스크립트 엔진은 코드를 실행하기 위하여 여러가지 정보를 알고 있어야 합니다. 

  • 변수: 전역변수, 지역변수, 매개변수, 객체의 프로퍼티
  • 함수 선언
  • 변수의 유효범위(scope)
  • this

위의 정보들을 물리적 객체의 형태로서 자바스크립트 엔진에서 관리합니다.

 


 

이벤트 루프를 언급할때 javascript engine에는 heap과 call stack이 존재한다고 하였습니다.

heap은 메모리 할당이 일어나는 영역(변수, 객체 등등)

call stack은 코드의 실행에 따라 호출 스택이 쌓이는 영역

2022.03.31 - [Frontend/Javascript] - event loop

함수가 호출되면 call stack에 실행 컨텍스트가 하나씩 쌓이게 됩니다.

코드의 실행과 함께 콜스택에 실행 컨텍스트가 쌓이는 모습을 살펴보겠습니다.

 

var a = 'a';

console.log(a)

위의 코드를 실행하면 아래와 같이 콜스택에 올려져 실행되게 됩니다.

전역 범위에서 실행되었으므로 전역 실행컨텍스트 하나만 스택에 존재하는 것을 확인할 수 있습니다.

실행컨텍스트 스택

 

 

아래는 좀 더 복잡한 코드의 실행입니다.

var a = 'a'

function outerFunc() {
  var b = 'b'
  
  function innerFunc() {
    console.log(a + b)
  }
  
  innerFunc()
}

outerFunc()

1. 전역 코드가 실행 (global EC 추가)

2. outerFunc가 실행됨(outerFunc EC 추가)

3. innerFunc가 실행됨(innerFunc EC 추가)

 

총 3개의 스택이 추가되는 것을 볼 수 있고 

실행 후에는 해당 함수의 실행 컨텍스트를 파기하고 컨트롤을 다음 스택으로 반환하게 됩니다!

 

실행 컨텍스트 스택

 


실행컨텍스트는 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이지만

물리적으로는 객체의 형태를 가지며 아래의 3가지 프로퍼티를 가지고 있습니다.

 

실행 컨텍스트 구조

 

Variable Object

 

위에서 언급한 여러가지 정보중 변수, 매개변수, 함수선언에 대한 정보가 담길 객체 입니다.

  • 변수
  • 매개변수, 인수정보
  • 함수 선언

Variable Object는 실행 컨텍스트의 property이기 때문에 상황에 따라 다른 값을 가지게 됩니다.

하나의 예로 전역 컨텍스트에는 매개변수가 없지만 함수 컨텍스트에는 매개변수가 있습니다.

전역 컨텍스트
함수 컨텍스트

 


Scope Chain

프로그래밍에는 동적 스코프, 정적 스코프(lexical scope)가 존재합니다. 

동적 스코프는 함수가 호출될때 스코프가 정해지는 것이고 

정적 스코프는 함수가 선언된 위치를 기준으로 스코프가 정해지는 것입니다.

javascript에서는 정적 스코프를 체택하고 있습니다.(대부분의 언어에서는 정적 스코프를 체택하고 있습니다.)

선언된 위치를 기준으로 스코프를 가지게 된다는 것입니다.

(var는 함수 스코프, let, const는 블록 스코프를 가집니다. 이것 또한 정적 스코프 입니다.)

 

실행컨텍스트에서 Scope Chain은 자신이 선언된 위치를 기준으로 생성됩니다.

innerFunc은 outerFunc내부에서 선언되었습니다.

outerFunc는 전역 스코프에서 선언되었습니다.

그러므로 innerFunc의 스코프 체인은 [innerFunc, outerFunc, 전역컨텍스트]의 값을 가지게 됩니다.

스코프 체인은 변수의 값을 탐색할때 사용됩니다. 자세한 예시는 아래에서 언급하겠습니다.

위에서 등장한 전역컨텍스트, outerFunc, innerFunc의 실행컨텍스트를 살펴보겠습니다.

전역컨텍스트: {
  vo: {
    a: 'a',
    outerFunc: function Object
  },
  sc: ['전역 변수객체'],
  this: window
}

outerFunc: {
  vo: {
    b: 'b',
    innerFunc: function Object
    arguments: {}
  },
  sc: ['outerFunc 변수객체', '전역 변수객체'],
  this: window
}

innerFunc: {
  vo: {
    arguments: {}
  },
  sc: ['innerFunc 변수객체', 'outerFunc 변수객체', '전역 변수객체'],
  this: window
}

크롬의 개발자 도구에서는 Scope, CallStack을 확인할 수 있습니다.

중간에 debugger를 찍으면 아래의 사진과 같이 확인할 수 있습니다.

함수의 감춰진 프로퍼티 중 [[scope]]는 스코프 체인이 아니라 현재 참조하고 있는 스코프를 의미합니다.

 

 

 

function test1() {
    function test2() {
        function test3() {
            var x = 1;
            function test4() {
                console.log("test4", x)
            }
            console.dir(test4)
            test4();
        }
        console.dir(test3)
        test3();
    }
    console.dir(test2)
    test2();
}
console.dir(test1)
test1();

 

위의 예시를 보면 더 확실히 알 수 있습니다.

아래의 코드는 각 실행 컨텍스트를 구체화 시킨 것입니다.

전역컨텍스트: {
  vo: {
    test1: function Object
  },
  sc: ['전역 변수객체']
  this: window
},

test1: {
  vo: {
    test2: function Object
  },
  sc: ['test1 변수객체','전역 변수객체']
  this: window
}

test2: {
  vo: {
    test3: function Object
  },
  sc: ['test2 변수객체', 'test1 변수객체','전역 변수객체']
  this: window
}

test3: {
  vo: {
    x: 1
    test4: function Object
  },
  sc: ['test3 변수객체', 'test2 변수객체', 'test1 변수객체','전역 변수객체']
  this: window
}

test4: {
  vo: {},
  sc: ['test4 변수객체', 'test3 변수객체', 'test2 변수객체', 'test1 변수객체','전역 변수객체']
  this: window
}

개발자도구 console 에서 출력한 [[Scopes]]는 함수 실행컨텍스트가 가지는 스코프체인과 다릅니다.

[[Scopes]]는  스코프 체인이 아니라 현재 참조하고 있는 스코프를 의미하기 때문입니다.

 

test4가 실행되었을때 test4 내부에는 x 변수객체가 존재하지 않습니다.

그래서 스코프체인에 나와있는대로 다음 스코프인 test3의 변수 객체를 가져와서 출력해 줍니다.

이렇게 최상위 스코프 까지 가도 출력하고자 하는 변수가 없다면 ReferenceError를 출력해줍니다.

// 생략
function test3() {
    var x = 1;
    function test4() {
        console.log("test4", x)
    }
    console.dir(test4)
    test4();
}
console.dir(test3)
test3();
// 생략


// 실행 컨텍스트
test3: {
  vo: {
    x: 1
    test4: function Object
  },
  sc: ['test3 변수객체', 'test2 변수객체', 'test1 변수객체','전역 변수객체']
  this: window
}

test4: {
  vo: {},
  sc: ['test4 변수객체', 'test3 변수객체', 'test2 변수객체', 'test1 변수객체','전역 변수객체']
  this: window
}

ReferenceError 출력 모습

 

참조하는 값에 따라 달라진 [[Scopes]] 모습은 아래의 이미지 입니다.

 

var name = 'name'

function hi() {
  var name = 'same'
  console.log(name) // 'same'
  sayWord('hello') // 'hello name'
}

function sayWord(word) {
  console.log(word, name)
}

hi()

위 코드에서는 전역에서 hi를 호출하고 hi 내부에서 sayWord를 호출하였습니다.

콜스택에 쌓이는 모습은 아래와 같습니다.

실행 컨텍스트는 아래와 같이 생성됩니다.

// 실행 컨텍스트

전역컨텍스트: {
  vo: {
    name: 'name',
    sayWord: function Object,
    hi: function Object,
  },
  sc: ["전역 변수객체"]
  this: window,
},

hi: {
  vo: {
    name: 'same'
  },
  sc: ['hi 변수객체', '전역 변수객체'],
  this: window,
}

sayWord: {
  vo: {
  	arguments: {
      word: 'hello'
    }
  },
  sc: ["sayWord 변수객체", "전역 변수객체"]
  this: window,
},

아래는 위에서 사용한 함수를 실행컨텍스트의 스코프 체인을 이용하는 상황을 같이 서술한 것입니다.

var name = 'name'

function hi() {
  var name = 'same'
  console.log(name) // 'same' // name은 hi 컨텍스트의 변수객체 내에 존재하므로 가져와 사용합니다.
  sayWord('hello') // 'hello name' 
  // sayWord는 hi컨텍스트의 변수 객체에 존재하지 않으므로 상위 스코프인 전역 컨텍스트의 변수객체를 참조합니다.
}

function sayWord(word) {
  console.log(word, name)
  // name은 sayWord의 변수 객체에 존재하지 않으므로 상위 스코프인 전역 컨텍스트의 변수객체를 참조합니다.
}

hi()

 

 

javascript에서 실행 컨텍스트 내부에 사용하고자 하는 변수가 존재하지 않는다면 상위 스코프의 변수객체를 참조합니다.

최상위 스코프까지 변수를 탐색하고 있으면 return, 찾지 못하면 referenceError를 반환합니다.

객체를 찾는것은 프로토타입 체인 입니다.

2022.02.28 - [Frontend/Javascript] - 🔥Javascript questions


 

this Value

this는 동적으로 결정됩니다. 이는 예측이 어려운 상황을 종종 만들어냅니다.

this는 함수와 다르게 선언되는 위치가 아니라 호출되는 상황에 따라 동적으로 값이 할당 됩니다.

 

this가 결정되는 상황은 크게 5가지 상황이 있습니다.

1. 일반 함수로 실행

2. 메소드로서 실행

3. 생성자 함수

4. bind, apply, call 을 사용한 명시적 바인딩

5. 콜백함수에서의 실행

2022.02.28 - [Frontend/Javascript] - 🔥Javascript questions


실행 컨텍스트가 어떤 과정으로 생성되는지 알아보겠습니다.

함수를 실행하면 함수의 실행 컨텍스트가 생성되고 콜스택에 함수 실행 컨텍스트가 추가됩니다. 

추가된후에 아래의 1,2,3 단계가 실행됩니다.

이후 함수의 실행 컨텍스트 내용을 기반으로 함수 내의 코드가 실행됩니다.

 

1. 스코프 체인 생성 및 초기화
2. 변수 객체화(Variable Instantiation) 실행
3. this값 동적 할당

 

아래의 코드 실행과정을 따라가 보면서 위의 단계를 살펴보겠습니다.

var a = 'a'

function outerFunc() {
  var b = 'b'
  function innerFunc() {
    console.log(a, b)
  }
  
  innerFunc()
}

outerFunc()

 

전역 코드 실행

 

위의 코드가 실행되면 제일 먼저 전역 컨텍스트가 생성된 후 콜스택에 추가됩니다.

전역컨텍스트 : {
  vo: {},
  sc: [],
  this: window
}

외로운 콜스택

1. 스코프 체인 생성 및 초기화

스코프 체인은 위에서 언급한 대로 선언된 위치의 영향을 받아 생성됩니다.

 

전역컨텍스트 : {
  vo: {},
  sc: ['전역 변수객체'],
  this: window
}


// 전역 객체에는 DOM, BOM, Built-in Object등이 설정되어 있습니다!

 

전역 컨텍스트에서는 전역 객체에 접근할 수 있고

전역 객체에는 DOM, BOM, Built-in Object등이 설정되어 있습니다!!

 

2. 변수 객체화(Variable Instantiation) 실행

 

변수 객체화에서는 아래의 3단계 순서대로 처리를 수행합니다.

순서는 변경되지 않습니다!!!

2-1  함수 컨텍스트의 생성에서는 매개변수를 먼저 처리합니다.
2-2 함수 선언식을 끌어올려 변수 객체화를 수행합니다. (함수 호이스팅,  함수 표현식은 변수만 호이스팅)
2-3 변수들을 끌어올려 변수 객체화를 수행합니다.(변수 호이스팅, undefined로 초기화)
전역컨텍스트 : {
  vo: {
    a: 'a',
    outerFunc: function Object
  },
  sc: ['전역 변수객체'],
  this: window
}


// 전역 객체에는 DOM, BOM, Built-in Object등이 설정되어 있습니다!

 

이 단계에서 호이스팅이 발생합니다. 

호이스팅이란 변수나 함수의 선언이 스코프 상단으로 끌어 올려 지는 것을 의미합니다.

호이스팅으로 인해 코드 상의 선언 전에 사용이 가능해 집니다.

console.log(name) // undefined
var name = 'lab'
console.log(name) // lab

hihi() // hihi
function hihi() {
  console.log('hihi')
}

위의 코드는 실행 컨텍스트가 생성될때 변수 객체화 과정으로 인해 아래 코드처럼 변모합니다.

function hihi() {
  console.log('hihi')
}

var name = 'undefined'
console.log(name) // undefined
name = 'lab'
console.log(name) // lab

hihi() // hihi

함수 호이스팅이 먼저 되고 변수 호이스팅이 됩니다.

 

변수 객체화는 전역 컨텍스트의 생성과 함수 컨텍스트의 생성에서 과정이 약간 다릅니다.

함수 컨텍스트의 생성에서는 매개변수를 먼저 처리합니다.

 

3. this값 동적 할당

this는 기본값을 window로 가지고 있습니다.

호출되는 상황에 따라 this의 값은 바뀌게 됩니다.

 


outerFunc 실행

 

위의 1,2,3 과정이 동일하게 실행됩니다.

 

실행컨텍스트 생성후 콜스택 추가

"outerFunc" : {
  vo: {},
  sc: [],
  this: window,
}

1. 스코프 체인 생성 및 초기화

"outerFunc" : {
  vo: {},
  sc: ["outerFunc 변수객체", "전역 변수객체"],
  this: window,
}

함수는 lexical scope(정적스코프)를 참조합니다.

outerFunc가 선언된 위치는 전역 컨텍스트 이므로 스코프 체인에는 전역컨텍스트가 있고 자기 자신의 스코프가 추가됩니다.

스코프 체인은 변수객체를 참조할때 이용됩니다. 

변수를 먼저 자기자신의 스코프에서 찾고 존재하지 않으면 다음 스코프체인의 스코프에서 찾는 흐름 입니다.

 

 

2. 변수 객체화 실행

2-1. 함수면 매개변수 arguments 객체에 추가
2-2. 함수 호이스팅
2-3. 변수 호이스팅
"outerFunc" : {
  vo: {
    arguments: null,
    b: 'b'
    innerFunc: function Object,
  },
  sc: ["outerFunc 변수객체", "전역 변수객체"],
  this: window,
}

만약 outerFunc의 코드에서 a를 출력한다면

1. 먼저 자기 자신의 변수 객체에서 찾아보고 없으므로

2. 다음 스코프 체인인 전역컨텍스트의 변수객체에서 a를 가져와 출력하게 됩니다.

전역컨텍스트 : {
  vo: {
    a: 'a',
    outerFunc: function Object,
  },
  sc: ['전역 변수객체'],
  this: window
}

 

3. this값 할당 


innerFunc 실행

 

실행컨텍스트 생성 후 콜스택에 추가

"innerFunc": {
  vo: {},
  sc: [],
  this: window,
}

1. 스코프 체인 생성 및 초기화

"innerFunc": {
  vo: {},
  sc: ["innerFunc 변수객체", "outerFunc 변수객체", "전역 변수객체"],
  this: window,
}

2. 변수 객체화 실행

2-1. 함수면 매개변수 arguments 객체에 추가
2-2. 함수 호이스팅
2-3. 변수 호이스팅

innerFunc내부에는 변수가 존재하지 않으므로 객체화할 변수는 없습니다.

 

 

3. this값 할당 

 

var a = 'a'

function outerFunc() {
  var b = 'b'
  function innerFunc() {
    console.log(a, b)
  }
  
  innerFunc()
}

outerFunc()

자 이제 innerFunc가 실행되었을때

a 변수를 출력하기 위해 innerFunc 실행컨텍스트의 스코프 체인을 타고 이동하여 전역 컨텍스트의 변수 객체에서 a의 값을 가져옵니다.

b 변수를 출력하기 위해 innerFunc 실행컨텍스트의 스코프 체인을 타고 이동하여 outerFunc 컨텍스트의 변수 객체에서 b의 값을 가져옵니다.

 


이제 클로저를 실행컨텍스트를 이용하여 설명할 수 있습니다.

 

대표적인 클로저의 예시 하나를 가져왔습니다.

function counter(){
  var count = 0
  return {
    getCount: function() {
      return count;
    },
    addCount: function() {
      count += 1
    },
    subCount: function() {
      count -= 1
    },
  }
}

var counter1 = counter()

counter1.addCount()
console.log(counter1.getCount()) // 1
counter1.addCount()
console.log(counter1.getCount()) // 2
counter1.subCount()
console.log(counter1.getCount()) // 1 
counter1.subCount()
console.log(counter1.getCount()) // 0

 

getCount, addCount, subCount의 실행 컨텍스트를 살펴보겠습니다.

 

getCount: { 
  vo: {}, 
  sc: ["getCount 변수객체", "counter 변수객체", "전역 변수객체"],
  this: window,
}

addCount: { 
  vo: {}, 
  sc: ["addCount 변수객체", "counter 변수객체", "전역 변수객체"],
  this: window,
}

subCount: { 
  vo: {}, 
  sc: ["subCount 변수객체", "counter 변수객체", "전역 변수객체"],
  this: window,
}

counter: {
  vo: {
    count: 0,
    {
      getCount: function Object,
      addCount: function Object,
      subCount: function Object,
    }
  },
  sc: ["counter 변수객체", "전역 변수객체"],
  this: window,
}

"전역 컨텍스트": {
  vo: {
    counter: function Object,
  },
  sc: ["전역 변수객체"],
  this: window
}

 

getCount를 호출하게 되면 getCount내의 변수 객체에는 존재하지 않으므로 counter 변수객체에서 count를 가져와 출력해줍니다!

counter 실행컨텍스트는 종료되었지만 scope chaining에 의하여 counter의 변수 객체가 참조 되는 것입니다.

이러한 경우 자바스크립트 엔진에서는 해당 변수의 메모리를 계속 남겨둡니다.(garbage collection이 되지 않습니다.)

그래서 클로저를 많이 사용하게 되면 메모리가 부족해 질수도 있습니다.

 

 

 

 

 

 

 

 

 

 

 

출처 : https://poiemaweb.com/js-execution-context

 

Execution Context | PoiemaWeb

Execution Context(실행 컨텍스트)는 scope, hoisting, this, function, closure 등의 동작원리를 담고 있는 자바스크립트의 핵심원리이다. 실행 컨텍스트를 바로 이해하지 못하면 코드 독해가 어려워지며 디버

poiemaweb.com

http://dmitrysoshnikov.com/ecmascript/chapter-1-execution-contexts/

 

ECMA-262-3 in detail. Chapter 1. Execution Contexts.

Read this article in: Russian, Chinese, French.

dmitrysoshnikov.com

https://www.zerocho.com/category/JavaScript/post/5741d96d094da4986bc950a0

 

https://www.zerocho.com/category/JavaScript/post/5741d96d094da4986bc950a0

 

www.zerocho.com

 

728x90

'Frontend > Javascript' 카테고리의 다른 글

Javascript Prototype 🤢  (1) 2022.04.15
React-query 🌱  (1) 2022.04.09
npm, yarn  (0) 2022.04.03
Javascript custom array 🌹  (0) 2022.04.02
Javascript createObjectURL, revokeObjectURL 🤥  (0) 2022.04.01

npm, yarn 둘다 node 환경에서 패키지를 관리해주는 도구 입니다.

주로 yarn을 사용했었는데 어떤 차이점이 있는지 알아보려 합니다.

 


속도 

npm은 의존성 파일을 순차적으로 설치합니다. 이전파일이 설치되고 다음파일을 설치합니다.

a를 설치하고 b를 설치하고 c를 설치하는 형식입니다.

yarn은 의존성 파일들을 병렬적으로 설치합니다. a, b, c를 병렬로 설치합니다.

yarn은 다운받은 패키지들을 캐시에 저장합니다. 중복된 패키지를 설치해야 할 경우 캐시에 저장되어 있다면 캐시를 활용합니다.

캐시를 사용하기 때문에 npm에 비해 디스크의 사용이 더 큽니다

 

보안성

npm은 의존 관계를 가지는 다른 패키지를 즉시 포함합니다. 편하다는 장점이 있습니다. 하지만 보안문제에서 취약점을 불러올 수 있습니다.

yarn은 yarn.lock이나 package.json 파일에 명시된 것들만 설치를 합니다.

이러한 보안성은 모든 디바이스에 같은 버전의 패키지들이 설치되는 것을 보장하고 버전의 차이로 인한 버그를 방지해줍니다.

 


 

  • 2010년 : npm이 등장하였습니다.
  • 2016년 : yarn이 등장합니다.
    npm보다 더 좋은 성능을 보여주었고 패키지들의 버전에 대한 정보를 가진 yarn.lock 파일이 자동으로 생성됩니다.
    npm에서는 npm shrinkwrap을 통해 직접 생성해주어야 했습니다.
  • 2017년 : npm 5 가 등장합니다.
    package-lock.json 파일을 자동으로 생성해주고 이전 버전에 비해 속도가 월등히 빨라졌습니다.
    이때부터 yarn과 npm의 패키지 설치 속도는 크게 차이가 나지 않게 되었습니다.
  • 2018년 : npm 6가 등장합니다.
    이 버전에서는 보안성이 강화되었습니다.
    이제 npm에서 의존성이 설치되기 전에 보안 취약성을 검사하여 설치합니다.(npm audit)
  • 2020년 : yarn2, npm 7 이 등장합니다. 
    yarn2 에서는 pnp(plug and play)라는 개념이 도입됩니다. (pnp는 즉시시작이라는 뜻입니다.)
  • 2021년 : yarn3가 등장하였습니다.

 


npm 등장

 

2010년 이전에는 프로젝트의 의존성은 직접 다운로드하고 관리하여야 했습니다.

이러한 불편함에 node js를 개발한 ryan dhar과 함께 npm이 등장합니다.

npm의 방향은 아래와 같습니다.

 

* 여러 버전의 동일한 패키지를 한 프로젝트에서 사용할 수 있게 하자

* 설치 방식을 통일하자

* 패키지 관련 정보가 들어있는 메타 데이터를 간소화 하자

* 누구나 배포할 수 있도록 하자

 


npm v3

package hoisting이 등장하게 됩니다.

패키지 의존성 트리에서 공통으로 설치되는 패키지들은 hoisting하여 tree를 flatten하게 만들어 줍니다.

하지만 이런 과정에서 유령 의존성(phantom dependency)가 발생하게 됩니다.

또한 여전히 패키지 트리를 flatten 하게 만드는 알고리즘은 너무 어렵습니다.

package hoisting

위의 사진을 보면 A(1.0), B(1.0) 버전의 패키지는 중복됩니다.

이러한 경우 상단으로 끌어올려 패키지의 중복 설치를 방지합니다.

하지만 package-1에서 B(1.0)를 직접 import 하게 되었습니다.

이러한 현상을 유령 의존성이라 합니다.

 


yarn

npm의 단점을 보완하기 위해 등장하였습니다. 

 

* lock 파일을 매번 수동으로 생성해주어야 함

* 의존성 트리가 달라질 수 있음

* 느린 설치 속도

 

yarn은 yarn.lock을 자동으로 생성해 줍니다.

 

npm에서는 패키지의 설치 순서에 따라 의존성 트리의 변형이 생길수 있었습니다.

yarn에서는 특별한 선택 알고리즘을 도입하여 의존성 트리가 변형되지 않도록 하였습니다.

 

yarn에서는 설치한 모든 것들을 캐시에 저장합니다.

yarn의 캐시를 확인하려면 /Users/<user>/Library/Caches/yarn의 경로로 이동하면 확인할 수 있습니다.

그래서 npm에 비해 디스크 용량이 요구됩니다.

하지만 캐시를 사용하기에 이미 설치된 패키지들은 다시 설치하지 않아 설치가 더 빠릅니다.

 

yarn은 설치된 패키지들이 실행되기 전에 checksum을 사용하여 보안을 강화하였습니다.


특징

Offline Mode : 이전에 설치했었던 패키지 들은 다시 설치할때는 offline에서도 설치가 가능합니다.

Deterministic : 설치 순서에 상관없이 같은 의존성 트리가 생성됩니다.

Network Performance : 네트워크 활용을 극도로 하여 큐를 사용하여 네트워크 요청을 효율적으로 관리합니다.

Network Resilience : 하나의 요청이 실패해도 전체 설치과정이 실패하지 않습니다. 실패 이후에 재요청 됩니다.

Flat Mode : 패키지의 중복 생성을 방지하기 위해 서로 다른 버전의 종속성을 단일 버전으로 해결하였습니다.

 

장점이 많아보이는 yarn에서 등장한 단점은 아래와 같습니다.

 

yarn install을 하게 되면 아래와 같은 과정을 거치게 됩니다

1. 명시된 버전에서 설치할 의존성 버전이 결정됩니다(semantic version 내에서는 의존성 버전의 범위를 지정할 수 있습니다 ^, ~등 사용)

2. 패키지들의 각 버전이 source로 부터 다운 받아지고 offline mirror에 저장됩니다(offline mirror는 내부의 buffer정도로 이해하였습니다.)

3. offline mirror에서 캐시에 unpacked 됩니다(위에서 언급한 캐시 경로)

4. 캐시에서 node_modules 폴더로 복사됩니다.

 

-> 4번의 과정은 무거운 I/O 작업입니다. 이러한 작업으로 속도의 저하가 발생할 수 있습니다.

 

node.js에서는 어떤 모듈을 사용하고자 하면 모듈을 찾기 위해 부모 ~ 루트 까지 node_modules 폴더를 찾는 방식을 사용합니다.

repl 에서 require.resolve.paths('react') 명령어를 사용하게 되면 

부모 ~ 루트 까지 node_modules 찾는 경로를 보여줍니다.

패키지를 찾으려고 readdir 같은 느린 I/O 호출이 반복되게 됩니다.

-> 런타임에서 이러한 과정은 속도의 저하를 발생시킵니다.


npm v5

package.lock 파일이 자동으로 생성되게 되었습니다.

이전에는 npm shrinkwrap 명령어를 통해 직접 생성해주어야 하였습니다.

5 버전부터 yarn과의 패키지 설치 속도 차이가 거의 나지 않게 되었습니다.

 


npm v6

npm은 설치 중간에 패키지들의 보안 취약성을 검사하고 취약성이 발견되면 사용자에게 알려줍니다.

npm audit 명령어를 통해 직접 확인할 수도 있습니다.

만약에 취약성이 발견되면 어떠한 행동을 취해야 하는지 알려주기도 합니다.

 


yarn berry

 

yarn version 2가 등장합니다. 

위에서 등장한 yarn의 단점을 해결하고자 .pnp.cjs라는 파일을 생성합니다.

 

기존의 package.json
.pnp.cjs

.pnp.cjs 파일은 정적인 정보들을 제공해 줍니다.

  • 의존성 트리에서 어떤 패키지들이 사용 가능한지
  • 패키지들이 어떻게 연결되어 있는지
  • 디스크 어디에 위치하여 있는지

yarn berry는 node_modules 폴더를 생성하지 않습니다. 대신 yarn 폴더 아래에 cache 폴더가 생성됩니다.

이곳에 npm registry에서 설치한 파일이 zip 형태로 압축되어 저장됩니다.

.pnp.cjs에서는 이 경로를 가리킵니다.

 

  • cache 파일내의 파일들은 압축되어 저장되므로 기존의 node_modules 폴더보다 훨씬 작은 크기를 가지게 되었습니다.
  • yarn/cache에서 copy와 같은 무거운 I/O 작업을 수행하지 않아도 됩니다.
  • yarn berry 에서는 cache폴더를 git에 직접 업로드 하기를 추천합니다.
    패키지를 설치없이 프로젝트를 동작하게 하기 위함입니다. 
    설치없이 동작하게 하기 위한 개념을 zero-install이라 합니다.
  • 모든 의존성 트리는 flatten 되어 집니다. 그리고 의존성 관계는 설치된 hard link로 연결됩니다 (바로가기와 같이)
    1 depth로 모든 패키지를 설치하고 .pnp.cjs에서 의존성에 따라 link만 명시해주면 됩니다. 
    따라서 유령의존성이 해결되었습니다.
  • 패키지 파일이 어디에 위치하여있는지도 같이 명시가 되어있습니다. 더이상 node에서는 부모 ~ 루트의 node_modules를 탐색하지 않아도 됩니다.

package 호이스팅 해결 내용

 

yarn set version berry 명령어를 통해 버전 설정이 가능합니다.

프로젝트의 root directory에서 yarn의 버전을 명시하면 됩니다.

 

yarn berry가 만능처럼 보이지만 아직 여러 패키지들과의 호환성 문제와 같은 이슈들이 존재한다고 합니다.

이는 이제껏 새로 나오는 패키지들이 그랬듯 언젠가 해결될 문제일것 같습니다.

요번에 npm, yarn 개념을 알아보면서 꽤나 멀리까지 갔는데 yarn berry를 개인 프로젝트에 틈틈이 적용해봐야 겠습니다.

 

 

 


 

 

출처: https://medium.com/wantedjobs/yarn-berry-%EC%A0%81%EC%9A%A9%EA%B8%B0-1-e4347be5987

 

yarn berry 적용기(1)

안녕하세요. 원티드 프론트엔드 팀 남동훈 입니다.

medium.com

https://www.cleancoder.dev/package-manager-history/

 

자바스크립트 패키지 매니저의 여정

자바스크립트 패키지 매니저가 어떻게 발전했는지 알아보자.

www.cleancoder.dev

https://www.sitepoint.com/yarn-vs-npm/

 

Yarn vs npm: Everything You Need to Know - SitePoint

Modern web development is virtually impossible without a package manager. Our Yarn vs npm comparison will help you decide which to use.

www.sitepoint.com

https://seogeurim.tistory.com/12?category=981579 

 

npm? yarn? 그 차이가 뭐길래...

본 글은 2020년에 작성된 글입니다. node 개발 환경에서는 패키지 매니저로 npm 또는 yarn을 쓰곤 한다. 나는 그냥 npm이 편해서 npm을 써왔었는데, 한 프로젝트를 진행하다가 팀원들이 다 yarn을 쓰자고

seogeurim.tistory.com

https://github.com/yarnpkg/yarn

 

GitHub - yarnpkg/yarn: The 1.x line is frozen - features and bugfixes now happen on https://github.com/yarnpkg/berry

The 1.x line is frozen - features and bugfixes now happen on https://github.com/yarnpkg/berry - GitHub - yarnpkg/yarn: The 1.x line is frozen - features and bugfixes now happen on https://github.co...

github.com

https://github.com/yarnpkg/rfcs/blob/master/accepted/0000-plug-an-play.md

 

GitHub - yarnpkg/rfcs: RFCs for changes to Yarn

RFCs for changes to Yarn. Contribute to yarnpkg/rfcs development by creating an account on GitHub.

github.com

https://toss.tech/article/node-modules-and-yarn-berry

 

node_modules로부터 우리를 구원해 줄 Yarn Berry

토스 프론트엔드 레포지토리 대부분에서 사용하고 있는 패키지 매니저 Yarn Berry. 채택하게 된 배경과 사용하면서 좋았던 점을 공유합니다.

toss.tech

https://yarnpkg.com/features/pnp

 

Plug'n'Play

An overview of Plug'n'Play, a powerful and innovative installation strategy for Node.

yarnpkg.com

 

728x90

'Frontend > Javascript' 카테고리의 다른 글

React-query 🌱  (1) 2022.04.09
Javascript 실행 컨텍스트(Execution Context)  (1) 2022.04.03
Javascript custom array 🌹  (0) 2022.04.02
Javascript createObjectURL, revokeObjectURL 🤥  (0) 2022.04.01
Javascript custom event 🐕  (0) 2022.04.01

javascript에서 빈 배열을 만드는 것을 계속 까먹어서 아래와 같이 정리합니다.

 

빈 배열 만들기 

const arr = new Array(10) // [empty x 10]
arr[5] = 1
arr // [empty x 5, 1, empty x 4]

const brr = Array.from(5) // []
brr[5] = 1
brr // [empty x 5, 1]

 

빈 배열 채우기

const arr = new Array(10)
arr.fill(4)
arr // [4, 4, 4, 4, 4, 4, 4, 4, 4, 4]

 

String에서 배열 만들기

Array.from('arr') // ['a', 'r', 'r']

Array.from('x'.repeat(5)) // ['x', 'x', 'x', 'x', 'x']

 

유사배열객체에서 배열 만들기

function f() {
  return Array.from(arguments)
}

f(1,2,3) // [1, 2, 3]

 

Array.from과 화살표 함수 사용

Array.from([1,2,3,4], x => x * 2) // [2,4,6,8]

// 이때 v는 undefined
Array.from({length: 4}, (v, i) => (i + 1) * 2) // [2,4,6,8]

 

 

 

출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/from#array.from%EA%B3%BC_%ED%99%94%EC%82%B4%ED%91%9C_%ED%95%A8%EC%88%98_%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

Array.from() - JavaScript | MDN

Array.from() 메서드는 유사 배열 객체(array-like object)나 반복 가능한 객체(iterable object)를 얕게 복사해 새로운Array 객체를 만듭니다.

developer.mozilla.org

 

728x90

'Frontend > Javascript' 카테고리의 다른 글

Javascript 실행 컨텍스트(Execution Context)  (1) 2022.04.03
npm, yarn  (0) 2022.04.03
Javascript createObjectURL, revokeObjectURL 🤥  (0) 2022.04.01
Javascript custom event 🐕  (0) 2022.04.01
컴파일러, 인터프리터  (0) 2022.04.01

javascript에서는 createObjectURL을 통해 Blob 객체를 나타낼 수 있습니다.

URL.createObjectURL 메소드를 사용하면 생성된 window의 document에서만 접근 가능한 url이 생성됩니다.

이를 통해 image를 입력받았을때 화면에 보여줄 수 있는 임시 url을 생성할 수 있습니다.

 

Blob(Binary large Object) 객체는 javascript에서 이미지, 영상, 멀티미디어 데이터를 다룰때 사용합니다.
createObjectUrl에 입력 가능한 (File, Blob, MediaSource)중 File은 Blob에 기반한 인터페이스 입니다.
input에 type을 file로 주면 입력받은 파일에 input.files 메소드를 통해 접근할 수 있습니다.
아래의 embedded 예제에서 보면 확인할 수 있습니다.

 

이 url은 다른 window에서는 접근이 되지 않습니다.

 

URL.revokeObjectURL을 통해 createObjectURL을 통해 생성된 Url을 폐기 시킬수 있습니다

폐기시키지 않으면 javascript engine에서는 생성된 url을 계속 사용하는 것으로 판단하여 GC가 동작하지 않게 됩니다.

불필요한 메모리 영역을 할당하게 되는 셈이 됩니다.

그래서 생성된 url을 해제해 주어야 합니다. => 메모리 누수를 막을 수 있습니다.

 


createObjectURL

const objectURL = URL.createObjectURL(object)

// object에는 File, Blob, MediaSource 객체 와 같은것들이 입력되어야 함

위의 명령어를 통해 호출되는 url은 blob:https://cdpn.io/e11de577-1cd4-4ae5-8ff0-7dccff70203d  

와 같이 생성됩니다.

이 url은 위에서 설명했듯이 createObjectURL을 호출한 document에서만 접근이 가능합니다.

 

revokeObjectURL

URL.revokeObjectURL(objectUrl)

위의 방식으로 revokeObjectURL을 호출하면 createObjectURL을 통해 생성된 url을 폐기 시킬 수 있습니다.

이 url은 더이상 쓸일이 없기 때문에 revokeObjectURL을 통해 폐기 시켜야 합니다.

 

 

See the Pen Untitled by 정우병 (@woobottle) on CodePen.

 

 

출처 : https://heropy.blog/2019/02/28/blob/

 

Blob(블랍) 이해하기

JavaScript에서 Blob(Binary Large Object, 블랍)은 이미지, 사운드, 비디오와 같은 멀티미디어 데이터를 다룰 때 사용할 수 있습니다. 대개 데이터의 크기(Byte) 및 MIME 타입을 알아내거나, 데이터를 송수신

heropy.blog

https://developer.mozilla.org/ko/docs/Web/API/URL/createObjectURL

 

URL.createObjectURL() - Web API | MDN

URL.createObjectURL() 정적 메서드는 주어진 객체를 가리키는 URL을 DOMString으로 반환합니다.

developer.mozilla.org

https://developer.mozilla.org/ko/docs/Web/API/File/Using_files_from_web_applications#%EC%98%88%EC%8B%9C_%EA%B0%9D%EC%B2%B4_url%EC%9D%84_%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC_%EC%9D%B4%EB%AF%B8%EC%A7%80_%ED%91%9C%EC%8B%9C%ED%95%98%EA%B8%B0

 

웹 어플리케이션에서 파일 사용하기 - Web API | MDN

HTML5의 DOM으로 추가된 File API를 사용하여, 이제 웹 컨텐츠가 사용자에게 로컬 파일을 선택한 후 파일의 컨텐츠를 읽도록 요청할 수 있습니다. 이 선택은 HTML <input> 엘리먼트나 드래그 앤 드랍을

developer.mozilla.org

 

728x90

'Frontend > Javascript' 카테고리의 다른 글

npm, yarn  (0) 2022.04.03
Javascript custom array 🌹  (0) 2022.04.02
Javascript custom event 🐕  (0) 2022.04.01
컴파일러, 인터프리터  (0) 2022.04.01
Javascript 함수 => 1급 객체 ❓  (0) 2022.04.01

javascript에서는 미리 정의되어 있는 이벤트 종류외에 추가로 custom event를 추가할 수 있습니다.

 

반복되는 동작을 customEvent로 정의후에 

eventHandler를 등록한 dom 요소에서 dispatchEvent 메소드를 통해 customEvent를 동작하게 할 수 있습니다.

 

customEvent에는 type과 옵션이 들어갑니다.

옵션 객체를 통해 원하는 데이터를 전달할 수도 있습니다.

CustomEvent(type, [options]?)


options => { detail: {객체 정의}}

 

아래는 간단한 사용 예시 입니다.

const cat = new CustomEvent('animal', { detail: {name: 'cat' }})
const dog = new CustomEvent('animal', { detail: {name: 'dog'}})

const div = document.querySelctor('div');
div.addEventListener('animal', (e) => {e.detail.name})

div.dispatchEvent(cat) // cat
div.dispatchEvent(dog) // dog
div.dispatchEvent(new CustomEvent('animal', {detail: {name: 'elephant'}})) // elephant
div.dispatchEvent(new CustomEvent('animalTest', {detail: {name: 'elephant'}})) // 발동 x, animalTest를 div에 등록해두지 않았으므로

div.addEventListener('click', () => div.dispatchEvent(cat)) // 클릭하면 cat 출력

 

 

순서를 이해하는게 중요한것 같습니다.

1. 이벤트 발생을 원하는 객체에 customEventName을 addEventListener을 통해 등록합니다. 이때 콜백함수를 같이 등록합니다.

2. customEventName을 가진 customEvent를 변수에 담거나 직접 dispatchEvent를 호출합니다.

3. 변수에 담긴 customEvent가 실행되면 옵션의 detail 속성을 통해 원하는 정보를 전달할 수도 있습니다.

4. 옵션 없이 dispatchEvent(customEventName)을 호출하게 되면 미리 등록한 콜백 함수가 실행될 것입니다!

 

 

 

See the Pen Untitled by 정우병 (@woobottle) on CodePen.

 

 

 

 

출처 : https://developer.mozilla.org/ko/docs/Web/API/CustomEvent/CustomEvent

 

CustomEvent() - Web API | MDN

CustomEvent() 생성자는 새로운 CustomEvent를 생성합니다.

developer.mozilla.org

 

728x90

'Frontend > Javascript' 카테고리의 다른 글

Javascript custom array 🌹  (0) 2022.04.02
Javascript createObjectURL, revokeObjectURL 🤥  (0) 2022.04.01
컴파일러, 인터프리터  (0) 2022.04.01
Javascript 함수 => 1급 객체 ❓  (0) 2022.04.01
Javascript event loop  (1) 2022.03.31

+ Recent posts