<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>blank_.log</title>
        <link>https://velog.io/</link>
        <description>불안을 안고 구르는 작은 모난 돌</description>
        <lastBuildDate>Mon, 07 Oct 2024 06:16:09 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>blank_.log</title>
            <url>https://velog.velcdn.com/images/blank_/profile/573b5a66-1681-4b3d-802d-4149ce11e13c/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. blank_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/blank_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[git squash merge]]></title>
            <link>https://velog.io/@blank_/git-squash-merge</link>
            <guid>https://velog.io/@blank_/git-squash-merge</guid>
            <pubDate>Mon, 07 Oct 2024 06:16:09 GMT</pubDate>
            <description><![CDATA[<h3 id="사건의-발단---merge-방식의-변경">사건의 발단 - merge 방식의 변경</h3>
<p>이번 프로젝트에서 merge 방식을 바꿔보기로 했다.
SJ 님의 의견이었는데, back 레파지토리에서 하는 대로 front 레파지토리에도 적용해보기로 했다.
기존 merge 방식과 다른 두가지 merge 방식인 reabse, squash merge방식을 사용해보고 싶었기 때문이다.
세가지 merge 방식의 차이는 -&gt; <a href="https://hudi.blog/git-merge-squash-rebase/">3-way merge </a> 이 블로그에 잘 정리되어 있다. </p>
<blockquote>
<p>우리가 정한 merge 룰은 다음과 같았다.</p>
</blockquote>
<ol>
<li>deploy는 main 브랜치 작업물로 하고,</li>
<li>dev브랜치에서 main브랜치로의 merge는 rebase방식을 사용하고,</li>
<li>그 외 다른 브랜치에서 dev브랜치로의 merge는 squash방식을 사용하기로 했다.
간단한 git-flow를 따르기로 한 것이다.</li>
</ol>
<h3 id="사건의-전개---pr-commit-log-폭탄">사건의 전개 - PR commit log 폭탄</h3>
<p>깃헙에 KS 님의 PR이 올라와서 리뷰를 하러갔더니 commit log 폭탄이 있었다.
알고보니 브랜치 구분을 안하고 올리셨던 것..!
이때는 초반 PR이어서 내용에 대한 comment만 남기고 Approve를 드렸다.<br><del>이 결정을 아주 후회 했다ㅎ</del>
<img src="https://velog.velcdn.com/images/blank_/post/0bbfc4f8-317a-4b52-b9b5-05170f7380e5/image.png" alt=""></p>
<p>그리고 이 커밋들은 feature -&gt; dev로의 PR이라 하나의 커밋으로 squash  되어 merge되었다. </p>
<h3 id="사건의-위기---또-다시-나타난-폭탄">사건의 위기 - 또 다시 나타난 폭탄</h3>
<p>다른 기능을 위한 KS님의 PR이 다시 올라왔다.
그런데 그 PR의 commit log가 뭔가 이상했다.
<img src="https://velog.velcdn.com/images/blank_/post/d1888371-a54d-4cd4-bf25-4992cd5f25f0/image.png" alt=""></p>
<p>몇가지 commit이 추가된것 말고는 거의 모든 커밋이 다시 올라온 것이다.
그래서 나는 해당 이슈에 대해서 피드백을 남겼다. </p>
<blockquote>
<p>풀리퀘 커밋 로그와 file changed가 맞지 않습니다. 확인 한번만 부탁드려요!</p>
</blockquote>
<p>그리고 KS님의 답변이 달렸다.</p>
<blockquote>
<p>파일 자체는 문제가 없는데 로그가 이상하네요. 수정해서 올리겠습니다!</p>
</blockquote>
<p>그리고 다음 날 회의에서 우리는 1시간 30분동안 이 커밋로그가 왜 그런것인지 토의했다. <del>회의 좋잖아 한잔해</del></p>
<p><img src="https://velog.velcdn.com/images/blank_/post/0796c9d7-b28f-4ab5-aacc-de39b9720ef7/image.png" alt="">
<strong>KS님이 작업하신 branch graph는 위와 같았다.
Feature 1 에서 작업한 A, B, C 커밋이 squash merge 된 이후 인데,
분기된 Feature 2 브랜치에서 PR할 때 A, B, C, D, E, F가 모두 올라간 것이다.</strong></p>
<h4 id="해결-방법">해결 방법</h4>
<p>회의에 참석한 SJ님이 rebase를 하면 해결할 수 있다고 하셨다.</p>
<pre><code class="language-shell">git checkout [pr branch]
git pull origin dev
git reset --hard HEAD^
git rebase dev</code></pre>
<p>이 방식으로 진행했더니 dev브랜치에서 갈라져 나온 순간부터의 모든 commit에 대해서 commit 한개마다의 conflict를 해결하라는 경고 문구가 떴다.</p>
<h4 id="왜-그랬을까">왜 그랬을까?</h4>
<p>현재 local은 <code>git pull origin dev</code>로 인해서 origin dev의 상태를 갖고 있었다.
그런데 rebase가 진행되면서 feature2의 첫 커밋과 현재 dev 브랜치(= origin dev)가 가지고 있는 파일의 변경 사항이 충돌을 일으킨 거였다.</p>
<h4 id="rebase과정의-실수">rebase과정의 실수...!</h4>
<p>중복된 커밋을 제거하고 싶은 경우, conflict 해결 과정에서 중복된 내용이 들어간 커밋에 대해서는 incoming(dev)을 선택했어야 한다. 
하지만 KS님은 rebase conflict 해결 과정에서 계속 incoming이 아닌 current를 선택했다.
왜냐하면 결론적으로 dev상태의 코드를 만들고 싶었기 때문이다. 하지만, 커밋의 incoming을 쭉 따라가면 현재 dev상태가 되는거였기 때문에 incoming을 누르는게 우리의 의도와 맞는 rebase conflict 해결과정이었다.</p>
<p>그래서 우리는 결론적으로, 약 커밋이 4개 정도 줄어든 commit log를 force push 하게 되었다. ㅎㅎㅎㅎㅎ</p>
<h4 id="의문-1-왜-dev에-merge를-했는데-중복된-log를-탐지하지-못하는걸까">의문 1. 왜 dev에 merge를 했는데 중복된 log를 탐지하지 못하는걸까?</h4>
<p>왜냐하면, squash merge과정에서 commit log와 무관한 새로운 commit id가 만들어졌기 때문에, dev브랜치 입장에서는 전혀 다른 commit이라고 인식하는 것.</p>
<h4 id="의문-2-그렇다면-rebase과정을-통하면-중복된-log가-사라지는데-그건-어떻게-가능한걸까">의문 2. 그렇다면 rebase과정을 통하면 중복된 log가 사라지는데, 그건 어떻게 가능한걸까?</h4>
<p>그건 사람이 수동으로 각 커밋마다의 변경점을 보고 확인해서 중복된 내용이라는 것을 확인해주니까 가능한거! git이 자동으로 해줄 수는 없다 ㅠㅠ</p>
<h4 id="의문-3-squash-merge가-아닌-merge를-사용했다면-이런-일이-발생하지-않았을까">의문 3. squash merge가 아닌 merge를 사용했다면 이런 일이 발생하지 않았을까?</h4>
<p>YES!! merge를 했을 경우 dev브랜치에 merge를 위한 커밋이 하나 추가될 뿐, 내가 올린 PR의 모든 commit log 가 dev에 포함되게 된다. 
dev브랜치 입장에서는 똑같은 commit id에 대해서 다시 반영해달라고 하는 것이므로, 중복된 commit id를 제외하고 PR에 보여졌을 것이다.</p>
<h4 id="의문-4-pr에서-보여주는-commit-log는-결국-dev-branch와-변경점이-있으니까-보이는거-아닌가-근데-왜-file-changed와-일치하지-않을까">의문 4. PR에서 보여주는 commit log는 결국 dev branch와 변경점이 있으니까 보이는거 아닌가? 근데 왜 file changed와 일치하지 않을까?</h4>
<p>이건 우리가 rebase과정에서 실수를 범했기 때문이다. 결론적으로는 dev branch와 같은 코드를 갖게 되기 때문에 file changed에는 보이지 않지만, 각 commit 마다는 변경점이 있다고 판단하기 때문에 PR에서 해당 commit을 보여주는것!</p>
<h3 id="사건의-결론---cherry-pick-으로-해결해보자">사건의 결론 - cherry pick 으로 해결해보자</h3>
<p>이 풀리퀘에 남은 commit log 가 약 30개에 달했기 때문에 우리는 조금 더 쉬운 방식을 다시 시도해보기로 했다.
새로운 브랜치를 만들고, PR 에 올라가야하는 5개의 커밋에 대해서 cherry-pick명령을 통해 새로운 브랜치로 옮기는 것이다.</p>
<p>cherry pick 명령어 - <a href="https://brownbears.tistory.com/606">블로그 참고 </a></p>
<h3 id="이-사건에서-배운것">이 사건에서 배운것</h3>
<ol>
<li>PR이 올라간 후에는 꼭 dev 브랜치를 rebase하거나 merge하고 작업을 이어가자.</li>
<li>commit log는 죄가 없다. 대부분은 HUMAN ERROR다.</li>
<li>이런 문제를 방지하려면 그냥 dev브랜치에서 다시 분기를 하자.</li>
<li>rebase와 squash merge가 동작하는 방식을 제대로 이해하게 된것 같다.</li>
<li>모르는 것을 서로 나누는 회의는 즐겁다.하핳</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Car Thing 서비스 종료]]></title>
            <link>https://velog.io/@blank_/Car-Thing-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%A2%85%EB%A3%8C</link>
            <guid>https://velog.io/@blank_/Car-Thing-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%A2%85%EB%A3%8C</guid>
            <pubDate>Tue, 25 Jun 2024 08:17:47 GMT</pubDate>
            <description><![CDATA[<p>혹시 당신도 어떤 음악을 재생하고 있는지 보이지 않는 구형차를 타는가? 
혹시 음악 앱으로 스포티파이를 쓰거나, 쓸 생각이 있는가?
그렇다면 추천한다. Car Thing <a href="https://carthing.spotify.com/">Spotify 웹사이트</a>
<img src="https://velog.velcdn.com/images/blank_/post/b1e2b360-a528-4449-9aeb-420b92f64c3e/image.png" alt="">
<del>진짜 예쁘다</del></p>
<p>그리고 당신은 지금 DisContinued 될 것이라는 글을 보았을 것이다.
2021년 출시되어 스포티파이 고객들이 구매했던 CarThing은 2022년 대폭 할인과 함께 단종되었다.
그리고 2024년 5월, 앞으로의 지원도 없고 2024년 12월 9일 부로 더 이상 기계를 쓸 수 없게 될 것이라는 기사가 올라왔다.
아니 서비스 지원 안하는 것 까지는 그렇다치고 아예 기계를 벽돌을 만들겠다고..? 너무하잖아ㅜㅜ
<del>사람들도 다들 분노중이다 e-waste를 만든거나 다름 없다면서, 환불요청이 쇄도했다</del></p>
<p>한 2022년 12월쯤 내가 처음으로 알게되었던 카씽은 당시 단종된 상태여서 중고나라/ 번개장터 등에는 약 30만원 가까운 금액으로 올라오고,
ebay에서는 200불 이상의 가격대를 형성하고 있었다.
당시 나의 의문은 이러했다. 
이렇게 높은 가격을 형성할 만큼 사람들에게 인정받는 기계를 왜 단종했을까..?
아무튼 그때는 가격이 너무 비싸서 구매를 못하고 가끔가다가 한번 찾아보곤 했다.</p>
<p>그리고 어제 그냥 한번 검색해봤다가 2024년 6월 단종을 넘어 <strong>기계 벽돌 예정 소식</strong>을 들은 순간 호기심의 정점을 찍었다.</p>
<h3 id="첫번째-궁금증">첫번째 궁금증</h3>
<p>왜? 어차피 지금까지 펌웨어 업데이트도 없었고, 단순히 노래 재생을 돕는 기계였을 뿐인데 이게 스포티파이에 어떤 해를 끼치길래 이걸 서비스 종료까지 하는거람? </p>
<p>그래서 해외 기사까지 찾아보기 시작했다. (스포티파이가 미국이 본사라 얼마나 다행인지 ㅋㅋㅋㅋ)</p>
<p><del>이것 저것 찾아보다가 레딧을 흘러들어가고 그러다가 카씽 해킹을 위한 디스코드까지 흘러간 나 ㅋㅋㅋㅋ</del></p>
<p>사람들이 내린 결론은 크게 두가지였다.</p>
<ol>
<li>카씽은 음성을 기반으로 검색해주는, 그리고 기계를 조작해주는 서비스가 있었는데, 이걸 유지하는 서버 비용이 너무 커서 서비스를 종료하는 것</li>
<li>카씽은 기본적으로 스포티파이의 음악을 재생하기 위한 기계로 스포티파이에서 만들어졌기 때문에 스포티파이의 음악을 재생이 잘 되지 않는 순간 브랜드 이미지가 안좋아진다는 것</li>
</ol>
<p>두가지 다 합리적인 이유였다. 이해가 되었다.
스포티파이가 서비스를 종료할 수 밖에 없다고도 생각했다. </p>
<h3 id="두번째-궁금증">두번째 궁금증</h3>
<p>그러면, 나는 이제 궁금해졌다.
스포티파이에서 제공한 펌웨어를 조금 변경해서 계속 사용할 수는 없을까? </p>
<p>기사를 찾다가 또 흘러들어간 스포티파이 디벨로퍼 사이트에 car thing의 sdk를 제공해달라는 글에 약 1800명이 추천을 눌렀는데, 그것에 대해 스포티파이는 어떤 답도 없었다. 
심지어 어떤 사람의 추측으로는 spotify가 코드를 너무 얼레벌레 짜서 open source로 제공할 수 없는 것이라는 글도 있었다.ㅋㅋㅋ</p>
<h3 id="해결은-안됬지만">해결은 안됬지만,</h3>
<p>그래서 지금 카씽을 해킹하기 위한 디스코드에서는 carthing의 포맷하고 새로운 ui와 backend 서버를 구성해서 carthing을 어떻게든 유지하려고 하는 노력을 하고 있다.
포맷하거나 새로운 UI로 변경하는 youtube영상들도 있는데, 보다보니 음... 아직은 위험을 감수하고 따라할 만큼 안정적이지는 않아 보인다. 또 일반인은 따라하기가 어려워보인다.</p>
<p>이건 번외지만, 디스코드 채널 내부에서는 carthing에 wifi모듈이 없다는거에 놀라기도 했더라ㅋㅋㅋ 
생각보다 기기가 작아서 RAM도 4GB밖에 안되고, 메모리도 512MB라고 하던데, 그래도 외국인들도 다들 구형 차에서 터치형식이 아니라 버튼 형식이어서 운전에 안정성이 보장되는 카씽을 사랑하는거 같던데 계속 어떻게든 카씽을 쓸 수 있는 방법이 나오면 좋겠다..🙏</p>
<h3 id="그래서-나는">그래서 나는,</h3>
<p>내가 처음 carthing을 접했을 때는 나와 고작 10살 차이나는 07년산 우리 구형차에게 없는 블루투스 오디오랄까 내비게이션이랄까 이런게 있는 건줄 알았는데, 이제는 보다보니 정들어서 그렇게까진 필요없는데도 갖고싶달까ㅋㅋㅋ 
처음엔 카씽의 앞의 디스플레이나, 디자인, 우측의 버튼은 굉장히 맘에 들어서 뽐뿌가 왔었다.</p>
<p>사실 카씽이 내가 원하는 스펙이 되려면 앞판은 남겨두고 뒷판에 와이파이 모듈도 달고 내비나 다른 앱도 실행할 수 있도록 OS도 갈아야되고, 심지어 그러다가 메모리나 CPU가 부족하면 바꿔야되고...ㅎㅎㅎㅎㅎㅎ 그렇게 된거 누가 출시해주면 좋겟다....</p>
<p>아 그래서 어제 검색했을 때는 carthing이 약 7만원-20만원 정도 예전보다 반값에 중고가가 형성되어있던데
진짜 하나같이 서비스 종료라는거는 안써놨더라...
이제 고작해야 6개월 사용하고 벽돌될 기기를 20만원...? 판매자도 모르고 파는 걸 수도 있지만 알고 그가격에 파는거면 진짜 양애취<del>~</del>ㅋ</p>
<p>아무튼,
해킹에 성공해서 카씽을 계속 쓸 수 있게 되기를, 그리고 아무도 카씽 중고사기를 당하지 않기를 바라면서~
<strong>나의 carthing 탐방기는 여기까지!</strong></p>
<p>참고 문헌
<a href="https://community.spotify.com/t5/Live-Ideas/Car-Thing-Developer-SDK-for-Spotify-Car-Thing/idi-p/5437643">https://community.spotify.com/t5/Live-Ideas/Car-Thing-Developer-SDK-for-Spotify-Car-Thing/idi-p/5437643</a></p>
<p><a href="https://www.notebookcheck.net/Hacking-community-saves-Spotify-Car-Thing-from-becoming-an-e-waste.845472.0.html">https://www.notebookcheck.net/Hacking-community-saves-Spotify-Car-Thing-from-becoming-an-e-waste.845472.0.html</a></p>
<p><a href="https://www.reddit.com/r/carthinghax/">https://www.reddit.com/r/carthinghax/</a></p>
<p><a href="https://www.reddit.com/r/carthinghax/comments/10i1ohg/my_windows_hacking_tutorial/?embed_host_url=https://publish.reddit.com/embed">https://www.reddit.com/r/carthinghax/comments/10i1ohg/my_windows_hacking_tutorial/?embed_host_url=https://publish.reddit.com/embed</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 포트폴리오 제작기]]></title>
            <link>https://velog.io/@blank_/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EC%A0%9C%EC%9E%91%EA%B8%B0</link>
            <guid>https://velog.io/@blank_/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EC%A0%9C%EC%9E%91%EA%B8%B0</guid>
            <pubDate>Tue, 09 Apr 2024 08:04:26 GMT</pubDate>
            <description><![CDATA[<h3 id="제작이유">제작이유</h3>
<p>사실, 포트폴리오의 필요성을 느낀지는 꽤 오래되었다. 
원티드, 프로그래머스 등의 채용사이트에서 제공하는 이력서 템플릿에는 사진을 넣을 곳이 없고, 개발은 했지만 현재 운영중이지 않은 프로젝트도 있어서 이력서만으로는 불충분하다고 느꼈다.
그래서 내가 이만큼 할 줄 아는 사람이다고 자기소개<del>라고 쓰고 자랑이라고 읽는다</del> 를 하려면 사진과 함께 내가 구현한 부분을 명확히 쓴 포트폴리오가 필요하다고 생각했다.</p>
<p>하지만 사람은 늘 그렇듯이, 게으르니까...ㅎㅎ
미루고 미루어보았는데, 이제 정말 미루면 안될 때가 온 것 같다.</p>
<h3 id="제작방식">제작방식</h3>
<p>포트폴리오를 만드는 방식은 2가지를 생각했는데, 역시 프론트엔드 개발자니까 웹사이트로 전달하는 방식이 있는게 더 매력적일것이라고 생각했다. 
물론, 종이로 뽑아 보는 게 익숙한 사람들을 위해 ppt형식의 포트폴리오도 만들어야겠지..</p>
<h3 id="lets-go">Let&#39;s go<del>~</del></h3>
<p>그래서 시작해봅니다. 나도 만든다 포트폴리오.</p>
<blockquote>
<p>*<em>포트폴리오 제작기 Part.1 *</em></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바스크립트 스터디 17장]]></title>
            <link>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-17%EC%9E%A5</link>
            <guid>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-17%EC%9E%A5</guid>
            <pubDate>Fri, 22 Mar 2024 04:29:22 GMT</pubDate>
            <description><![CDATA[<h2 id="17장-생성자-함수에-의한-객체-생성">17장 생성자 함수에 의한 객체 생성</h2>
<blockquote>
<h3 id="이번장에서-꼭-알아야-하는-것">이번장에서 꼭 알아야 하는 것!</h3>
</blockquote>
<ol>
<li>다양한 객체 생성 방식 중에 생성자 함수를 사용해 객체를 생성하는 방식</li>
<li>객체 리터럴을 사용해 객체를 생성하는 방식 vs 생성자 함수를 사용해 객체를 생성하는 방식 (장단점, 차이)</li>
</ol>
<h3 id="171-object-생성자-함수">17.1 Object 생성자 함수</h3>
<p>new 연산자와 함께 Object 생성자 함수를 호출하면, 빈객체 {}를 생성해서 반환한다. </p>
<pre><code class="language-javascript">const person = new Object();
console.log(person); // {}</code></pre>
<p>하지만, 반드시 Object 생성자 함수를 이용해서 빈 객체를 생성해야하는 것은 아니다. 오히려 객체 리터럴이 더 간편하므로, 특별한 이유가 없다면 Object 생성자 함수를 이용하지 않는 것을 권장한다.</p>
<h4 id="q-그렇다면-특별한-이유는-어떨때">Q. 그렇다면, 특별한 이유는 어떨때??</h4>
<p>아래에서 알려주겠음~~</p>
<h4 id="생성자-함수-constructor-란-">생성자 함수 constructor 란 ?</h4>
<p>new 연산자와 함께 호출하여 객체 (인스턴스 instance)를 생성하는 함수.</p>
<blockquote>
<h5 id="자바스크립트는-기본적으로-object-string-number-boolean-function-array-date-regexp-promise등의-빌트인-생성자-함수를-제공한다">자바스크립트는 기본적으로 Object, String, Number, Boolean, Function, Array, Date, RegExp, Promise등의 빌트인 생성자 함수를 제공한다.</h5>
</blockquote>
<pre><code class="language-javascript">const strObj = new String(&#39;Lee&#39;);
console.log(typeof strObj); //object
console.log(strObj); //String {&#39;Lee&#39;}</code></pre>
<p>이처럼, 생성자 함수에 의해 생성하면 결과로 반환되는 건 원시값이 아닌 <strong>객체</strong>이다.
<strong>왜??</strong> 객체를 생성하는 함수를 호출했으니까!</p>
<h3 id="172-생성자-함수">17.2 생성자 함수</h3>
<h4 id="객체-리터럴에-의한-객체-생성방식의-문제점--한번에-한-객체만-만드는-것">객체 리터럴에 의한 객체 생성방식의 문제점 : 한번에 한 객체만 만드는 것</h4>
<p>객체 리터럴에 의한 객체 생성 방식은 직관적이고, 간편하지만 단 하나의 객체만 생성한다.그래서 <strong>동일한 프로퍼티 구조를 갖는 여러개의 객체를 만들 때</strong>는 매번 같은 프로퍼티와 메서드를 기술해야 하므로 중복적인 코드를 쓰게되고, 비효율적이다. </p>
<pre><code class="language-javascript">const circle1 =  {
  radius : 5, 
  getDiameter() {
    return 2 * this.radius;
  }};
const circle2 = {
  radius : 10,
  getDiameter() {
    return 2 * this.radius;
  }};</code></pre>
<p>위의 예제처럼, 프로퍼티 구조가 동일한 두개의 객체를 만들때, 
<strong>생성자 함수를 써준다!</strong></p>
<h4 id="왜--생성자-함수에-의한-객체-생성-방식의-장점이-있으니까">왜 ? 생성자 함수에 의한 객체 생성 방식의 장점이 있으니까!</h4>
<p>마치 객체(인스턴스)를 생성하기 위한 클래스(템플릿)처럼 생성자 함수를 사용하여 프로퍼티 구조가 동일한 객체 여러개를 간편하게 생성할 수 있다.
실제로 클래스처럼 동작한다는 건 아니다! </p>
<pre><code class="language-javascript">function Circle(radius){
  // 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킴.
  this.radius = radius;
  this.getDiameter = function (){
    return 2 * this.radius;
  };
}

const circle1 = new Circle(5);
const circle2 = new Circle(10);</code></pre>
<h4 id="q-return이-없는데-객체가-반환된다">Q. return이 없는데, 객체가 반환된다..?</h4>
<h4 id="q-new가-없으면-어떻게-될까-에러가-날까">Q. new가 없으면 어떻게 될까..? 에러가 날까?</h4>
<p>짜잔! 한번에 두개나 생성이 가능하다구~~ 멋지지! 
하지만 단점없는 기술은 없는법</p>
<h4 id="생성자-함수의-작동방식">생성자 함수의 작동방식</h4>
<p>자바와 같은 클래스 기반 객체지향 언어의 생성자와 다르게, 형식이 정해져있지 않고, 일반 함수와 동일한 방법으로 함수를 정의하고 new 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작한다.</p>
<p>이게 무슨 뜻이냐면, 놀랍게도 </p>
<pre><code class="language-javascript">const circle3 = Circle(15);
// 이렇게 new없이 호출하면 생성자함수가 아닌 일반 함수로서 호출이 된다는 것이다. 미쳤다. 
console.log(circle3); //undefined
// 하지만 반환문이 없으니, 암묵적으로 undefined를 반환한다.</code></pre>
<p>그리고, this가 가리키는 객체가 반환할 인스턴스가 아닌 전역객체가 되어, radius가 전역객체의 프로퍼티가 되는 일이 발생한다! 미친거같아<del>~</del></p>
<p><img src="https://velog.velcdn.com/images/blank_/post/8fe92c2b-01f3-42ac-baca-8f4b0e2ed2df/image.png" alt=""></p>
<h4 id="q-그러면-어떻게해야해-아예-쓰지-말아야하나-그럴수는-없잖슴">Q. 그러면, 어떻게해야해? 아예 쓰지 말아야하나? 그럴수는 없잖슴!</h4>
<p>이건 밑에서 알려줌~~</p>
<h4 id="생성자-함수의-인스턴스-생성-과정">생성자 함수의 인스턴스 생성 과정</h4>
<p>생성자 함수는 프로퍼티 구조가 동일한 인스턴스를 생성하기 위한 템플릿(클래스)로서 동작해야하므로, 함수 몸체 내에서</p>
<ol>
<li><strong>인스턴스를 생성</strong>하고</li>
<li><strong>생성된 인스턴스를 초기화</strong> (프로퍼티 추가 및 프로퍼티 초기값 할당) 
을 해야한다.</li>
</ol>
<p>다시 위의 Circle 생성자 함수를 보면, 프로퍼티 초기화 및 초기값 할당 부분을 보이지만, 인스턴스를 생성하고 반환하는 코드는 보이지 않는다. </p>
<pre><code class="language-javascript">function Circle(radius){
  //인스턴스 초기화 및 초기값 할당
  this.radius = radius;
  this.getDiameter = function (){
    return 2 * this.radius;
  };
}

//생성자 함수 호출
const circle4 = Circle(100);</code></pre>
<p>보이지않으면 뭐다? 암묵적으로 생성한거다. 여기서 자바스크립트 엔진의 도움이 들어가게 된다.
new 연산자와 함께 생성자 함수를 호출하면, 자바스크립트 엔진은 암묵적인 처리를 통해 <strong>암묵적으로 인스턴스를 생성</strong>하고 인스턴스를 초기화하고 <strong>암묵적으로 인스턴스를 반환</strong>한다.</p>
<ol>
<li><p>암묵적 인스턴스 생성과 this 바인딩 (<strong>자바스크립트 엔진</strong>)
new 연산자와 함께 <strong>생성자 함수를 호출하는 순간, 암묵적으로 빈객체 {}</strong>를 만든다. 
그리고, 이 빈 객체를 this에 바인딩한다.
(생성자 함수 내부의 this가 생성자 함수가 생성할 인스턴스를 가리키는 이유)</p>
</li>
<li><p>인스턴스 초기화 (<strong>개발자</strong>)</p>
</li>
</ol>
<p><strong>함수 몸체 내부에 기술된 코드가 실행</strong>되어, this에 바인딩 되어있는 인스턴스를 초기화한다.
프로퍼티나 메서드를 추가하고, 생성자 함수가 인수로 전달받은 초기값을 인스턴스 프로퍼티에 할당하여 초기화하거나 고정값을 할당한다.</p>
<ol start="3">
<li>인스턴스 반환 (<strong>자바스크립트 엔진 / 개발자</strong>)
생성자 함수 내부의 모든 처리가 끝나면, 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다. 
(이게 아까 질문의 대답! return이 없는데, 객체가 반환되는 이유!)</li>
</ol>
<pre><code class="language-javascript">function Circle(radius){
  // 1. 암묵적으로 빈객체 생성, this에 바인딩.
  console.log(this); // Circle {}

  // 2. this에 바인딩 된 인스턴스 초기화
  this.radius = radius;
  this.getDiameter = function(){
    return 2 * this.radius;
  };

  // 3. 인스턴스 반환
  // 1. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환.
  // return {}; -&gt; 2. 명시적으로 빈객체 반환
  // return 1; -&gt; 3. 명시적으로 원시값 반환 =&gt; this 반환
}

const circle = new Circle(1);
console.log(circle); 
// Circle {radius: 1, getDiameter: f} -&gt; 1, 3
// {} -&gt; 2. 명시적으로 빈객체를 반환한 경우</code></pre>
<blockquote>
<p>반환되는 값은 무조건 2개 중 하나!
    1. return문을 명시하지 않은 경우 -&gt; 바인딩 된 this 객체
    2. 명시적으로 <strong>다른 객체를 반환</strong>한 경우 -&gt; 다른 객체
    3. 명시적으로 <strong>원시값을 반환</strong>한 경우 -&gt; 바인딩 된 this 객체
    명시적으로 this가 아닌 다른 값을 반환하면, 생성자 함수의 기본 동작을 훼손하기 때문에, <strong>생성자 함수 내에서는 무조건 return문을 생략</strong>해야한다!!</p>
</blockquote>
<h4 id="1724-내부-메서드-call-과-construct">17.2.4 내부 메서드 [[Call]] 과 [[Construct]]</h4>
<p>함수 선언문 / 함수 표현식으로 정의한 함수는 <strong>일반적인 함수로서 호출 + 생성자 함수로서 호출</strong>이 둘 다 가능하다</p>
<ul>
<li><strong>함수는 객체</strong>이므로 일반 객체와 동일하게 동작할 수 있다.
-&gt; 일반 객체의 내부 슬롯, 내부 메서드를 가지고 있다.</li>
<li>일반 객체는 호출할 수 없지만 <strong>함수 객체는 호출</strong> 할 수 있다.
-&gt; 함수로 동작하기 위해 [[Environment]], [[FormalParameters]]등의 내부 슬롯과, [[Call]], [[Construct]]같은 내부 메서드를 추가로 가지고 있다.</li>
</ul>
<p>함수는 호출되는 방식에 따라 호출되는 내부 메서드가 다르다. 
<strong>일반함수로서 호출되면 [[Call]]이 호출되고,
생성자 함수로서 호출되면 [[Construct]]가 호출된다.</strong></p>
<pre><code class="language-javascript">function foo(){} // 함수 선언문으로 생성한 함수
foo(); // 일반 함수 호출 -&gt; Call
new foo(); // 생성자 함수 호출 -&gt; Constructor</code></pre>
<ul>
<li><p>내부 메서드 [[Call]]은 호출할 수 있는 객체를 뜻하며, <strong>모든 함수가 가지고 있고</strong>, [[Call]]메서드를 가지면 Callable이라고 한다.</p>
</li>
<li><p>내부 메서드 [[Construct]]는 생성자함수로서 호출할 수 있는 함수만 가지고 있다.</p>
</li>
<li><p>*[[Construct]]를 가진 함수객체**는 일반함수, 생성자 함수로서 호출할 수 있는 함수객체이고 constructor라고 하며,</p>
</li>
<li><p>*[[Construct]]를 갖지 않는 함수객체**는 일반 함수로서만 호출할 수 있는 함수객체로, non-constructor라고 한다. </p>
</li>
</ul>
<p>결론적으로 함수 = callable이고, 
함수 중 일부는 constructor, 나머지는 모두 non-constructor이다.</p>
<p>constructor와 non-constructor는 함수 정의방식에 따라 구분된다.
*<em>함수 선언문, 함수 표현식, 클래스 = constructor
메서드 (ES6메서드 축약표현), 화살표 함수 = non-constructor *</em></p>
<blockquote>
<p>👀 주의할 점
ES6의 메서드 축약 표현만이 메서드로 인정된다.
<strong>함수가 어디 할당되어있는지가 아니라 함수 정의방식에 따라</strong> 구분하기 때문</p>
</blockquote>
<pre><code class="language-javascript">function foo(){} // constructor
const bar = function (){}; // constructor
const baz = {
  x : function (){}, // (주의!!) constructor
  //-&gt; 이건 일반함수로 정의되었다고 판단 -&gt; 메서드 인정 xx
  y() {}, // non-constructor
};
const arrow = () =&gt; {}; // non-constructor
new foo(); // foo {}
new bar(); // bar {}
new baz.x(); // x {}
new arrow(); // TypeError
new obj.x(); // TypeError</code></pre>
<p>non-constructor인 함수 객체는 내부 메서드 [[Construct]]를 갖지 않기 때문에 에러가 발생함.</p>
<p><strong>주의 !!!! 따라서, 생성자 함수가 아닌 일반함수를 기대하고 생성한 함수가 생성자 함수처럼 호출될 경우 생성자 함수처럼 동작할 수 있음을 알고 있어야함!!</strong></p>
<pre><code class="language-javascript">// 함수를 정의할 때 의도는 생성자 함수가 아니었지만,
function add(x, y){
  return x + y;
}
// 함수 선언문으로 생성했기 때문에 생성자 함수처럼 호출이 가능
let inst = new add();
// 반환문이 원시값이므로 무시되고 암묵적으로 빈객체가 생성되어 반환됨.
console.log(inst); // {}</code></pre>
<p>이런 상황이 발생할 수도 있다.</p>
<p><strong>반대로, 생성자 함수로 기대하고 만든 함수가 일반함수처럼 불렸을 때도 동작할 수 있음을 알고 있어야함!!</strong></p>
<pre><code class="language-javascript">// 위에서 생성한 Circle 생성자 함수가 있다고 해보자.
function Circle(radius){
  ...
}
// new 없이 호출했을 경우, 생성자함수가 아닌 일반함수처럼 호출이 된다.
const circle = Circle(5);
console.log(circle); // 반환값이 없으므로 undefined가 암묵적으로 반환된다.
console.log(radius) // 5
console.log(getDiameter()); // 10
console.log(circle.getDiameter()); // TypeError</code></pre>
<p>전혀 예측하지 않은대로 동작하게 된다. 
인스턴스의 프로퍼티가 되길 바랐던 radius, getDiameter는 전역객체의 프로퍼티가 되는것.</p>
<h4 id="따라서-생성자함수는-일반적으로-첫-문자를-대문자로-기술하는-파스칼-케이스로-명명해-일반-함수와-구별할-수-있도록-하자">따라서, 생성자함수는 일반적으로 첫 문자를 대문자로 기술하는 파스칼 케이스로 명명해 일반 함수와 구별할 수 있도록 하자.</h4>
<p>하지만, 모든걸 사람에게 맡기면 난리가 나니까.
 -&gt; ES6에서는 new.target을 지원한다.
new.target은 this와 유사하게 constructor인 모든 함수 내부에서 암묵적인 지역 변수와 같이 사용된다.</p>
<p>new.target을 사용할 경우 new연산자와 함께 호출되었는지를 판별할 수 있다.</p>
<ul>
<li>new 연산자와 함께 호출되었을 경우 (생성자 함수로서의 호출)
=&gt; new.target = 함수 자신을 가리킴</li>
<li>new 연산자 없이 호출되었을 경우 (일반 함수로서의 호출)
=&gt; new.target = undefined</li>
</ul>
<pre><code class="language-javascript">function Circle(radius){
  if (!new.target){
    return new Circle(radius)}
  .....
}</code></pre>
<p>이처럼 new 연산자와 함께 호출되지 않았을 경우 생성자함수를 재귀 호출하는 방식을 선택하면, 생성자함수는 늘 일반함수로서의 호출이 아닌 생성자함수로서의 호출을 보장할 수 잇다.</p>
<p>ES6이전에는 스코프 세이프 생성자 패턴이라는걸 썼음</p>
<pre><code class="language-javascript">function Circle(radius){
  if (!this instanceof Circle){
    return new Circle(radius)}
  ....
}</code></pre>
<p>this가 전역객체인지, Circle의 인스턴스인지를 판별해서 재귀적으로 호출하는것 </p>
<p>그래서, 이런 문제가 있는 생성자 함수지만, 대부분의 빌트인 생성자함수는 new연산자와 함께 호출되었는지를 확인한 뒤에 적절한 값을 반환하지만,</p>
<p>String, Number, Boolean생성자함수의 경우 new 연산자 없이 호출하면 원시값을 반환한다.</p>
<pre><code class="language-javascript">const str = String(123);
console.log(str, typeof str); //123 string</code></pre>
<p><strong>각각의 래퍼 객체 별로 생성자 함수로서 호출되지 않았을 경우에 대한 처리가 다르므로 가급적 사용하지 않는것을 권장한다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바스크립트 스터디 16장]]></title>
            <link>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-16%EC%9E%A5</link>
            <guid>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-16%EC%9E%A5</guid>
            <pubDate>Thu, 21 Mar 2024 10:06:26 GMT</pubDate>
            <description><![CDATA[<h2 id="16장-프로퍼티-어트리뷰트">16장 프로퍼티 어트리뷰트</h2>
<h3 id="161-내부-슬롯과-내부-메서드">16.1 내부 슬롯과 내부 메서드</h3>
<p>내부 슬롯(internal slot)과 내부 메서드(internal method)는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 이중 대괄호([[...]])로 감싼 이름들인 의사 프로퍼트 (pseudo property)와 의사 메서드(pseudo method)이다. </p>
<p>내부 슬롯, 내부 메서드는 자바스크립트 엔진 내부 로직으로, 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티가 아니다. 하지만 일부 내부 슬롯과 내부 메서드에 한해서 <strong>간접적으로 접근할 수 있는 수단</strong>을 제공한다. </p>
<p>그 중 하나가 <strong><code>[[Prototype]]</code></strong> 이라는 내부 슬롯인데, <strong><code>__proto__</code></strong> 라는 프로퍼티를 통해 간접적인 접근을 허용한다.</p>
<center><span style="color:grey">👇 아래는 [[Prototype]]에 대한 접근을 허용하지 않는 예시👇</span>
<img src="https://velog.velcdn.com/images/blank_/post/2cf3266c-d973-4ed7-aa5f-85c508699ed6/image.png" width="80%" >
</center>

<center><span style="color:grey">👇 아래는 __proto__에 대한 접근 허용예시👇</span>
<img src="https://velog.velcdn.com/images/blank_/post/818fb52e-8974-4b33-86ec-0fbfbae4a700/image.png" width="80%">
</center>
하지만, `__proto__` 라는 프로퍼티가 console.dir로 확인했을때는 안보인다.
<center>
<img src="https://velog.velcdn.com/images/blank_/post/972e2105-da3f-466a-ad4b-02a0098bcf9b/image.png" width="80%"></center>

<h4 id="q-왜-안보이는데-가능하지">Q. 왜 안보이는데 가능하지?</h4>
<p>ES6에서 표준화 되어있기 때문.
근데 mdn에서는 deprecated라고 표시함. 두둥.
<img src="https://velog.velcdn.com/images/blank_/post/f5cf7aa2-a0a8-4a23-8a56-874a607b25aa/image.png" alt=""></p>
<h4 id="q-왜-표준이라는데-deprecated지">Q. 왜 표준이라는데 deprecated지??</h4>
<p>ES6가 도입되기 이전에 브라우저들이 자의적으로 만든 프로퍼티 접근방식인데, 비표준인데도 너무 많이 퍼져있다보니 ES6에서 일단 허용해줬다고 함. 그래서 일단 표준이긴한데, 없앨 예정이고, 없애려고 하는 중.
그래서 <code>Object.getPrototypeOf</code>라는 새로운 메서드를 제공해줌. 가급적 이걸 사용하는게 좋음!</p>
<img src="https://velog.velcdn.com/images/blank_/post/d9bbff09-1837-44f5-b011-7c589c9580fb/image.png" width="60%">



<h3 id="162-프로퍼티-어트리뷰트와-프로퍼티-디스크립터-객체">16.2 프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체</h3>
<p>자바스크립트 엔진을 <strong>프로퍼티를 생성할 때, 프로퍼티 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의</strong>한다.
프로퍼티 어트리뷰트에는 자바스크립트 엔진이 관리하는 내부 상태 값(meta-property)인 내부슬롯들로,</p>
<ol>
<li>프로퍼티의 값 ( [[Value]])</li>
<li>갱신 가능 여부 ( [[Writable]])</li>
<li>열거 가능 여부( [[Enumerable]])</li>
<li>재정의 가능 여부( [[Configurable]])
을 포함한다. </li>
</ol>
<p>아래 사진과 같이, 내부 상태 값들이므로 직접 접근은 불가능하지만, <code>Object.getOwnPropertyDescriptor</code> 메서드를 사용해 간접적으로 확인할수는 있다. 
메서드의 첫번째 매개변수에 <strong>객체의 참조를 전달</strong>하고, 
두번째 매개변수에 <strong>프로퍼티 키를 문자열로 전달</strong>하면, 
프로퍼티 어트리뷰트 정보를 제공하는 <strong>프로퍼티 디스크립터 (Property Descriptor)객체를 반환</strong>한다.</p>
<center><span style="color:grey">👇 아래는 getOwnPropertyDescriptor 예시👇</span>
<img src="https://velog.velcdn.com/images/blank_/post/9ddf314f-3b4d-4e6b-8d15-e7f99a6bbad8/image.png" width="80%">
</center>

<h5 id="💛-더하기-정보-1-">💛 더하기 정보 1 )</h5>
<p>만약 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면, undefined가 반환된다.</p>
<h5 id="💛-더하기-정보-2">💛 더하기 정보 2)</h5>
<p>Object.getOwnPropertyDescriptor : 1개의 프로퍼티에 대해 프로퍼티 디스크립터 객체 반환 
ES8 : Object.getOwnPropertyDescriptors : 모든 프로퍼티에 대해 프로퍼티 디스크립터 객체들을 반환 </p>
<h3 id="163-데이터-프로퍼티와-접근자-프로퍼티">16.3 데이터 프로퍼티와 접근자 프로퍼티</h3>
<table>
<thead>
<tr>
<th>프로퍼티 종류</th>
<th>데이터 프로퍼티</th>
<th>접근자 프로퍼티</th>
</tr>
</thead>
<tbody><tr>
<td></td>
<td>data property</td>
<td>accessor property</td>
</tr>
<tr>
<td>설명</td>
<td>키와 값으로 구성된 일반적인 프로퍼티</td>
<td>자체적으로 값을 갖지 않고, 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수 (accessor function)으로 구성된 프로퍼티</td>
</tr>
<tr>
<td>예시</td>
<td><code>name: &#39;Lee&#39;</code></td>
<td><code>get fullName(){return &#39;${this.name}&#39;}</code></td>
</tr>
</tbody></table>
<p>프로퍼티는 위와 같이 두개로 구분할 수 있다.</p>
<h4 id="1631-데이터-프로퍼티">16.3.1 데이터 프로퍼티</h4>
<p>데이터 프로퍼티는 4개의 프로퍼티 어트리뷰트를 갖는다.
(프로퍼티 어트리뷰트  - 프로퍼티 디스크립터 객체의 프로퍼티 순서로 기재)</p>
<blockquote>
<ol>
<li>[[Value]] - value</li>
</ol>
</blockquote>
<ul>
<li>프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값. </li>
<li>프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당.
이때 프로퍼티가 없으면, 프로퍼티를 동적생성하고 [[Value]]에 값을 저장</li>
</ul>
<blockquote>
<ol start="2">
<li>[[Writable]] - writable</li>
</ol>
</blockquote>
<ul>
<li>프로퍼티의 값의 변경 가능 여부를 나타내고, <strong>불리언 값</strong>을 가짐.</li>
<li>[[Writable]]값이 false인 경우, 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없는 <strong>읽기 전용 프로퍼티</strong>가 됨.</li>
</ul>
<blockquote>
<ol start="3">
<li>[[Enumerable]] - enumerable </li>
</ol>
</blockquote>
<ul>
<li>프로퍼티의 열거 가능 여부를 나타내고, <strong>불리언 값</strong>을 가짐.</li>
<li>[[Enumerable]]값이 false인 경우, 해당 프로퍼티는 for..in문이나, Object.keys메서드 등으로 <strong>열거 불가능</strong>.</li>
</ul>
<blockquote>
<ol start="4">
<li>[[Configurable]] - configurable</li>
</ol>
</blockquote>
<ul>
<li>프로퍼티의 재정의 가능 여부를 나타내고, <strong>불리언 값</strong>을 가짐.</li>
<li>[[Configurable]]값이 false인 경우, <strong>해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지</strong>됨. </li>
<li>*<em>But, [[Writable]]이 true인경우, [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것이 허용된다. *</em></li>
</ul>
<p>프로퍼티가 생성될 때 (동적생성을 포함) [[Value]]의 값은 프로퍼티의 값으로 초기화되고, 
[[Writable]], [[Enumerable]], [[Configurable]]의 값은 true로 초기화된다. </p>
<h4 id="1632--접근자-프로퍼티">16.3.2  접근자 프로퍼티</h4>
<p>접근자 프로퍼티도 4개의 프로퍼티 어트리뷰트를 갖는다. 데이터 프로퍼티와는 다른게 2개 있다. </p>
<blockquote>
<ol>
<li>[[Get]] - get</li>
</ol>
</blockquote>
<ul>
<li>접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자함수. </li>
<li>접근자 프로퍼티 키로 프로퍼티 값에 접근하면, 프로퍼티 어트리뷰터 [[Get]]의 값인 <strong>getter 함수가 호출</strong>되고, 그 결과가 <strong>프로퍼티 값으로 반환</strong>됨.</li>
</ul>
<blockquote>
<ol start="2">
<li>[[Set]] - set</li>
</ol>
</blockquote>
<ul>
<li>접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자함수.</li>
<li>접근자 프로퍼티 키로 프로퍼티 값에 접근하면, 프로퍼티 어트리뷰터 [[Set]]의 값인 <strong>setter함수가 호출</strong>되고, 그 결과가 <strong>프로퍼티 값으로 저장</strong>됨.</li>
</ul>
<blockquote>
<ol start="3">
<li>[[Enumerable]] - enumerable</li>
</ol>
</blockquote>
<ul>
<li>데이터 프로퍼티의 프로퍼티 어트리뷰트와 동일</li>
</ul>
<blockquote>
<ol start="4">
<li>[[Configurable]] - configurable</li>
</ol>
</blockquote>
<ul>
<li>데이터 프로퍼티의 프로퍼티 어트리뷰트와 동일</li>
</ul>
<p>접근자 함수는 getter/setter라고도 부르는데, 접근자 프로퍼티는 getter, setter를 두개 다 정의할 수도 있고, 하나만 정의할 수도 있다.</p>
<center><span style="color:grey">👇 아래는 접근자 프로퍼티 예시👇</span>
<p>
person객체 생성

<img src="https://velog.velcdn.com/images/blank_/post/7186aa37-d2f4-4155-9c6d-56c6720d4ca6/image.png" width="80%">

<p>person 객체의 fullName setter, getter 호출
<img src="https://velog.velcdn.com/images/blank_/post/034b7acb-e1b5-482e-8333-423769bb335f/image.png" width="80%"></p>
<p>person 객체의 데이터 프로퍼티, 접근자 프로퍼티의 프로퍼티 어트리뷰트 확인</p>
<img src="https://velog.velcdn.com/images/blank_/post/583a181c-e21b-4514-892a-50d507520eb8/image.png" width="80%">
</center>

<h4 id="q-접근자-프로퍼티는-객체당-하나만-할-수-있을까---xxx-여러개-가능">Q. 접근자 프로퍼티는 객체당 하나만 할 수 있을까? -&gt; XXX 여러개 가능!</h4>
<h4 id="q-접근자-프로퍼티의-이름은-getter-setter가-꼭-동일하게-설정해야할까---xxx-다르게도-할수-있음">Q. 접근자 프로퍼티의 이름은 getter, setter가 꼭 동일하게 설정해야할까? -&gt; XXX 다르게도 할수 있음!</h4>
<center><span style="color:grey">👇 아래는 접근허용자가 여러개의 이름을 가지는 person객체의 예시👇</span>

<p><img src="https://velog.velcdn.com/images/blank_/post/91bd0a9e-5a79-4585-85a9-dfcc56893289/image.png" width="80%"></center></p>
<p><strong>내부 슬롯/ 내부 메서드 관점</strong>에서 보는 
접근자 프로퍼티 fullName으로 프로퍼티 값에 접근했을 때
내부적으로 [[Get]]내부 메서드가 호출되고 동작하는 방식은 ? </p>
<ol>
<li>프로퍼티 키가 문자열 / 심벌인지 확인한다. (유효성 확인)<ul>
<li>&#39;fullName&#39;은 문자열이므로 유효.</li>
</ul>
</li>
<li>유효하다면, 프로토타입 체인에서 프로퍼티를 검색한다.<ul>
<li>person 객체에 fullName 프로퍼티가 존재하는지.</li>
</ul>
</li>
<li>존재한다면, 검색된 프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인한다.<ul>
<li>fullName 프로퍼티는 접근자 프로퍼티.</li>
</ul>
</li>
<li>접근자 프로퍼티라면, 프로퍼티 어트리뷰트 [[Get]]의 값인 getter함수를 호출한다.</li>
</ol>
<p>3번의 <strong>데이터 프로퍼티/ 접근자 프로퍼티를 구별하는 방법</strong>은 프로퍼티 어트리뷰트를 객체로 표현한 <strong>프로퍼티 디스크립터 객체의 프로퍼티가 다른것</strong>을 보고 알 수 있다. 
 = 즉, value, writable을 가지고 있는 객체인지, get, set을 가지고 있는 객체인지 확인하면 됨.</p>
<h3 id="164-프로퍼티-정의">16.4 프로퍼티 정의</h3>
<p> 프로퍼티 정의 = 새로운 프로퍼티를 추가하면서, 프로퍼티 어트리뷰트를 명시적으로 정의하거나 / 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것.
 = <strong>writable, enumerable, configurable의 불리언 값을 정의 / 재정의</strong>하는것</p>
<h4 id="방법--objectdefineproperty-메서드">방법 : Object.defineProperty 메서드</h4>
<p>메서드의 첫번째 매개변수로 <strong>객체의 참조</strong>를 전달하고,
두번째 매개변수로 <strong>데이터 프로퍼티의 키</strong>인 문자열을 전달하고,
세번째 매개변수로 <strong>프로퍼티 디스크립터 객체</strong>를 전달한다.</p>
<center><span style="color:grey">👇 아래는 Object.defineProperty메서드 이용 예시👇</span></center>

<pre><code class="language-javascript">const person = {};
Object.defineProperty(
  person, 
  &#39;firstName&#39;,
  { value : &#39;Ungmo&#39;, writable : true, enumerable : true, configurable : true }
  ); // 1번 방식
Object.defineProperty(
  person,
  &#39;lastName&#39;,
  { value : &#39;lee&#39; }
  ); // 2번 방식</code></pre>
<p>Object.defineProperty 메서드로 프로퍼티를 정의할 때, 2번 방식 처럼 <strong>프로퍼티 디스크립터 객체의 프로퍼티를 일부 생략</strong>할 수 있다.
이 경우에는 
** value, get, set 은 undefined,
writable, enumerable, configurable 은 false **
의 기본값이 적용된다. </p>
<center><span style="color:grey">👇 아래는 데이터 프로퍼티를 Object.defineProperty한 결과 👇</span></center>

<p><img src="https://velog.velcdn.com/images/blank_/post/7737e6f8-b9e7-4e75-95fb-4ca1a461e84d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/blank_/post/8a5f5853-26ee-4907-a500-08cb2014dc92/image.png" alt=""></p>
<p>enumerable, writable, configurable이 false인 lastName프로퍼티에 대한 변경을 시도했을 경우 모두 무시됨.</p>
<p><img src="https://velog.velcdn.com/images/blank_/post/48012772-e275-4d51-84bf-b235e450ce98/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/blank_/post/b91349e6-f3ef-4fd9-b04f-00f3e5d161f2/image.png" alt=""></p>
<p>configurable 속성이 false이므로, 프로퍼티 어트리뷰트에 대한 재정의는 에러가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/blank_/post/dba28798-0cd7-4b6e-ba4d-de51b65d7afe/image.png" alt=""></p>
<center><span style="color:grey">👇 아래는 접근자 프로퍼티를 Object.defineProperty한 결과 👇</span></center>

<p><img src="https://velog.velcdn.com/images/blank_/post/ebadc204-dc3c-4409-b068-96141bffeeaf/image.png" alt=""></p>
<p>fullName 접근자 함수로 변경을 시도해도, lastName은 변경이 되지 않음!</p>
<p><img src="https://velog.velcdn.com/images/blank_/post/ed23ff97-6c23-48be-8406-5d0d82748664/image.png" alt=""></p>
<h4 id="q-enumerable-과-iterable은-다른건가">Q. enumerable 과 iterable은 다른건가?</h4>
<h5 id="💛-더하기-정보-1">💛 더하기 정보 1)</h5>
<p>Object.defineProperty : 한번에 1개의 프로퍼티를 정의
Object.defineProperties : 여러개의 프로퍼티를 한번에 정의가능</p>
<h3 id="165-객체-변경-방지">16.5 객체 변경 방지</h3>
<p>객체는 변경 가능한 값이므로 재할당 없이 직접 변경가능하다. 
프로퍼티 값 갱신뿐만 아니라, 프로퍼티 어트리뷰터를 재정의할 수도 있다. </p>
<p>객체에서 변경가능한 부분은 5가지로 정리로, 
프로퍼티 추가, 프로퍼티 삭제, 프로퍼티 값 읽기, 프로퍼티 값 쓰기, 프로퍼티 어트리뷰터 재정의가 있다.</p>
<p>따라서, 자바스크립트는 객체의 변경을 방지하는</p>
<ol>
<li>Object.preventExtensions</li>
<li>Object.seal</li>
<li>Object.freeze
3 개의 메소드를 제공하며, 각각 객체의 변경을 금지하는 강도가 다르다.</li>
</ol>
<table>
<thead>
<tr>
<th>메소드</th>
<th>Object.preventExtensions</th>
<th>Object.seals</th>
<th>Object.freeze</th>
</tr>
</thead>
<tbody><tr>
<td>구분</td>
<td>객체 확장 금지</td>
<td>객체 밀봉</td>
<td>객체 동결</td>
</tr>
<tr>
<td>금지강도</td>
<td>프로퍼티 추가만 불가 (프로퍼티 동적 추가, Object.defineProperty메소드 둘 다 금지)</td>
<td>프로퍼티 값 읽기 / 쓰기만 허용</td>
<td>프로퍼티 값 읽기만 허용</td>
</tr>
<tr>
<td>객체 확인 메서드</td>
<td>Object.isExtensible</td>
<td>Object.isSealed</td>
<td>Object.isFrozen</td>
</tr>
<tr>
<td>메서드의 반환값 = false</td>
<td>확장 가능</td>
<td>밀봉되어 있지 않음</td>
<td>동결되어 있지 않음</td>
</tr>
</tbody></table>
<h5 id="💛-더하기-정보-1--1">💛 더하기 정보 1 )</h5>
<p>객체 변경 방지 메서드를 사용하더라도 <strong>중첩 객체에 대해서는 동결되지 않는다.</strong>
얕은 변경 방지로 동작하기 때문에 직속 프로퍼티에 대해서만 변경이 방지되기 때문이다. 
따라서, <strong>변경이 불가능한 읽기전용의 불변객체</strong>를 구현하기 위해 중첩객체에 대해서도 변경방지를 원할경우 (깊은 변경 방지) 객체를 값으로 갖는 모든 프로퍼티에 대해 <strong>재귀적으로 메서드를 호출</strong>해야한다.
(불변객체는 일반적으로 자바스크립트에서 객체를 다루는 기법인데, 책에서는 바꿀수 없는 객체를 일컫고 있음.)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좋은 코드 리뷰의 기준]]></title>
            <link>https://velog.io/@blank_/CodeReview</link>
            <guid>https://velog.io/@blank_/CodeReview</guid>
            <pubDate>Wed, 20 Mar 2024 08:34:34 GMT</pubDate>
            <description><![CDATA[<h3 id="코드리뷰는-필요하다">코드리뷰는 필요하다.</h3>
<p>이런 당연한 이야기를 왜 하나 싶겠지만, 충격적이게도 나의 첫 회사는 코드리뷰가 없는 회사였다.</p>
<p>커밋을 하면 단번에 푸시가 되고, 테스트용 서버에 올려서 테스트를 해보다가, 안되면 피드백을 받고 다시 커밋하고...와웅</p>
<h3 id="코드리뷰가-없으면-무섭다">코드리뷰가 없으면 무섭다.</h3>
<p>Q. 온보딩 없이 받은 업무에서 처음 써보는 svn에 코드리뷰없이 40페이지 짜리 코드 컨벤션을 읽고 커밋을 해야되는 주니어의 심정은..? 
A. 아주 ...아주 무서웠다. 내 코드 한줄로 서버가 터질수도, 페이지 접속이 안될 수도 있는거니까.</p>
<h3 id="이런-상황을-방지하기-위해-코드리뷰는-필요하다">이런 상황을 방지하기 위해 코드리뷰는 필요하다.</h3>
<p>42서울을 다니면서 나는 코드리뷰에 익숙해져 있었고, 리뷰 과정에서 받게 되는 피드백을 통해 코드 품질이 향상되는 것도 알고 있었다. 
코드리뷰가 있다는 것만으로도 코드 작성자는 내 코드의 가독성을 고민하고, 자연스럽게 컨벤션 문화에 익숙해진다.</p>
<p>** 좋은 코드리뷰 태도 ** 란 아직도 어려운 주제라고 생각하지만, 이제 어느정도 기준이 잡힌 것도 같다. 내 기준은 다음과 같다. </p>
<p>** 1. 개발자와 개발자의 코드를 동일선상에 두지 말자. **
내 코드가 지적받았다고, 수정사항이 생겼다고 내가 지적받거나 수정해야되는 것이 아니다. 나는 주니어고, 모르는게 당연하고, 수정사항이 안 생기면 이상한거다. 오히려 다른 사람의 눈에 내 코드가 괜찮아 보였다면 나는 무서워한다. 내가 생각지도 못한 곳에서 에러가 발생할 가능성이 높아진거니까...!</p>
<p>** 2. 개선이 필요한 이유를 명확하게 전달하자.**
단순히 <code>A을 개선하면 어떨까요?</code> (q1) 보다는 <code>저는 B가 걱정되니 A을 개선하면 어떨까요?</code> (q2) 라는 피드백을 남기면 코드 작성자는 혼자서 B에 대한 충분한 고민을 진행한 후 A를 개선할지 말지를 결정할 수 있다. 내가 q1처럼 질문했다면, 코드작성자는 왜?라는 의문을 갖게 되고, 그에 대해서 나와 한번 더 대화를 나누어야 하지만 q2처럼 질문한다면 그 과정이 없어질 수 있다. 프로세스가 단축되는거다!</p>
<p>** 3. 긍정적인 피드백을 꼭 남기기 **
사람이라는 동물은 늘 칭찬을 갈구하고, 인정받고 싶어한다. 
아무것도 아닌 긍정적인 한마디, 칭찬이 오히려 성장에 대한 욕구를 불러 일으킨다는 것을 나는 개인적으로 경험했기 때문에 꼭 처음이든 마지막이든 수고했다거나 이전보다 성장했다거나, 코드가 깔끔하다거나 가독성이 좋다는 등의 칭찬을 포함하려고 노력한다.</p>
<p>** 4. 이건 정말 중요한데, 물어보자 **
모르는게 있다? 축하한다. 당신에게 축복인 사람을 만난거다. 
코드나, 리뷰에서 모르는 걸 발견했다면 수줍어말고 물어보자. 
그건 주니어의 특권이나 마찬가지니까. 
언제나 아낌없이 물음표를 날려보자. 얍????얍???</p>
<p>** 5. 코드 그 너머의 사람을 보자 **
아무리 코드가 거지같다고 해도, 가독성이 떨어진다고 해도, 왜 이런방식으로 구현했는지 모르겠다고 해도, 리뷰는 부드럽게 작성하자. 
내가 코드와 나를 분리한다고 해서, 상대방도 그럴 거라는 보장은 없다. 
내가 리뷰하는 대상이 코드라고 해서 그 글을 읽는 것도 코드는 아니니까. 늘 사람의 기분을 신경쓰고 리뷰를 하자. 
문자로 전달되는 글은 그 글을 쓰는 사람의 감정을 100% 전달해주지 못하니까 잘못 전달될 수도 있다는 걸 잊지말자. </p>
<p>** 6. 문제 해결 방식과, 리소스를 전달하자 **
이건 사소하다고 생각할 수 있지만 생각보다 코드 작성자에게 도움이 되는 정보다. 
내가 알고 있다고 상대도 알고 있는건 아니니까. 상대가 충분히 고민을 해서 이 방식을 선택했다고 해도 나는 그걸 아는 건 아니니까.
내가 아는 방식, 그 방식에 대한 리소스를 전달하면 그에 대한 피드백으로 내가 알고 있었는데 이 방식을 선택한 이유라던가, 아니면 몰랐는데 이런걸 시도해보겠다는 피드백이 돌아올거다. 
그러니까 일단 말해보자. 이게 지식공유의 아주 작은 시작이 될거다. </p>
<p>** 마지막으로 당연하겠지만, 이건 나의 기준이므로, 절대적으로 적용하려 하지 않으면 좋겠다..( by. 쭈구리 주니어)**</p>
<p>앞으로 모든 회사들이 코드 리뷰에 진심이 되기를 바라며 🙏
짧은 제 견해를 읽어주셔서 감사합니다. :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바스크립트 스터디 14, 15장]]></title>
            <link>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-14-15%EC%9E%A5</link>
            <guid>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-14-15%EC%9E%A5</guid>
            <pubDate>Fri, 15 Mar 2024 04:57:05 GMT</pubDate>
            <description><![CDATA[<h2 id="14장-전역변수의-문제점">14장 전역변수의 문제점</h2>
<h3 id="141-변수의-생명주기">14.1 변수의 생명주기</h3>
<p>변수는 선언에 의해 생성되고, 할당을 통해 값을 가짐. =&gt; 생명주기가 있음 (life cycle)</p>
<h4 id="q-만약-변수에-생명주기가-없다면">Q. 만약 변수에 생명주기가 없다면?</h4>
<p>한번 선언된 변수가 프로그램이 끝날 때까찌 영원히 메모리 공간을 점유함-&gt; 와.. 컴퓨터 터질듯</p>
<p>지역변수 </p>
<p>지역변수는 함수가 호출되면 생성되고, 함수가 종료되면 소멸함.
= <strong>지역변수의 생명주기는 함수의 생명주기와 일치</strong></p>
<blockquote>
<p>Q. 지역 변수가 함수보다 오래 생존하는 경우? 
변수의 생명주기 : 메모리 공간이 확보된 시점 ~ 메모리 공간이 해제되어 가용 메모리 풀에 반환 되는 시점
but, 만약 누군가 스코프를 참조하고 있으면 스코프는 소멸하지 않고 생존함</p>
</blockquote>
<p>호이스팅은 스코프 단위로 동작함.
지역변수의 호이스팅은 지역 변수의 선언이 지역 스코프의 선두로 끌어 올려진 것처럼 동작함.</p>
<p>전역변수
전역코드는 코드가 로드되지마자 곧바로 해석되고 실행되고, 더이상 실행될 문이 없을 때 종료함 
var키워드로 선언한 전역 변수는 전역객체의 프로퍼티가 됨.
브라우저 환경에서 전역객체는 window이고, 전역객체 window는 웹페이지를 닫기 전까지 유효함.
-&gt; <strong>전역 변수의 생명주기는 전역객체의 생명주기와 일치</strong></p>
<h3 id="142-전역변수의-문제점">14.2 전역변수의 문제점</h3>
<ol>
<li>암묵적 결합 : 모든 코드가 전역변수를 참조, 변경가능 -&gt; 가독성 나빠짐, 상태 변경의 위험이 큼</li>
<li>긴 생명주기 : 생명주기가 길어서, 메모리 리소스를 오랜기간 소비함, 상태 변경 가능 시간이 길고, 기회가 많음. -&gt; 의도하지 않은 코드 생성가능</li>
<li>스코프 체인 상에서 종점에 존재 : 전역 변수를 검색할 때 가장 마지막에 검색 -&gt; 검색속도가 제일 느림 </li>
<li>네임 스페이스 오염 : 파일이 분리되어있어도 스코프를 공유하기 때문에 동일한 이름으로 명명된 다른 식별자가 있을 경우 예상치 못한 문제 발생가능</li>
</ol>
<h3 id="143-전역-변수의-사용을-억제하는-방법">14.3 전역 변수의 사용을 억제하는 방법</h3>
<p>전역변수 사용을 가급적 금지하고, 변수의 스코프가 좁은 지역변수 사용을 권장. </p>
<ol>
<li>즉시실행함수</li>
<li>네임스페이스 객체 </li>
<li>모듈패턴</li>
<li>ES6 모듈</li>
</ol>
<h2 id="15장-let-const-키워드와-블록레벨-스코프">15장 let, const 키워드와 블록레벨 스코프</h2>
<h3 id="151-var-키워드로-선언한-변수의-문제점">15.1 var 키워드로 선언한 변수의 문제점</h3>
<ol>
<li>변수 중복 선언 허용</li>
<li>함수레벨 스코프</li>
<li>변수 호이스팅</li>
</ol>
<h3 id="152-let-키워드">15.2. let 키워드</h3>
<ol>
<li>변수 중복 선언 금지 
같은 이름의 변수를 선언시 문법 에러 (Syntax Error)발생</li>
<li>블록 레벨 스코프 
모든 코드 블록을 지역 스코프로 인정하는 블록레벨 스코프를 따름<h4 id="--q이건-지역-스코프라는데">-&gt; Q.이건 지역 스코프라는데..?</h4>
</li>
<li>변수 호이스팅
변수 호이스팅이 발생하지 않는 것처럼 동작함-&gt; 일시적 사각지대 (Temporal Dead Zone)발생가능. 
변수 선언 시점에 초기화 발생 -&gt; 선언 단계 / 초기화 단계 가 분리되어 진행되기 때문 -&gt; 
스코프 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간 발생 =&gt; ReferenceError</li>
<li>전역객체와 let 
let은 전역객체의 프로퍼티가 아님 -&gt; 개념적인 블록 내에 존재하게 됨
전역으로 선언해도 블록이 따로 만들어지고, 전역객체의 프로퍼티에 속하지 않게됨!</li>
</ol>
<h3 id="153-const-키워드">15.3 const 키워드</h3>
<ol>
<li>선언과 초기화 
선언과 초기화를 동시에 진행해야함. 아닐경우 -&gt; Syntax Error발생</li>
<li>재할당 금지 
당연함. 상수변수니까</li>
<li>상수
원시값을 할당한 경우 변수값을 변경할 수 없음.
보통 상수를 의미하는 변수의 경우 대문자 + snake_case로 표기
ex ) <code>const TAX_RATE = 0.1;</code> </li>
<li>const 키워드와 객체 
객체를 할당한 경우 값을 변경할 수 있음. </li>
</ol>
<h3 id="154-var-vs-let-vs-const">15.4 &quot;var&quot; vs &quot;let&quot; vs &quot;const&quot;</h3>
<p>ES6을 사용한다면, 변수 선언시 기본적으로 const를 사용, 재할당이 필요한 경우 let을 한정적으로 사용한다. 
let사용시 변수의 스코프는 최대한 좁게 사용하기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바스크립트 스터디 13장]]></title>
            <link>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-13%EC%9E%A5</link>
            <guid>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-13%EC%9E%A5</guid>
            <pubDate>Fri, 15 Mar 2024 04:36:11 GMT</pubDate>
            <description><![CDATA[<h3 id="131-스코프란">13.1 스코프란?</h3>
<p>스코프 (scope) 란 <strong>모든 식별자(변수이름, 함수이름, 클래스 이름 ,...)가 자신이 선언된 위치에 의해 결정된 다른 코드가 식별자 자신을 참조할 수 있는 유효범위</strong></p>
<p>자바스크립트 엔진이 이름이 같은 두개의 변수 중에서 어떤 변수를 찹조해야 할 것인지를 결정하는 것  = 식별자 결정 (identifier resolution)
이때 자바스크립트 엔진이 식별자를 검색할 때 어떤 규칙을 갖고 찾는데, 이 규칙을 스코프라고 볼 수도 있음</p>
<blockquote>
<p>자바스크립트 엔진은 언제나 <strong>코드의 문맥</strong>에 따라서 코드를 실행함.
 -&gt; 이 코드의 문맥 : 23장에서 설명하는 렉시컬 환경</p>
</blockquote>
<h4 id="q-만약-스코프가-없다면">Q. 만약 스코프가 없다면?</h4>
<p>같은 이름을 갖는 변수는 충돌을 일으킴. -&gt; 프로그램을 통틀어 전체에서 하나의 이름을 한번만 사용할 수 있음.
예를들어, 컴퓨터에서 똑같은 파일 이름을 쓸 수 없다면? 오우....쉣...
그래서 쓰는데 디렉토리 ! 디렉토리 내에서는 똑같은 이름을 쓸 수 없지만, 다른 디렉토리에서는 쓸 수 잇음 -&gt; 이게 바로 네임스페이스이고, js에서는 스코프.</p>
<h3 id="132-스코프의-종류">13.2 스코프의 종류</h3>
<ol>
<li><p>전역</p>
<ul>
<li>코드의 가장 바깥 영역</li>
<li>전역 스코프 (global scope)</li>
<li>변수 선언시 전역 변수 (global variable)이 됨</li>
<li>*<em>전역변수는 어디서든지 참조 가능 *</em></li>
</ul>
</li>
<li><p>지역</p>
<ul>
<li>함수의 몸체 내부</li>
<li>지역 스코프 (local scope)</li>
<li>변수 선언시 지역 변수 (local variable)이 됨</li>
<li><strong>지역 변수는 자신의 지역 스코프 + 하위 지역 스코프에서 유효</strong></li>
</ul>
<h4 id="q-그럼-코드-블록-내부는-지역인가">Q. 그럼 코드 블록 내부는 지역인가?</h4>
</li>
</ol>
<h3 id="133-스코프-체인">13.3 스코프 체인</h3>
<p> 함수는 충첩될 수 있으므로 함수의 지역 스코프도 중첩될 수 있다. 
 =&gt; 함수의 중첩에 의해 스코프가 <strong>계층적 구조</strong>를 갖게 됨 !!</p>
<blockquote>
<p>전역스코프 &lt;- outer 지역 스코프 &lt;- inner 지역 스코프 </p>
</blockquote>
<p>이것 처럼 모든 스코프는 하나의 계층적 구조로 연결되고, 모든 지역 스코프의 최상위 스코프는 전역 스코프이다. 
스코프가 계층적으로 연결된 것  = <strong>스코프 체인 (scope chain)</strong></p>
<p>식별자를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 식별자를 참조하는 코드의 스코프에서 시작해,상위 스코프 (-&gt;)방향으로 이동하며 식별자를 검색한다. 
따라서, 방향(단방향)으로 연결되어있기 때문에 <strong>상위스코프에서 유효한 식별자는 하위스코프에서 자유롭게 참조할 수 있지만, 하위 스코프에서 유효한 식별자를 상위 스코프에서 참조할 수는 없다.</strong></p>
<p>만약 전역 스코프에서도 식별자를 찾지 못했다면 ReferenceError가 발생한다. </p>
<h3 id="134-함수레벨-스코프">13.4 함수레벨 스코프</h3>
<p>지역은 함수 몸체 내부를 말한다. 이는, <strong>코드 블록이 아닌 함수에 의해서만 지역 스코프가 생성</strong>된다는 의미이다.
var키워드로 선언된 변수는 함수 레벨 스코프를 갖는다.(function level scope)</p>
<h4 id="q-그렇다면-코드-블록-내부는-어떤-스코프">Q. 그렇다면 코드 블록 내부는 어떤 스코프..?</h4>
<h3 id="135-렉시컬-스코프">13.5. 렉시컬 스코프</h3>
<p><strong>동적 스코프 : dynamic Scope</strong></p>
<ul>
<li>함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정함.</li>
<li>함수를 정의하는 시점에는 함수가 어디서 호출될지 알 수 없음 -&gt; 호출 시점에 동적으로 상위 스코프 결정 </li>
</ul>
<p><strong>렉시컬 스코프 / 정적 스코프 : lexical scope / static scope</strong></p>
<ul>
<li>함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정함. </li>
<li>대부분의 프로그래밍 언어는 렉시컬 스코프</li>
<li>함수가 호출된 위치는 상위 스코프 결정에 영향이 x</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 자바스크립트 스터디 23장]]></title>
            <link>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-23%EC%9E%A5</link>
            <guid>https://velog.io/@blank_/%EB%AA%A8%EB%8D%98-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8A%A4%ED%84%B0%EB%94%94-23%EC%9E%A5</guid>
            <pubDate>Fri, 15 Mar 2024 04:16:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="실행컨텍스트-execution-context">실행컨텍스트 execution context</h4>
<p>실행컨텍스트를 이해하면 자바스크립트의 동작원리를 이해할 수 있다. 키야</p>
</blockquote>
<ol>
<li>스코프를 기반으로 식별자와 식별자에 바인딩된 값 (식별자 바인딩)을 관리하는 방식</li>
<li>호이스팅이 발생하는 이유</li>
<li>클로저의 동작방식</li>
<li>태스크큐와 함께 동작하는 이벤트 핸들러</li>
<li>비동기 처리의 동작방식 
을 모두 이해할 수 있다<del>~
이 장에 대한 이해를 끝낸 뒤에 다시 생각해보자</del></li>
</ol>
<h3 id="231-소스코드의-타입">23.1 소스코드의 타입</h3>
<p>ECMAScript사양은 소스코드를 4가지 타입으로 구분</p>
<ol>
<li>전역코드 (global code)</li>
</ol>
<ul>
<li>전역에 존재하는 소스코드.</li>
<li>전역에 정의된 함수, 클래스등의 내부 코드는 포함 x     </li>
</ul>
<ol start="2">
<li>함수코드 (function code)<ul>
<li>함수 내부에 존재하는 소스코드</li>
<li>함수내부에 중첩된 함수, 클래스등의 내부 코드는 포함 x</li>
</ul>
</li>
<li>eval 코드 (eval code)<ul>
<li>빌트인 전역함수인 eval함수에 인수로 전달되어 실행되는 소스코드</li>
</ul>
</li>
<li>모듈 코드 (module code)<ul>
<li>모듈 내부에 존재하는 소스코드</li>
<li>모듈 내부의 함수, 클래스등의 내부 코드는 포함 x</li>
</ul>
</li>
</ol>
<p>각 타입에 따라 실행 컨텍스트를 생성하는 과정과 관리 내용이 다름</p>
<ol>
<li><strong>전역 코드 -&gt; 전역 실행 컨텍스트</strong></li>
</ol>
<ul>
<li>최상위 스코프인 전역 스코프를 생성. </li>
<li>var 키워드로 선언된 전역 변수와 함수선언문으로 정의된 전역함수를 전역객체의 프로퍼티와 메서드로 바인딩하고 참조하기 위해 전역객체와 연결</li>
</ul>
<ol start="2">
<li><strong>함수 코드 -&gt; 함수 실행 컨텍스트</strong></li>
</ol>
<ul>
<li>지역 변수, 매개변수, arguments 객체를 관리 </li>
<li>생성한 지역 스코프를 스코프 체인에 연결</li>
</ul>
<ol start="3">
<li>eval 코드 -&gt; eval 실행 컨텍스트</li>
</ol>
<ul>
<li>strict mode에서 자신만의 독자적인 스코프를 생성</li>
</ul>
<ol start="4">
<li>모듈 코드 -&gt; 모듈 실행 컨텍스트</li>
</ol>
<ul>
<li>module별로 독립적인 모듈 스코프를 생성 </li>
</ul>
<p>-&gt; 여기서 eval과 module은 다음에 다루기로.
전역 실행 컨텍스트, 함수 실행 컨텍스트를 위주로 진행함.</p>
<h3 id="232-소스코드의-평가와-실행">23.2 소스코드의 평가와 실행</h3>
<p>소스코드 평가과정 :</p>
<ul>
<li>실행컨텍스트 생성</li>
<li>선언문 실행</li>
<li>식별자를 기로 실행 컨텍스트가 관리하는 스코프에 등록</li>
</ul>
<p>소스코드 실행과정 : </p>
<ul>
<li>실행에 필요한 정보 (변수, 함수의 참조)를 실행 컨텍스트가 관리하는 스코프에서 검색해서 취득</li>
<li>변수값의 변경등 실행의 결과를 실행 컨텍스트가 관리하는 스코프에 등록</li>
</ul>
<pre><code class="language-javascript">var x = 1;</code></pre>
<p> 평가하는 과정 : 실행컨텍스트가 관리하는 스코프에 등록, undefined로 초기화
x : undefined</p>
<p>실행하는 과정 : x 변수의 등록 확인, 실행 결과 값 등록
x : 1</p>
<h3 id="233-실행-컨텍스트의-역할">23.3 실행 컨텍스트의 역할</h3>
<p>전역코드와 함수코드로 구성된 예제 &gt;</p>
<pre><code class="language-javascript">// 전역 변수 x, y 선언 
const x = 1;
const x = 2;

function foo(a){
  // 지역변수 x, y 선언
  const x = 10;
  const y = 20;

  console.log(a + x + y);
}
foo(100); // foo의 호출 순간 ①

console.log(x + y); // 3</code></pre>
<h4 id="q-이-코드의-평가-및-실행-순서는-어떻게-될까">Q. 이 코드의 평가 및 실행 순서는 어떻게 될까?</h4>
<p>내 생각 : </p>
<ol>
<li>foo , local x, local y, global x, global y 가 실행 컨텍스트에 등록</li>
<li>x = 1, y = 2 전역 변수를 각각 실행해서 찾아서 값을 변경</li>
<li>foo(100)호출</li>
<li>x = 10, y = 20 지역변수를 각각 실행해서 찾아서 값을 변경</li>
<li>console.log(a + x + y)실행</li>
<li>console.log(x + y)실행 </li>
</ol>
<p>이렇게 되지 않을까 생각했다. 
책을 읽어보니, 함수 호출의 순간, 함수의 실행 컨텍스트가 만들어진다고 한다. 
내가 1번으로 진행될 것이라고 생각한 local x, local y 가 3번 foo(100)호출 이후에 진행된다는 것이다. </p>
<p>그래서 실행 순서는 다음과 같다.</p>
<ol>
<li>전역 코드 평가</li>
<li>전역 코드 실행</li>
<li>함수 코드 평가</li>
<li>함수 코드 실행</li>
<li>함수 코드가 끝나면 다시 전역 코드 실행</li>
</ol>
<p>이처럼 코드가 실행되려면 <strong>스코프, 식별자, 코드 실행 순서등의 관리</strong>가 필요하다. 
이걸 해주는건 실행컨텍스트~
실행 컨텍스트는 <strong>소스코드를 실행하는 데 필요한 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역</strong>이다. 
<strong>식별자와 스코프는 렉시컬 환경으로 관리하고, 코드 실행 순서는 실행컨텍스트 스택으로 관리한다.</strong></p>
<h3 id="234-실행-컨텍스트-스택">23.4 실행 컨텍스트 스택</h3>
<p>제목에서 스포를 당했지만, 실행 컨텍스트는 스택 자료구조로 관리된다.
코드가 실행되는 시간의 흐름에 따라 실행 컨텍스트가 추가되고, 제거 된다.</p>
<p>함수의 실행시점에 함수의 실행 컨텍스트가 생성되고 실행 컨텍스트 스택에 추가되고, 
함수의 종료 시점에 함수의 실행 컨텍스트를 제거하고(pop), 이전 실행 컨텍스트를 실행한다. 
말로 설명하면 어려움 (그림을 보자 23-5)</p>
<h3 id="235-렉시컬-환경">23.5 렉시컬 환경</h3>
<p>Lexical Environment는 식별자와 식별자에 바인딩된 값, 상위 스코프에 대한 참조를 기록하는 자료구조이다.</p>
<p>사실, lexical Environtment 컴포넌트와 variable Environment 컴포넌트로 구성되는데, 하나의 동일한 렉시컬 환경을 참조한다. </p>
<h4 id="q-그렇다면-variable-environment-컴포넌트가-필요한-이유는-뭘까">Q. 그렇다면 variable Environment 컴포넌트가 필요한 이유는 뭘까?</h4>
<p>아무튼, 렉시컬 환경은</p>
<ol>
<li>환경 레코드 (Environment Record)
스코프에 포함된 식별자를 등록
등록된 식별자에 바인딩된 값을 관리하는 저장소</li>
<li>외부 렉시컬 환경에 대한 참조 (Outer Lexical Environment Reference)
상위 스코프를 가리킴 =  상위 코드의 렉시컬 환경
단방향 링크드 리스트인 스코프 체인을 구현함</li>
</ol>
<p>이렇게 두개로 구성된다.</p>
<h3 id="236-실행-컨텍스트의-생성과-식별자-검색-과정">23.6 실행 컨텍스트의 생성과 식별자 검색 과정</h3>
<p>이 단원에서는 이거 두개만 알면 된다.</p>
<ol>
<li>실행 컨텍스트가 생성되고 코드 실행 결과가 관리되는 방법</li>
<li>실행 컨텍스트를 통해 식별자를 검색하는 방법</li>
</ol>
<pre><code class="language-javascript">var x =1 ;
const y = 2; 

function foo (a){
  var x = 3;
  const y = 4;

  function bar (b){
      const z = 5;
    console.log(a+b+x+y+z);
  }
  bar(10);
}
foo(20);</code></pre>
<p>이 코드를 실행하기 위한 전체적인 순서는 다음과 같다.</p>
<ol>
<li><p>전역 객체의 생성</p>
</li>
<li><p>전역 코드의 평가</p>
<blockquote>
<p>전역 코드의 평가는 다음과 같은 순서로 진행된다.</p>
<ol>
<li>전역 실행 컨텍스트 생성</li>
<li>전역 렉시컬 환경 생성</li>
<li><ol>
<li>전역 환경 레코드 생성
ㄴ2.1.1. 객체 환경 레코드 생성
ㄴ2.1.2. 선언적 환경 레코드 생성</li>
</ol>
</li>
<li><ol start="2">
<li>this 바인딩</li>
</ol>
</li>
<li><ol start="3">
<li>외부적 렉시컬 환경에 대한 참조 결정</li>
</ol>
</li>
</ol>
</blockquote>
</li>
<li><p>전역 코드의 실행</p>
</li>
<li><p>foo 함수 코드 평가</p>
<blockquote>
<p>함수 코드 평가는 다음과 같은 순서로 진행된다.</p>
<ol>
<li>함수 실행 컨텍스트 생성</li>
<li>함수 렉시컬 환경 생성</li>
</ol>
</blockquote>
</li>
<li><ol>
<li>함수 환경 레코드 생성</li>
</ol>
</li>
<li><ol start="2">
<li>this 바인딩</li>
</ol>
</li>
<li><ol start="3">
<li>외부 렉시컬 황경에 대한 참조 결정</li>
</ol>
</li>
<li><p>foo 함수 코드 실행</p>
</li>
<li><p>bar 함수 코드 평가</p>
</li>
<li><p>bar 함수 코드 실행</p>
<blockquote>
<p>console.log(a+ b+x+y+z)실행 </p>
<ol>
<li>console 식별자 검색</li>
<li>log 메소드 검색</li>
<li>표현식 a+b+x+y+z의 평가</li>
</ol>
</blockquote>
</li>
<li><p>bar 함수 코드 실행 종료</p>
</li>
<li><p>foo 함수 코드 실행 종료</p>
</li>
<li><p>전역 코드 실행 종료</p>
</li>
</ol>
<p>이렇게 10단계의 순서가 끝나면 실행 컨텍스트에 아무것도 남아있지 않게 된다.</p>
<h3 id="237-실행-컨텍스트와-블록-레벨-스코프">23.7 실행 컨텍스트와 블록 레벨 스코프</h3>
<p>var 로 선언한 변수 : 함수레벨 스코프
let, const 변수 : 블록레벨 스코프 </p>
<p>이때, 블록레벨 스코프를 생성하기 위해 선언적 환경 레코드를 갖는 렉시컬 환경을 새롭게 생성하여 기존의 렉시컬 환경을 교체한다. 새롭게 생성된 코드 블럭을 위한 렉시컬 환경의 외부 렉시컬 환경에 대한 참조는 교체하기 이전의 렉시컬 환경을 가리킨다.
-&gt; 따라서 스코프 체인이 끊기지 않고 이어짐!</p>
<p><code>for (let i = 0; i &lt; 5; i++){}</code> 와 같이 for 문의 변수 선언문에 let키워드를 사용하면, 코드블록이 반복해서 실행될 때마다 코드블록을 위한 새로운 렉시컬 환경을 생성하고, for문이 포함된 상위 스코프와 독립적인 렉시컬 환경을 생성해 식별자의 값을 유지한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[github에 올라가면 안되는 파일을 올려버렸다면..?]]></title>
            <link>https://velog.io/@blank_/github%EC%97%90-%EC%98%AC%EB%9D%BC%EA%B0%80%EB%A9%B4-%EC%95%88%EB%90%98%EB%8A%94-%ED%8C%8C%EC%9D%BC%EC%9D%84-%EC%98%AC%EB%A0%A4%EB%B2%84%EB%A0%B8%EB%8B%A4%EB%A9%B4</link>
            <guid>https://velog.io/@blank_/github%EC%97%90-%EC%98%AC%EB%9D%BC%EA%B0%80%EB%A9%B4-%EC%95%88%EB%90%98%EB%8A%94-%ED%8C%8C%EC%9D%BC%EC%9D%84-%EC%98%AC%EB%A0%A4%EB%B2%84%EB%A0%B8%EB%8B%A4%EB%A9%B4</guid>
            <pubDate>Wed, 13 Mar 2024 04:55:22 GMT</pubDate>
            <description><![CDATA[<p>트센 깃허브에 올리면 안되는 .env를 올려버렸다. 
그때는 private으로 둘거니까 상관없다고 생각했었는데, public으로 돌려야하는 상황이 되면서 문제가 발생했다.</p>
<p>아니나 다를까, public으로 돌리자마자 이메일이 도착했다.
<img src="https://velog.velcdn.com/images/blank_/post/4d96f776-c031-4c21-a1dd-559ca49a6829/image.png" alt="">
내용은 시크릿키 누출이 의심된다는 경고메일이었다.</p>
<p>맞다. GitGuardian은 똑똑했다. 
나는 웹에서 인증용 메일을 전송하기 위해 gmail 을 이용해 smtp 메일 발송을 하는 코드를 만들었는데, 이 과정에서 <strong>gmail의 client secret</strong>이 .env에 포함되었고, 그게 누출이 된거다.</p>
<p>그래서 .env를 삭제해야했다.
만약 commit history에서는 삭제하지 않아도 되거나, 혹은 로컬에서 삭제하면 된다면 이 방법을 쓰자.
이건 그렇게 어렵지 않았다. 이과정은 알고 있었고, 익숙했다.</p>
<ol>
<li>혹시 원격 저장소에 저장되어있다면 <code>git rm --cached [File Name]</code> 명령어로 원격 저장소에 저장된 file을 삭제하고,</li>
<li>로컬에 저장된 파일을 다른 곳으로 옮기거나, 삭제하고,</li>
<li>gitignore파일에 삭제해야하는 파일에 대해 추가해주고, </li>
<li>gitignore를 add, commit, push 해주면 되었다. </li>
</ol>
<p>근데 문제는 이거였다. </p>
<h4 id="q-이전에-커밋한-기록들에-포함된-env는-어떻게-하지">Q. 이전에 커밋한 기록들에 포함된 .env는 어떻게 하지?</h4>
<p> 커밋 히스토리에서도 파일을 삭제할 수 있나??</p>
<p> 찾아보니,  <code>git filter-branch</code> 명령어가 있었다. 
 git에서 히스토리를 변경하는데 사용하는 명령어인데, 특정 디렉토리나 파일을 히스토리에서 제거하는데도 유용하게 사용할 수 있었다. </p>
<p> 로컬 레파지토리 root level에서 <code>git filter-branch -f --index-filter &quot;git rm --cached --ignore-unmatch backend/.env&quot; --prune-empty -- --all</code> 를 입력했다. </p>
<p>명령어의 의미는 이렇다고한다. -f로 강제로 덮어쓰고, --index-filter로 각 커밋에서 특정 파일을 필터링해서 제거하고, --prune-empty 파일이 제거된 뒤에 커밋이 빈커밋이면 커밋을 제거하고, --all 모든 브랜치와 태그에 대해서 작업을 수행하는 것.</p>
<p>순식간에 엄청나게 많은 로그들이 지나가고~~
나는 불안해졌다. 혹시 모든 커밋들이 다 지워졌으면 어떻게하지? 
그래서 git log로 확인했다. 다행히 지워지진 않았더라. 휴...</p>
<p>끝으로 <code>git push --force --all</code>명령어로 main브랜치에 강제로 푸시를 했다.</p>
<p>이렇게 올라가면 안되는 파일을 삭제하는 작업도 완료~~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[git 레파지토리 분리]]></title>
            <link>https://velog.io/@blank_/git-%EB%A0%88%ED%8C%8C%EC%A7%80%ED%86%A0%EB%A6%AC-%EB%B6%84%EB%A6%AC</link>
            <guid>https://velog.io/@blank_/git-%EB%A0%88%ED%8C%8C%EC%A7%80%ED%86%A0%EB%A6%AC-%EB%B6%84%EB%A6%AC</guid>
            <pubDate>Wed, 13 Mar 2024 04:32:00 GMT</pubDate>
            <description><![CDATA[<p>어디있니 프로젝트를 진행할 때는 springboot과 react를 이용해서 프로젝트를 만들면 하나의 레파지토리에 담아야한다고 생각했었다.</p>
<p>이후 여러개의 프로젝트를 진행하다보니 백엔드와 프론트의 레파지토리가 분리되어있는 게 훨씬 작업하기도 다른 사람들이 코드를 읽기도 편하다는 것을 알게 되었다. </p>
<p>늦었지만 지금이라도 분리해보려고 한다.</p>
<p>목표는 다음과 같다. 
where42 레파지토리의 src/main/where42에 담겨있는 모든 프론트엔드 파일을 새로운 레파지토리인 where42_front_v1레파지토리에 옮기는 것이다.</p>
<p>그래서 다음과 같이 진행했다. </p>
<h4 id="1-우선-새로운-레파지토리인-where42_front_v1을-생성한다">1. 우선 새로운 레파지토리인 where42_front_v1을 생성한다.</h4>
<p><img src="https://velog.velcdn.com/images/blank_/post/43898c74-db0c-4395-bf2f-e1d19d124dcb/image.png" alt=""></p>
<h4 id="2-where42-와-where42_front_v1레파지토리를-각각-로컬에-clone해준다">2. where42 와 where42_front_v1레파지토리를 각각 로컬에 clone해준다.</h4>
<p>(나는 이때 혹시 몰라서 예전에 클론해둔 디렉토리가 아닌 새로운 디렉토리를 생성해서 작업했다.)
<img src="https://velog.velcdn.com/images/blank_/post/58e074f5-7020-4122-81ff-eb0924b095c0/image.png" alt=""></p>
<h4 id="3-작업물이-들어있는-where42디렉토리로-이동해서-subtree를-생성해준다">3. 작업물이 들어있는 where42디렉토리로 이동해서, subtree를 생성해준다.</h4>
<p>subtree를 생성하면 작업 했던 커밋 내역도 함께 분리된다.
이때 명령어는 <code>$ git subtree split -P &lt;옮길 폴더 명&gt; -b &lt;브랜치 이름&gt;</code> 이다.</p>
<p><img src="https://velog.velcdn.com/images/blank_/post/ddadf6bc-1bc6-4ca8-9f0d-be9b446b734e/image.png" alt=""></p>
<h4 id="4-subtree-생성이-끝나면-작업물을-복사할-디렉토리로-이동한뒤-생성한-subtree를-pull-받는다">4. subtree 생성이 끝나면 작업물을 복사할 디렉토리로 이동한뒤, 생성한 subtree를 pull 받는다.</h4>
<p>이때 명령어는 <code>git pull &lt;기존 레파지토리&gt; &lt;브랜치명&gt;</code>이다. </p>
<h4 id="5-git-remote--v-명령어로-옮겨-갈-레파지토리가-잘-연결-되었는지-확인-후-git-push를-진행한다">5. <code>git remote -v</code> 명령어로 옮겨 갈 레파지토리가 잘 연결 되었는지 확인 후 <code>git push</code>를 진행한다.</h4>
<p><img src="https://velog.velcdn.com/images/blank_/post/f89bbaf6-f5eb-46b7-940f-2bf86eb18480/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/blank_/post/f0e0d3ed-50c8-407e-ad54-73f883e041b7/image.png" alt=""></p>
<h4 id="6-git-log-명령어로-commit-history도-잘-복사되었는지-확인한다">6. <code>git log</code> 명령어로 commit history도 잘 복사되었는지 확인한다.</h4>
<p>복사끄읕<del>~</del></p>
<p>이제 이전 레파지토리에 남아있는 디렉토리가 필요가 없다면 rm-rf로 삭제하고, git commit도 진행해주면 된다. </p>
<p>나는 이전 레파지토리에 남아있는 디렉토리의 내용은 삭제하고 새로 만든 레파지토리의 서브모듈을 연결해주려고 한다.</p>
<p>삭제를 위해 브랜치를 생성하고, 삭제를 진행하고, 커밋을 하고, 풀리퀘를 올리고, 머지까지 진행했다.</p>
<h4 id="이제-서브모듈을-연결한다">이제, 서브모듈을 연결한다.</h4>
<p><code>git submodule add &lt;연동할 깃 저장소 주소.git&gt; &lt;연동할 메인 저장소의 경로&gt;</code> 이게 서브모듈의 기본 명령어인데, 
나의 경우엔 where42/src/main/where42에 where42_front_v1을 연결해주려고 하므로, 
<code>git submodule add &lt;where42_front_v1 레파지토리 주소&gt; src/main/where42</code> 를 where42 디렉토리의 루트레벨에서 진행해준다.</p>
<p><img src="https://velog.velcdn.com/images/blank_/post/8f40f47d-6193-4ca7-86ab-9377feb7151a/image.png" alt=""></p>
<h4 id="마지막으로-서브모듈-업데이트를-해준다">마지막으로, 서브모듈 업데이트를 해준다.</h4>
<p>서브모듈이 추가되면 <code>git submodule update --init --recursive</code> 서브모듈을 초기화하고 업데이트해주는 명령어로 서브모듈을 업데이트한다. </p>
<p>서브모듈이 업데이트 되면 .gitmodules 파일에 서브모듈 관련 정보가 추가된다. 이 파일을 다시 브랜치에서 commit하고
풀리퀘하고, 머지까지 해주면..?</p>
<p>서브모듈까지 완성<del>~</del></p>
<p><img src="https://velog.velcdn.com/images/blank_/post/23869063-901c-4ac1-8033-b68c91f6d1c2/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[383. Ransom Note]]></title>
            <link>https://velog.io/@blank_/leetcode383</link>
            <guid>https://velog.io/@blank_/leetcode383</guid>
            <pubDate>Sun, 25 Jun 2023 05:25:17 GMT</pubDate>
            <description><![CDATA[<h2 id="🚥-문제-🚥">🚥 문제 🚥</h2>
<p>Top Interview 150 중 <a href="https://leetcode.com/problems/ransom-note/description/?envType=study-plan-v2&amp;envId=top-interview-150">383. Ransom Note</a></p>
<p>🍿 난이도 : 리트코드 기준 Easy
🚧 알고리즘 : hash table</p>
<h2 id="🚦-풀이1-🚦">🚦 풀이1 🚦</h2>
<pre><code>class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -&gt; bool:
        for alpha in ransomNote:
            if alpha in magazine:
                i = magazine.find(alpha)
                magazine = magazine[:i] + magazine[i+1:]
            else:
                return False
        return True</code></pre><p>🚧 우선 생각나는대로, ransomnote에 있는 알파벳이 magazine에 있는지 확인했다.</p>
<p>🚧 만약 있는 알파벳이라면 magazine을 해당 알파벳을 뺀 값으로 변경했고, 없는 알파벳이라면 바로 false를 돌려줬다. </p>
<h3 id="풀이-결과">풀이 결과</h3>
<p> <img src="https://velog.velcdn.com/images/blank_/post/b04b5a23-00e1-47fc-9e09-e7a04ce79e2e/image.png" alt="결과1"></p>
<p> 나름 괜찮은 런타임과 메모리사용량을 보여줬고, 바로 풀었기 때문에 나쁘지 않았지만, 이 문제가 해쉬를 이용하는 문제로 출제되었음을 알고 있었기 때문에, 다시 풀어야겠다고 생각했다.</p>
<h2 id="🚦-풀이2-🚦">🚦 풀이2 🚦</h2>
<pre><code>class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -&gt; bool:
        notecnt = Counter(ransomNote)
        magcnt = Counter(magazine)
        for i in notecnt :
            if (magcnt[i] &lt; notecnt[i]):
                return False
        return True</code></pre><p>🚧 다른 사람이 풀어낸 문제를 풀어내면 좋은 순서를 읽다가, <strong>Counter를 이용</strong>할 수 있다는 것을 알았다.</p>
<p>🚧 두 string을 각각 counter객체로 만들어줬다.</p>
<pre><code>&quot;a&quot;   =&gt; Counter({&#39;a&#39;: 1})
&quot;aab&quot; =&gt; Counter({&#39;a&#39;: 2},{&#39;b&#39;: 1})</code></pre><p>🚧 notecnt에 있는 하나의 dict마다, 동일한 키값을 갖고 있는 dict를 magcnt에서 찾아서, value값을 비교해줬다. value값이 notecnt의 value값보다 작으면, 사용할 수 있는 알파벳이 충분하지 않다는 의미이므로 false를 리턴했다.</p>
<h3 id="풀이-결과-1">풀이 결과</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/1bd15817-ccbe-42c0-8973-c3cb81a02ee7/image.png" alt="결과2"></p>
<p>런타임 속도가 조금 느려졌지만, 메모리 사용량이 줄어들었다. 
📌 hash를 위해서 counter객체를 사용한다는 사실을 알게되었다.</p>
<h3 id="타인의-코드">타인의 코드</h3>
<pre><code>notecnt = Counter(ransomNote)
magcnt = Counter(magazine)
return (notecnt &amp; magcnt == notecnt)</code></pre><p>Counter() 함수는 collections module에 포함되어있다.
카운터 객체의 사용방법을 더 다양하게 알게되었다.
<code>counter객체 &amp; counter객체</code>를 하면, 결과값은 counter객체인데, dict의 값에 key는 동일하고, value중에서 중복값만 저장된다.
&amp; 연산이 조금 시간이 오래걸리겠지만, 코드가 간결해지고, 의미가 바로 전달된다는 점에서 좋다고 생각했다.</p>
<p>추가적으로, counter객체에 저장할 수 있는 예제를 공부해보자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[71. Simplify Path]]></title>
            <link>https://velog.io/@blank_/leetcode71</link>
            <guid>https://velog.io/@blank_/leetcode71</guid>
            <pubDate>Fri, 23 Jun 2023 09:10:41 GMT</pubDate>
            <description><![CDATA[<h2 id="🚥-문제-🚥">🚥 문제 🚥</h2>
<p>Top Interview 150 중
<a href="https://leetcode.com/problems/simplify-path/description/?envType=study-plan-v2&amp;envId=top-interview-150">71. Simplify Path</a></p>
<p>🍿 난이도 : 리트코드 기준 Medium
🚧 알고리즘 : stack</p>
<h2 id="🚦-풀이1-🚦">🚦 풀이1 🚦</h2>
<pre><code>class Solution:
    def simplifyPath(self, path: str) -&gt; str:
        stack = []
        paths = path.split(&#39;/&#39;)
        for dir in paths:
            if (dir == &#39;&#39;):
                if (stack and stack[-1] == &#39;/&#39;):
                    continue
                else:
                    stack.append(&#39;/&#39;)
            else:
                if (dir == &#39;.&#39; or dir == &#39;..&#39;):
                    continue
                if (stack and stack[-1] == &#39;/&#39;):
                    stack.append(dir)
        if (len(stack) &gt; 1 and stack[-1] == &#39;/&#39;):
            stack.pop()
        return &quot;&quot;.join(stack)</code></pre><p>🚧 python의 stack을 위해 list 자료구조를 사용했다. </p>
<p>🏗 paths : 입력값 path가 <code>&quot;/&quot;</code>를 기준으로 나누어진 리스트</p>
<p>🚧 <code>&quot;/&quot;</code>를 기준으로 나누게 되면서, path의 기준이 되는 <code>&quot;/&quot;</code>값이 사라졌다. 
세번째 예시인 <code>&quot;/home//foo/&quot;</code>입력값을 기준으로, paths에 들어가는 값은 <code>[&#39;&#39;, &#39;home&#39;, &#39;&#39;, &#39;foo&#39;, &#39;&#39;]</code>이다.
그래서 <code>&quot;&quot;</code>로 들어간 값에 대해서 <code>&quot;/&quot;</code>라고 생각하고 진행했다. 중복 값을 제거해주기 위해 <code>&quot;/&quot;</code>가 stack의 top에 있고, 지금 디렉토리가 <code>&quot;/&quot;</code>일 경우에는 continue로 지나쳤다.</p>
<p>🚧 처음에는 문제를 읽을 때, <code>&quot;.&quot;, &quot;..&quot;</code>은 각각 현재 디렉토리, 부모디렉토리를 가리키는 값이고, 이 값들에 대해서는 제외하고 파싱하라는 것으로 이해했다. 그래서, 
<code>if (dir == &#39;.&#39; or dir == &#39;..&#39;): continue</code>와 같이 <code>&quot;.&quot;, &quot;..&quot;</code>에 대해서는 무시하도록 했다. </p>
<h3 id="풀이-결과--실패">풀이 결과 : 실패</h3>
<p> 🥲 테스트케이스 <strong><code>&quot;/a/./b/../../c/&quot;</code></strong>에서 실패했다.</p>
<p>위의 테스트케이스를 통해서 <code>&quot;.&quot;, &quot;..&quot;</code>에 대해서도 생각해줘야 한다는 것을 알게되었다. 
<strong>이미 위치가 루트인 경우를 제외</strong>하고는 모두 적용해줘야 하는 문제였다.
테스트케이스에서 <code>&quot;/&quot;</code>에 대한 처리만 하고 난 이후의 path값은 <code>&quot;/a/b/../../c&quot;</code>가 된다. 
풀어보면, a디렉토리 하위에 b디렉토리가 있고, 그 부모의 부모의 디렉토리에서 다시 c디렉토리로 이동해야한다. 
그래서 b 디렉토리의 부모인 a, a의 부모인 root까지 가서, 다시 c디렉토리로 이동했기 때문에 이 테스트케이스의 답은 <strong><code>&quot;/c&quot;</code></strong>가 된다. 아래와 같은 폴더 구조를 가지고 있다고 생각하면 쉽다.</p>
<blockquote>
<p>root
├ a/
├├ b/ 
├├ c/</p>
</blockquote>
<h2 id="🚦-풀이2-🚦">🚦 풀이2 🚦</h2>
<pre><code>class Solution:
    def simplifyPath(self, path: str) -&gt; str:
        stack = []
        paths = path.split(&#39;/&#39;)
        for dir in paths:
            if (dir == &#39;&#39;):
                if not stack:
                    stack.append(dir)
            else:
                if (dir == &#39;.&#39;):
                    continue
                if (dir == &quot;..&quot;):
                    if (len(stack) &gt; 1):
                        stack.pop()
                    continue
                stack.append(dir)
        if (len(stack) == 1):
            return &quot;/&quot;
        return &quot;/&quot;.join(stack)</code></pre><p>🚧 <strong>join할 때 <code>&#39;/&#39;</code>를 기준으로 다시 넣어줄 수 있다</strong>는 것을 떠올리고, 이전에 풀때 리스트의 <code>&#39;&#39;</code>값을 스택에 <code>&#39;/&#39;</code>로 넣어주는 부분을 삭제했다. 
첫 <code>&#39;/&#39;</code>를 제외하고는 <code>&#39;//&#39;</code>값이 있을 때만 <code>&#39;&#39;</code>값이 생긴다는 것을 알게 되어, <code>&#39;/&#39;</code>값은 처음빼고 모두 무시했다. </p>
<p>🚧 단독으로 stack에 들어있는 <code>&#39;&#39;</code>값은 <code>&#39;/&#39;</code>와 join될 때 결과가 &#39;&#39;로 나왔기 때문에, <strong>마지막에 stack에 1개의 값만 있을 때는 <code>&#39;/&#39;</code>을 리턴</strong>해주었다.</p>
<p>🚧 이번에 작성할 때는 위의 테스트 케이스를 참고해서 만들었다.
사실 현재 디렉토리를 의미하는 <code>&#39;.&#39;</code>은 이 문제에서는 의미가 없기 때문에, continue를 이용해서 무시했다.</p>
<p>🚧 <code>&#39;..&#39;</code>은 stack에 값이 있을 경우 stack에 쌓인 원소를 하나 삭제했다. 
앞의 디렉토리가 사라졌으므로, 부모디렉토리 단계로 올라간다는 의미다. 
stack에 값이 없을 경우엔, 이미 root디렉토리에 있다는 의미이므로 continue를 통해 무시했다.</p>
<p>🚧 마지막으로 <code>&#39;.&#39;, &#39;..&#39;, &#39;&#39;</code>이 아닌 리스트의 원소들은 stack에 쌓아주었다.</p>
<h3 id="풀이-결과">풀이 결과</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/37788ef8-cb66-4a49-a87c-fdae8ad6c1e0/image.png" alt="결과1">
나쁘지 않은 결과였지만, 통과하고도 논리적으로 중복되는 것 같은 부분이 있다고 느꼈다. </p>
<h2 id="🚦-풀이3-🚦">🚦 풀이3 🚦</h2>
<pre><code>class Solution:
    def simplifyPath(self, path: str) -&gt; str:
        stack = []
        paths = path.split(&#39;/&#39;)
        for dir in paths:
            if (dir == &#39;.&#39; or dir == &#39;&#39;):
                continue
            if (dir == &quot;..&quot;):
                if stack:
                    stack.pop()
                continue
            stack.append(dir)
        return &quot;/&quot; + &quot;/&quot;.join(stack)</code></pre><p>🚧 훨씬 훨씬 간결해졌다.</p>
<p>🚧 첫 번째 <code>&#39;&#39;</code>를 처리하는 부분과, 맨 마지막에 스택에 하나남은 <code>&#39;&#39;</code>를 처리하는 부분을 다듬었다. <strong>리스트에 존재하는 모든 <code>&#39;&#39;</code>를 무시하고, 마지막에 join한 result string에 <code>&#39;/&#39;</code>를 맨 앞에 더해서 정답문자열</strong>을 만들었다.</p>
<p>🚧 이전풀이와 같이 <code>&#39;.&#39;</code>은 무시했고, <code>&#39;..&#39;</code>의 경우 stack에 있을 때만 pop하고 나머지의 경우 무시했다.</p>
<h3 id="풀이-결과-1">풀이 결과</h3>
<p> <img src="https://velog.velcdn.com/images/blank_/post/78eb7c2b-0e0a-46eb-a674-c0987d52ec92/image.png" alt="결과2"></p>
<p>이정도로 좋아질 거라고 기대하진 않았는데, &#39;/&#39;처리 부분만 수정했을 뿐인데 런타임 속도와 메모리부분에서 개선이 엄청 많이 되었다. </p>
<blockquote>
<h3 id="🥷-solution-cheat">🥷 Solution Cheat</h3>
</blockquote>
<pre><code>class Solution:
    def simplifyPath(self, path: str) -&gt; str:
        return __import__(&#39;os&#39;).path.abspath(path)</code></pre><p>타인의 코드. 
<code>os.path.abspath()</code> 함수는 <strong>Python의 내장 모듈인 os 모듈</strong>에 있는 함수이고, 주어진 경로를 절대 경로로 변환해주는 기능을 제공한다.
주어진 path를 절대 경로로 변환한 후 그 결과를 반환하는 아주아주 짧고 충격적인 코드였다. 
별거아닌데, 나도  os모듈을 알고 있었는데. 전혀 생각지도 못했다.
이런 코드를 망설임없이 내뱉을 수 있어야 파이썬 마스터라고 할 수 있는걸까?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[17. Letter Combinations of a Phone Number]]></title>
            <link>https://velog.io/@blank_/leetcode17</link>
            <guid>https://velog.io/@blank_/leetcode17</guid>
            <pubDate>Tue, 20 Jun 2023 02:07:50 GMT</pubDate>
            <description><![CDATA[<h2 id="🚥-문제-🚥">🚥 문제 🚥</h2>
<p>Top Interview 150 중
<a href="https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/?envType=study-plan-v2&amp;envId=top-interview-150">17. Letter Combinations of a Phone Number</a></p>
<p>🍿 난이도 : 리트코드 기준 Medium
🚧 알고리즘 : 백트랙킹 (backtracking)</p>
<h2 id="🚦-풀이1-🚦">🚦 풀이1 🚦</h2>
<pre><code>class Solution:
    def letterCombinations(self, digits: str) -&gt; List[str]:
        letter = {&quot;2&quot;: [&quot;a&quot;,&quot;b&quot;,&quot;c&quot;], &quot;3&quot;: [&quot;d&quot;,&quot;e&quot;,&quot;f&quot;], &quot;4&quot;:[&quot;g&quot;,&quot;h&quot;,&quot;i&quot;], &quot;5&quot;:[&quot;j&quot;,&quot;k&quot;,&quot;l&quot;], \
                &quot;6&quot;:[&quot;m&quot;,&quot;n&quot;,&quot;o&quot;], &quot;7&quot;:[&quot;p&quot;,&quot;q&quot;,&quot;r&quot;,&quot;s&quot;], &quot;8&quot;:[&quot;t&quot;,&quot;u&quot;,&quot;v&quot;], &quot;9&quot;:[&quot;w&quot;,&quot;x&quot;,&quot;y&quot;,&quot;z&quot;]}
        zipping = []
        for num in digits:
            # print(letter[num])
            if len(zipping):
                newzip = []
                for i in zipping:
                    for j in letter[num]:
                        newzip.append(i + j)
                zipping = newzip
            else:
                zipping = letter[num]
        return (zipping)</code></pre><p>🚧 python의 list, dictionary 자료구조를 사용했다. </p>
<p>🚧 백트랙킹 문제인걸 알았지만 바로 어떻게 구현할 지 생각이 나지 않아서 우선 <strong>brute force</strong>로 풀어봤다.</p>
<p>🏗 <strong>letter</strong> : 문제에서 제시한 번호판대로 만들어 둔 dictionary.
🏗 <strong>zipping</strong> : 최종 결과값을 담는 배열
🏗 <strong>new zip</strong> : 한번 순환할 때 임시로 값을 저장하는 배열 </p>
<p>🚧 digits에 들어있는 번호와 같은 번호를 letter에서 찾아서 해당하는 알파벳 리스트를 하나씩 돌아가면서 진행한다.</p>
<p>🚧 만약, zipping에 담겨있는 값이 없다면, 1번째 순회이므로, letter에서 digits[0]에 해당하는 알파벳 리스트를 찾아서 zipping에 넣어준다. </p>
<p>🚧 만약, zipping에 담겨있는 값이 있다면, 2번째 순회이므로, 앞서 넣어둔 zipping의 값들과 현재 digits에 해당하는 알파벳 리스트를 <strong>1:1로 매칭</strong>해서 newzip 리스트에 담는다. 한번의 순회가 끝나면 새로 만든 newzip의 값으로 zipping의 값들을 대체한다.</p>
<h3 id="풀이-결과">풀이 결과</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/9b3f6471-d638-4869-8d85-6f85c74bf0c0/image.png" alt="결과1">
나쁘지 않은 결과였지만, 백트랙킹 방식으로도 풀어보고 싶어서 다시 문제를 풀었다.</p>
<h2 id="🚦-풀이2-🚦">🚦 풀이2 🚦</h2>
<pre><code>class Solution:
    def letterCombinations(self, digits: str) -&gt; List[str]:
        if (digits == &quot;&quot;):
            return []
        letter = {&quot;2&quot;: [&quot;a&quot;,&quot;b&quot;,&quot;c&quot;], &quot;3&quot;: [&quot;d&quot;,&quot;e&quot;,&quot;f&quot;], &quot;4&quot;:[&quot;g&quot;,&quot;h&quot;,&quot;i&quot;], &quot;5&quot;:[&quot;j&quot;,&quot;k&quot;,&quot;l&quot;], \
                &quot;6&quot;:[&quot;m&quot;,&quot;n&quot;,&quot;o&quot;], &quot;7&quot;:[&quot;p&quot;,&quot;q&quot;,&quot;r&quot;,&quot;s&quot;], &quot;8&quot;:[&quot;t&quot;,&quot;u&quot;,&quot;v&quot;], &quot;9&quot;:[&quot;w&quot;,&quot;x&quot;,&quot;y&quot;,&quot;z&quot;]}
        if (len(digits) == 1):
            return letter[digits]
        beforelist = self.letterCombinations(digits[:-1])
        newlist = [i+j for i in beforelist for j in letter[digits[-1]]]
        return newlist</code></pre><p>🚧 이번에는 <strong>재귀방식</strong>으로 풀었다. 백트랙킹 알고리즘은 경우의 수를 나무가지처럼 뻗어나가다가 조건에 맞지 않으면 거기서 다시 나무가지의 분기점으로 돌아와서 다른방향으로 진행하는 방식이라고 알고 있었는데, 문득 재귀로 했던 것 같은 생각이 스쳤다. 재귀를 도입해보기로 했다.</p>
<p>🏗 <strong>letter</strong> : 문제에서 제시한 번호판대로 만들어 둔 dictionary.
🏗 <strong>beforelist</strong> : 재귀함수에서 돌려받은 리스트
🏗 <strong>newlist</strong> : return할 리스트</p>
<p>🚧 digits기준으로 뒤에 있는 알파벳부터 하나씩 보면서 재귀를 진행한다. 가장 깊은 단계의 재귀에서는 맨 앞의 1개의 digit을 보고, 해당하는 알파벳 리스트를 담아서 돌려준다. 중간 단계들에서는 앞서 받은 beforelist와 현재 digit에 맞는 알파벳리스트를 1:1매칭해서 새로운 newlist에 담아준다. 이때는 2중 for문 대신 list comprehension을 사용해봤다.</p>
<p>🚧 leetcode에서 제공해주는 solution class 안에 있는 함수를 사용할 때는 <code>self.letterCombinations(digits[:-1])</code> 이런 식으로 작성해줘야한다는 것을 처음알았다. <strong>두둥탁.</strong> 이와 관련해서 찾아본 참고질문 -&gt; <a href="https://leetcode.com/discuss/general-discussion/1178931/Why-would-LeetCode-request-self.functionName()-in-recursive-functions/919748">leetcode general discussion</a></p>
<h3 id="풀이-결과-1">풀이 결과</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/157859f0-4f24-4d36-bff7-320df0273a60/image.png" alt="결과2"></p>
<p>와 메모리 기준에서 90퍼 달성해본 건 처음이다. 
백트랙킹 방식에 가까워진듯하지만 여전히 백트랙킹 방식은 아닌 것 같다고 느꼈다. 어디서? 가지치기가 없다는 점에서. </p>
<p>가지치기는 효율을 위해 도입한 백트랙킹 알고리즘의 핵심개념이다.
앞서 말했던 백트랙킹 알고리즘에 대한 설명 <del>백트랙킹 알고리즘은 경우의 수를 나무가지처럼 뻗어나가다가 <strong>조건에 맞지 않으면</strong> 거기서 다시 나무가지의 분기점으로 돌아와서 다른방향으로 진행하는 방식</del> 중에서 <strong>&quot;조건에 맞지 않으면&quot;</strong>에 해당한다.</p>
<p>그래서 가지치기에 대한 힌트를 얻었다. <strong>: 앞에부터 digit을 보세요 :</strong></p>
<h2 id="🚦-풀이3-🚦">🚦 풀이3 🚦</h2>
<pre><code>class Solution:
    def letterCombinations(self, digits: str) -&gt; List[str]:
        if (digits == &quot;&quot;):
            return []
        letter = {&quot;2&quot;: [&quot;a&quot;,&quot;b&quot;,&quot;c&quot;], &quot;3&quot;: [&quot;d&quot;,&quot;e&quot;,&quot;f&quot;], &quot;4&quot;:[&quot;g&quot;,&quot;h&quot;,&quot;i&quot;], &quot;5&quot;:[&quot;j&quot;,&quot;k&quot;,&quot;l&quot;], &quot;6&quot;:[&quot;m&quot;,&quot;n&quot;,&quot;o&quot;], &quot;7&quot;:[&quot;p&quot;,&quot;q&quot;,&quot;r&quot;,&quot;s&quot;], &quot;8&quot;:[&quot;t&quot;,&quot;u&quot;,&quot;v&quot;], &quot;9&quot;:[&quot;w&quot;,&quot;x&quot;,&quot;y&quot;,&quot;z&quot;]}
        if (len(digits) == 1):
            return letter[digits]
        result = []
        def backtracking(res : string):
            if (len(res) == len(digits)):
                result.append(res)
                return
            for i in letter[digits[len(res)]]:
                backtracking(res + i)
        backtracking(&quot;&quot;)
        return result</code></pre><p>🚧 드디어 백트랙킹 방식의 약 90퍼쯤 온 것 같다. 주로 백트랙킹 알고리즘을 dfs, bfs등 트리에 대한 알고리즘으로 선택을 했었기에 string에 대한 순회방식과 함께 사용하는 게 어색했다.</p>
<p>🚧 비어있는 digits와 하나의 number에 대한 리스트를 리턴하는 방식은 이전 풀이와 같다. </p>
<p>🏗 result : 결과를 return해줄 리스트
🏗 res : 백트랙킹 함수에서 사용하는 하나의 문자열. </p>
<p>🚧 이번에는 힌트에 따라서 뒤부터가 아닌 앞부터 뜯어보기로 했다. </p>
<p>🚧 그리고 이전의 재귀함수에서는 리스트 자체를 리턴해줬는데, 백트랙킹함수에서는 함수 바깥에 리턴해줄 리스트 하나를 만들어두고, 함수 내부에서는 하나의 string에 대해서만 추적했다. 
그래서 이전 풀이의 재귀함수에서는 digit 1개당 1번의 재귀를 호출했지만, backtracking함수에서는 string마다 1개의 char에 대해서 1번의 재귀함수를 호출했기 때문에, depth는 비슷하지만 재귀함수를 호출하는 횟수자체가 달라진다. 
그림으로 표현하면 이렇다.
<img src="https://velog.velcdn.com/images/blank_/post/8521b45d-757d-4ff2-a6fe-8c879a444752/image.png" alt=""></p>
<p>그래서 풀이과정에서, <code>backtracking(&quot;&quot;)</code> <code>backtracking(&quot;ad&quot;)</code> 이런식으로 전달한다.</p>
<p>🚧 하나의 string이 완성되었는지, 아닌지를 판단하는 기준은 digits의 길이와 같은지 아닌지이다. 재귀를 들어가기전에 맨 앞의 digit에 대해서 문자열을 세팅하고, 재귀에 들어가서 두번째 digit에 대해서 세팅하고, 알파벳 리스트의 길이만큼 다시 backtracking을 호출한다.</p>
<h3 id="풀이-결과-2">풀이 결과</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/87e1b1f1-7314-4344-8dd6-c48273f4f196/image.png" alt="결과3"></p>
<p>결과적으로 런타임 실행속도에서 많이 좋아졌다.
그리고, 스트링을 재귀하는 방식과 리스트를 재귀하는 방식을 비교하는 그림을 그리다보니 <strong>사실상 리스트를 재귀하는 방식은 재귀함수를 썼을 뿐이고 brute force</strong>라는 걸 깨달아버렸다. 크흡... 
아무튼 오늘도 이렇게 알고리즘 하나 풀기 완료 ⭐️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[36. Valid Sudoku]]></title>
            <link>https://velog.io/@blank_/leetcode36</link>
            <guid>https://velog.io/@blank_/leetcode36</guid>
            <pubDate>Thu, 08 Jun 2023 02:49:17 GMT</pubDate>
            <description><![CDATA[<h2 id="🚥-문제-🚥">🚥 문제 🚥</h2>
<p>Top Interview 150 중
<a href="https://leetcode.com/problems/valid-sudoku/">36. Valid Sudoku</a></p>
<p>🍿 난이도 : 리트코드 기준 Medium
🚧 알고리즘 : 행렬(matrix)</p>
<h2 id="🚦-풀이1-🚦">🚦 풀이1 🚦</h2>
<pre><code>class Solution:
    def isValidSudoku(self, board: List[List[str]]) -&gt; bool:
        def checkRow(row):
            checklist = []
            for i in range(9):
                if (row[i] != &quot;.&quot; and row[i] in checklist):
                    return False
                checklist.append(row[i])
            return True
        def checkCol(board, col):
            checklist = []
            for i in range(9):
                if board[i][col] != &quot;.&quot; and board[i][col] in checklist:
                    return False
                checklist.append(board[i][col])
            return True

        # row checking
        for i in range(9):
            if (checkRow(board[i]) == False):
                return False

        # column checking
        for j in range(9):
            if (checkCol(board, j) == False):
                return False

        # box checking
        for k in range(0,9,3):
            for m in range(0,9,3):
                if (checkRow(board[k][m:m+3] + board[k+1][m:m+3] + board[k+2][m:m+3]) == False):
                    return False

        return True</code></pre><p>🚧 python의 list 자료구조를 사용했다. </p>
<p>🚧 하나의 list를 체크할 때는 checkRow를 사용했고, column checking과정에서는 각각 하나의 원소씩 체크했다. checklist라는 새로운 list를 만들어서 원소를 삽입했고, 만약 삽입과정에서 중복이 발생했을 경우에는 바로 false를 리턴했다.</p>
<p>🚧 이중배열로 구성된 board가 valid한지 확인하기 위해서 row, column, box 형태 순서로 체크했고, 체크 과정에서 문제가 발생하지 않았다면 True를, 한번이라도 False가 발생했다면 즉시 False를 리턴했다.</p>
<h3 id="풀이-결과">풀이 결과</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/c95a3f40-850c-4e69-a8a5-f794e5aefd78/image.png" alt="결과1"></p>
<p>생각보다 메모리 사용량도 많았고, 런타임시간도 오래걸려서, 다른 사람들의 솔루션을 보고 바꿔봤다.</p>
<h2 id="🚦-풀이2-🚦">🚦 풀이2 🚦</h2>
<pre><code>class Solution:
    def isValidSudoku(self, board: List[List[str]]) -&gt; bool:
        def checkRow(row):
            if (len(set(row)) != len(row)):
                return False
            return True
        def checkCol(board, col):
            column = []
            for i in range(9):
                column.append(board[i][col])
            return checkRow([i for i in column if i != &#39;.&#39;])

        # row checking
        for row in board:
            if (checkRow([i for i in row if i != &#39;.&#39;]) == False):
                return False

        # column checking
        for j in range(9):
            if (checkCol(board, j) == False):
                return False

        # box checking
        for k in range(0,9,3):
            for m in range(0,9,3):
                box = board[k][m:m+3] + board[k+1][m:m+3] + board[k+2][m:m+3]
                if (checkRow([i for i in box if i != &#39;.&#39;]) == False):
                    return False
        return True</code></pre><p>🚧 위의 풀이와 비슷하게 python의 list 자료구조를 사용했다. 이번에는 checklist라는 새로운 배열을 사용하지 않고 <code>[i for i in column if i != &#39;.&#39;]</code>와 같은 list comprehension으로 함수를 호출했다.</p>
<p>🚧 중복값이 있는지 확인하는 과정에서도 <code>if (len(set(row)) != len(row)):</code> 와 같이 set을 사용한 뒤 두 값의 길이를 비교했다. set이 중복값을 삭제시키므로, 두 값의 길이가 같지 않으면 중복값이 있었다는 뜻이 된다.</p>
<h3 id="풀이-결과-1">풀이 결과</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/99b2c735-9138-44ae-bd5c-38d48a440b37/image.png" alt="결과2"></p>
<p>런타임 기준에서 약 15프로 개선되었지만 메모리 사용량에서는 25프로정도 감소했음을 볼 수 있다.
한번 더 다른 사람의 코드를 보고 개선했다.</p>
<h2 id="🚦-풀이3-🚦">🚦 풀이3 🚦</h2>
<pre><code>class Solution:
    def isValidSudoku(self, board: List[List[str]]) -&gt; bool:
        sudoku = []
        for i, row in enumerate(board):
             for j, num in enumerate(row):
                if num != &#39;.&#39;:
                    sudoku += [(i, num), (num, j), (i // 3, j // 3, num)]
        # print(sudoku)
        return len(sudoku) == len(set(sudoku))</code></pre><p>🚧 python의 list, tuple을 사용했다. </p>
<p>🚧 <code>enumerate(board)</code>같은 파이썬 내장함수 enumerate를 사용했다. enumerate() 함수는 인덱스와 원소로 이루어진 튜플(tuple)을 만든다. 문제에서 제시한 첫 번째 예제라고 했을 때 <code>enumerate(board)</code>의 첫 번째 결과는 <code>(0, [&#39;5&#39;, &#39;3&#39;, &#39;.&#39;, &#39;.&#39;, &#39;7&#39;, &#39;.&#39;, &#39;.&#39;, &#39;.&#39;, &#39;.&#39;])</code>가 될 것이다.</p>
<p>🚧 row, column, box일 때의 튜플을 각각 sudoku list에 저장한 뒤 set했을 때의 길이와 실제 길이를 비교해서 return했다.
  box일때는 9칸을 1칸으로 보고 거대한 3*3 array라고 생각하고 첫번재 칸을 (0, 1)인덱스로 지정해서 튜플을 생성했다.</p>
<h3 id="풀이-결과-2">풀이 결과</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/52e14332-efdb-4406-9f7b-60ffdad085b5/image.png" alt="결과3"></p>
<p>일단 코드 길이가 어마어마하게 짧아졌다... 와우
결과적으로도 런타임 실행속도에서 많은 개선이 일어났다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[20. Valid Parentheses]]></title>
            <link>https://velog.io/@blank_/leetcode20</link>
            <guid>https://velog.io/@blank_/leetcode20</guid>
            <pubDate>Mon, 05 Jun 2023 09:18:54 GMT</pubDate>
            <description><![CDATA[<h2 id="🚥-문제-🚥">🚥 문제 🚥</h2>
<p>Top Interview 150 중
<a href="https://leetcode.com/problems/reverse-words-in-a-string/description/?envType=study-plan-v2&amp;envId=top-interview-150">20. Valid Parentheses!
</a></p>
<p>🍿 난이도 : 리트코드 기준 Easy</p>
<h2 id="🚦-풀이1-🚦">🚦 풀이1 🚦</h2>
<pre><code>class Solution:
    def isValid(self, s: str) -&gt; bool:
        stack = []
        for i in s:
            if (i == &quot;(&quot; or i == &quot;{&quot; or i == &quot;[&quot;) : 
                stack.append(i)
                continue
            if (len(stack) and i == &quot;)&quot; and stack[-1] == &quot;(&quot;):
                stack.pop()
                continue
            if (len(stack) and i == &quot;}&quot; and stack[-1] == &quot;{&quot;):
                stack.pop()
                continue
            if (len(stack) and i == &quot;]&quot; and stack[-1] == &quot;[&quot;):
                stack.pop()
                continue
            stack.append(i)
        if (len(stack)):
            return False
        return True</code></pre><p>🚧 알고리즘 : 구현</p>
<p>🚧 stack구현을 위해서 <strong>python의 list 자료구조</strong>를 사용했다.
🚧 여는괄호인 (, {,  [일경우 stack에 추가해주고, 닫는 괄호일경우 stack에 마지막으로 들어간 괄호와 비교해서 삭제해주거나, 맞지 않을 경우 stack에 추가해주었다. 
🚧 마지막에 stack의 길이를 체크해서 <strong>만약 stack에 남은 값이 있을 경우 false를, stack이 비었을 경우 true를 리턴</strong>해주었다.</p>
<h3 id="풀이-결과">풀이 결과</h3>
<p> <img src="https://velog.velcdn.com/images/blank_/post/c725a756-0ac6-492f-8051-4084452c385d/image.png" alt="결과1"></p>
<h2 id="🚦-풀이2-🚦">🚦 풀이2 🚦</h2>
<pre><code>class Solution:
    def isValid(self, s: str) -&gt; bool:
        stack = []
        check = dict({&#39;()&#39;,&#39;{}&#39;,&#39;[]&#39;})
        for i in s:
            if i in &#39;({[&#39; :
                stack.append(i)
                continue
            if not stack: #empty list is fals in if not :
                return False
            if (i == check[stack[-1]]):
                stack.pop()
                continue
            else : 
                return False
        return not stack</code></pre><p>🚧 알고리즘 : 구현</p>
<p>🚧 위의 풀이와 비슷하게 stack을 사용했지만 <strong>추가로 python의 dict 구조</strong>도 사용했다.</p>
<p>🚧 <code>dict({&#39;()&#39;,&#39;{}&#39;,&#39;[]&#39;})</code> 의 결과가 
<code>{&#39;(&#39;: &#39;)&#39;, &#39;[&#39;: &#39;]&#39;, &#39;{&#39;: &#39;}&#39;}</code> 이렇게 생긴 dictionary를 만든다는 것을 처음 알았다.
 <strong>딱 2글자의 string들로 이루어진 tuple에 대해서만 변환</strong>이 된다. </p>
<p>🚧 if문들이 체크하는 공통적인 조건인 stack에 남은 값이 있는지 없는지를 빼서 하나의 조건문으로 만들었다. <code>if not stack:</code> 이 조건문은 list에 대해서 체크하는데, <strong>list가 비어있을 경우 True</strong>가 되어 최종적으로 함수에서 False를 리턴한다.</p>
<h3 id="풀이-결과-1">풀이 결과</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/ce32d1d6-4675-4773-9556-2a4061522341/image.png" alt="결과2"></p>
<p><strong>런타임에서 10%정도 개선</strong>되었지만 보다 메모리 사용량에서 상당부분 사용했음을 알 수 있다. 아무래도 자료구조를 1개의 리스트만 사용하던 1번 풀이법보다 2번 풀이법이 자료구조 개수가 증가해 <strong>더 많은 메모리를 사용</strong>한 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dockerfile & Image]]></title>
            <link>https://velog.io/@blank_/Dockerfile-Image</link>
            <guid>https://velog.io/@blank_/Dockerfile-Image</guid>
            <pubDate>Wed, 05 Apr 2023 08:30:34 GMT</pubDate>
            <description><![CDATA[<h2 id="지금까지-배운거">지금까지 배운거</h2>
<p>image를 pull받아서 container run시키기
근데 나의 이미지로 container를 만들 수는 없을까?</p>
<h2 id="나의-도커-이미지-만들기">나의 도커 이미지 만들기</h2>
<p><img src="https://velog.velcdn.com/images/blank_/post/f52eb9f9-3671-4e4e-aae3-544444825e06/image.png" alt=""></p>
<h3 id="방법은-2개">방법은 2개</h3>
<h4 id="1-container를-수정하고-commit하기">1. container를 수정하고, commit하기</h4>
<h4 id="2-원하는-방식으로-dockerfile을-작성하고-build하기">2. 원하는 방식으로 dockerfile을 작성하고 build하기</h4>
<blockquote>
<p>Q. commit과 build의 차이는?
둘다 결과적으로 <strong>이미지를 만드는 명령어</strong>이다.
commit : 이미 사용하고 있는 컨테이너가 있을 때 이미지로 만드는 것 ( <strong>백업</strong> 느낌)
build : 도커 파일을 바탕으로 구체적으로 시간순서에 따라서 <strong>새로 이미지를 생성</strong>하는 것</p>
</blockquote>
<p>도커파일을 이용하면 어떤 과정을 통해서 이미지가 만들어졌는지 분명하게 이해할 수 있다. 
나뿐만 아니라 남들도 마찬가지!</p>
<p><del>이번 수업은 빌드가 위주이지만, commit도 보긴해야지</del></p>
<h2 id="웹서버-이미지를-만들고-싶다">웹서버 이미지를 만들고 싶다.</h2>
<h3 id="진짜요-그래요그럼-만듭시다-br-같은-이미지를-commit-방식과-build방식-두가지로-만들어보아요">진짜요? 그래요.그럼 만듭시다. <br/> 같은 이미지를 commit 방식과 build방식 두가지로 만들어보아요</h3>
<p><del>vscode기능중에 docker확장기능 추천</del></p>
<h3 id="commit-방식">commit 방식</h3>
<ul>
<li>ubuntu 20.04 이미지 다운로드 후 컨테이너 생성
<code>docker run -p 8888:8000 --name web-server -it ubuntu:20.04</code> </li>
<li>web-server컨테이너를 web-server-commit 이름으로 이미지 생성
<code>docker commit web-server web-server-commit</code></li>
<li>이미지 생성확인
<code>docker images</code></li>
<li>컨테이너 안으로 이동 </li>
<li>파이썬3 다운로드
<code>apt update</code>
<code>apt install python3</code></li>
<li>이순간에 커밋하면 이미지 생성가능</li>
<li>사용자 요청을 저장할 디렉토리 생성하기
<code>mkdir -p /var/www/html</code>
<code>cd /var/www/html</code></li>
<li>index.html 파일 생성
<code>echo &quot;Hello, &lt;strong&gt;Docker&lt;/strong&gt;&quot; &gt; index.html</code>
<code>ls</code></li>
<li>python3에 내장된 <strong>웹서버를 실행</strong>하고, 8000번 포트를 리스닝
<code>python3 -m http.server</code></li>
<li><a href="http://0.0.0.0:8888">http://0.0.0.0:8888</a> 로 접속하면 작성한 index.html이 보인다.</li>
</ul>
<br/>

<h3 id="build-방식">build 방식</h3>
<ul>
<li><p>** docker file 생성 **</p>
<pre><code>FROM ubuntu:20.04 //기본 이미지를 ubuntu로 설정
RUN apt update &amp;&amp; apt install -y python3 //파이썬 3 다운로드
WORKDIR /var/www/html //directory가 없다면 만들고, 그 위치로 이동시킴
//이제부터 실행하는 run, entrypoint, cmd같은 명령어가 실행될때는 저 디렉토리 위치에서 실행됨.
COPY [&quot;index.html&quot;, &quot;.&quot;] //host에 있는 index.html을 WORKDIR로 복사하기
==== 이미지 생성 끝
CMD [&quot;python3&quot;, &quot;-u&quot;, &quot;-m&quot;, &quot;http.server&quot;] //-u 옵션을 포함시켜야만 실행중인 현재 상태의 로그가 화면으로 출력됨
==== 컨테이너 실행 후 실행</code></pre><br/>
</li>
<li><p>이미지의 이름을 <strong>-t(tag)로 설정</strong>후 pwd에 위치시키기
<code>docker build -t web-server-build .</code></p>
</li>
<li><p>build 후 container 실행
<code>docker rm --force web-server;</code>
<code>docker run -p 8888:8000 --name web-server web-server-build;</code></p>
</li>
<li><p><a href="http://0.0.0.0:8888">http://0.0.0.0:8888</a> 로 접속하면 host디렉토리에 작성한 index.html이 보인다.</p>
</li>
</ul>
<ul>
<li>컨테이너 실행시 CMD를 실행하고 싶지 않다면 pwd를 쓰기
pwd가 cmd대신에 실행되어서, 시작명령을 변경할 수 있음 (overriding)
<code>docker run -p 8888:8000 --name web-server web-server-build pwd;</code></li>
</ul>
<blockquote>
<p>RUN 명령어는 실행될 때마다 layer가 생성된다. 따라서 가급적 하나의 명령어로 연결실행하는게 유리하다.
<code>RUN apt update;</code>
<code>RUN apt install python3;</code>보다
<code>RUN apt update &amp;&amp; apt install python3</code>으로 실행시키는 걸 추천</p>
</blockquote>
<blockquote>
<p>index.html을 생성하는, 다른 방법이 있다. (commit과 유사)
<code>RUN echo &quot;Hello, &lt;strong&gt;Docker&lt;/strong&gt;&quot; &gt; index.html</code> 
이렇게 작성하면 <strong>WORKDIR로 설정한 디렉토리 아래에 생성</strong>이 된다. 
하지만, dockerfile이 있는 같은 디렉토리에 index.html 파일을 만들어서, 도커파일이 build될 때 index.html파일을 host에서 image로 복사할 수 있으면 더 좋겠죠? 왜죠?
이전에 <a href="https://velog.io/@blank_/INCEPTION">도커 볼륨</a>에서 학습했음</p>
</blockquote>
<blockquote>
<p>dockerfile에 의해서 만들어진 image를 container로 생성할 때 명령어를 실행해서 바로 웹서버를 사용할 수 있는 상태가 되길 바란다면, 도커파일에 <strong>CMD명령어</strong>를 이용</p>
</blockquote>
<p><del>이제 도커 이미지를 생산하는 생산자가 되었음 와우아</del></p>
<p><strong>🤦‍♂️찾아봅시다</strong></p>
<blockquote>
<p>Q. <code>python3 -u -m http.server</code> 명령어의 뜻은?
원래 http.server는 파이썬이 일반적으로 제공하는 모듈중 하나인데, -m 옵션과 함께 실행하면, 직접실행이 가능하다. 평소엔 확장자가 py인 파일에 import해서 사용해야함. 
<code>python3 -m http.server</code> 의 기본 포트는 8000 이고, 만약 변경하고 싶으면 뒤에 포트번호를 써주면 된다. ex) <code>python3 -m http.server 9000</code></p>
</blockquote>
<blockquote>
<p>Q. 포트포워딩을 하지 않고 run 했을 때 <a href="http://0.0.0.0:8000%EC%9D%80">http://0.0.0.0:8000은</a> 왜 접속이 안되는지?
왜냐면 컨테이너의 포트와 호스트의 포트가 연결되지 않았기 때문! 이해가 되지 않는다면 <a href="https://velog.io/@blank_/INCEPTION">도커 입문</a>으로 가시죠</p>
</blockquote>
<blockquote>
<p>Q. 왜 run명령어 뒤에 pwd를 붙이면 CMD명령이 있는데도 일회성으로 실행이 되는지?
Q. CMD명령을 여러개를 쓸수 있나?
Q. 만약 CMD명령이 여러개였다면 모두 실행이 안되고 pwd가 실행이 되는걸까?
세 개의 질문을 묶어서 설명해보자. 
CMD명령은 컨테이너가 생성될 때 단 하나의 유효한 값을 갖는다. 
그래서 이미 CMD명령이 있었더라도, 이후에 다른 CMD명령어가 입력된다면 이전의 값 대신에 실행된다.
따라서 가장 마지막에 입력된 CMD명령인 pwd가 실행되는 것이다.
원한다면, dockerfile내부에 CMD명령을 여러 개 쓸 수 있다! 그러나 여러 줄을 쓸 경우, 가장 <strong>마지막 명령만 유효</strong>하게 실행된다. 
어떻게든 여러개를 한번에 실행하고 싶다면 &gt; <a href="https://code-mania.tistory.com/151">여기</a>를 참고하자.
CMD명령에 대한 <a href="https://docs.docker.com/engine/reference/builder/#cmd">doc</a>페이지</p>
</blockquote>
<ul>
<li>추가로 알게 된 지식 : <code>CMD echo &quot;This is a test.&quot; | wc -</code> 이렇게 명령어 형식으로도 쓸 수 있다. 명령어 형식으로 쓸 경우 /bin/sh -c 상황에서 실행되게 된다. json array형식으로 작성하면 shell없이 실행된다.</li>
</ul>
<blockquote>
<p>Q. build로 생성한 이미지로 실행중인 컨테이너도 commit이 가능한지?
논리적으로 생각해보면, build로 생성한 이미지나 commit으로 생성한 이미지나 같은 이미지 타입일 것 같다. 따라서 가능하지 않을까?
=&gt; 가능합니다! 
빌드된 이미지 파일로 실행한 컨테이너 파일시스템에서 새로운 파일을 생성하고 commit하니, <strong>이미지 레이어가 하나 더 생기고</strong>, 새로운 이미지로 commit됩니다 :) </p>
</blockquote>
<blockquote>
<p>Q. mkdir -p /var/www/html에서 -p옵션의 의미는?
필요하다면 경로 중간의 디렉토리를 rwxrwxrwx 권한으로 생성한다. 
만약 -p 옵션이 주어지지 않았을 때는 경로에 쓰여진 모든 디렉토리가 존재해야만 에러가 발생하지 않는다.</p>
</blockquote>
<blockquote>
<p>Q. commit 은 어느범위까지 저장(백업)이 되는지? 파일? 실행중인 프로세스? 
변경된 컨테이너의 file 혹은 settings 에 대해서 저장된다.</p>
</blockquote>
<blockquote>
<p>Q. 이미지는 어떤 위치에 주로 저장하는지? (docker build시)
docker가 설치된 가상환경에서, 기본 Docker 경로 /var/lib/docker에 저장된다. </p>
</blockquote>
<blockquote>
<p>Q. ENTRYPOINT는 무엇인가?
CMD명령문과 같이 컨테이너가 실행하게 될때 수행하게 되는 명령을 정의하는 명령문이다. 
CMD와 다르게, 컨테이너 시작시 실행명령에 대한 Default값을 지정하게 되는데, 그래서 변경이 CMD보다 어렵고, 해당 컨테이너가 실행될 때 반드시 ENTRYPOINT에서 지정한 명령을 수행한다.
만약 ENTRYPOINT가 지정되었다면, CMD는 추가 인자값으로 활용된다. 예를 들어, <code>df -h</code>가 entrypoint로 지정되고, <code>ps -aef</code>가 CMD값으로 지정되었다면, 컨테이너가 실행될 때 <code>df -h ps -aef</code>로 실행된다.
따라서, 메인 프로세스인 ENTRYPOINT에서 실행한 명령어에 대한 옵션값을 CMD로 정의해주면 좋다. &gt; <a href="https://bluese05.tistory.com/77">참고</a></p>
</blockquote>
<blockquote>
<p>Q. RUN명령어 실행시에 만들어진다는 layer는 무엇일까?
<strong>이미지는 레이어로 구성</strong>되어있고, 파일이 추가되거나 수정되면 새로운 레이어가 생성된다. 
그래서 pull받을 때 여러줄로 분리된 것처럼 받아지는데, 여기서 한 줄씩 분리된 데이터를 Layer라고 한다.
layer는 이미지에 따라 구성이 다른데, 예를 들어 ubuntu 이미지는 1개의 레이어가 있고, nginx 이미지는 5개의 레이어가 있다<br><img src="https://velog.velcdn.com/images/blank_/post/7317bbe2-22b7-480e-b071-098f5bef2a65/image.png" alt="">
Dockerfile에 정의된 명령문 중에 <strong>(RUN, ADD, COPY)</strong> 3가지 단계만이 레이어로 저장된다.
(CMD, LABEL, ENV, EXPOSE) 등 메타정보를 다루는 명령문은 임시레이어로 생성은 되지만 저장되지 않아서 도커 이미지 사이즈에 영향을 주지 않는다.
레이어를 pull 이후에 확인해보고 싶다면 <code>docker inspect [image 이름]</code>을 실행하면 layer, env등의 정보를 볼 수 있고 <code>docker history [image 이름]</code>을 실행하면 이미지에 포함된 layer들에 대해서 자세히 보여준다.
<img src="https://velog.velcdn.com/images/blank_/post/57bac4e1-1703-4885-8d8e-ffb2b8e6ec48/image.png" alt=""> 이미지를 생성할 때 Layer가 쌓이게 되면 파일을 삭제해도 이미지 사이즈는 줄어들지 않는다. 따라서 layer수를 줄이는 것은 이미지를 가볍게 만들 수 있는 가장 핵심적인 방법중 하나다.</p>
</blockquote>
<ul>
<li>컨테이너를 생성할 때도 기존의 이미지 레이어 위에 쓰기가 가능한 레이어를 추가 하는데, 이부분에 대한 추가 설명이 필요하다면 &gt; <a href="https://woochan-autobiography.tistory.com/468">여기</a></li>
</ul>
<blockquote>
<p>Q. ubuntu:20.04 에서 20.04는 태그다. <strong>태그</strong>는 무엇일까?
도커에서 <strong>이미지의 버전을 관리하기 위해서 사용하는 이미지에 부여되는 별칭</strong>이다.
일반적으로 버전 관리를 위해 <code>latest</code>태그보다는 <code>patch number</code>나 <code>git commit</code>을 활용한다. 
따라서, 여기서 20.04는 우분투 이미지의 버전을 의미한다.
image생성시 tag명 지정을 생략하면 default값으로 마지막에 build/tag된 이미지라는 뜻의 <code>latest</code>태그가 붙는다. </p>
</blockquote>
<ul>
<li><strong>주의사항</strong> <code>latest</code> 태그가 언제나 가장 최신 버전을 의미하지는 않는다. (tag가 없는 image에 디폴트로 주어진 값일 수도 있으므로)</li>
</ul>
<h3 id="push">push</h3>
<p>원하는 이미지를 바탕으로 컨테이너가 잘 생성된다면, docker hub 에 push 명령어를 통해서 이미지를 레지스트리에 공유해봅시다.</p>
<ul>
<li>주의사항 : docker hub는 github와 유사합니다. <strong>미리 가입</strong>을 해야합니다.</li>
</ul>
<br/>

<h2 id="참조">참조</h2>
<p><a href="https://www.youtube.com/watch?v=0kQC19w0gTI">생활코딩 도커파일</a>
<a href="https://www.youtube.com/watch?v=RMNOQXs-f68">생활코딩 도커 commit</a>
<a href="https://www.44bits.io/ko/post/building-docker-image-basic-commit-diff-and-dockerfile#%EB%93%A4%EC%96%B4%EA%B0%80%EB%A9%B0">읽어볼거리</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker]]></title>
            <link>https://velog.io/@blank_/INCEPTION</link>
            <guid>https://velog.io/@blank_/INCEPTION</guid>
            <pubDate>Tue, 04 Apr 2023 07:40:55 GMT</pubDate>
            <description><![CDATA[<h2 id="도커docker-란-무엇인가">&#39;도커(Docker)&#39; 란 무엇인가?</h2>
<blockquote>
<p>vmware, virtualbox와 같은 가상 머신처럼 독립된 실행환경을 제공하면서도, 컴퓨터에 직접 애플리케이션을 설치한 것과 같이 빠르고, apt, npm, pip 처럼 명령어 한줄로 원하는 앱이 포함된 실행환경을 손쉽게 설치할 수 있는 환상적인 개발환경을 제공하는 툴.</p>
</blockquote>
<h3 id="웹서버를-구동해보기">웹서버를 구동해보기</h3>
<ol>
<li>세개의 컴퓨터에 운영체제부터 설치 -&gt; 잘 안됨</li>
<li>하나의 컴퓨터에 가상으로 컴퓨터를 만들고, 운영체제를 만든후에 웹서버를 설치하기-&gt; 이게 가상머신 (vmware, virtualbox)
웹서버를 위해서 가상머신을 설치한다면? <strong>굉장히 느려진다!!</strong></li>
<li><strong>한대의 컴퓨터</strong> 안에서 각각의 앱을 격리된 환경에서 실행시키기
호스트에서 실행되는 격리된 각각의 실행환경 = <strong>컨테이너</strong>
<img src="https://velog.velcdn.com/images/blank_/post/3ace49be-3caa-404b-b67f-f945c1ccd0f3/image.png" alt="">
각각의 컨테이너에는 운영체제 전체가 아닌 앱을 실행하는데 필요한 라이브러리/ 실행파일만 포함된다. 그래서 무척 <strong>가벼워짐</strong>!!
<img src="https://velog.velcdn.com/images/blank_/post/f3f44429-0661-461f-a31b-5d111a40c1e8/image.png" alt="">
운영체제가 하나니까 속도도 빨라지고, 저장장치의 용량도 아낄수 있다. 
이런 컨테이너를 제공하는 툴중에 하나가 <strong>도커</strong>.</li>
</ol>
<p>🤦‍♂️** 찾아봅시다**</p>
<blockquote>
<p>Q. 도커와 가상머신의 차이점, 공통점은 뭔가요?</p>
</blockquote>
<ul>
<li>공통점 : 하나의 물리적 공간에서 여러가지 서비스를 사용할 수 있게 하기 위해서 독립적인 공간을 생성한다.</li>
<li>차이점 :<blockquote>
</blockquote>
<table>
<thead>
<tr>
<th align="left"><center>가상머신 VM</center></th>
<th><center>도커 Docker</center></th>
</tr>
</thead>
<tbody><tr>
<td align="left">호스트 운영체제를 공유 x</td>
<td>호스트 운영체제를 공유 O</td>
</tr>
<tr>
<td align="left">보안적 측면에서 유리</td>
<td>보안위험, 취약점 많음</td>
</tr>
<tr>
<td align="left">여러 운영체제 환경에 유리</td>
<td>단일 운영체제 환경에 유리</td>
</tr>
<tr>
<td align="left"><br/></td>
<td></td>
</tr>
<tr>
<td align="left">가상머신은 하나의 독립된 컴퓨터를 컴퓨터 안에 생성하는 것이라서, OS, binary, Library등 여러가지 필수로 들어가야 할 정보들의 있기 때문에 무겁다. 도커 엔진 위에서 도커 image를 실행시켜서 독립된 App을 실행시키는 원리이기 때문에 훨씬 가볍다.</td>
<td></td>
</tr>
<tr>
<td align="left">보안적 측면에서 보면, VM은 운영체제를 공유하지 않기 때문에 호스트 커널에서 분리가 되므로 도커에 비해서 안전하다. 반대로 도커는 호스트 커널을 공유하고, 리소스도 공유되기 때문에 하나의 컨테이너에 접근가능하면 클러스터에 속한 모든 컨테이너에 접근이 가능해지므로, 보안 위험과 취약점이 많다.</td>
<td></td>
</tr>
<tr>
<td align="left">도커의 경우에는 이식성이 뛰어나고, 영구적으로 리소스를 할당할 필요가 없고, 심지어 운영체제도 설치할 필요가 없기 때문에, 컨테이너를 확장하고, 복제하는것도 가상시스템에 비해 간단하다.</td>
<td></td>
</tr>
</tbody></table>
</li>
<li>따라서, 도커는 단일 운영체제에서 여러 응용프로그램을 실행하려는 경우에 더 적합하고, 서로 다른 운영체제환경에서 실행해야하는 어플리케이션이나 서버가 있는 경우에는 VM이 더 적합할 수 있다. </li>
</ul>
<p><del>Anyway, 도커를 쓰십시요.</del></p>
<h2 id="도커-설치">도커 설치</h2>
<h3 id="도커를-이용하기-위해-알아야할-것">도커를 이용하기 위해 알아야할 것</h3>
<p><strong>컨테이너 기술은 리눅스 운영체제 기술이다.</strong></p>
<blockquote>
<ol>
<li>그러므로, 컨테이너와, 컨테이너에서 동작하는 앱들은 모두 리눅스 운영체제에서 동작한다는 뜻.</li>
<li>만약 내 컴퓨터가 리눅스 운영체제가 아니라면? <strong>그래도 괜찮다!</strong>
윈도우, 맥 OS위에 가상머신을 깔고 <strong>가상머신에 linux를 깔면 되니까 **
도커는 리눅스 운영체제 기반으로 만들어졌으므로, 리눅스 운영체제상에서는 괜찮지만, **다른 운영체제에서는 가상머신을 이용해야하므로, 속도저하가 발생한다</strong>.
But, 그걸 감수하고서라도 도커를 설치해서 사용하는게 엄청난 성능향상을 가져온다.</li>
</ol>
</blockquote>
<p>도커는 윈도우 /맥 OS에서 그래픽환경을 지원하는 데스크탑 앱을 제공하지만, <strong>명령어를 이용하는 cli환경에서 docker를 사용하는게 도커를 100% 사용하는 방법</strong>이다.
<del>리눅스 OS에서 docker를 쓸 경우 &quot;sudo&quot;를 붙여서 실행해야함.</del>
terminal에서 <code>docker images</code>  명령어가 에러없이 작동되면 docker설치가 정상적으로 종료되었다는 뜻이다.</p>
<h2 id="이미지-설치">이미지 설치</h2>
<p>도커에서는 <strong>dockerhub</strong>에서 <strong>image</strong>를 다운로드해서 실행하면 <strong>container</strong>가 됨.</p>
<ul>
<li>docker hub 에서 image를 다운로드 받는것 : <strong>pull</strong></li>
<li>image를 실행시키는것 : run.
<img src="https://velog.velcdn.com/images/blank_/post/5309d051-3595-42c0-a201-e443d5e3c57f/image.png" alt=""></li>
</ul>
<p><a href="https://hub.docker.com/search?q=&amp;type=image">Docker Hub</a>
apache 웹서버 프로그램 : httpd라는 이미지로 등록되어 있음.
<del>docker official image : 도커에서 공식적으로 관리하는 이미지들 태그.</del>
<a href="https://docs.docker.com">Docker Docs : 도커 사용설명서
</a>
매뉴얼을 참고해서 명령어를 사용하면 됨.</p>
<ul>
<li>dockr hub 에서 httpd 이미지를 pull받기 ( 다운로드 받기 )
<code>docker pull httpd</code></li>
<li>image 현황 확인하기
<code>docker images</code> </li>
</ul>
<p><del>이제 수많은 도커 이미지들을 다운로드받을 수 있게 됨!</del></p>
<h2 id="컨테이너-실행">컨테이너 실행</h2>
<p>run : 이미지를 기반으로 컨테이너를 실행시키는 명령어. 
run 명령을 실행할 때는 <strong>컨테이너들의 이름을 서로 헷갈리지 않게</strong> 지정하는게 중요함!
실행중인 컨테이너가 컴퓨터의 자원을 이용하는 중이다 -&gt; 자원의 사용률을 줄이고 싶다면, <strong>컨테이너를 중지</strong>시키면 된다.
만약 컨테이너를 아예 삭제하고 싶다면 rm명령어를 이용하기.</p>
<h3 id="컨테이너를-만들고-삭제해보기">컨테이너를 만들고 삭제해보기</h3>
<blockquote>
<h3 id="명령어-정리">명령어 정리</h3>
</blockquote>
<ul>
<li>httpd 기반으로 컨테이너를 만들고, 실행하기
<code>docker run httpd</code></li>
<li><strong>실행중인 컨테이너의</strong> 상황을 보기
<code>docker ps</code></li>
<li><strong>실행중이지 않은 컨테이너까지</strong> 모두 현재상황 보기
<code>docker ps -a</code></li>
<li>새로운 컨테이너 이름을 ws2로 설정해서 생성하기
<code>docker run --name ws2 httpd</code></li>
<li>실행중인 컨테이너를 중지하기
<code>docker stop ws2</code></li>
<li>중지시켰던 컨테이너를 다시 실행하기
<code>docker start ws2</code></li>
<li>컨테이너의 로그를 <strong>한번만</strong> 출력하기
<code>docker logs ws2</code></li>
<li>컨테이너의 로그를 <strong>실시간으로</strong> 출력하기
<code>docker logs -f ws2</code></li>
<li>컨테이너를 삭제하기 
<code>docker rm ws2</code></li>
</ul>
<hr>
<h4 id="컨테이너-실행과정-실습">컨테이너 실행과정 실습</h4>
<pre><code>docker run httpd 
docker ps 
docker run --name ws2 httpd 
docker stop ws2 
docker ps// 꺼졌는지 확인
docker ps -a
docker start ws2 //이때 로그가 안보임 
docker logs ws2 
docker logs -f ws2
docker rm ws2 //현재 실행중인 컨테이너는 바로 삭제 불가능 -&gt; 에러발생 
//방법 1. 실행중지 후 삭제
docker stop ws2 
docker rm ws2
//방법 2. 강제 삭제
docker rm --force ws2</code></pre><hr>
<blockquote>
<p>image를 command line에서 삭제하고 싶을 때 
: <code>docker rmi httpd</code></p>
</blockquote>
<h2 id="네트워크-설정">네트워크 설정</h2>
<p><del>서버 클라이언트, 포트, 네트워크에 대한 기본지식 필요.</del></p>
<h3 id="도커-없이-웹서버-만들기-">도커 없이 웹서버 만들기 :</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/7591c577-e697-4786-aed7-c1ed751ea955/image.png" alt=""></p>
<ol>
<li>2개의 컴퓨터 필요 ( 웹브라우저/ 웹서버 용)</li>
<li>웹페이지를 파일로 만들어서 특정 디렉토리에 위치 시키기.</li>
<li>index.html파일을 /usr/local/apache2/httdocs/에 위치시킴. </li>
<li>0-65535 포트중에 하나로 들어와야함. (80번 포트를 사용)</li>
<li>웹서버의 주소는 example.com로 가정했을 때,</li>
<li><a href="http://example.com:80/index.html">http://example.com:80/index.html</a> 로 접속</li>
<li>80번 포트에서 대기중엔 웹서버에 요청 전달</li>
<li>index.html파일을 찾고, 코드를 전달.</li>
</ol>
<h3 id="도커를-사용해서-웹서버-만들기-">도커를 사용해서 웹서버 만들기 :</h3>
<p><img src="https://velog.velcdn.com/images/blank_/post/dc5535df-3f3d-4258-9739-20182432cb78/image.png" alt=""></p>
<h4 id="웹서버를-컨테이너에-설치하기">웹서버를 컨테이너에 설치하기</h4>
<ul>
<li><code>docker run httpd</code>
host의 80번 포트가 container의 80번 포트와 연결되지 않았기 때문에 <a href="http://example.com:80/index.html">http://example.com:80/index.html</a> 로 접속해도 연결되지 않음</li>
<li><code>docker run -p 80:80 httpd</code></li>
<li>p 80:80은 <strong>포트포워딩</strong>을 위한 명령어로, 첫 80은 host의 80 포트, 두번째 80은 container의 80포트로, <strong>host의 80포트로 들어오는 신호를 container의 80포트로 전송한다는 의미</strong>이다.</li>
<li><code>docker run -p 8000:80 httpd</code>
호스트의 8000번 포트를 container의 80번 포트로 전송</li>
</ul>
<hr>
<h4 id="연결-과정-실습">연결 과정 실습</h4>
<ul>
<li><code>docker pull httpd</code></li>
<li><code>docker ps</code></li>
<li><code>docker run --name ws3 --publish 8081:80 httpd</code>
이미지를 만든 사람이 80포트를 기본으로 설정함.</li>
<li><code>docker ps</code> 
포트포워딩 확인</li>
<li><a href="http://localhost:8081/index.html">http://localhost:8081/index.html</a> 로 접속</li>
<li>container 로그 확인하기 </li>
</ul>
<hr>
<p><del>이제 http라는 어플리케이션을 실행할 수 있는 상태가 됨.</del></p>
<h2 id="컨테이너-내부에-접근하기">컨테이너 내부에 접근하기</h2>
<p>index.html을 편집해서 web app을 만들기 위해서는, container안으로 들어가서 파일을 수정할 수 있어야 한다. </p>
<h3 id="host에서-container안으로-들어가는-방법">host에서 container안으로 들어가는 방법</h3>
<ol>
<li>docker desktop에서 <strong>해당 container 의 cli버튼 클릭</strong> 시 terminal이 실행되는데, 이때 여기서 입력하는 명령어는 host가 아닌 container내부에서 실행되는 명령어이다.<blockquote>
<p><code>pwd</code> 
host에서 pwd가 아니라 container안에서 pwd실행한거
<code>ls -a</code> 
container안에 있는 파일을 보여줌</p>
</blockquote>
</li>
<li>terminal에서 접근
sh대신 bash를 쓰고 싶을 경우 : /bin/bash로 실행
(container에 따라서 bash실행이 안되는 경우 sh로 실행하기)<blockquote>
</blockquote>
<code>docker ps</code>
<code>docker exec ws3 pwd</code>
pwd명령을 컨테이너 대상으로 커맨드 1회 실행 
<code>docker exec ws3 -it /bin/sh</code>
이때부터 내리는 명령은 모두 컨테이너 안에서 실행됨.</li>
</ol>
<p>-it옵션 없이 실행하면, shell프로그램이 실행되자마자 바로 종료된다. 
<code>exit</code>
container에서 host로 나가기</p>
<p>** 🤦‍♂️찾아봅시다 **</p>
<blockquote>
<p>Q. -it 옵션의 의미?
-i --interactive : 명령어를 입력받을 수 있도록 상호 입출력가능하게 세션을 열어두도록 변경
-t --tty : tty를 활성화해서 셸(shell)을 사용하도록 활성화 (터미널과 유사한 환경을 제공)</p>
</blockquote>
<h3 id="web-server의-indexhtml-파일-수정해보기">web server의 index.html 파일 수정해보기</h3>
<p><a href="https://hub.docker.com/_/httpd">httpd manual</a></p>
<hr>
<p>index.html파일의 경로는 매뉴얼페이지에 적혀있다.</p>
<ul>
<li><code>/user/local/apache2/htdocs/</code> 폴더로 이동한다.</li>
<li>경로에 존재하는 index.html을 읽으려고 하면, 
vim / nano등의 텍스트 에디터가 설치되어있지 않아서 에러가 난다.</li>
<li>원하는 에디터를 설치해서 index.html을 읽고, 
내용을 <code>It, workts</code> 에서 <code>Hello, Docker!</code> 로 변경 후 저장한다.</li>
<li>웹 브라우저에서 리로드를 해보면, 내용이 변경된 페이지를 확인할 수 있다.</li>
</ul>
<hr>
<p><del>컨테이너안에 있는 index.html파일을 직접 수정할 수 있게 되었습니다. ㄱ와우ㄴ</del></p>
<h2 id="볼륨">볼륨</h2>
<p><img src="https://velog.velcdn.com/images/blank_/post/d8db940c-2fe8-45e1-96b1-288bb6c9051e/image.png" alt=""></p>
<p>위의 사진처럼, 컨테이너 안의 index.html파일만 수정해서 웹페이지를 변경했는데, 만약 컨테이너가 삭제된다면, 수정된 index.html파일도 삭제된다. <del>흑흑</del> </p>
<ul>
<li>컨테이너를 사용하는 이유는, 필요할 때 언제든지 생성/ 삭제 가능하기 때문인데, 파일이 컨테이너에 종속되면 그 의의가 사라진다고도 할 수 있다. </li>
</ul>
<p>만약, <strong>host에 있는 특정 폴더와 container의 특정 폴더를 연결</strong>하고, host에서 수정했을 때 <strong>container에 연동</strong>될 수 있다면? </p>
<ul>
<li>너무너무 좋겠다! 버전 관리도 훨씬 쉽고, 실행환경과 파일 수정환경을 분리할 수 있고, 백업 정책 수립도 가능하고, 원하는 ide로 편집도 가능하다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/blank_/post/d345a119-a748-48bf-828b-c537c082abd7/image.png" alt=""></p>
<hr>
<h4 id="볼륨-설정-실습">볼륨 설정 실습</h4>
<ul>
<li><code>mkdir ~/Desktop/htdocs &amp;&amp; cd ~/Desktop/htdocs</code></li>
<li><code>vi index.html</code>
index.html을 원하는 소스로 수정하기 (ex)hello, docker?)</li>
<li><code>docker run -p 8080:80 -v ~/Desktop/htdocs:/usr/local/apache2/htdocs/ httpd</code>
파일시스템 연결</li>
<li><code>vi index.html</code>
index.html 수정하기</li>
<li>웹페이지 리로드시 수정된 파일이 보인다.</li>
</ul>
<hr>
<p>*<em>🤦‍♂️찾아봅시다 *</em></p>
<blockquote>
<p>Q. 도커 볼륨과 바인드마운트의 차이점이 무엇인가요? 
둘의 가장 큰 차이점은, docker가 mount point를 관리해 주느냐의 여부로 나뉜다. 컨테이너화된 개발 환경을 구축하고 싶을 때는 bind-mount가 더 유리하기 때문에 로컬에서 개발할 때는 바인드 마운트 방식이 적합하다.</p>
</blockquote>
<table>
<thead>
<tr>
<th></th>
<th><center>도커 볼륨</center></th>
<th><center>바인드마운트</center></th>
</tr>
</thead>
<tbody><tr>
<td>Docker가 mount point관리</td>
<td>O (docker가 제시한 경로에 맞춰서 생성)</td>
<td>X (원하는 경로를 명시)</td>
</tr>
<tr>
<td>Type</td>
<td>volume</td>
<td>bind</td>
</tr>
<tr>
<td><br/></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<blockquote>
</blockquote>
<p>Q. 지금은 바인드마운트를 사용한것 같은데 과제에서는 볼륨을 써야하나요? 
<strong>subject에 volume2개를 사용하라고 명시되어있어서 yes로 추정</strong> 근데 docker-compose 보다보니 아닌거같기도해요..</p>
<blockquote>
</blockquote>
<p>Q. 컨테이너를 run한 뒤에 나중에 volume 을 설정해줄 수는 없나요?
<a href="https://www.howtogeek.com/devops/how-to-add-a-volume-to-an-existing-docker-container/">가능하기는 하다</a> 하지만 권장하지 않음. 컨테이너는 <strong>시작시에 볼륨설정</strong>을 끝내야하므로, 볼륨을 추가하려면 재시작을 권고한다.</p>
<hr>
<h3 id="참고">참고</h3>
<p><a href="https://www.youtube.com/playlist?list=PLuHgQVnccGMDeMJsGq2O-55Ymtx0IdKWf">생활코딩 도커입문수업</a></p>
<blockquote>
<h4 id="jayoon님-의견">JAYOON님 의견</h4>
<p>&lt;웹서버 구동해보기 파트 첨언&gt;
세 개의 컴퓨터에 운영체제부터 설치하는 게 잘 안 되는 게 아니라 웹 서비스 하나를 위해 컴퓨터 세 개나 필요한 게 문제입니다!
겨우 웹서버를 위해 가상 머신을 설치하는 것 때문에 굉장히 느려지는 게 아니라 host 운영체제 위에 다른 OS 를 설치하니까 느려집니다. 웹 브라우저와 웹 서버는 저수준 언어로 만들어져 있는데 이런 저수준 언어 프로그램을 사용하기 위해 OS 위에 또다른 OS 설치하는 것이 오버헤드가 너무 큽니다.</p>
</blockquote>
<h4 id="오늘의-깨달음-45">오늘의 깨달음 (4/5)</h4>
<ol>
<li>웹브라우저와 웹 서버는 저수준이다.</li>
<li>Q. exec과 run의 차이점은?</li>
<li><del>왜 공부할수록 질문만 늘어나는 것 같을까요?</del></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[IRC]]></title>
            <link>https://velog.io/@blank_/IRC</link>
            <guid>https://velog.io/@blank_/IRC</guid>
            <pubDate>Tue, 14 Feb 2023 07:45:10 GMT</pubDate>
            <description><![CDATA[<p>code : <a href="https://github.com/zoovely/IRC.git">LAZYIRC</a> 
co-work : with @sojoo @yachoi @hyunjcho
date : 2023/01 - 2023/02</p>
<h2 id="socket-programming">Socket Programming</h2>
<p>컴퓨터 네트워크를 대상으로 하는 입출력 행위를 다루는 프로그래밍
socket : 전화기
전화기에 연결되는 번호 : 포트번호
전화기에 번호를 연결 : bind
각 전화기가 전화를 기다리는 상태 : listen
전화기에 전화가 오는 상태 : accept
수화기를 들어서 전화를 연결 : read</p>
<p>1.Socket</p>
<blockquote>
<p>프로그램이 네트워크에서 데이터를 송수신할 수 있도록, 
&quot;네트워크 환경에 연결할 수 있게 만들어진 연결부&quot;가 바로 <strong>네트워크 소켓(Socket)</strong>
네트워크에 연결하기 위한 소켓 또한 정해진 규약, 즉, 통신을 위한 프로토콜(Protocol)에 맞게 만들져야 함. 
보통 OSI 7 Layer(Open System Interconnection 7 Layer)의 네 번째 계층인 TCP(Transport Control Protocol) 상에서 동작하는 소켓을 주로 사용하는데, &quot;TCP 소켓&quot; 또는 &quot;TCP/IP 소켓&quot;이라고 함. (UDP에서 동작하는 소켓은 &quot;UDP 소켓&quot;)</p>
</blockquote>
<p>2.CLIENT SOCKET &amp; SERVER SOCKET</p>
<blockquote>
<p>클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)의 역할.
데이터를 주고받기 위해서는 먼저 소켓의 연결 과정이 선행되어야 하고, 그 과정에서의 연결 요청과 수신이 각각 클라이언트 소켓과 서버 소켓의 역할.
마치 클라이언트 소켓(Client Socket)과 서버 소켓(Server Socket)이 태생적으로 구조가 다른, 전혀 별개의 소켓(Socket)인 것처럼 보일 수 있지만, 두 소켓(Socket)은 태생적으로 동일함. 소켓의 역할과 역할에 따라 처리되는 흐름, 즉, 호출되는 API 함수의 종류와 순서들이 달라서 다르게 부르는 것이고, 전혀 다른 형태의 소켓이 아님.</p>
</blockquote>
<p>3.연결 순서</p>
<blockquote>
<p>두 개의 시스템(또는 프로세스)이 소켓을 통해 네트워크 연결(Connection)을 만들기 위해서는, 최초 어느 한 곳에서 그 대상이 되는 곳으로 연결요청이 선행. IP 주소와 포트 번호로 식별되는 대상에게, 자신이 데이터 송수신을 위한 네트워크 연결을 수립할 의사가 있음을 알리는 것.
<img src="https://velog.velcdn.com/images/blank_/post/efe471d6-5bc5-413c-a927-e95b94f8053d/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>한 곳에서 연결 요청을 보낸다고 하더라도 그 대상 시스템이 그 요청을 받아들일 준비가 되어 있지 않다면, 해당 요청은 무시되고 연결은 만들어지지 않음.
<img src="https://velog.velcdn.com/images/blank_/post/abd930f5-6722-4e56-92a5-3d0621240eac/image.png" alt=""></p>
</blockquote>
<p>4.클라이언트 소켓의 실행흐름</p>
<blockquote>
<p>클라이언트 소켓(Client Socket)은 처음 소켓(Socket)을 <strong>1.생성(create)</strong>한 다음, 서버 측에 <strong>2.연결(connect)</strong>을 요청. 그리고 서버 소켓에서 연결이 받아들여지면 데이터를 <strong>3.송수신(send/recv)</strong>하고, 모든 처리가 완료되면 소켓(Socket)을 <strong>4.닫는다(close).</strong></p>
</blockquote>
<p>5.서버 소켓의 실행흐름</p>
<blockquote>
<p>소켓(Socket)을 <strong>1.생성(create)</strong>하고 서버가 사용할 IP 주소와 포트 번호를 생성한 소켓에 <strong>2. 결합(bind).</strong> 그런 다음 클라이언트로부터 연결 요청이 수신되는지 <strong>3.주시(listen)</strong>하고, 요청이 수신되면 요청을 <strong>4.받아들여(accept)</strong> 데이터 통신을 위한 소켓을 생성. 일단 새로운 소켓을 통해 연결이 수립(ESTABLISHED)되면, 클라이언트와 마찬가지로 데이터를 <strong>5.송수신(send/recv)</strong>할 수 있습니다. 마지막으로 데이터 송수신이 완료되면, 소켓(Socket)을 <strong>6.닫는다(close).</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/blank_/post/9e91b5dd-220b-497d-b86e-250c9ce60776/image.png" alt=""></p>
<p>6.서버소켓 프로그래밍</p>
<blockquote>
<h4 id="bind">BIND</h4>
<p>각 소켓은 시스템이 관리하는 포트번호들 중 하나의 포트 번호를 사용하게 되는데, 이 번호가 중복될 경우 어떤 포트로 수신된 데이터가 어떤 소켓을 위한건지 알수 없게됨. 소트가 중복된 포트 번호를 사용하지 않도록, 내부적으로 포트번호와 소켓연결 번호를 관리 -&gt; 만약 다른 소켓이 해당 번호를 사용하고 있다면 에러를 리턴함. 일반적으로 서버소켓이 고정된 포트번호를 사용. 
=&gt; 이제 클라이언트의 연결요청을 수신할 준비가 됨.</p>
<h4 id="listen">LISTEN</h4>
<p>클라이언트의 연결요청이 수신되기를 기다리는 곳.
서버 소켓에 바인딩된 포트번호롤 클라이언트의 연결 요청이 있는지 확인하면서 대기상태에 머무름. 요청이 수신되면, 대기상태를 종료하고 리턴함.
listen이 대기상태에서 빠져나오는 경우는 2가지 1. 클라이언트의 요청이 수신된 경우, 2. 에러가 발생한 경우 (소켓이 close됨)
리턴되는 값으로 클라이언트 요청이 수신되었는지, error로 인한건지 확인 가능 (client에 대한 정보는 포함 x)</p>
<h4 id="accept">ACCEPT</h4>
<p>소켓 연결을 실질적으로 수행하는 절차.
<strong>데이터통신을 위해 연결되는 소켓이 bind, listen에서 쓰는 소켓이 아님에 주의해야함.</strong>
accept내부에서 연결을 위해서 새로 만들어지는 소켓에 연결됨.</p>
</blockquote>
<h3 id="poll">POLL</h3>
<p>동시에 여러개의 I/O를 대기할 경우에 특정한 fd에 blocking되지 않고 I/O를 할 수 있는 상태인 지를 모니터링하여 I/O 가능한 상태의 fd인지를 검사하는 함수. 대기하고싶은 특정fd에 대해서 저장하고있는 pollfd라는 구조를 인자로 넘긴다.</p>
<pre><code>int poll(struct pollfd *fds, nfds_t nfds, int timeout);</code></pre><p><a href="https://www.it-note.kr/23">poll 함수 reference</a></p>
<h3 id="fcntl">FCNTL</h3>
<p>파일 컨트롤 함수로, 열려 있는 파일의 특성을 변경하기 위해 사용하는 함수.
F_SETFL : arg 에 지정된 값으로 파일지정자 fd 의 플래그를 재 설정한다. 리눅스에서 이 명령은 O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, O_NONBLOCK 플래그만 바꿀 수 있다.
O_NONBLOCK : read/ recieve시에 기다리지 않고 바로 리턴하게하는 플래그. </p>
<pre><code>int fcntl(int fd, int cmd, int arg); </code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Push_Swap - Radix Sort]]></title>
            <link>https://velog.io/@blank_/PushSwap-Radix-Sort</link>
            <guid>https://velog.io/@blank_/PushSwap-Radix-Sort</guid>
            <pubDate>Wed, 03 Aug 2022 07:14:50 GMT</pubDate>
            <description><![CDATA[<h3 id="radix-sort란">radix sort란?</h3>
<p>한국어로는 기수정렬로 bucket sort의 일종으로, 기수별로 비교없이 수행하는 정렬 알고리즘이다. 
끝자리부터 각 자릿수를 비교하여 정렬해나가는 방식으로 비교가 없다는 것이 특징이다.
유효숫자가 두개 이상인 경우, 모든 숫자요소에 대해서 수행될 때까지 각 자릿수에 대해서 반복된다.
=&gt; 따라서, 전체 시간복잡도 : <em>O(nw)(n은 숫자의 개수, w는 기수의 크기)</em> 이다</p>
<p><strong>기수</strong>란 기수법에서 말하는 수학적 용어로, 기수법(記數法, numeral system)은 수를 시각적으로 나타내는 방법인데, 기수법을 통해서 나타나는 각각의 숫자는 다른 수들과 구별되는 표기 방식을 가진다.
고대에는 각 나라별로 <strong>로마숫자, 그리스 숫자, 한자, 아라비아 숫자</strong>등이 사용되었지만 현대에 이르러서는 <strong>2진법, 10진법, 60진법</strong>등을 주로 사용하며, 대부분의 숫자는 10진법으로 표기된다.
따라서, 기수법은 쉽게 진법이라고 생각할 수 있다.</p>
<h4 id="ex-10진법의-숫자-10개">EX) 10진법의 숫자 10개</h4>
<p>87, 487, 781, 100, 101, 0, 1, 512, 66, 24의 숫자들을 정렬한다고 생각해보자.
우선 기수정렬에는 기수 테이블의 크기만한 메모리가 더 필요하다.
기수테이블에는 각 진법에서 필요한 수들의 바구니가 있다고 생각하면 되는데,
지금의 예제에서는 10진법의 숫자들이므로 10진법에서 필요한 수인 0,1,2,3,4,5,6,7,8,9에 해당하는 바구니가 있다.</p>
<p>그림으로 보면, 이런식으로 진행된다.
<img src="https://velog.velcdn.com/images/blank_/post/5c84187c-1371-4f20-9638-8c848298f1f3/image.JPG" alt=""></p>
<p>이제 끝자리인 1의 자리부터 정렬을 해보자.
그러면, 각자 순서대로 1의 자리에 위치한 수와 동일한 바구니를 찾아가게 된다.
1의자리의 정렬이 완료되면, 0-9의 순서대로 다시 하나의 배열로 합친다.
<img src="https://velog.velcdn.com/images/blank_/post/d589b57c-a8b2-4912-90eb-25cfd0107d4a/image.JPG" alt=""></p>
<p>다음으로는, 10의 자리수 정렬이 시작된다.
0, 1같은 경우에는 10의 자리수가 0이라고 생각하고 진행된다.
<del>아 그림 하나씩 캡쳐해서 올리기 귀찮으....</del>.
<img src="https://velog.velcdn.com/images/blank_/post/53ccd05f-deb9-4f9a-9b74-4e883e17c00b/image.JPG" alt=""></p>
<p>정렬이 되고난 후에는 다시 하나의 배열을 갖게 되고,
이후 100의 자리수 정렬이 시작되고, 100미만의 수들에 대해서는 100의 자리수가 0이라고 생각하고 진행된다.</p>
<p>마지막으로 100의 자리수에대해서도 0-9 순서로 하나의 배열로 합쳐주면,
짜잔- 정렬이 완료된다.</p>
<p>지금은 수의 최대값이 100의자리기 때문에 100의자리까지만 비교를 했지만, 만약 10억자리의 수가 포함된 10진법 10개라면, 최대 10억자리까지 비교를 해야 할 수도 있다.</p>
<h4 id="ex-2진법의-숫자-5개">EX) 2진법의 숫자 5개</h4>
<p>다음으로는 컴퓨터가 주로 사용하는 진법인 2진법에 대해서 살펴보자.
5, 3, 4, 1, 2의 숫자들을 정렬한다고 생각해보자,
이 숫자들을 2진법으로 바꿔보면, 101, 011, 100, 001, 010가 된다.
이제 1의 자리수부터 바구니가 0과 1이 있다고 생각하고 진행하면 된다.
<img src="https://velog.velcdn.com/images/blank_/post/306ac515-33fc-4bc5-9911-2cd579e89539/image.jpg" alt=""></p>
<h2 id="♠️-push_swap을-위한-radix-sort를-구현">♠️ push_swap을 위한 radix sort를 구현</h2>
<p>push_swap 과제에서는 우리는 스택A, 스택B만을 이용해야 하고, 명령어를 통해서 2번안에 접근가능한 인덱스는 top과 bottom 뿐이다.
그래서 독립적으로 생각하고 사용할 수 있는 메모리 공간이 A의 위, 아래 B의 위 아래 공간이 있다.
2진법으로 구현할 수도 있고, 3진법으로 구현할 수도 있었지만, 두 가지 방법 모두 계산해보니 2진법으로 구현했을 때의 방법이 명령어 사용개수의 최댓값이 적어서 2진법으로 정했다.</p>
<p>0바구니의 위치와 1바구니의 위치를 지정해준뒤 하나씩 돌려가며 해당 자리수가 0인지 1인지 확인하고, 옮겨주고, 모든 숫자에 대해서 확인이 끝나면 다시 A스택으로 옮기도록 했다.</p>
<p>이때, A의 위는 데이터를 빼내는 곳으로 사용하고, A의 아래는 1바구니를 저장하고, B의 위를 0바구니를 저장한다고 생각했다.</p>
<p>더 자세히 말하자면, 수를 shifting 연산과 bit연산을 이용해서, </p>
<pre><code>for (All of stack_A)
{
    if ((A-&gt;stack[A-&gt;top] &gt;&gt; i) &amp; 1)
        //ra command 
    else
        //pb command
}</code></pre><p><strong>i</strong> 번만큼 오른쪽으로 shift한 수가 1일 경우, ra를 하고, 0일 경우 pb를 하게 했다.</p>
<h2 id="정리">정리</h2>
<p>요즘 대세는 push_swap을 퀵소트로 3개의 chunk씩 나눠서 푸는 것이지만 나는 스택이 2개면 2개의 메모리 공간을 이용하는 radix로 해야되지 않나? 에 꽂혀서 결국 여기까지 와버렸다...
열심히 명령어개수를 줄여보아도 100점받을 개수까지는 못줄여서 일단 평가를 진행할 예정이다.</p>
<p>이후에 기수정렬로 할 이 글을 읽고 있는 여러분들을 위해 최적화 아이디어 몇개를 써둔다.
<del>이건 단지 아이디어일 뿐이므로, 원하는 정도의 효과를 내지 못할 수도 있다. 당연히...!</del></p>
<ol>
<li><p>3,4,5개에 대해서도 하나의 알고리즘을 가진채 구현했다면, 해당 알고리즘에서 쓰는 명령어 개수보다 작은 개수로 정렬할 수 있는 집합에 대해서 하드코딩을 진행한다.
예를 들어, 2 1 3 4는 알고리즘으로 진행할 경우 5개의 명령어를 사용한다고 하면, 사실 사람이 보면 딱 sa만 하면 된다는걸 아니까. 이런경우들에 대해서 하드코딩해주면 명령어개수가 줄어들지 않을까?</p>
</li>
<li><p>radix소트는 &quot;기수&quot;개수에 따라서 시간복잡도, 즉 push_swap에서 요구하는 명령어의 개수가 줄어들게 된다. 
따라서, 큰 개수를 여러개의 작은 개수를 가진 chunk로 나눠서 각 chunk를 1-chunk개수로 세팅후 정렬한다.
500개를 정렬할 때 (1-500의 청크 * 1 개 -&gt; 최대 9개의 기수(2^9 = 512) * 1)에 대해서 정렬하는대신 (1- 100의 청크 * 5개 -&gt; 7개의 기수에 대해 정렬(2^7 = 128) * 5)로 을 줄일 수 있지 않을까??</p>
</li>
</ol>
<p>-&gt; 대신 이건 수를 변경하는 순간 원래의 수를 잃게 되므로 저장을 따로 해주던지..아니면 재귀를 이용해서 잘 스택 A에 순서대로 쌓는 방법을 고안하든 해야될듯.</p>
<ol start="3">
<li>이건, @jibang님의 아이디어인데, radixsort를 하나의 기수에 대해서 진행하지 말고, 여러개의 기수를 묶어서 십진법을 비교해서 하면 어떨까하고 말씀해주셨다.
그러면 기수가 많을 경우 예를 들어 500 -&gt; 기수가 9개 이면 3개/3개/3개로 나눠서 각각 3개짜리 기수에 대해서 radix sort를 진행한다. 각각 a의 바텀, b의 탑, b의 바텀으로 옮기고 거기서 다시 소팅을 해서 합치는 걸로 하면 어떨지...</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>