<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>ProdYou</title>
    <link>https://prodyou.tistory.com/</link>
    <description>Love Music, Interested in Developing.</description>
    <language>ko</language>
    <pubDate>Thu, 25 Jun 2026 15:40:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ProdMoon</managingEditor>
    <image>
      <title>ProdYou</title>
      <url>https://tistory1.daumcdn.net/tistory/5593238/attach/63db56d512d4454a840d3d7717e9a307</url>
      <link>https://prodyou.tistory.com</link>
    </image>
    <item>
      <title>JS Deep Dive 18장. 함수와 일급 객체</title>
      <link>https://prodyou.tistory.com/53</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2026년 6월 16일 오후 08_19_11.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvVKy7/dJMcaaMq0dI/R2soSCgTQvlmZH71BkpGa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvVKy7/dJMcaaMq0dI/R2soSCgTQvlmZH71BkpGa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvVKy7/dJMcaaMq0dI/R2soSCgTQvlmZH71BkpGa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvVKy7%2FdJMcaaMq0dI%2FR2soSCgTQvlmZH71BkpGa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;300&quot; data-filename=&quot;ChatGPT Image 2026년 6월 16일 오후 08_19_11.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개념 알아보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 조건을 만족하는 객체를 일급 객체라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 무명의 리터럴로 생성할 수 있다. 즉 런타임 생성이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 변수 또는 자료구조(객체, 배열)에 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수의 매개변수에 전달할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 함수의 반환값으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 함수는 위 조건들을 모두 만족하므로 일급 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수는 일급 객체이므로, 함수는 객체처럼 쓸 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 객체는 값이므로 함수는 값처럼 쓸 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 함수는 값이 들어갈 수 있는 자리에 다 들어갈 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(변수 할당문, 객체의 프로퍼티 값, 배열의 요소, 함수 호출의 인수, 함수 반환문 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 일급 객체면 뭐가 좋냐, 바로 함수형 프로그래밍이 가능하다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 짓을 할 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781607616105&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function add(a, b) {
  return a + b;
}

// 함수를 인수로 넣을 수 있다!
add(1, add(2, 3)); // 6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 일반 객체와 구별되는 점이라면, 일반 객체는 호출할 수 없지만 함수는 호출할 수 있다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781607638059&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const normalObject = {};
const functionObject = function () {};

normalObject(); // Uncaught TypeError: normalObject is not a function
functionObject(); // 에러 없이 실행됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 객체에만 있는 메서드들이 있습니다. `call()`, `bind()`, `apply()` 같은 것들이 있습니다. 요것들이 뭔지 궁금하시다면 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/apply&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MDN Web Docs&lt;/a&gt;로...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 또 함수 객체는 일반 객체한테는 없는 프로퍼티들이 있어요. `arguments, caller, length, name, prototype`이 있습니다. 하나씩 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;arguments&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arguments는 iterable한 유사 배열 객체입니다. 이게 무슨 말이냐, for...of로 순회 가능하다는 말입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781607718507&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function foo() {
  for (const argument of arguments) {
    // ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 어디까지나 &quot;유사&quot; 배열이기 때문에 slice같은 배열 메서드는 사용할 수 없습니다. 꼭 사용하고 싶으면 Array.prototype.slice.call()을 사용해서 간접 호출해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arguments 객체는 함수 호출 시 전달된 인수들의 정보를 담고 있습니다. 이들은 함수 내부에서 지역 변수처럼 사용됩니다. (함수 외부에서는 참조 불가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arguments[0] arguments[1] 처럼 참조 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재밌는 사실은 함수에 선언된 매개변수의 개수보다 인수를 적게 전달했을 때, 초과된 인수들이 버려지지 않고 arguments 객체에 들어간다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 성질을 이용해서 매개변수 개수가 정해지지 않은 함수(가변 인자 함수)를 구현하는 것도 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781607876839&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function sum() {
  let result = 0;
  for (const argument of arguments) {
    result += argument;
  }
  return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6에서는 arguments 객체 대신 Rest 파라미터 방식을 써서 좀 더 깔끔하게 구현할 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781607921128&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function sum(...args) {
  return args.reduce((acc, cur) =&amp;gt; acc + cur, 0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;caller&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 자신을 호출한 함수를 가리킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세히 알아보지는 않겠습니다. 어차피 비표준이라서요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;length&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수의 개수를 가리킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의점! 함수의 length와 함수 arguments 객체의 length는 다를 수 있답니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781608199668&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function foo(a, b, c) {
  return arguments.length;
}

console.log(foo.length); // 매개변수의 개수가 나옵니다. 3
console.log(foo(1, 2, 3, 4, 5)); // 전달한 인수의 개수가 나옵니다. 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;name&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 이름을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781608299565&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function hello() {}
console.log(hello.name); // &quot;hello&quot;

const world = function() {}
console.log(world.name); // ES5 이전에는 &quot;&quot;. ES6 이후에는 &quot;world&quot;.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;prototype&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prototype 프로퍼티는 모든 함수의 프로퍼티는 아니고, 생성자 함수로 쓰일 수 있는 함수만 가지는 프로퍼티입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 constructor 만이 소유하는 프로퍼티입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 함수가 생성하는 인스턴스의 프로토타입 객체를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토타입은 이 글의 범위를 벗어나므로 다음 글에서 더 자세히 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무에 적용해보자! 리빙 포인트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에 나온 개념은 실무에 거의 사용되지 않습니다. 하나만 빼구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 일급 객체로 사용하는 것은 자바스크립트 실무에서 굉장히 많이 쓰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 매개변수로 받는 메서드들이 많이 있죠. 위에서 본 reduce만 해도 그렇구요. map 같은 것도 함수를 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저의 addEventListener() 메서드도 함수를 받는 대표적인 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781608654669&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 웹페이지에서 클릭 이벤트가 발생할때마다 count가 올라갑니다.
// 두번째 인자로 함수를 받고 있죠!
let count = 0;
function addCount() {
  count++;
}
document.addEventListener(&quot;click&quot;, addCount);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/53</guid>
      <comments>https://prodyou.tistory.com/53#entry53comment</comments>
      <pubDate>Tue, 16 Jun 2026 20:19:41 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트의 함수를 잘못 사용하는 방법</title>
      <link>https://prodyou.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 모든 함수는 호출할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연한 얘기죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수는 호출하라고 있는 것 아닌가요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 호출하면 안되는 함수도 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 무슨 소릴까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생성자 함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에는 생성자 함수라는 개념이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781003280082&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Dog(name) {
  this.name = name;
  this.bark = function() {
    console.log(name + &quot;(이)가 짖었습니다! 월월&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 함수는 생성자 함수로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 함수를 쓰는 표준적인 방법은 new 키워드를 사용해서 인스턴스를 생성하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드처럼 말입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781003415672&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Dog(name) {
  this.name = name;
  this.bark = function() {
    console.log(name + &quot;(이)가 짖었습니다! 월월&quot;);
  }
}

const peanut = new Dog(&quot;땅콩&quot;);
peanut.bark(); // 땅콩(이)가 짖었습니다! 월월

const choco = new Dog(&quot;초코&quot;);
choco.bark(); // 초코(이)가 짖었습니다! 월월&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 생성자 함수는, 함수이기 때문에 그냥 호출하는 것도 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781003583293&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Dog(name) {
  this.name = name;
  this.bark = function() {
    console.log(name + &quot;(이)가 짖었습니다! 월월&quot;);
  }
}

const moongchi = Dog(&quot;뭉치&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 호출하면 어떻게 될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무서운 일이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;this가 함수를 호출한 맥락(컨텍스트)에 따라 바인딩됩니다. 전역에서 호출했다면 전역 객체에 강아지 이름이 등록되는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781003908607&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Dog(name) {
  this.name = name;
  this.bark = function() {
    console.log(name + &quot;(이)가 짖었습니다! 월월&quot;);
  }
}

const moongchi = Dog(&quot;뭉치&quot;); // 애초에 moongchi에는 undefined가 반환됩니다.

console.log(name); // &quot;뭉치&quot;
console.log(moongchi.name); // TypeError: Cannot read properties of undefined (reading 'name')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짜잔! 여기까지가 자바스크립트의 함수를 잘못 사용하는 방법이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생성자 함수와 일반 함수가 문법적으로 같은 모양을 하게 된 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 끝내기엔 아쉬우니 이유나 좀 더 설명해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;앞에서 봤듯이, &lt;/span&gt;&lt;span&gt;문법적으로는 일반 함수나 생성자 함수나 똑같이 생겼어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;function Dog(name) {
  this.name = name;
}

// 일반 함수처럼 호출도 가능하고, new 키워드를 쓰면 생성자로 사용하는 것도 가능하다.
Dog(&quot;뭉치&quot;);
new Dog(&quot;뭉치&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;`new`&lt;/span&gt;&lt;span&gt;를 붙여 호출하면 자바스크립트 엔진은 내부 메서드 `construct`를 호출해서 새로운 객체를 만들고, 그 객체를 함수 내부의 &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;로 바인딩합니다. 그리고 특별히 다른 객체를 반환하지 않는 한, 그 새 객체를 결과로 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;반대로 &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; 없이 호출하면 그냥 일반 함수 호출입니다. 이때 &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;는 함수가 호출된 방식에 따라 결정됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;왜 이렇게 되었는지 이유를 알려면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;자바스크립트의 초기 설계를 봐야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;자바스크립트는 클래스 기반 언어로 시작하지 않았습니다. 내부적으로는 프로토타입 기반 객체 모델을 사용했습니다. 객체는 다른 객체를 프로토타입으로 참조하고, 그 연결을 따라 속성과 메서드를 찾습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 겉으로는 Java와 비슷해 보이는 문법이 필요했습니다. 그 당시에 유행하던 Java 때문에, Netscape 경영진이 마케팅 목적에서 Java 스타일을 원했기 때문이에요...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그래서 &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;가 아닌 &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt;과 &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;를 이용해 객체를 생성하는 방식이 만들어졌습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;결과적으로 하나의 함수가 두 역할을 갖게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;자바스크립트의 함수를 잘못 사용하지 않는 방법&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;현대 자바스크립트에서는 생성자 함수보다 &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; 문법을 사용하는 편이 좋습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;class Dog {
  constructor(name) {
    this.name = name;
  }

  bark() {
    console.log(this.name + &quot;(이)가 짖었습니다! 월월&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이렇게 작성하면 의도가 분명해집니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;const peanut = new Dog(&quot;땅콩&quot;);
peanut.bark();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그리고 &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;는 일반 함수처럼 호출할 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;Dog(&quot;뭉치&quot;); // TypeError&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이것이 중요한 차이입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;생성자 함수는 &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; 없이 호출해도 문법 오류가 아닙니다. 하지만 &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;는 &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; 없이 호출하면 바로 오류가 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;실수를 더 빨리 발견할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이미 생성자 함수를 사용해야 하는 코드라면 &lt;/span&gt;&lt;span&gt;new.target&lt;/span&gt;&lt;span&gt;을 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Dog(name) {
  if (!new.target) {
    throw new TypeError(&quot;Dog must be called with new&quot;);
  }

  this.name = name;
  this.bark = function() {
    console.log(name + &quot;(이)가 짖었습니다! 월월&quot;);
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;new.target&lt;/span&gt;&lt;span&gt;은 함수가 &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;로 호출되었는지 확인할 수 있는 문법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; 없이 호출되면 &lt;/span&gt;&lt;span&gt;new.target&lt;/span&gt;&lt;span&gt;은 &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;가 됩니다. 그래서 잘못된 호출을 직접 막을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Dog(&quot;뭉치&quot;);      // TypeError
new Dog(&quot;뭉치&quot;);  // 정상 동작&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또 하나의 방법은 네이밍 규칙을 지키는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;자바스크립트에서는 생성자 함수 이름을 대문자로 시작하는 관례가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;function Dog(name) {
  this.name = name;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;반대로 일반 함수는 소문자로 시작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;function createDog(name) {
  return {
    name,
    bark() {
      console.log(name + &quot;(이)가 짖었습니다! 월월&quot;);
    }
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;물론 네이밍 규칙은 문법적 보호 장치가 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 코드를 읽는 사람에게 의도를 전달하는 데 도움이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;생성자가 필요하지 않다면 팩토리 함수를 사용하는 방법도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function createDog(name) {
  return {
    name,
    bark() {
      console.log(name + &quot;(이)가 짖었습니다! 월월&quot;);
    }
  };
}

const moongchi = createDog(&quot;뭉치&quot;);
moongchi.bark();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 방식은 &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;가 필요 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;함수는 객체를 직접 만들어 반환합니다. 그래서 생성자 함수처럼 &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; 바인딩에 의존하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;정리하면 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;새로운 객체 타입을 정의해야 한다면 &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;class Dog {
  constructor(name) {
    this.name = name;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;단순히 객체를 만들어 반환하면 된다면 팩토리 함수를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;function createDog(name) {
  return { name };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존 생성자 함수를 유지해야 한다면 &lt;/span&gt;&lt;span&gt;new.target&lt;/span&gt;&lt;span&gt;으로 잘못된 호출을 막습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;function Dog(name) {
  if (!new.target) {
    throw new TypeError(&quot;Dog must be called with new&quot;);
  }

  this.name = name;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;자바스크립트의 함수는 강력합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 그 강력함 때문에 하나의 문법이 여러 의미를 가질 때가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;생성자 함수가 대표적인 예입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;함수처럼 생겼지만, 함수처럼 호출하면 안 되는 경우가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그래서 현대 자바스크립트에서는 의도를 문법으로 드러내는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;생성자는 &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;로 작성하고, 일반적인 객체 생성은 팩토리 함수로 작성하는 편이 더 안전합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그렇게 하면 함수가 잘못 호출되는 일을 줄일 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/52</guid>
      <comments>https://prodyou.tistory.com/52#entry52comment</comments>
      <pubDate>Tue, 9 Jun 2026 20:34:15 +0900</pubDate>
    </item>
    <item>
      <title>프로퍼티 어트리뷰트가 뭔가요?</title>
      <link>https://prodyou.tistory.com/51</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;df50d41e-b2fa-44be-91aa-47cdf81c6b66.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o3my4/dJMcacQP13O/V5IjcW3KXprfpEmIcIb17K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o3my4/dJMcacQP13O/V5IjcW3KXprfpEmIcIb17K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o3my4/dJMcacQP13O/V5IjcW3KXprfpEmIcIb17K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo3my4%2FdJMcacQP13O%2FV5IjcW3KXprfpEmIcIb17K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;340&quot; height=&quot;255&quot; data-filename=&quot;df50d41e-b2fa-44be-91aa-47cdf81c6b66.png&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에는 프로퍼티 어트리뷰트(Property Attribute)라는 것이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 그냥 보면 이해하기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향 프로그래밍을 먼저 이해해야 프로퍼티 어트리뷰트가 왜 존재하는 것인지, 어떤 역할을 하는건지 감을 잡을 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체지향 프로그래밍&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OOP(Object Oriented Programming)라고도 불리는 객체지향 프로그래밍은 소프트웨어 개발 방식의 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 것을 객체로 보고, 그 객체들이 서로 협력하며 프로그램을 이루어냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향이 뭐가 좋냐, 하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실 세계의 개념을 그대로 코드로 가져오기가 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쇼핑몰 웹사이트를 만들어야 한다고 생각해볼까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실 세계의 경우를 먼저 생각해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소비자가 쇼핑몰에 가서 마음에 드는 상품을 카트에 담고 카운터로 가서 계산하겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 객체지향 방식으로 구현한다고 하면, 아래와 같은 객체들이 나올 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780397361210&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;User         - 소비자
ShoppingMall - 쇼핑몰
Product      - 상품
Cart         - 카트
Order        - 주문하는 곳(카운터)
Payment      - 결제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 역할에 맞게 객체를 나누면, 현실에서의 행동 흐름과 비슷한 방식으로 코드를 쓸 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780397631555&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;shoppingMall.carts = [new Cart(), new Cart()];    // 쇼핑몰에 카트를 비치합니다
shoppingMall.orders = [new Order(), new Order()]; // 쇼핑몰에 카운터도 비치합니다
shoppingMall.orders.forEach((order) =&amp;gt; {
  order.payment = new Payment(); // 카운터마다 포스기도 하나씩 놓습니다
});

user.cart = shoppingMall.carts[0]; // 소비자가 쇼핑몰에서 카트를 하나 가져갑니다
user.cart.products.push(product);  // 소비자가 카트에 제품 하나를 담습니다

shoppingMall.orders[0].makeOrder(user.cart.products); // 소비자 카트에 담긴 것들을 0번 카운터에 가져가서 주문을 생성합니다
shoppingMall.orders[0].payment.pay(user.account); // 계산합니다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 이런 식으로 쓸 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 객체가 현실 세계의 물건(또는 사람) 하나와 매핑되기 때문에, 그 역할을 나누기가 쉽고, 코드를 이해하기도 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 객체지향 프로그래밍 방식이 유행하고 발전하면서, 이런 코드 작성 방식을 더욱 단단하게 만들기 위한 개념들이 등장했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서도 캡슐화라는 개념이 있는데, 이게 자바스크립트의 프로퍼티 어트리뷰트와 개념적으로 되게 비슷해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;캡슐화란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캡슐이 어떻게 생겼죠? 단단하게 보호막으로 싸여서, 외부에서 내부를 들여다보거나 사용할 수 없도록 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까 캡슐화는 어떤 객체의 프로퍼티와 메서드에 직접 접근을 막고, 내부와 외부를 분리하는 설계라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캡슐화를 하게 되면 해당 객체 내부에서만 쓰는 프로퍼티와 외부에 드러내는 프로퍼티를 분리할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 어떤 장점이 생기냐, 여러 장점이 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서도 코드의 유지보수성이 증가한다는 장점이 있습니다. 왜냐!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부에서만 쓰는 프로퍼티는 외부에 의해 변경되지 않는다는 보장이 되죠. 그래서 우리는 캡슐화된 내부 프로퍼티를 다룰 때 외부에 의한 변경을 신경쓸 필요가 없습니다. 인지적 부하가 감소하니까 코드를 변경하기가 더 쉽겠죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로퍼티 어트리뷰트와 캡슐화의 연관성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 글의 제목! 프로퍼티 어트리뷰트는 도대체 뭐고, 캡슐화랑은 무슨 연관이 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티 어트리뷰트와 캡슐화는 &amp;ldquo;객체의 프로퍼티를 어떻게 다룰 수 있게 할 것인가&amp;rdquo;라는 점에서 연결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캡슐화가 객체의 내부와 외부를 나누는 설계라면, 프로퍼티 어트리뷰트는 자바스크립트에서 프로퍼티의 동작 방식을 세밀하게 정하는 장치입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 무슨 말이냐,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 프로퍼티는 외부에서 값을 바꾸지 못하게 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 프로퍼티는 반복문에 노출되지 않게 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 프로퍼티는 삭제하거나 재정의하지 못하게 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전한 private 접근 제어와 같지는 않지만, 객체의 내부 구조를 보호하고 외부에 드러낼 부분을 조절한다는 점에서 캡슐화와 비슷한 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;프로퍼티 어트리뷰트란?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;프로퍼티 어트리뷰트는 자바스크립트 객체의 프로퍼티가 가지고 있는 내부 설정값입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 어떤 객체에 &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;이라는 프로퍼티가 있다고 해봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const user = {
  name: &quot;woni&quot;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;우리가 보기에는 &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;이라는 키와 &lt;/span&gt;&lt;span&gt;&quot;woni&quot;&lt;/span&gt;&lt;span&gt;라는 값만 있는 것처럼 보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 자바스크립트 내부에서는 이 프로퍼티를 더 자세히 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;{
  value: &quot;woni&quot;,
  writable: true,
  enumerable: true,
  configurable: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;여기서 &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;는 실제 값입니다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;writable&lt;/span&gt;&lt;span&gt;은 값을 바꿀 수 있는지 정합니다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;enumerable&lt;/span&gt;&lt;span&gt;은 반복문이나 &lt;/span&gt;&lt;span&gt;Object.keys()&lt;/span&gt;&lt;span&gt; 같은 곳에 노출되는지 정합니다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;configurable&lt;/span&gt;&lt;span&gt;은 프로퍼티를 삭제하거나 설정을 다시 바꿀 수 있는지 정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이런 설정값을 직접 확인하고 싶으면 &lt;/span&gt;&lt;span&gt;Object.getOwnPropertyDescriptor()&lt;/span&gt;&lt;span&gt;를 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const user = {
  name: &quot;woni&quot;
};

console.log(Object.getOwnPropertyDescriptor(user, &quot;name&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또 직접 설정하고 싶으면 &lt;/span&gt;&lt;span&gt;Object.defineProperty()&lt;/span&gt;&lt;span&gt;를 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const user = {};

Object.defineProperty(user, &quot;name&quot;, {
  value: &quot;woni&quot;,
  writable: false,
  enumerable: true,
  configurable: false
});

user.name = &quot;minami&quot;;

console.log(user.name); // &quot;woni&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위 코드에서 &lt;/span&gt;&lt;span&gt;writable&lt;/span&gt;&lt;span&gt;을 &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;로 설정했기 때문에 &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; 값은 바뀌지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉 프로퍼티 어트리뷰트는 객체의 프로퍼티를 그냥 값으로만 보는 것이 아니라, 그 값이 어떻게 읽히고, 변경되고, 노출되고, 삭제될 수 있는지까지 정하는 설정이라고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;자바스크립트 객체가 단순한 key-value 묶음이 아니죠?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;내부적으로 꽤 정교한 규칙을 가지고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그리고 그 덕분에 객체지향의 캡슐화 비슷한 기능들.. 을 보장하게 되었다. 라고 이해하면 무리가 없을 것 같아요.&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/51</guid>
      <comments>https://prodyou.tistory.com/51#entry51comment</comments>
      <pubDate>Tue, 2 Jun 2026 20:35:34 +0900</pubDate>
    </item>
    <item>
      <title>const를 써라. let을 써야 할 것 같다고? 그럼 다시 한 번 생각해보고 const를 써라.</title>
      <link>https://prodyou.tistory.com/50</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwyVvs/dJMcaglkhvK/USa8gkURZcf5W26ODtB740/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwyVvs/dJMcaglkhvK/USa8gkURZcf5W26ODtB740/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwyVvs/dJMcaglkhvK/USa8gkURZcf5W26ODtB740/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwyVvs%2FdJMcaglkhvK%2FUSa8gkURZcf5W26ODtB740%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;240&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;589&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 변수를 선언하기 위해 사용하는 키워드는 세 가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var, const, let 요렇게 세 가지죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001766445&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;모던&amp;nbsp;자바스크립트&amp;nbsp;Deep&amp;nbsp;Dive&lt;/a&gt; 의 책 내용에도 그렇고, 우리 주변 개발자들의 얘기를 들어봐도 var는 이제 쓰지 말라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 let 보다는 가능하면 const를 쓰라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그런걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;var는 쓰지 마라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var는 구식 문법입니다. ES6 이전의 버전에서는 변수를 선언하는 방법이 var 밖에 없었기 때문에 var를 썼지만, 이제는 완벽한 상위 호환인 const, let이 있기 때문에 var 를 사용할 이유가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var가 발생시킬 수 있는 문제점들엔 어떤 것들이 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우선 var는 변수를 중복으로 선언해도 에러가 발생하지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 상황이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779794985853&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 앨범 가격을 반환하는 함수
function getAlbumPrice(price) {
  return Math.floor(price * (1 - discountRate));
}

var discountRate = 0.2;

// ...
// 코드가 엄청나게 길어지는 바람에, 백엔드 개발자 카리나는
// discountRate가 이미 선언한 전역 변수라는 사실을 까맣게 잊고 말았습니다.

var discountRate = 0.5;

// 배포가 이뤄진 다음 날, 개발 총괄 이수만 선생님이 길길이 날뛰며 카리나를 찾아왔습니다.
// &quot;어떤 자식이 신규 앨범에 할인율 50%를 적용했어!!&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(var를 중복 선언했다는 사실과 별개로 위의 코드 자체가 정말 별로지만, 예시를 들기 위한 코드니 이해하고 넘어갑시다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 무서운 상황이죠? const나 let을 쓰면 이런 문제를 애초에 방지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저렇게 중복 선언하려고 하면 SyntaxError를 발생시키기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아예 첫 실행 시점에서 에러가 발생해버리니, 문제를 조기에 식별할 수 있죠! (만약 ESLint를 쓴다면 IDE에서 아예 코드 편집할 때 에러를 내버리는 것도 가능합니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리고 var 키워드는 함수 레벨 스코프만 지원하기 때문에, 블록이 중첩되어 있는 코드에서 변수를 잘못 사용하기 쉽습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779795018841&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function getPrice(isMember) {
  const price = 10000;

  // isMember가 false인 경우, 아래 코드가 실행되지 않으므로
  // discountRate는 undefined로 남아있게 됩니다!
  if (isMember) {
    var discountRate = 0.1;
  }

  return price * (1 - discountRate);
}

console.log(getPrice(true)); // 9000
console.log(getPrice(false)); // NaN&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;또한 변수 호이스팅이 가능하기 때문에, 변수 선언문 이전에 변수를 실수로 사용할 수도 있습니다. 그래도 에러가 발생하지 않죠!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779795058791&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(message); // undefined

var message = &quot;안녕하세요&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const와 let을 사용할 이유는 충분한 것 같습니다. 위의 문제들을 모두 조기에 방지해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 왜 let보다는 const를 쓰라고 하는 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;const를 써라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const는 상수를 선언할 때 사용하는 키워드입니다. 이름부터가 상수(constant)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수는 변하지 않는 값이죠. 이름 자체에 힌트가 있다고 볼 수 있겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const 키워드를 사용해서 선언했다는 것은 해당 변수가 앞으로 변하지 않는다는 사실을 보장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그러니 의도치 않은 재할당을 막을 수 있어요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에 의해 실수를 막을 수 있다! 우리가 코딩을 하는 이유 아니겠어요? 어떤 일을 컴퓨터에게 맡겨서 100%의 정확도를 보장할 수 있다면 당장 맡기지 않을 이유가 없죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 그런데 let을 써야할 것 같은 상황이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 변수가 항상 상수처럼 변하지 않을 수 있겠어요? 애초에 변할 수 있다고 해서 변수(variable) 아니겠어요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 아래와 같은 상황에는 let을 쓰는 것이 아주 합리적으로 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779795072482&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];

let sum = 0;

for (const number of numbers) {
  sum += number;
}

console.log(sum); // 15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 let을 쓰지 않아도 되는 상황일 수 있어요! 예를 들면, 아래와 같이 코드를 좀 바꿔 보면 어떨까요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779795081036&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce((total, number) =&amp;gt; {
  return total + number;
}, 0);

console.log(sum); // 15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript 내장 함수를 이용했더니, let 변수를 쓰지 않아도 코드가 완성되었네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 동작이 보장된 함수를 이용해서 실수를 줄이는 것도 좋은 코딩 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 상황에 딱 맞는 내장 함수가 없다고 하더라도, 커스텀 유틸 함수를 만들어서 분리해서 사용할 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 let이 나쁘다는 건 아닙니다. 아래처럼 명확하고 작은 범위의 코드에서는 let을 써도 나쁘지 않아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1779795088632&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function getAlbumPrice(price, hasCoupon) {
  let discountRate = 0.2; // 기본 할인율

  if (hasCoupon) {
    discountRate = 0.5;
  }

  return price * (1 - discountRate);
}

getAlbumPrice(10000, true); // 5000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(여전히 상수 DISCOUNT_RATE와 함께 early return을 하거나, 아예 할인율 자체를 이 함수에서 관리하지 않도록 빼는 것이 좋아 보이지만, 예시 코드니 일단 넘어갑시다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 코드가 길어지거나, 스코프가 너무 넓어지기 시작하면 인간은 실수를 할 수 있겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const를 쓰면 이 실수를 없앨 수 있으니까, let을 사용하기 전에 조금 고민해보는 것은 좋은 습관인 것 같아요.&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/50</guid>
      <comments>https://prodyou.tistory.com/50#entry50comment</comments>
      <pubDate>Tue, 26 May 2026 20:32:49 +0900</pubDate>
    </item>
    <item>
      <title>재귀 함수 왜 써요? 함수의 return과 매개변수, 스코프 200% 활용하기</title>
      <link>https://prodyou.tistory.com/48</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7sPvu/dJMcahLaPs4/rKfu4FtDLOXamatSAucnlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7sPvu/dJMcahLaPs4/rKfu4FtDLOXamatSAucnlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7sPvu/dJMcahLaPs4/rKfu4FtDLOXamatSAucnlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7sPvu%2FdJMcahLaPs4%2FrKfu4FtDLOXamatSAucnlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;1086&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 Deep Dive의 12장 함수 파트를 읽어보면, 자바스크립트에 관한 내용도 물론 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함수&lt;/b&gt;라는 프로그래밍 언어 본질적인 녀석에 대한 설명을 많이 마주하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 나오는 재귀 함수라는 개념을 살펴보고, 이것이 왜 좋은지! 그리고 어떨때 써야 하는지를 간단하게 정리해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자기 자신을 반복하는 함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 종료되어버린 알고리즘 사이트 백준에는 &lt;b&gt;재귀함수가 뭔가요?&lt;/b&gt; 라는 문제가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehArcN/dJMcabYuPTw/rPC3YKpppcDtFOcK7wBayK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehArcN/dJMcabYuPTw/rPC3YKpppcDtFOcK7wBayK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehArcN/dJMcabYuPTw/rPC3YKpppcDtFOcK7wBayK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FehArcN%2FdJMcabYuPTw%2FrPC3YKpppcDtFOcK7wBayK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1490&quot; height=&quot;900&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1092&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xiobq/dJMcacpyWf7/KESoUhYXJhHKotpMvZxnKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xiobq/dJMcacpyWf7/KESoUhYXJhHKotpMvZxnKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xiobq/dJMcacpyWf7/KESoUhYXJhHKotpMvZxnKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxiobq%2FdJMcacpyWf7%2FKESoUhYXJhHKotpMvZxnKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1500&quot; height=&quot;1092&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1092&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 출력 예제처럼, 뭔가 반복되는 구조로 답을 출력해야 하는 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 프로그래밍을 배울때, 반복되는 것들을 다루기 위한 방법으로 가장 처음 배우는 것은 반복문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문은 쉽고 단순하고 강력하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사실 위의 문제는 반복문으로도 풀 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1778587341071&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(n) {
  console.log('어느 한 컴퓨터공학과 학생이 유명한 교수님을 찾아가 물었다.');

  for (let i = 0; i &amp;lt; n; i++) {
    const prefix = '____'.repeat(i);
    console.log(prefix + `&quot;재귀함수가 뭔가요?&quot;`);
    console.log(prefix + `&quot;잘 들어보게. 옛날옛날 한 산 꼭대기에 이세상 모든 지식을 통달한 선인이 있었어.`);
    console.log(prefix + `마을 사람들은 모두 그 선인에게 수많은 질문을 했고, 모두 지혜롭게 대답해 주었지.`);
    console.log(prefix + `그의 답은 대부분 옳았다고 하네. 그런데 어느 날, 그 선인에게 한 선비가 찾아와서 물었어.&quot;`);
  }
  
  const prefix = '____'.repeat(n);
  console.log(prefix + `&quot;재귀함수가 뭔가요?&quot;`);
  console.log(prefix + `&quot;재귀함수는 자기 자신을 호출하는 함수라네&quot;`);
  console.log(prefix + `라고 답변하였지.`);

  for (let i = n - 1; i &amp;gt;= 0; i--) {
    const prefix = '____'.repeat(i);
    console.log(prefix + '라고 답변하였지.');
  }
}

solution(4);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 뭔가 비효율적인 기분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 더 단순하게 풀어낼 수 있을 것만 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 재귀함수를 이용하여 풀어내면 아래와 같아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1778587507921&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(n) {
  console.log('어느 한 컴퓨터공학과 학생이 유명한 교수님을 찾아가 물었다.');

  function recursion(depth) {
    const prefix = '____'.repeat(depth);

    console.log(prefix + `&quot;재귀함수가 뭔가요?&quot;`);

    if (depth === n) {
      console.log(prefix + `&quot;재귀함수는 자기 자신을 호출하는 함수라네&quot;`);
      console.log(prefix + `라고 답변하였지.`);
      return;
    }

    console.log(prefix + `&quot;잘 들어보게. 옛날옛날 한 산 꼭대기에 이세상 모든 지식을 통달한 선인이 있었어.`);
    console.log(prefix + `마을 사람들은 모두 그 선인에게 수많은 질문을 했고, 모두 지혜롭게 대답해 주었지.`);
    console.log(prefix + `그의 답은 대부분 옳았다고 하네. 그런데 어느 날, 그 선인에게 한 선비가 찾아와서 물었어.&quot;`);

    recursion(depth + 1);

    console.log(prefix + `라고 답변하였지.`);
  }

  recursion(0);
}

solution(4);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 `recursion(0)` 으로 함수를 호출해서, 깊이가 n이 될때까지 그 내부에 있는 `recursion(depth + 1);` 을 호출하는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안타깝게도 더 복잡해진 것 같네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네, 그렇습니다. 이 문제는 사실 반복문으로 푸는 것이 재귀를 이용하는 방법보다 읽기 쉽고 구현도 간단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀를 아무데나 쓰면 안 된다는 교훈(?)같은 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;재귀함수가 읽기 어려운 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제에서 본 것처럼 재귀함수는 종종 코드 줄 수가 비슷하거나 더 짧은데도 불구하고 읽기는 더 어렵게 느껴집니다. 왜 그럴까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 이유는 &lt;b&gt;우리가 코드를 읽는 방식&lt;/b&gt;과 &lt;b&gt;재귀가 실행되는 방식&lt;/b&gt;이 다르기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 코드를 위에서 아래로 순서대로 읽습니다. 반복문도 그 흐름을 그대로 따라가요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`for (let i = 0; i &amp;lt; n; i++)` 라는 반복문 코드는 i가 0에서 n-1까지 반복되며 위에서 아래로 순서대로 한 번씩 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머릿속에 잘 그려지죠!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 재귀는 다릅니다. 위의 예제에서 이 부분을 다시 살펴봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1778588811734&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function recursion(depth) {
  const prefix = '____'.repeat(depth);
  console.log(prefix + `&quot;재귀함수가 뭔가요?&quot;`);
  if (depth === n) {
    // ...종료 조건
    return;
  }
  // ...중간 출력들
  recursion(depth + 1);                  // &amp;larr; 자기 자신 호출
  console.log(prefix + `라고 답변하였지.`); // &amp;larr; ??? 얘는 언제 실행되는 거지?
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 `console.log(prefix + '라고 답변하였지.');`는 도대체 언제 실행될까요?&lt;br /&gt;`recursion(depth + 1)`이 호출되고, 그 안에서 또 호출되고, 또 호출되고... `depth === n`이 되어 종료 조건을 만나야 비로소 &lt;b&gt;거꾸로 돌아오면서&lt;/b&gt; 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1778588857974&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;recursion(0) 호출
  ├─ &quot;재귀함수가 뭔가요?&quot; 출력
  ├─ recursion(1) 호출
  │   ├─ &quot;재귀함수가 뭔가요?&quot; 출력
  │   ├─ recursion(2) 호출
  │   │   ├─ ... (계속 깊어짐)
  │   │   └─ 종료 조건 도달
  │   └─ &quot;라고 답변하였지.&quot; 출력  &amp;larr; 여기서 실행!
  └─ &quot;라고 답변하였지.&quot; 출력      &amp;larr; 그 다음 여기서 실행!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그니까 재귀함수는 &lt;b&gt;깊이 파고 들어갔다가 다시 거슬러 올라오는&lt;/b&gt; 흐름을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요게 머릿속에 안 그려지면 코드 흐름을 따라가면서 읽기가 정말 어려워요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 개발을 처음 접하면 많이 헤매고는 합니다 (저도 그랬구요..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;return, 매개변수, 스코프를 이용해서 재귀함수 200% 활용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 &quot;재귀함수가 뭔가요?&quot; 예제에서는 사실 재귀의 강점이 잘 드러나지 않았어요. 단순히 들여쓰기 깊이를 늘리고 줄이는 정도라서, 반복문으로도 충분히 깔끔하게 해결되는 문제였거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀가 진짜로 빛을 발하는 순간은 다음 세 가지를 함께 활용할 때입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 매개변수: 상태를 깊이 따라 내려보낸다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제에서 depth가 그런 역할을 했어요. 함수가 호출될 때마다 depth + 1을 넘겨주면서, &quot;지금 몇 번째 깊이에 있는지&quot;를 매번 자연스럽게 전달했죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 반복문으로 비슷한 동작을 하려면 별도의 변수를 외부에서 관리해야 하지만, 재귀에서는 그냥 인자로 넘기면 끝입니다. 상태를 호출 스택을 따라 함께 끌고 가는 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1778589066687&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function traverse(node, depth) {
  console.log(' '.repeat(depth * 2) + node.name);
  for (const child of node.children) {
    traverse(child, depth + 1); // depth가 자연스럽게 따라 내려감
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. return: 결과를 거꾸로 쌓아 올린다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀의 진짜 힘은 return에서 나옵니다. 깊이 내려갔다가 거꾸로 돌아오면서, 각 단계의 결과를 모아 위로 올려보낼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 고전적인 예시는 팩토리얼입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1778589100987&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function factorial(n) {
  if (n &amp;lt;= 1) return 1;          // 종료 조건
  return n * factorial(n - 1);   // 자기 자신을 호출한 결과를 활용
}
factorial(5); // 120&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;factorial(5)는 factorial(4)의 결과가 필요하고, 그건 또 factorial(3)의 결과가 필요하고... 이렇게 깊이 내려갔다가 factorial(1)이 1을 반환하는 순간부터 위로 올라오면서 결과가 누적됩니다. 5 * 4 * 3 * 2 * 1이라는 곱셈이 거꾸로 쌓이는 거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 반복문으로도 짤 수 있지만, &lt;b&gt;문제 자체가 &quot;자기 자신으로 정의되는&quot; 구조&lt;/b&gt;라면 재귀가 압도적으로 깔끔하게 표현됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 스코프: 각 호출은 자기만의 세계를 가진다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 호출될 때마다 &lt;b&gt;새로운 실행 컨텍스트&lt;/b&gt;가 생성됩니다. 즉, 각 호출마다 독립된 지역 스코프를 갖죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1778589125395&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function recursion(depth) {
  const prefix = '____'.repeat(depth); // 호출마다 새로 만들어지는 지역 변수
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recursion(0)의 prefix와 recursion(1)의 prefix는 완전히 다른 변수예요. 서로 간섭하지 않습니다. 너무 당연한 얘기처럼 들리지만, 재귀에서는 이 특성이 정말 중요해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 스코프가 없어서 변수가 공유됐다면, 깊이 들어갔다가 돌아올 때 값이 덮어써져서 엉망이 됐을 겁니다. &lt;b&gt;호출 스택과 지역 스코프가 함께 작동하기 때문에&lt;/b&gt;, 우리는 &quot;지금 이 호출에서의 값&quot;을 다른 호출과 헷갈리지 않고 안전하게 다룰 수 있어요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세 가지가 합쳐지면: 깊이를 알 수 없는 구조 다루기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매개변수, return, 스코프 이 세 가지가 합쳐지면, 반복문으로는 처리하기 까다로운 문제도 우아하게 풀어낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예가 &lt;b&gt;중첩된 자료구조&lt;/b&gt;예요. 얼마나 깊이 중첩되어 있을지 미리 알 수 없는 경우죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1778589155341&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 깊이가 정해지지 않은 중첩 배열의 모든 숫자 더하기
function sumNested(arr) {
  let sum = 0;
  for (const item of arr) {
    if (Array.isArray(item)) {
      sum += sumNested(item); // 재귀 호출 &amp;rarr; return으로 결과 합치기
    } else {
      sum += item;
    }
  }
  return sum;
}

sumNested([1, [2, [3, [4, [5]]]], 6]); // 21&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 반복문으로 짜려면 별도의 스택을 직접 만들어서 관리해야 합니다. 하지만 재귀로는 &lt;b&gt;호출 스택이 알아서 그 역할을 해주죠.&lt;/b&gt; 매개변수로 현재 배열을 넘기고, 스코프 덕분에 각 호출의 sum이 섞이지 않고, return으로 안쪽 결과를 바깥쪽으로 올려보냅니다. 세 가지가 동시에 일하고 있는 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 패턴이 잘 어울리는 사례들이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DOM 트리 순회&lt;/li&gt;
&lt;li&gt;파일 시스템 탐색&lt;/li&gt;
&lt;li&gt;JSON 객체 깊은 복사 (deep clone)&lt;/li&gt;
&lt;li&gt;트리/그래프 자료구조 다루기&lt;/li&gt;
&lt;li&gt;백트래킹 알고리즘&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통점은 &lt;b&gt;자료구조 자체가 재귀적&lt;/b&gt;이라는 거예요. 자기 자신과 똑같은 구조를 안에 또 품고 있는 형태죠. 이런 문제에는 자료구조의 모양과 코드의 모양이 자연스럽게 일치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리: 언제 재귀를 써야 할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 봤던 &quot;재귀함수가 뭔가요?&quot; 문제처럼 단순한 반복이라면 반복문이 훨씬 낫습니다. 읽기 쉽고, 함수 호출 비용도 없고, 디버깅도 편하니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀를 꺼내드는 게 자연스러운 때는 이런 경우입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문제 자체가 자기 자신으로 정의될 때 (팩토리얼, 피보나치)&lt;/li&gt;
&lt;li&gt;데이터 구조가 재귀적일 때 (트리, 중첩 객체)&lt;/li&gt;
&lt;li&gt;깊이를 미리 알 수 없을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 매개변수로 상태를 내려보내고, return으로 결과를 합쳐 올리고, 스코프로 각 호출의 독립성을 보장하면, 반복문으로는 흉내내기 힘든 우아한 코드가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀를 두려워하지 말고, 그렇다고 남용하지도 말고, 문제의 모양에 맞춰 적재적소에 활용해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/48</guid>
      <comments>https://prodyou.tistory.com/48#entry48comment</comments>
      <pubDate>Tue, 12 May 2026 21:36:16 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트의 함수 알아보기 - 1</title>
      <link>https://prodyou.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트도, 여타 프로그래밍 언어들과 마찬가지로 함수를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 함수의 주요한 특징들을 나열하면서, 궁금한 점들을 하나씩 알아보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함수의 본질적 특성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학에서 함수라는 것은 입력을 받으면 출력을 하는 일련의 과정을 정의한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서도 이와 비슷하게 동작합니다. 인수를 받아서 결과값을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 함수의 시작에서부터 결과값이 반환되기까지 그 사이에 있는 코드들을 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 함수는 한번 정의해 놓으면 여러 번 호출해서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;순수함수가 좋은 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순수함수라고 하면, 부수효과(side effect)가 없는, 같은 값을 넣으면 항상 같은 결과가 출력되며 외부의 상태가 변경되지 않는 함수를 가리킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로 일하다 보면 &quot;순수함수를 써야 한다&quot;같은 얘기를 듣게 됩니다. 왜 그런 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 본 함수의 본질적 특성에 따르면, 함수란 녀석은 보통 여러번 쓰기 위해 정의하는 녀석입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 사용할 때마다 결과가 달라지는 함수라면 코드의 결과를 예측하기 어렵겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 예측하기 위해 매번 그 함수 내부를 들여다보며 관련된 코드를 읽어야 할 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 순수함수는, 그 함수 내부에서 무슨 동작을 하는지 신경쓰지 않아도 결과를 예측할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 나중에 코드를 읽을 때, 매번 들여다보지 않아도 술술 읽어내려갈 수 있겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 순수함수로 만들 수 있으면 순수함수로 만드는 게 좋다고 하는 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한번에 하나의 일만 하는 함수가 좋은 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 한번에 하나의 일만 하면 일단 예측이 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에는 이름을 붙여야 합니다. 그래야 호출할 수 있으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 이름이 &quot;processUser()&quot;라면, 함수 이름만 가지고 그 함수가 유저를 어떻게 처리하는지 예측할 수 있는 사람은 없을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 &quot;validateUser()&quot;, &quot;saveUser()&quot; 같은 함수는 이름을 봤을때 대략 어떤 일을 할지 예측할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 버그가 발생했다든지 하는 여러 이유로 코드를 수정할 일이 생겼을 때, 수정할 부분을 찾기가 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 봤듯 함수 이름만 봐도 어느 정도 함수가 하는 역할을 파악할 수 있기 때문에, 코드에서 수정하지 않아도 되는 부분을 슉슉 제외할 수 있어요. 모든 라인을 읽을 필요가 없는거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;매개변수와 인수의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의할 때는 매개변수(parameter)라고 부르며, 호출할 때는 인수(argument)라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777463580659&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function greet(name) { // &amp;lt;- name은 매개변수입니다
  console.log('Hello, ' + name);
}

greet('John'); // &amp;lt;- 'John'은 인수입니다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로도 매개변수에 정의된 식별자는 함수 내에서 변수처럼 쓰일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함수는 선언한 위치에 따라 &quot;함수 선언문&quot;이 될 수도 있고 &quot;함수 리터럴 표현식&quot;이 될 수도 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 엔진이 코드를 해석하는 문맥에 따라 함수 선언문 또는 함수 리터럴 표현식이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를&amp;nbsp;들어&amp;nbsp;함수가&amp;nbsp;단독으로&amp;nbsp;작성되어&amp;nbsp;있으면&amp;nbsp;함수&amp;nbsp;선언문으로&amp;nbsp;해석됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777465601103&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function foo() {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만&amp;nbsp;함수가&amp;nbsp;변수에&amp;nbsp;할당되거나,&amp;nbsp;괄호&amp;nbsp;안에&amp;nbsp;들어가거나,&amp;nbsp;다른&amp;nbsp;표현식의&amp;nbsp;일부로&amp;nbsp;사용되면&amp;nbsp;함수&amp;nbsp;리터럴&amp;nbsp;표현식으로&amp;nbsp;해석됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777465614113&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const foo = function () {};

(function foo() {});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;즉,&amp;nbsp;같은&amp;nbsp;function&amp;nbsp;키워드를&amp;nbsp;사용하더라도&amp;nbsp;코드가&amp;nbsp;놓인&amp;nbsp;위치와&amp;nbsp;문맥에&amp;nbsp;따라&amp;nbsp;자바스크립트&amp;nbsp;엔진은&amp;nbsp;이를&amp;nbsp;다르게&amp;nbsp;해석합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자바스크립트에서 함수는 함수 이름으로 호출하는 것이 아니라, 함수 객체를 가리키는 식별자로 호출한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 함수 이름은 함수 몸체 내부에서 자기 자신을 참조할 때만 사용됩니다.&lt;br /&gt;실제로&amp;nbsp;함수를&amp;nbsp;호출할&amp;nbsp;때는&amp;nbsp;함수&amp;nbsp;객체가&amp;nbsp;할당된&amp;nbsp;식별자를&amp;nbsp;사용합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777465735053&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const foo = function bar() {
  console.log(&quot;hello&quot;);
};

foo(); // 가능
bar(); // ReferenceError&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위&amp;nbsp;예시에서&amp;nbsp;함수&amp;nbsp;이름은&amp;nbsp;bar이지만,&amp;nbsp;함수&amp;nbsp;객체를&amp;nbsp;가리키는&amp;nbsp;식별자는&amp;nbsp;foo입니다.&lt;br /&gt;따라서&amp;nbsp;외부에서는&amp;nbsp;foo()로&amp;nbsp;호출해야&amp;nbsp;합니다.&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/47</guid>
      <comments>https://prodyou.tistory.com/47#entry47comment</comments>
      <pubDate>Wed, 29 Apr 2026 21:30:45 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트 객체 때문에 에러를 겪는 개발자들을 위한 실무 안내서</title>
      <link>https://prodyou.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jjalbang_image_1.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FE7tg/dJMb9967eQu/iGYOsPkFpXorT0bdaYBK9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FE7tg/dJMb9967eQu/iGYOsPkFpXorT0bdaYBK9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FE7tg/dJMb9967eQu/iGYOsPkFpXorT0bdaYBK9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFE7tg%2FdJMb9967eQu%2FiGYOsPkFpXorT0bdaYBK9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;482&quot; data-filename=&quot;jjalbang_image_1.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거창하게 안내서라고 적어뒀지만 사실은 실무 팁같은 느낌의 글이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 객체는 잘못 다루기 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다루는 난이도가 높은 것에 비해 쓰기는 쉽죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 개발자들은 실무에서 React, Vue 같은 라이브러리, NestJS 같은 프레임워크가 제공하는 &lt;b&gt;높은 수준으로 추상화된 객체&lt;/b&gt;를 빈번하게 다뤄야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 흔히 발생하는 에러와 원인을 살펴보고, 에러를 방지하기 위해서는 어떤 마음가짐으로 객체를 다뤄야 하는지 정리해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JavaScript 객체의 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 에러 케이스를 알아보기 전에 JavaScript 객체의 본질적인 특징부터 알아볼까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- mutable (변경 가능)&lt;br /&gt;객체는&amp;nbsp;한&amp;nbsp;번&amp;nbsp;생성된&amp;nbsp;후에도&amp;nbsp;내부의&amp;nbsp;값을&amp;nbsp;자유롭게&amp;nbsp;바꿀&amp;nbsp;수&amp;nbsp;있어요.&lt;br /&gt;const로&amp;nbsp;선언해도&amp;nbsp;변수에&amp;nbsp;담긴&amp;nbsp;참조가&amp;nbsp;바뀌지&amp;nbsp;않을&amp;nbsp;뿐,&amp;nbsp;객체&amp;nbsp;내부의&amp;nbsp;프로퍼티는&amp;nbsp;얼마든지&amp;nbsp;수정할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- pass by reference (참조에 의한 전달)&lt;br /&gt;객체를&amp;nbsp;함수&amp;nbsp;인자로&amp;nbsp;넘기거나&amp;nbsp;다른&amp;nbsp;변수에&amp;nbsp;할당할&amp;nbsp;때,&amp;nbsp;값이&amp;nbsp;복사되는&amp;nbsp;게&amp;nbsp;아니라&amp;nbsp;같은&amp;nbsp;객체를&amp;nbsp;가리키는&amp;nbsp;참조가&amp;nbsp;전달됩니다.&lt;br /&gt;그래서 한쪽에서 객체 내부를 수정하면, 그 객체를 참조하고 있던 다른 모든 곳에서도 바뀐 값이 보이게 돼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- shallow copy (얕은 복사)&lt;br /&gt;spread&amp;nbsp;연산자나&amp;nbsp;Object.assign으로&amp;nbsp;객체를&amp;nbsp;복사하면,&amp;nbsp;가장&amp;nbsp;바깥쪽&amp;nbsp;한&amp;nbsp;겹만&amp;nbsp;새&amp;nbsp;객체로&amp;nbsp;만들어집니다.&lt;br /&gt;내부에&amp;nbsp;중첩된&amp;nbsp;객체들은&amp;nbsp;여전히&amp;nbsp;원본과&amp;nbsp;같은&amp;nbsp;참조를&amp;nbsp;공유하므로,&amp;nbsp;깊은&amp;nbsp;곳을&amp;nbsp;수정하면&amp;nbsp;원본도&amp;nbsp;함께&amp;nbsp;영향을&amp;nbsp;받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;[React]&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분명 값을 바꾸고 setState를 호출했는데, 화면이 바뀌지 않아도 당황하지 마세요.&lt;br /&gt;React는 객체 내부 변이를 감지하지 못할 뿐입니다.&lt;/h3&gt;
&lt;pre id=&quot;code_1776255349652&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [user, setUser] = useState({
  name: '철수',
  address: { city: '서울' }
});

// 아래 코드를 아무리 실행해도 화면에는 '철수', '서울'이 나옵니다...
const handleChange = () =&amp;gt; {
  user.address.city = '부산';
  user.name = '영희';
  setUser(user);
};

return (
  &amp;lt;div&amp;gt;
    &amp;lt;span&amp;gt;이름: {user.name}&amp;lt;/span&amp;gt;
    &amp;lt;span&amp;gt;지역: {user.address.city}&amp;lt;/span&amp;gt;
    
    &amp;lt;button onClick={handleChange}&amp;gt;바꾸기&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`setUser()`를 분명 호출했는데 화면에 렌더링된 결과가 바뀌지 않아서 많이 당황하셨죠?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;React는 state 변경을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;참조간의 비교(===)&lt;/b&gt;로 감지하기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이는 사실 React의 특성이라기보다는, JavaScript의 특성인데요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일치 연산자 `===`는 두 객체 안의 내용물과 관계 없이, 참조하는 주소가 같은지를 비교합니다. (동등 연산자 `==`도 마찬가지)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉 객체 내부의 내용을 바꿔도 React는 그 사실을 모릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시에서 `user`라는 변수에 할당된 객체는 바뀐 적이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 `user`가 가리키는 메모리 주소는 그대로이기 때문에, React는 이전 state와 새 state를 같다고 판단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부의 `city`, `name` 값이 바뀌었든 말든 말이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의도대로 화면을 리렌더링 하려면, 아래와 같이 객체의 참조 주소가 바뀌도록 새 객체를 만들어주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776255416843&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleChange = () =&amp;gt; {
  const newUser = {
    ...user,
    address: { ...user.address, city: '부산' },
  };
  setUser(newUser);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 배열도 마찬가지입니다. `push(), sort(), reverse(), splice()`는 모두 원본 배열을 직접 변이하는 메서드이므로, 같은 문제를 일으킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776255529944&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [items, setItems] = useState(['a', 'b', 'c']);

// 이렇게 하면 리렌더링이 안됩니다
items.push('d');
setItems(items);  // 같은 참조

// 이렇게 하면 리렌더링 됩니다
setItems([...items, 'd']);  // 새 배열&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩이 깊어질수록 spread가 장황해지는 것이 고민이라면, &lt;a href=&quot;https://immerjs.github.io/immer/&quot;&gt;Immer&lt;/a&gt;의 produce를 활용해 보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 변이하는 것처럼 작성할 수 있는데, 내부적으로는 새 객체를 만들어 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;state가 예상치 못한 타이밍에 불규칙하게 바뀐다면, 자식 컴포넌트가 props 객체를 수정하고 있지는 않은지 확인해보세요.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 살펴 봤듯, React는 객체 내부의 변화를 감지하지 못합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 자식 컴포넌트에서 props로 받은 객체 내부를 수정할 경우 당장은 리렌더링이 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보이는 것에 변화가 없으니까, 조용한 버그를 일으키기도 쉽겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776255578693&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Parent() {
  const [config, setConfig] = useState({ theme: 'light', fontSize: 14 });
  return &amp;lt;Child config={config} /&amp;gt;;
}

function Child({ config }) {
  const handleClick = () =&amp;gt; {
    config.theme = 'dark';  // 부모가 모르는 사이에 부모의 state가 바뀜
  };
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식이 config.theme = 'dark'를 실행하면, 부모의 config state 원본이 직접 바뀝니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수의 인자로 객체를 전달할 때, 원시값은 복사된 값이 전달되지만 객체는 원본이 전달되기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(엄밀히 말하자면, 얕은 복사본이 전달되므로 같은 원본 객체를 참조한다고 볼 수 있습니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 setConfig가 호출되기 전까지 부모는 리렌더링되지 않고 있다가, 이후 부모가 다른 이유로 리렌더링되면 그제서야 'dark'가 반영되어, 타이밍에 따라 됐다 안됐다 하는 유령 같은 버그가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이, 상태를 변경하는 함수를 따로 만들어서 함께 인자로 내리고 자식이 해당 함수를 호출하도록 해보세요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트가 상태변경 로직을 관리하기 때문에 더 예측 가능한 변경이 일어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776255642981&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Parent() {
  const [config, setConfig] = useState({ theme: 'light', fontSize: 14 });

  // 변경 함수를 만들어서, 이 함수를 props로 내려주는 방식
  const updateTheme = (newTheme) =&amp;gt; {
    setConfig(prev =&amp;gt; ({ ...prev, theme: newTheme }));
  };

  return &amp;lt;Child config={config} onChangeTheme={updateTheme} /&amp;gt;;
}

function Child({ config, onChangeTheme }) {
  const handleClick = () =&amp;gt; {
    onChangeTheme('dark');  // 부모에게 변경을 요청
  };
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;state를 소유한 쪽이 변경도 책임진다&quot;라고 생각하면 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;useEffect에서 무한루프가 발생한다면, 컴포넌트에서 선언한 객체를 의존성 배열에 넣은 건 아닌지 확인하세요.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트 함수는 리렌더될 때 항상 다시 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 아래와 같은 코드가 있다고 하면..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776592870409&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function SearchResults({ query }) {
  const [results, setResults] = useState([]);

  const options = { page: 1, limit: 10 }; // 문제의 객체

  useEffect(() =&amp;gt; {
    fetchResults(query, options).then(setResults);
  }, [query, options]); // &amp;lt;- 컴포넌트 내부에서 새로 만들어지는 객체인 options가 의존성 배열에 들어있다
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;options에 새 객체가 만들어지고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 렌더링에서 useEffect가 실행되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect는 setResults를 실행하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;results가 바뀌면서 SearchResults의 리렌더링을 촉발하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;options에 다시 새 객체가 만들어지고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect가 options 변화를 감지하여 다시 실행되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect는 setResults를 실행하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;results가 바뀌면서 SearchResults의 리렌더링을 촉발하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;options에 다시 새 객체가 만들어지고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 렌더마다 options에 새로운 객체 리터럴을 할당하게 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect는 새로 렌더할 때마다 이전의 options와 새로운 options를 비교합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 객체는 항상 다르기 때문에(새로 만들기 때문에), 무한루프가 발생하는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 방지하기 위해서는 useMemo를 사용해서 리렌더링에서도 객체가 변하지 않게 하거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 의존성 배열에 원시값을 사용하는 방법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776596207737&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 방법 1: useMemo로 참조를 안정화
const options = useMemo(() =&amp;gt; ({ page: 1, limit: 10 }), []);

// 방법 2: 객체 대신 원시값을 의존성으로 사용
const page = 1;
const limit = 10;

useEffect(() =&amp;gt; {
  fetchResults(query, { page, limit }).then(setResults);
}, [query, page, limit]);  // 원시값은 참조비교가 아닌 값비교를 사용하므로 안전&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;[NestJS]&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;request.body가 뭔가 이상하다면, Interceptor에서 request 객체를 수정하고 있지 않은지 확인하세요.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 trim, 빈 값 정리처럼 모든 요청에 공통으로 적용하고 싶은 처리가 있으면, 글로벌 인터셉터에서 request.body를 직접 손대고 싶어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776596288629&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// request.body의 string값들의 앞뒤 공백을 트리밍하는 글로벌 인터셉터
@Injectable()
export class TrimInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest();

    for (const key of Object.keys(request.body)) {
      if (typeof request.body[key] === 'string') {
        request.body[key] = request.body[key].trim();
      }
    }

    return next.handle();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 잘 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이 인터셉터의 존재를 모르는 다른 개발자가 코드를 작성할 때 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776597622222&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;제목:       이런 제목을 쓰고 싶을수도 있어요

@Controller('posts')
export class PostsController {
  @Post()
  async createPost(@Body() dto: CreatePostDto) {
    // 공백이 잘린 채 들어오지만 어디가 원인인지 찾기 어렵다
    // dto.title -&amp;gt; '이런 제목을 쓰고 싶을수도 있어요'
    return this.postService.create(dto);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interceptor, Guard, Pipe, Controller는 모두 같은 request.body를 공유하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 예상치 못한 값을 보게 되는 어안이 벙벙한 상황이 일어날 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동료가 내가 짠 코드 때문에 1시간 동안 머리를 쥐어뜯으며 디버깅하는 모습을 보고 싶다면, 뭐 나쁘지 않을지도요...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TypeORM, Prisma 등으로 조회한 DB entity 객체를 여기저기 들고 다니지 마세요.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아, 사실 들고 다니는 건 괜찮을지도 모릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 의도치 않은 수정이 발생하지 않도록 조심하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에서 주문을 조회하고, 계산서를 발행한 후 발행시점을 기록하는 로직이 있다고 가정해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776598256385&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async issueInvoice(orderId: number): Invoice {
  const order = await this.orderRepo.findOne({
    where: { id: orderId },
    relations: ['items', 'customer'],
  });

  await this.applyDiscount(order);    // order.price -= discountAmount 같은 짓을 함
  await this.applyTax(order);         // order.price += tax
  const invoice = await this.generateInvoice(order);
  
  order.invoiceIssueDate = new Date();
  await this.orderRepo.save(order);   // 의도하지 않은 값이 DB에 저장됨
  
  return invoice;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드가 실행된다면, DB는 할인금액과 세금이 붙은 price를 저장하게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 코드가 여러 번 실행된다면, 실행되는 횟수만큼 price는 아득한 값으로 변형되겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 entity는 읽기 전용으로 사용하고, 의도한 변이만 저장되도록 하는 것이 단단한 코드를 만드는 데 도움이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776598491157&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async issueInvoice(orderId: number): Invoice {
  const order = await this.orderRepo.findOne({
    where: { id: orderId },
    relations: ['items', 'customer'],
  });

  const discountAmount = await this.getDiscountAmount(order); // order를 변형하지 않음
  const taxAmount = await this.getTaxAmount(order);           // order를 변형하지 않음
  const invoice = await this.generateInvoice({
    ...order,
    price: price - discountAmount + taxAmount,
  });
  
  order.invoiceIssueDate = new Date();
  await this.orderRepo.save(order);   // 의도된 변이인 invoiceIssueDate만 저장됨!
  
  return invoice;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자꾸 환경변수로 넣은 Global Config가 바뀐다면, 분명 어딘가에서 수정하고 있는 겁니다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Global Config 객체는 전역에서 Singleton으로 유지되는 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 어딘가에서 수정된다면, 그 수정된 값이 계속 유지됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 DB 타임아웃을 임시로 바꾸고 싶은 상황이 있다고 해 볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776600510279&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Injectable()
export class ReportService {
  constructor(private configService: ConfigService) {}

  // 월간 리포트를 조회하는 쿼리는 무거워서 5분 정도 걸린다고 가정해 봅시다.
  async generateMonthlyReport(month: string) {
    const dbConfig = this.configService.get('database');

    // &quot;월간 리포트는 무거우니까 이번 건만&quot; 타임아웃을 늘리고 싶었던 의도
    dbConfig.queryTimeout = 1000 * 60 * 10;
    dbConfig.statementTimeout = 1000 * 60 * 10;

    return this.dbClient.query(dbConfig, monthlyReportSQL(month));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 일시적인 문제가 있을 때 5초만에 빠르게 실패하도록 하기 위해서, 평상시 DB 쿼리 타임아웃을 5초로 했다고 칠게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월간 리포트 같은 무거운 집계 쿼리는 5초 안에 끝나기 어려우니, 이 메서드 안에서만 타임아웃을 10분으로 늘리고 싶었던 거죠.&lt;br /&gt;문제는&amp;nbsp;configService.get('database')가&amp;nbsp;반환하는&amp;nbsp;객체가&amp;nbsp;앱&amp;nbsp;전체에서&amp;nbsp;공유되는&amp;nbsp;설정의&amp;nbsp;참조라는&amp;nbsp;점입니다.&lt;br /&gt;이걸 수정하면 앱 전체의 DB 타임아웃이 10분이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;평소 같으면 5초 만에 빠르게 실패해서 클라이언트에 에러를 돌려주고, 커넥션 풀에서 빠져나갔을 느린 쿼리들이 이제는 10분 동안 커넥션을 점유하게 됩니다.&lt;br /&gt;커넥션 풀이 빠르게 고갈되고, 장애가 장기화될 수 있겠죠...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 복사본을 만들어 쓰는 것이 안전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776600814190&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async generateMonthlyReport(month: string) {
  const dbConfig = this.configService.get('database');

  const reportDbConfig = {
    ...dbConfig,
    queryTimeout: 1000 * 60 * 10,
    statementTimeout: 1000 * 60 * 10,
  };

  return this.dbClient.query(reportDbConfig, monthlyReportSQL(month));
  // 원본 dbConfig는 그대로 보존됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 근본적으로는 Config 객체에 Object.freeze()를 걸어두거나, structuredClone()으로 처음부터 복사본을 반환하도록 설계하면 실수를 원천차단할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공통 팁&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 모든 문제는 결국 객체를 함수에 넘기면, 함수 안에서 그 객체를 직접 변이할 수 있다는 사실에서 비롯됩니다.&lt;br /&gt;참 편리하지만 위험하죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수에서 객체를 안전하게 인자로 넘기고 받는 방법은 무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;formatUser라는, user 객체의 속성을 포맷하는 함수의 예시를 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776601114293&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function formatUser(user) {
  user.name = user.name.trim().toLowerCase();  // 원본 변이..!
  user.createdAt = new Date(user.createdAt).toISOString();
  return user;
}

const user = { name: '  Alice  ', createdAt: 1700000000000 };
const formatted = formatUser(user); // 호출자는 원본이 바뀔 거라고 예상하지 못함

console.log(user.name);             // 'alice' &amp;mdash; 원본이 바뀌어 있음
console.log(user === formatted);    // true - 같은 객체&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;formatUser라는&amp;nbsp;이름만&amp;nbsp;봐서는&amp;nbsp;원본을&amp;nbsp;건드릴&amp;nbsp;거라고&amp;nbsp;예상하기&amp;nbsp;어렵습니다.&lt;br /&gt;호출자는&amp;nbsp;formatted만&amp;nbsp;변환된&amp;nbsp;결과일&amp;nbsp;거라고&amp;nbsp;믿고,&amp;nbsp;원본&amp;nbsp;user는&amp;nbsp;그대로일&amp;nbsp;거라&amp;nbsp;기대했을&amp;nbsp;겁니다.&lt;br /&gt;이런&amp;nbsp;함수는&amp;nbsp;호출하는&amp;nbsp;쪽에서&amp;nbsp;원본의&amp;nbsp;무결성을&amp;nbsp;신경&amp;nbsp;써야&amp;nbsp;하는,&amp;nbsp;다루기&amp;nbsp;까다로운&amp;nbsp;함수가&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 받은 객체를 수정하지 않고, 새 객체를 만들어서 반환하면 더 예측하기 쉽겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1776601276156&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function formatUser(user) {
  // 새로운 객체를 반환
  return {
    ...user,
    name: user.name.trim().toLowerCase(),
    createdAt: new Date(user.createdAt).toISOString(),
  };
}

const user = { name: '  Alice  ', createdAt: 1700000000000 };
const formatted = formatUser(user);

console.log(user.name);          // '  Alice  ' &amp;mdash; 원본 보존
console.log(user === formatted); // false &amp;mdash; 서로 다른 객체&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 함수가 의도적으로 원본을 변이해야 하는 경우라면, 함수 이름에 그 의도가 드러나는 것이 좋아요.&lt;br /&gt;updateUserInPlace(user), mutateConfig(config) 같은 네이밍을 하면 호출자에게 &quot;이 함수는 원본을 바꾼다&quot;는 경고를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/46</guid>
      <comments>https://prodyou.tistory.com/46#entry46comment</comments>
      <pubDate>Wed, 15 Apr 2026 21:22:02 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트같은 약타입 언어에 강한 컨벤션을 강제하는 건 쓸데없는 짓일까? 그럴거면 다른거 쓰지</title>
      <link>https://prodyou.tistory.com/45</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체 리터럴, 그 참을 수 없는 가벼움&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 리터럴이라는 것이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 객체를 만드는 가장 쉽고 간편한 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775650104509&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 그냥 이렇게 하면 객체 인스턴스가 만들어집니다. 클래스 정의가 필요없습니다.
const kimMinji = {
  name: &quot;김민지&quot;,
};

// 심지어 기존에 없는 프로퍼티도 만들어 넣을 수 있습니다.
kimMinji.birthYear = 2004;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 객체를 생성하는 행위는 강타입 언어에서는 말도 안되는 짓입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강타입 언어인 C#의 예시를 보면...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1775649878865&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 먼저 클래스를 정의하고...
public class Person
{
    public string Name { get; set; }
    public int BirthYear { get; set; }
    
    public int Age()
    {
        return DateTime.Now.Year - BirthYear + 1;
    }
}

// 클래스의 인스턴스를 new 키워드로 만들어야 합니다.
var kimMinji = new Person
{
    Name = &quot;김민지&quot;,
    BirthYear = 2004,
};

// 이런건 말도 안됨
var kimMinji = {
    Name = &quot;김민지&quot;,
}
kimMinji.BirthYear = 2004;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 복잡하게 많은 코드를 작성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강타입 언어의 장점은, 이렇게 빡빡한 컨벤션이 언어 레벨에서 강제되기 때문에 오류가 컴파일 타임에 잡힌다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 단점은 코드를 좀 많이 작성해야 한다는 점이 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약타입 언어는 반대입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 유연하고 심플해지는 장점이 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 타임에 에러를 거의 잡지 못합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 자바스크립트는 원래 그렇게 쓰는 언어잖아요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹브라우저에서 간단히 사용하기 위해 만들어진 &quot;스크립트&quot; 언어다보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 타입을 정의하고 클래스를 정의하려면 비효율적이니까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 리터럴 같은 상큼한 방식으로 객체 인스턴스를 생성할 수 있도록 한거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이제 자바스크립트가 그리 간단한 용도로만 쓰이지 않는다는 사실입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입스크립트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트를 서버 구동에도 쓰고 싶어서 node.js 라는 것이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 생태계가 커지면서 온갖 곳에서 자바스크립트가 쓰입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 개발에도, 데스크탑 앱 개발에도 자바스크립트가 쓰이게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 심지어 웹 개발 자체도 예전에 비하면 복잡도가 엄청나게 높아졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 코드일수록, 약타입의 장점은 끔찍한 단점이 되기 시작합니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 어딘가에서 이런 함수를 만들었습니다.
function calculateAge(person) {
  return new Date().getFullYear() - person.birthYear + 1;
}

// 6개월 뒤, 다른 개발자가 이 함수를 호출합니다.
// 인자로 넣어야 하는 person이 뭔데?
// 알 수 없습니다. 함수 내부 구현을 직접 열어봐야 합니다.
calculateAge({ name: &quot;김민지&quot; }); // NaN. 에러도 안 남. 그냥 조용히 망함.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 100줄짜리 토이 프로젝트면 상관없습니다. 직접 눈으로 훑으면 되니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 10만 줄이면? 50명이 동시에 작업하는 코드베이스면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무도 person에 뭐가 들어가야 하는지 기억하지 못합니다. 주석을 열심히 달아도, 주석은 거짓말을 합니다. 코드는 바뀌는데 주석은 안 바뀌거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 타입스크립트가 등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입스크립트: 자바스크립트에 안전벨트를 달다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 2012년, 마이크로소프트에서 만들었습니다. 핵심 설계자는 C#을 만든 바로 그 사람, 아네르스 하일스베르(Anders Hejlsberg)입니다. 강타입 언어를 설계해본 사람이, 약타입 언어의 고통을 해결하러 온 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트의 철학은 심플합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바스크립트의 유연함은 유지하되, 타입 시스템을 얹자.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 건 &quot;얹자&quot;라는 표현입니다. 타입스크립트는 자바스크립트를 대체하는 언어가 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 &lt;b&gt;슈퍼셋(superset)&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 아까 그 코드, 타입스크립트로 다시 써보면...
class Person {
  name: string;
  birthYear: number;

  constructor(name: string, birthYear: number) {
    this.name = name;
    this.birthYear = birthYear;
  }

  age(): number {
    return new Date().getFullYear() - this.birthYear + 1;
  }
}

// birthYear 없이 만들려고 하면? 빨간 줄이 그어집니다. 컴파일도 안 됩니다.
const kimMinji = new Person(&quot;김민지&quot;);
// ❌ Expected 2 arguments, but got 1.

// 제대로 만들면 age()를 바로 쓸 수 있습니다.
const kimMinji = new Person(&quot;김민지&quot;, 2004);
kimMinji.age(); // 23&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러가 코드를 작성하는 순간 잡히니까 에러를 사전에 검출할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그리고 또 강력한 것이 있어요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자동완성입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;person.을 찍는 순간 IDE가 name과 birthYear를 제안해줍니다. 더 이상 함수 내부 구현을 열어볼 필요가 없습니다. 인터페이스가 곧 문서이고, 그 문서는 코드와 항상 동기화됩니다. 주석과 달리 거짓말을 할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그러면 그냥 C# 쓰면 되잖아&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합리적인 질문입니다. 강타입이 그렇게 좋으면 처음부터 강타입 언어를 쓰면 되는 거 아닌가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 &lt;b&gt;생태계&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 브라우저는 자바스크립트를 실행합니다. C#을 실행하지 않습니다. Java도, Go도, Rust도 실행하지 않습니다. (WASM이라는 게 있긴 한데, 아직은 메인스트림이 아니에요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발을 하려면 자바스크립트를 피할 수 없습니다. 이미 많이 쓰이는, React, Vue, Svelte 같은 라이브러리가 이미 전부 자바스크립트 생태계 위에 있습니다. npm에는 200만 개가 넘는 패키지가 있죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 거대한 생태계를 버리고 다른 언어로 갈아타는 건 &quot;이 동네 치안이 안 좋으니까 나라를 옮기자&quot;라고 말하는 것과 비슷합니다. 불가능하지는 않지만, 현실적이지 않죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트가 현명한 건 이 현실을 인정했다는 점입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 자바스크립트 코드는 유효한 타입스크립트 코드입니다.&lt;/li&gt;
&lt;li&gt;기존 JS 프로젝트에 .ts 확장자만 바꿔도 일단 돌아갑니다.&lt;/li&gt;
&lt;li&gt;타입을 점진적으로 추가할 수 있습니다. 전부 다 한번에 바꿀 필요 없습니다.&lt;/li&gt;
&lt;li&gt;컴파일 결과물은 그냥 평범한 자바스크립트입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존과의 호환성을 최대화하고 점진적으로 생태계를 먹는 전략을 폈죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이게 꽤 효과적이었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;숫자가 말해주는 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024년 Stack Overflow 개발자 설문조사에서 타입스크립트는 가장 사랑받는 언어 상위권에 올라 있습니다. GitHub에서 가장 빠르게 성장하는 언어 중 하나이기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Angular는 타입스크립트를 공식 언어로 채택했고, Deno와 Bun 같은 차세대 런타임은 타입스크립트를 네이티브로 지원합니다. 심지어 Node.js도 실험적으로 타입스크립트 직접 실행을 지원하기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 새로운 프로젝트를 시작하면서 타입스크립트를 쓰지 않을 이유를 찾는 게 더 어려운 상황이 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다시 처음 질문으로&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약타입 언어에 강한 컨벤션을 강제하는 건 쓸데없는 짓일까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닙니다. 자바스크립트가 처음 만들어졌을 때의 용도와 지금의 용도가 완전히 다르기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10일 만에 만들어진 브라우저 스크립트 언어가 전 세계에서 가장 널리 쓰이는 범용 프로그래밍 언어가 되어버렸고, 그 과정에서 태생적 한계가 드러났고, 타입스크립트는 그 한계를 &lt;b&gt;생태계를 부수지 않으면서&lt;/b&gt; 메꾸는 방법이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그럴 거면 다른 거 쓰지&quot;라는 말은 이론적으로는 맞지만, 현실에서는 &quot;이 언어를 더 안전하게 만들자&quot;가 훨씬 실용적인 답이었고, 실제로 그렇게 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트는 자바스크립트를 부정하지 않습니다. 자바스크립트를 인정한 위에서, 부족한 부분만 채워줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그게 바로, 약타입 언어에 컨벤션을 &quot;강제&quot;하는 것이 아니라 &lt;b&gt;선택지를 제공&lt;/b&gt;하는 것의 힘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰고 싶으면 쓰고, 급하면 any를 쓰면 됩니다. 하지만 대부분의 경우, 타입을 쓰는 쪽이 미래의 나를 살립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 간단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바스크립트를 사랑한다면, 타입스크립트를 쓰세요.&lt;/b&gt; 그게 자바스크립트를 더 오래, 더 건강하게 쓰는 방법입니다.&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/45</guid>
      <comments>https://prodyou.tistory.com/45#entry45comment</comments>
      <pubDate>Wed, 8 Apr 2026 21:36:03 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트의 빈 배열([])은 빈 문자열(&amp;quot;&amp;quot;), 0이지만 boolean일 땐 true로 변환되는 이유</title>
      <link>https://prodyou.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript의 타입 변환 시스템은 봐도봐도 새롭습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암묵적 타입 변환이 일어날 수 있어서, 결과를 예측하기 어려운 경우가 많아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서도 특히 헷갈리는, 빈 배열 (`[]`)의 타입 변환을 정리해 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;빈 배열을 문자열과 숫자로 변환하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 빈 배열을 문자열(string)과 숫자(number)로 각각 변환해 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 string, number같은 원시값(primitive)으로 변환될 때, 자바스크립트 엔진은 ToPrimitive 추상 연산을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 객체에 대해서 `.toString()`과 `.valueOf()`를 순서대로 시도해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 중에서도, 배열의 `.toString()`은 내부적으로 `Array.prototype.join()`을 먼저 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 배열을 join하면 빈 문자열 (`&quot;&quot;`)이 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 빈 배열을 string으로 변환하면 빈 문자열입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775043151897&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[].toString(); // &quot;&quot; (내부적으로 join을 먼저 호출한다)
[].join();     // &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 빈 배열을 number로 변환할 때, ToPrimitive 추상 연산이 수행되므로 내부적으로 `toString()` 호출이 먼저 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 빈 배열은 빈 문자열이 되고, 빈 문자열은 다시 숫자 0이 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775043191666&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Number([])  // [] &amp;rarr; &quot;&quot; &amp;rarr; 0
Number(&quot;&quot;)  // 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;근데 왜 Boolean으로 변환하면 true인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참 이상합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 배열의 string 변환 결과인 빈 문자열은 Boolean 변환했을 때 false입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 배열의 number 변환 결과인 0도 Boolean 변환했을 때 false입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 빈 배열은 Boolean 변환했을 때 true입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775044937574&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Boolean(&quot;&quot;); // false
Boolean(0);  // false
Boolean([]); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;229&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clGjMa/dJMcafTDlSw/epAYs7RY6EVHGcgV8xsRdk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clGjMa/dJMcafTDlSw/epAYs7RY6EVHGcgV8xsRdk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clGjMa/dJMcafTDlSw/epAYs7RY6EVHGcgV8xsRdk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclGjMa%2FdJMcafTDlSw%2FepAYs7RY6EVHGcgV8xsRdk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;229&quot; height=&quot;220&quot; data-origin-width=&quot;229&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀랍게도 ToBoolean 변환은 ToPrimitive를 거치지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Boolean은 원시값이지만, 다른 원시값이랑은 별개로 또다른 타입 변환 방식을 갖고 있는 것입니다..!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ToBoolean 규칙은 아주 단순합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;false로 평가되는 값은 딱 정해져 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775044179478&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;false, 0, -0, 0n, &quot;&quot;, null, undefined, NaN&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외의 값들은 모두 true로 평가됩니다(!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 빈 배열은 Boolean으로 변환하면 true입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 boolean만 차별해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 왜 boolean만 다른 원시값(primitive)들과는 다른 타입 변환 체계를 가지게 된 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECMAScript 명세 전문 저술가인 Axel Rauschmayer 박사의 글에 따르면, &lt;b&gt;객체에 toBoolean() 같은 메서드를 허용하는 것은&amp;nbsp;성능 문제를 야기할 수 있기 때문에 객체는 항상 true가 되도록 설계되었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근본적인 원인은 단축 평가 연산자(`&amp;amp;&amp;amp;, ||`) 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`&amp;amp;&amp;amp;`는 평가될 때 boolean을 반환하지 않고, &lt;b&gt;피연산자 원본 값을 그대로 반환합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 연속적인 `&amp;amp;&amp;amp;`는 원본 값이 계속 평가되어야 하는 경우를 유발할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 들어볼까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에, `toBoolean()`메서드의 커스터마이징이 가능해서 `new Boolean(false)` 같은 객체가 false로 변환된다고 가정해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 아래와 같은 단축 평가 표현식이 있을때,&lt;/p&gt;
&lt;pre id=&quot;code_1775045358556&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;new Boolean(false) &amp;amp;&amp;amp; 1 &amp;amp;&amp;amp; true;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 표현식을 평가하기 위해서 다음과 같은 과정이 진행됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1775045509325&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;new Boolean(false) &amp;amp;&amp;amp; 1
// 먼저 new Boolean(false)를 boolean으로 평가하기 위해 toBoolean()을 호출합니다.
// false라는 것을 알아냅니다.
// 하지만 평가 이후에, 평가된 값 false가 아닌 원본 값 new Boolean(false)이 반환됩니다.

new Boolean(false) &amp;amp;&amp;amp; true
// 그러면 또 toBoolean()을 호출해야 합니다!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 연쇄적으로 단축 평가가 이어졌을 때, 계속해서 객체 원본을 변환하는 비싼 변환이 발생하게 되죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 ECMAScript 1을 설계할 때 객체가 boolean 변환을 직접 설정(configure)할 수 없도록 결정되었다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 왜 toString이나 valueOf는 괜찮아요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 나 == 같은 연산자는 값의 평가 결과를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 객체가 다음으로 전달되지 않죠.&lt;/p&gt;
&lt;pre id=&quot;code_1775046682008&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[] + 1 + 2
// [] &amp;rarr; &quot;&quot; (변환 결과인 &quot;&quot;이 다음으로 넘어감)
// &quot;&quot; + 1 &amp;rarr; &quot;1&quot;
// &quot;1&quot; + 2 &amp;rarr; &quot;12&quot;
// []는 한 번만 변환되면 끝&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 toString은 괜찮고, toBoolean은 안되는 거랍니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ecma&amp;nbsp;International.&amp;nbsp;(2025).&amp;nbsp;ECMAScript&amp;reg;&amp;nbsp;2026&amp;nbsp;Language&amp;nbsp;Specification&amp;nbsp;(ECMA-262,&amp;nbsp;15th&amp;nbsp;ed.).&amp;nbsp;TC39.&amp;nbsp;&lt;a href=&quot;https://tc39.es/ecma262/#sec-toprimitive&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tc39.es/ecma262/#sec-toprimitive&lt;/a&gt;&lt;br /&gt;Rauschmayer,&amp;nbsp;A.&amp;nbsp;(2013,&amp;nbsp;August&amp;nbsp;20).&amp;nbsp;Why&amp;nbsp;all&amp;nbsp;objects&amp;nbsp;are&amp;nbsp;truthy&amp;nbsp;in&amp;nbsp;JavaScript.&amp;nbsp;2ality.&amp;nbsp;&lt;a href=&quot;https://2ality.com/2013/08/objects-truthy.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://2ality.com/2013/08/objects-truthy.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/44</guid>
      <comments>https://prodyou.tistory.com/44#entry44comment</comments>
      <pubDate>Wed, 1 Apr 2026 21:33:49 +0900</pubDate>
    </item>
    <item>
      <title>어차피 다 똑같은 반복인데 for문만 사용하면 안되는걸까</title>
      <link>https://prodyou.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에는 다양한 반복 방법이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 문의 형태인 `for, for...of, for...in, while, do while`이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 메서드의 형태인 `forEach, map, reduce, some, every, find, filter,` ...등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 어차피 반복하는 것이 목적인데, for 하나면 충분하지 않을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이렇게 다양한 반복 방법이 존재하는 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드는 반복이 싫어요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 반복을 위해서는 for문도 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 코드를 여러번 쓰면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;767&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dm8qYj/dJMcadnPjlp/0Ia9MNnSswzuOyIDySdMOk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dm8qYj/dJMcadnPjlp/0Ia9MNnSswzuOyIDySdMOk/img.jpg&quot; data-alt=&quot;초보 vs 고수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dm8qYj/dJMcadnPjlp/0Ia9MNnSswzuOyIDySdMOk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdm8qYj%2FdJMcadnPjlp%2F0Ia9MNnSswzuOyIDySdMOk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;384&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;767&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;초보 vs 고수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그림처럼 별 다섯 줄을 출력하는 코드는 그냥 여러번 쓰는 것이 더 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 별 5천 줄, 5만 줄을 출력해야 한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 실무에서 5백만 행의 데이터를 반복해서 처리해야 한다면 얘기가 달라질 거에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 비효율을 피하고, 효율적이고 빠르게 일을 처리하기 위해 프로그래밍을 하잖아요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 코드의 반복을 줄이기 위해 반복문을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;for는 가장 완벽한 반복문일지도 몰라요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 사실 거의 모든 반복 작업은 for 문으로 표현이 가능하니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로, while 문을 for 문으로 바꾸면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774365479849&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// while 문
let i = 0;
while (i &amp;lt; 5) {
  console.log(i);
  i++;
}

// for 문
for (let i = 0; i &amp;lt; 5; i++) {
  console.log(i);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 의사코드(pseudo-code)의 형태로 단순화해서 바라보면,&lt;/p&gt;
&lt;pre id=&quot;code_1774365690411&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (condition) {
  body
}

for (; condition; ) {
  body
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 느낌입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실상 하는 일은 거의 비슷하다고 보면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 것도 볼까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map은 이렇게 바꿀 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774365818068&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];

const result1 = arr.map((item) =&amp;gt; item * 2);

// result1은 아래 result2와 동일한 결과를 가집니다.
const result2 = [];
for (let i = 0; i &amp;lt; arr.length; i++) {
  result2.push(arr[i] * 2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 filter도 for문으로 구현할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774365827481&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3, 4, 5];

const result1 = arr.filter((item) =&amp;gt; item % 2 === 0);

// result1은 아래 result2와 동일한 결과를 가집니다.
const result2 = [];
for (let i = 0; i &amp;lt; arr.length; i++) {
  if (arr[i] % 2 === 0) {
    result2.push(arr[i]);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find도 마찬가지입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774365843566&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3, 4, 5];

const result1 = arr.find((item) =&amp;gt; item &amp;gt; 3);

// result1은 아래 result2와 동일한 결과를 가집니다.
let result2;
for (let i = 0; i &amp;lt; arr.length; i++) {
  if (arr[i] &amp;gt; 3) {
    result2 = arr[i];
    break;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 for문만 쓰면 되는데, 왜 이런 다양한 반복문과 메서드들이 존재하는 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다 의도가 있어서 그래&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XnVYD/dJMcagdPMJl/JH8DhShp4cjdSP8AX0kY1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XnVYD/dJMcagdPMJl/JH8DhShp4cjdSP8AX0kY1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XnVYD/dJMcagdPMJl/JH8DhShp4cjdSP8AX0kY1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXnVYD%2FdJMcagdPMJl%2FJH8DhShp4cjdSP8AX0kY1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;543&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;while문은 for문으로 풀어 쓸 수 있고, 마찬가지로 for문도 while문으로 구현할 수 있지만 두 반복문은 의도가 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 반복 횟수가 중요하고 증감이 분명한 경우에는 for문을 사용하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복 횟수보다는 종료 조건이 중요하고 증감 방식이 상대적으로 자유롭다면 while문을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 0에서 100까지 증가하며 반복해야 한다면 for문이 자연스럽고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 입력이 들어올 때까지 계속 반복해야 한다면 while문이 자연스럽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어 단어를 봐도 그래요. for는 주어진 조건을 &lt;b&gt;&quot;위해&quot;&lt;/b&gt; 반복하는 느낌이라면 while은 주어진 조건이 &lt;b&gt;&quot;지속되는 동안&quot;&lt;/b&gt; 반복하는 느낌이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;템플릿처럼... 필요할 때 쉽게 쓰는 메서드의 추상화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수분들은 이미 눈치채셨겠지만, for문으로 복잡한 반복을 구현한 것들은 좀 길고 장황합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드만 딱 봤을때 뭘 하려는 건지 한 눈에 들어오지 않아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 메서드를 사용한 코드는 명확하고 쉽게 읽힙니다.&lt;/p&gt;
&lt;pre id=&quot;code_1774366603243&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 좀 장황하고, 한 눈에 들어오지 않아요.
let hasNegative = false;
for (let i = 0; i &amp;lt; arr.length; i++) {
  if (arr[i] &amp;lt; 0) {
    hasNegative = true;
    break;
  }
}

// 쉽게 읽힙니다. 코드도 짧습니다.
const hasNegative = arr.some(x =&amp;gt; x &amp;lt; 0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 메서드는 이런 복잡한 구현을 추상화한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드에는 동작에 대한 명확한 의도가 담긴 이름이 붙어 있습니다. 그래서 읽고 이해하기도 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 메서드는 기본적으로 함수이기 때문에 재사용이 가능합니다. 그래서 중복 코드도 적어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 이런 언어 레벨에서 지원하는 메서드는 내부적으로 성능 최적화가 되어 있는 경우가 많습니다. 따라서 의도대로만 잘 사용한다면, 직접 구현하는 것보다 빠르고 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for는 거의 모든 반복을 표현할 수 있고, 잘 동작합니다.&lt;br /&gt;하지만 단지 동작하는 코드를 만드는 일이 프로그래밍의 전부라면 좀 아쉽겠죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1144&quot; data-start=&quot;1108&quot; data-ke-size=&quot;size16&quot;&gt;우리는 여러 명이 협업하여 만드는 큰 프로그램을 유지보수해야 하기에, 사람이 읽고 이해하기 쉬운 코드를 작성합니다.&lt;/p&gt;
&lt;p data-end=&quot;1204&quot; data-start=&quot;1146&quot; data-ke-size=&quot;size16&quot;&gt;그래서 같은 반복이라도 그 의도를 잘 드러내기 위한 알맞은 코드를 골라서 쓰는 것 같아요.&lt;br /&gt;무엇을 하려는지 그 의도가 바로 읽히는 쉽고 좋은 코드 작성.. 저도 계속 연습해 봐야겠습니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>개발/자바스크립트로의 깊은 잠수</category>
      <author>ProdMoon</author>
      <guid isPermaLink="true">https://prodyou.tistory.com/43</guid>
      <comments>https://prodyou.tistory.com/43#entry43comment</comments>
      <pubDate>Wed, 25 Mar 2026 20:00:02 +0900</pubDate>
    </item>
  </channel>
</rss>