<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>taetae.log</title>
        <link>https://velog.io/</link>
        <description>어서와</description>
        <lastBuildDate>Fri, 07 Mar 2025 19:05:14 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>taetae.log</title>
            <url>https://velog.velcdn.com/images/tae_1/profile/ae58b9f4-fc41-4350-a0ae-d57a45e737dd/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. taetae.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/tae_1" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Node.js] 카카오 로그인 구현(REST API)]]></title>
            <link>https://velog.io/@tae_1/Node.js-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84REST-API</link>
            <guid>https://velog.io/@tae_1/Node.js-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84REST-API</guid>
            <pubDate>Fri, 07 Mar 2025 19:05:14 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/tae_1/post/389f96e2-f8ae-4fa7-9ca6-5cc8076f9262/image.png" alt=""></p>
<p>게시판 웹페이지를 만들면서 로그인/회원가입 기능도 구현하고 있다.
여기에 간편 로그인도 할 수 있으면 좋을 것 같다는 생각이 들어 카카오 로그인을 넣어보았다.</p>
<h1 id="1-준비-단계">1. 준비 단계</h1>
<p><img src="https://velog.velcdn.com/images/tae_1/post/500f57a1-b669-465a-972f-046616c1699f/image.png" alt=""></p>
<p>먼저 카카오 developers에 들어가 내 애플리케이션을 클릭해준다.
그러면 애플리케이션을 추가할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/00d63e1b-df6e-4a2e-b3de-942950c195cd/image.png" alt=""></p>
<p>대충 이름이나 이런거 정해서 저장해주면 애플리케이션이 생긴다.</p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/96555e54-230a-4d09-ab4b-1fb27093b079/image.png" alt=""></p>
<p>해당 애플리케이션을 눌러 다음과 같은 화면을 볼 수 있는데 그 중에서 카카오 로그인 상태를 ON해줘야한다.</p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/66fa3fa9-ca49-4b4f-af0b-202553815a56/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/9c905ae0-2a24-456e-83b1-9995c3759c8a/image.png" alt=""></p>
<p>바로 밑에 Redirect URL도 있으니까 이것도 설정해준다.
이게 뭐냐면 페이지에서 로그인을 요청하게 되면 카카오의 로그인 페이지로 이동한다. 그리고 사용자가 로그인에 성공하면 다시 해당 웹페이지로 돌아가야 되는데 그 돌아갈 주소를 말하는 것이다.
즉!! 사진에 있는 주소를 따라하지 않아도 된다는 소리다.</p>
<p>자 이제 동의 항목에서 내가 가져오고 싶은 정보를 선택해보자...
엇...??
<img src="https://velog.velcdn.com/images/tae_1/post/c79f603a-9518-44d8-a2d3-7b23d3b7be95/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/01a1c82d-b705-42a8-9368-2ce462d960b7/image.png" alt=""></p>
<p>닉네임은 있는데 이름, 전화번호, 이메일이.. 권한이 없다...(두둥)
이럴수가...
어떻게 할 방법이 없는지 찾아보니까 비즈 앱으로 전환하면 된다고 한다. (휴.. 못하는 줄 알고 놀랐네)</p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/d9a6e0a9-c96a-4c1b-a522-29f112497b7e/image.png" alt=""></p>
<p>앱 아이콘을 등록해주고</p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/c54e3e77-53ea-4009-a3ce-b7a84d2c43e2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/5896e725-5302-441e-9d74-6ce59bd718f3/image.png" alt=""></p>
<p>사업자 정보 등록을 눌러 개인 개발자 비즈 앱 전환을 클릭해주었다.</p>
<p>그런 다음!!!
<img src="https://velog.velcdn.com/images/tae_1/post/2623da94-f18f-44de-a669-1a10cabe12ef/image.png" alt="">
테스트앱 생성을 해주면<del>~</del></p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/61cd3cdc-dda5-4656-b80c-ab2e0ebefebd/image.png" alt=""></p>
<p>해당 앱에서 내가 원하던 동의항목을 설정할 수 있다.</p>
<p>그럼 이제 vsCode로 넘어와서 앱 설정 &gt; 앱 키에 REST API 키랑 제품설정 &gt; 보안에 Client Secret을 .env파일에 잘 넣어둔다. 다른 사람한테 보여주면 안되는 키이기 때문에 .env파일에 넣고 .gitignore해주면 된다.</p>
<p>불러올 땐 이런 식으로~</p>
<blockquote>
<p>process.env.env파일에 적은 변수명</p>
</blockquote>
<p>app.js에 </p>
<pre><code class="language-javascript">const snsRouter = require(&quot;./routes/snsRouter&quot;);
app.use(&#39;/auth&#39;, snsRouter);</code></pre>
<p>라우터를 /auth로 연결해준다.</p>
<p>그리고 사용자가 카카오톡 로그인을 클릭할 수 있도록 /auth/kakao로 get요청을 보내는 a태그를 하나 만들어 놓는다.</p>
<pre><code class="language-html"> &lt;a id=&quot;kakao&quot; href=&quot;/auth/kakao&quot; class=&quot;btn&quot;&gt;카카오톡 로그인&lt;/a&gt;</code></pre>
<p>이제 라우터 파일에 카카오 로그인 페이지로 이동하여 콜백 처리를 해줄 것이다.</p>
<pre><code class="language-javascript">const express = require(&quot;express&quot;);
const router = express.Router();
const axios = require(&quot;axios&quot;);
const db = require(&quot;../models&quot;);
const loginModel = db.users; // user 모델 사용
const jwt = require(&quot;jsonwebtoken&quot;);
const secretKey = process.env.JWT_SECRET; // JWT 시크릿 키

// 카카오 로그인 API
const KAKAO_ID = process.env.KAKAO_ID;
const kakaoredirectURI = encodeURI(&quot;http://localhost:3000/auth/kakao/callback&quot;);
const kakao_secret = process.env.KAKAO_SECRET;
let kakaoAPI_URL = &quot;&quot;;

// 카카오 로그인 페이지로 이동 (url 생성)
router.get(&quot;/kakao&quot;, (req, res) =&gt; {
  kakaoAPI_URL = `https://kauth.kakao.com/oauth/authorize?response_type=code&amp;client_id=${KAKAO_ID}&amp;redirect_uri=${kakaoredirectURI}`;
  res.redirect(kakaoAPI_URL);
});

// 카카오 로그인 콜백 처리
router.get(&quot;/kakao/callback&quot;, async (req, res) =&gt; {
  const { code } = req.query;
  kakaoAPI_URL = &quot;https://kauth.kakao.com/oauth/token&quot;;
  try {
    // 접근 토큰 요청
    const tokenAxios = await axios({
      method: &quot;get&quot;,
      url: kakaoAPI_URL,
      params: {
        grant_type: &quot;authorization_code&quot;,
        client_id: KAKAO_ID,
        redirect_uri: kakaoredirectURI,
        code: code,
        client_secret: kakao_secret,
      },
      headers: {
        &quot;Content-Type&quot;: &quot;application/x-www-form-urlencoded;charset=utf-8&quot;,
      },
    });
    const accessToken = tokenAxios.data.access_token;

    // 사용자 프로필 정보 요청
    const profileRes = await axios({
      method: &quot;get&quot;,
      url: &quot;https://kapi.kakao.com/v2/user/me&quot;,
      headers: { Authorization: `Bearer ${accessToken}` },
    });

    const { email, name, phone_number } = profileRes.data.kakao_account;
    const { nickname } = profileRes.data.kakao_account.profile;
    const phone = phone_number.replace(&quot;+82 &quot;, 0);

    // DB에서 기존 회원 여부 확인
    let user = await loginModel.findOne({ where: { email } });
    if (!user) {
      // 없으면 신규 회원 가입
      user = await loginModel.create({
        email,
        username: name,
        nickname: nickname,
        phone,
        provider: &quot;kakao&quot;,
      });
    }

    // JWT 토큰 발급
    const token = jwt.sign({ id: user.id }, secretKey, { expiresIn: &quot;7d&quot; });

    // 쿠키에 토큰 저장
    res.cookie(&quot;token&quot;, token, { httpOnly: true });
    res.redirect(&quot;/&quot;);
  } catch (error) {
    console.error(&quot;카카오 로그인 실패:&quot;, error);
  }
});

module.exports = router;
</code></pre>
<p>아까 설정한 redirectURI와 API키로 카카오 로그인 페이지로 이동시켜준다.
그리고 로그인했을때 접근 토큰을 요청해서 해당 사용자의 프로필 정보를 요청해준다.
만약 DB에 없는 사용자라면 신규 회원가입을 위해 DB에 추가해준다.
후에 JWT 토큰을 발급해주고 메인 페이지로 돌아간다.</p>
<p>해당 사용법은 카카오 API 공식 문서에 잘 나와있으니 확인해보면 될 것이다.
<a href="https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api">https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api</a></p>
<p>우왕 이제 DB에 저장된다~~ 했는데!!!!!
<img src="https://velog.velcdn.com/images/tae_1/post/52b34d49-4fee-419b-a0d7-e65d05665ae8/image.jpg" alt=""></p>
<p>뭐야... 분명히 로그인 누르면 로그인이 되어서 db에 저장이 되는데...
로그인 창이.. 안 뜬다...? 분명히 떴는데...????????</p>
<p>그래서 계속 검색해보고 잘 썼는지 확인하고, 키 확인하고 별 짓 다하다가 네이버와 카카오는 기존 로그인 상태를 유지하는 기능이 있어서 이미 로그인한 상태라면 창을 띄우지 않고 바로 인증을 진행한다고 한다...</p>
<p>엣... 반신반의하면서 브라우저 시크릿 모드에서 로그인을 해보니까...
<img src="https://velog.velcdn.com/images/tae_1/post/27fd0f96-5daa-406e-9dcd-ae6ad3456d94/image.png" alt=""></p>
<p>된다!!! 미친.... 시간을 얼마나 날린거야...
그래도 되니까 좋다!!!</p>
<p>아 그리고 추가로 알게된 건데 </p>
<blockquote>
<p>kakaoAPI_URL = <code>https://kauth.kakao.com/oauth/authorize?response_type=code&amp;client_id=${KAKAO_ID}&amp;redirect_uri=${kakaoredirectURI}&amp;prompt=login</code>;</p>
</blockquote>
<p>이런 식으로 URL뒤에 prompt=login을 추가하면, 기존 로그인 여부와 상관없이 무조건 로그인 창을 띄운다고 한다.</p>
<p>그렇게 로그인 창을 항상 뜨게 만들어놨다 ㅎㅎ</p>
<p>끝끝~!!
이제 나머지 구현해야지...</p>
<p>마지막에 잘못 구현한줄 알고 삽질한게 너무 어이가 없어가지고 벨로그를 켜버렸다.</p>
<p>이제 다시.. 간다.. bye</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] Promise 객체]]></title>
            <link>https://velog.io/@tae_1/JavaScript-Promise-%EA%B0%9D%EC%B2%B4</link>
            <guid>https://velog.io/@tae_1/JavaScript-Promise-%EA%B0%9D%EC%B2%B4</guid>
            <pubDate>Mon, 17 Feb 2025 16:37:57 GMT</pubDate>
            <description><![CDATA[<p>Axios는 Node.js와 브라우저를 위한 Promise API를 활용한다.
비동기 HTTP 통신이 가능하고 return이 Promise 객체로 온다.</p>
<p>Fetch도 마찬가지로 Promise 기반이다.</p>
<h3 id="그렇다면-promise가-무엇인가">그렇다면!! Promise가 무엇인가?</h3>
<br>

<h1 id="promise-객체란">Promise 객체란?</h1>
<p>어떤 작업에 관한 <strong>상태 정보</strong>를 갖고 있는 객체를 말함
-&gt; 결과가 promise에 저장되기 때문에 작업이 성공했는지 알 수 있음</p>
<br>

<h1 id="promise-3가지-상태">Promise 3가지 상태</h1>
<ol>
<li>fulfilled 상태(성공): 작업이 성공적으로 완료했음을 의미 -&gt; promise객체는 성공 결과도 함께 가짐</li>
<li>rejected 상태(실패): 작업이 실패했음을 의미 -&gt; promise객체는 실패 이유에 관한 정보도 함께 가짐</li>
<li>pending 상태(진행중): 작업이 진행 중임을 의미</li>
</ol>
<blockquote>
<p>pending 상태에서 한번 성공이나 실패 상태가 되면 다시 다른 상태를 가질 수 없음</p>
</blockquote>
<br>

<h1 id="promise-핸들러">Promise 핸들러</h1>
<ul>
<li>then: promise 객체가 fulfilled 상태가 되면 실행할 콜백함수를 등록하는 메서드</li>
<li>catch: promise 객체가 rejected 상태가 되면 실행할 콜백함수를 등록하는 메서드</li>
<li>finally: 어떤 작업의 성공, 실패 여부와 상관없이 항상 실행하고 싶은 콜백함수를 등록하는 메서드</li>
</ul>
<br>

<h1 id="사용-이유">사용 이유</h1>
<ul>
<li>비동기 작업을 순차적으로 실행하기 위해 사용</li>
<li>callback 함수를 보기 좋게 하기 위해 사용<ul>
<li>callback 함수가 많아지면 콜백 지옥에 빠져 가독성이 매우 낮아짐</li>
<li>여러 개의 비동기 작업을 순차적으로 수행할때, 콜백 함수가 중첩되어 코드의 깊이가 깊어지는데 이러한 현상을 콜백 지옥이라고 함</li>
</ul>
</li>
</ul>
<blockquote>
<p>비동기적 처리가 가능하게 바꿔주는 문법이 아닌, callback함수를 대신해 보기 좋게 만들어주는 것임</p>
</blockquote>
<br>

<h1 id="참고-자료">참고 자료</h1>
<p><a href="https://velog.io/@minew1995/JavaScript-Promise-%EA%B0%9D%EC%B2%B4">https://velog.io/@minew1995/JavaScript-Promise-%EA%B0%9D%EC%B2%B4</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Node.js] MVC(Model View Controller)]]></title>
            <link>https://velog.io/@tae_1/Node.js-MVCModel-View-Controller</link>
            <guid>https://velog.io/@tae_1/Node.js-MVCModel-View-Controller</guid>
            <pubDate>Mon, 17 Feb 2025 16:32:39 GMT</pubDate>
            <description><![CDATA[<h1 id="mvc란">MVC란?</h1>
<p>소프트웨어 설계와 관련된 디자인 패턴
MVC를 이용하는 웹 프레임워크</p>
<ul>
<li>PHP</li>
<li>Django</li>
<li>Express</li>
<li>Angular 등</li>
</ul>
<blockquote>
<p>디자인 패턴이란? 상황에 따라 자주 쓰이는 설계 방법을 정리한 코딩 방법론</p>
</blockquote>
<h1 id="mvc의-장단점">MVC의 장단점</h1>
<h3 id="장점">장점</h3>
<ul>
<li>패턴들을 구분해 개발</li>
<li>유지보수 용이</li>
<li>유연성 높음</li>
<li>확장성 높음</li>
<li>협업에 용이</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>완벽한 의존성 분리가 어려움</li>
<li>설계 단계 복잡</li>
<li>설계 시간 오래걸림</li>
<li>클래스(단위)가 많아짐</li>
</ul>
<blockquote>
<p>여기서 완벽한 의존성 분리가 어려운 이유는 모델과 뷰 사이는 컨트롤러를 통해서 소통하기 때문에 완벽하게 의존성이 분리될 수 없기 때문이다.</p>
</blockquote>
<h1 id="mvc-흐름">MVC 흐름</h1>
<p><img src="https://velog.velcdn.com/images/tae_1/post/6997aa5b-b34a-4b9e-87df-fadc888e344f/image.png" alt=""></p>
<ul>
<li>Model: 데이터 처리</li>
<li>View: 사용자에게 보여지는 부분인 UI 관련된 것들을 처리하는 부분</li>
<li>Controller: View와 Model을 연결해주는 부분</li>
</ul>
<blockquote>
<p>view, browser는 프론트이고 나머진 다 백엔드</p>
</blockquote>
<h1 id="mvc-패턴-폴더-구조">MVC 패턴 폴더 구조</h1>
<p><img src="https://velog.velcdn.com/images/tae_1/post/2a156555-1e66-47d2-ab22-5c988b4e9460/image.png" alt="">
controller: view와 model을 연결하는 부분
model: 데이터 처리하는 부분(나중에 DB랑 연결되면 테이블을 저장)
routes: 경로 설정하는 부분
views: UI 관련 처리</p>
<h2 id="만드는-순서">만드는 순서</h2>
<p>모델 -&gt; 컨트롤러 -&gt; 라우터 -&gt; app.js -&gt; views</p>
<h2 id="예시">예시</h2>
<p><img src="https://velog.velcdn.com/images/tae_1/post/e92ed510-f861-43f4-9d8b-267e6d2378d4/image.png" alt=""></p>
<p>models폴더/userModel.js -&gt; 배열 안에 객체를 저장함(DB 대신 더미 데이터)</p>
<pre><code class="language-javascript">// 데이터 모델 만들거임//

// 간단한 데이터 모델 생성
const users = [
  { id: 1, name: &quot;마루&quot;, email: &quot;maru@naver.com&quot; },
  { id: 2, name: &quot;강아지&quot;, email: &quot;dog@naver.com&quot; },
];

// 유저 데이터 전부 가지고 오기
const getAllUser = () =&gt; {
  return users;
};

// id로 해당하는 유저 찾기
const getUserById = (id) =&gt; {
  //(매개변수 string으로 들어옴)
  return users.find((user) =&gt; user.id === parseInt(id));
};

// 다른 곳에서도 쓸 수 있게끔 내보냄(리액트에서도 내보내는거 있음)
// 해당 파일에서 만든 함수 내보내기(모듈화)
module.exports = { getAllUser, getUserById };
</code></pre>
<p>controllers폴더/userController.js -&gt; 경로와 연결될 함수 내용 정의(경로와 연결되는 함수이므로 req객체와 res객체 사용), 컨트롤러와 모델 연결</p>
<pre><code class="language-javascript">// 가져오기
const userModel = require(&quot;../models/userModel&quot;);

// 유저 전부 가져오는 컨트롤러
const getUsers = (req, res) =&gt; {
  // 전체 유저를 가지고 있음
  const users = userModel.getAllUsers();
  res.render(&quot;users/index&quot;, { users });
};

// 해당하는 유저 가져오기
const getUser = (req, res) =&gt; {
  // get요청이니까 params에 담김
  const user = userModel.getUserById(req.params.id);
  if (user) {
    // 프론트에서 보여주려면 그 데이터 던져줘야 함
    res.render(&quot;users/show&quot;, { user });
  } else {
       res.statusCode = 404;
    return res.send(&quot;해당하는 아이템이 없습니다.&quot;);
  }
};

module.exports = { getUsers, getUser };
</code></pre>
<p>routes폴더/userRoutes.js -&gt; 경로를 controller와 연결</p>
<pre><code class="language-javascript">// 쓴다고 명시
const express = require(&quot;express&quot;);
const router = express.Router();
const userController = require(&quot;../controllers/userController&quot;);

// 유저 전부 가져오기를 하는 라우터(app.get했던거)(앞에 users가 앞에 있음-&gt;use로 생략함)
router.get(&quot;/&quot;, userController.getUsers);

// 해당하는 유저 가져오기를 하는 라우터(url뒤에 id가 붙어서 날라옴)
router.get(&quot;/:id&quot;, userController.getUser);

module.exports = router;
</code></pre>
<p>app.js -&gt; router 불러오는 부분(특정 시작 url의 역할 구분 가능)</p>
<pre><code class="language-javascript">const express = require(&quot;express&quot;);
const app = express();
const port = 3000;

// 라우팅 파일 불러오기
const userRouters = require(&quot;./routes/userRoutes&quot;);

// body-parser
// x-www-form-urlencoded 방식, 객체 형태로 결과가 나옴(객체형태: {})
app.use(express.urlencoded({ extended: true }));
// json
app.use(express.json());
app.use(&quot;/public&quot;, express.static(&quot;public&quot;));

// users경로에 대한 라우팅 처리(/users경로로 들어감)
app.use(&quot;/users&quot;, userRouters); // &#39;/users&#39;에 대한 요청은 userRoutes로 처리

app.set(&quot;view engine&quot;, &quot;ejs&quot;);
app.set(&quot;views&quot;, &quot;./views&quot;);

// 기본 홈 라우트
app.get(&quot;/&quot;, (req, res) =&gt; {
  res.render(&quot;index&quot;, { title: &quot;MVC 패턴&quot; });
});

app.listen(port, () =&gt; {
  console.log(`Example app listening on port ${port}`);
});
</code></pre>
<p>views/index.ejs</p>
<pre><code class="language-javascript">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;

&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;
  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
  &lt;title&gt;Document&lt;/title&gt;
&lt;/head&gt;

&lt;body&gt;
  &lt;h1&gt;MVC 패턴 연습&lt;/h1&gt;
  &lt;!-- 파일주소가 아니라 요청 --&gt;
  &lt;a href=&quot;/users&quot;&gt;유저 리스트로 가기&lt;/a&gt;
&lt;/body&gt;

&lt;/html&gt;</code></pre>
<h2 id="실행-순서">실행 순서</h2>
<p>a태그 누름 -&gt; users로 요청보냄(url) -&gt; app.js -&gt; 라우터 -&gt; 컨트롤러 -&gt; 모델</p>
<h2 id="404-error">404 Error</h2>
<p>404 에러는 클라이언트가 잘못된 주소로 접속했을때 발생</p>
<pre><code class="language-javascript">app.get(&#39;*&#39;, (req, res) =&gt; {
    res.render(&#39;404&#39;);
});</code></pre>
<ul>
<li>맨 마지막 라우트로 선언</li>
<li>*: 그 외 나머지 주소는 모두 잘못된 요청임을 사용자에게 알려야함</li>
<li>클라이언트가 올바르지 않은 주소로 요청 시 Error 페이지 렌더링</li>
</ul>
<h2 id="주의할-점">주의할 점</h2>
<p>만약 라우터 파일이 다음과 같이 되어있다고 하자</p>
<pre><code class="language-javascript">const express = require(&quot;express&quot;);
const router = express.Router();
const ItemController = require(&quot;../controllers/itemController&quot;);

// 아이템 전부 가져오기를 하는 라우터
router.get(&quot;/&quot;, ItemController.getItems);

// 해당하는 아이템 가져오기를 하는 라우터
router.get(&quot;/:id&quot;, ItemController.getItem);

// 가장 비싼 아이템 가져오기를 하는 라우터
router.get(&quot;/price&quot;, ItemController.getPrice);

module.exports = router;
</code></pre>
<p>그러면 /price 요청을 보냈을때 원하는 결과가 나올까?
답은... NO!!!</p>
<p>왜냐하면 /:id에서 /밑에 오는건 다 id라고 인식하기 때문에 /price에서 price를 id로 인식하게 된다. 그래서 앞에 &quot;/item/:id&quot; 이런식으로 명시해주어야한다(혼동이 없도록).</p>
<pre><code class="language-javascript">const express = require(&quot;express&quot;);
const router = express.Router();
const ItemController = require(&quot;../controllers/itemController&quot;);

// 아이템 전부 가져오기를 하는 라우터
router.get(&quot;/&quot;, ItemController.getItems);

// 해당하는 아이템 가져오기를 하는 라우터
router.get(&quot;/item/:id&quot;, ItemController.getItem);

// 가장 비싼 아이템 가져오기를 하는 라우터
router.get(&quot;/price&quot;, ItemController.getPrice);

module.exports = router;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Node.js] 정적 파일 import]]></title>
            <link>https://velog.io/@tae_1/Node.js-%EC%A0%95%EC%A0%81-%ED%8C%8C%EC%9D%BC-import</link>
            <guid>https://velog.io/@tae_1/Node.js-%EC%A0%95%EC%A0%81-%ED%8C%8C%EC%9D%BC-import</guid>
            <pubDate>Mon, 17 Feb 2025 04:39:59 GMT</pubDate>
            <description><![CDATA[<p>서버 프레임워크 중 하나인 node.js의 static경로 설정 방법</p>
<h1 id="css-연결">CSS 연결</h1>
<ol>
<li>css파일을 만들어 css폴더에 넣어줌</li>
<li>ejs파일에 css링크를 <code>&lt;link rel=&quot;stylesheet&quot; href=&quot;css/파일이름.css&quot;&gt;</code>걸어놓음</li>
</ol>
<ul>
<li>express는 모든 폴더를 서버에 보여주지 않고 app.js와 views폴더에 있는 것만 기본으로 보여준다. 
(css나 js파일은 <a href="https://localhost:3000/css/styles.css%EB%A1%9C">https://localhost:3000/css/styles.css로</a> 찾을 수 없음)
그래서 express에 css/js도 사용하겠다는 명시를 해줘야한다(서버에 나오라고 명령).</li>
</ul>
<p>3.public폴더를 만들어 이 폴더를 서버에서 돌릴 수 있도록 만든다.
public/css폴더 밑에 연결하고 싶은 css파일을 넣음 
app.js에 app.use(express.static(&quot;public&quot;)); 추가하여 static 파일의 위치를 public으로 설정해준다.</p>
<hr>
<p>js는 정적 파일이라 ejs로 읽어줄 수 없음 그래서 한번 더 요청을 보냄(node.js_02 폴더 참조)
-&gt; 데이터는 계속 app.js에서 바뀜</p>
<hr>
<p>템플릿에서 태그로 참조하는 css,js,image 등 여러 정적 파일들을 사용하는 방법</p>
<ol>
<li>public 폴더에 정적 파일을 담는다.</li>
<li>모든 요청에 공공으로 사용하기 위해 use메서드 사용
const path = require(&#39;path&#39;); // 경로
app.use(express.static(path.join(__dirname, &#39;public&#39;)));</li>
</ol>
<p>(정적(static)한 파일을 이곳에 넣어두면 / 경로로 ejs파일내에서 public폴더내에 있는 파일을 접근 할 수 있다는 의미)
app.use(&#39;/ejs에서접근할경로&#39;, express.static(path.join(__dirname, &#39; /실제위치한디렉토리경로&#39;)));  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 지도 구현하기]]></title>
            <link>https://velog.io/@tae_1/JavaScript-%EC%A7%80%EB%8F%84-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@tae_1/JavaScript-%EC%A7%80%EB%8F%84-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 15 Feb 2025 18:22:43 GMT</pubDate>
            <description><![CDATA[<p>배달의 민족 웹 프로젝트에서 지도를 구현했던 것에 대해 이야기해보려고 한다.</p>
<p><br><br></p>
<p>배달받을 장소를 선택할 수 있는 페이지에서 구현할 지도 기능은 다음과 같다.</p>
<blockquote>
<ul>
<li>사용자가 해당 페이지에 접근하면, 현재 위치가 지도와 주소 검색창에 표시</li>
</ul>
</blockquote>
<ul>
<li>다음 우편번호 서비스를 사용하여 주소를 검색하고, 선택한 주소의 위치가 지도와 주소 검색창에 표시</li>
<li>지도 클릭시 클릭한 부분의 위치에 마커가 표시되고 해당 주소가 주소 검색창에 표시</li>
<li>현재 위치로 이동하는 버튼 클릭시 지도와 주소 검색창에 표시</li>
</ul>
<p><br><br></p>
<h2 id="필요한-api">필요한 API</h2>
<h3 id="다음-우편번호-서비스-api">다음 우편번호 서비스 API</h3>
<pre><code>&lt;!-- 다음 우편번호 서비스 API를 불러오는 스크립트 --&gt;
  &lt;script src=&quot;//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js&quot;&gt;&lt;/script&gt;</code></pre><ul>
<li>주소 검색을 제공하는 서비스</li>
<li>사용자가 주소를 검색하고 선택 가능</li>
<li>주소 검색 결과로 위도와 경도를 직접 제공하지 않음<ul>
<li>별도의 API를 이용하여 위도, 경도를 구해야 함</li>
</ul>
</li>
</ul>
<blockquote>
<p>다음 우편주소 API는 주소 검색 기능을 제공하나, 지도에서 마커를 찍기 위한 <strong>위도, 경도 정보를 제공하지 않기 때문에</strong> 주소로부터 위도, 경도를 얻는 추가적인 API가 필요 -&gt; <strong>네이버 지도 API의 Geocode 서비스를 함께 사용</strong></p>
</blockquote>
<br>

<h3 id="네이버-지도-api">네이버 지도 API</h3>
<p><a href="https://www.ncloud.com/">https://www.ncloud.com/</a> (네이버 클라우드 플랫폼에 접속) -&gt; 콘솔 클릭
<img src="https://velog.velcdn.com/images/tae_1/post/af60395d-5aa4-433b-a429-c8c607684dae/image.png" alt=""></p>
<br>

<p>Services -&gt; AI NAVER API 클릭 -&gt; Application 등록
<img src="https://velog.velcdn.com/images/tae_1/post/d74649d1-146a-4fc3-a02b-06c146e451f6/image.png" alt=""></p>
<br>

<p>Application 이름을 입력한 후 밑에 그림처럼 체크해준다. 여기서 Geocoding과 Reverse Geocoding을 선택해야 주소를 좌표로 변환하거나 좌표를 주소로 변환하는 것이 가능하다.
<img src="https://velog.velcdn.com/images/tae_1/post/b1e6c158-d81b-4c60-9274-dd17f35980ad/image.png" alt="">
Web 서비스에 이용할 것이기 때문에 We<img src="https://velog.velcdn.com/images/tae_1/post/b970388d-48ed-4821-936b-67682b70c2fd/image.gif" alt="">
b 서비스 URL부분에 해당 페이지의 URL을 입력해준다.</p>
<br>

<p>등록을 하면 다음과 같이 인증 정보를 볼 수 있다.
<img src="https://velog.velcdn.com/images/tae_1/post/216b0e65-ca43-4035-b007-7e46f8a44eba/image.png" alt=""></p>
<br>

<pre><code>&lt;!-- 네이버 지도 API를 불러오는 스크립트 --&gt;
&lt;script type=&quot;text/javascript&quot;
    src=&quot;https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=**인증 받은 키**&amp;submodules=geocoder&quot;&gt;&lt;/script&gt;</code></pre><ul>
<li>네이버 클라우드 플랫폼에서 발급받은 클라이언트 ID를 <strong>인증 받은 키</strong> 부분에 입력</li>
<li>기본적인 지도 렌더링과 기본 기능 사용 가능</li>
<li>submodules=geocoder로 주소를 좌표로 변환하거나 좌표를 주소로 변환할 때 필요한 Geocoder 모듈을 추가로 불러옴</li>
</ul>
<br>
<br>

<h2 id="구현">구현</h2>
<h3 id="주소-검색창에서-주소-선택">주소 검색창에서 주소 선택</h3>
<pre><code class="language-javascript">// 주소 검색창에서 주소 선택 시
const changeInputText = () =&gt; {
  new daum.Postcode({
    oncomplete: function (data) {
      const addr = data.address; // 선택한 주소
      // 주소로 네이버 지도 검색 및 표시
      searchAddressdaum(addr); // 주소로 좌표 검색하여 지도에 표시
    },
  }).open();
};
</code></pre>
<ul>
<li>다음 우편번호 서비스를 사용하여 주소 검색</li>
<li>new daum.Postcode().open()으로 주소를 검색하고 선택할 수 있는 UI 제공<ul>
<li>daum.Postcode는 주소 검색을 위한 API를 호출</li>
</ul>
</li>
<li>주소 선택시 oncomplete 콜백이 실행되면서 선택한 주소인 data.address를 가져옴</li>
<li>해당 주소(addr)를 활용하여 searchAddressdaum 함수에서 네이버 지도에 표시<ul>
<li>다음 우편번호 API로 주소를 선택 -&gt; 해당 주소의 위도와 경도를 네이버 지오코딩 API로 변환하여 사용</li>
</ul>
</li>
</ul>
<blockquote>
<ol>
<li>사용자 입력 단순화: 사용자가 직접 주소를 입력하지 않고, 편리하게 선택</li>
<li>지도 서비스 연동: 선택한 주소를 기반으로 좌표 검색 및 지도 표시같은 추가적인 작업 수행</li>
</ol>
</blockquote>
<br>
<br>


<h3 id="검색한-주소를-지도에-표시">검색한 주소를 지도에 표시</h3>
<pre><code class="language-javascript">// 주소를 검색하여 지도에 표시하고 마커 생성(다음주소api에서 사용할 함수)
const searchAddressdaum = (address) =&gt; {
  naver.maps.Service.geocode(
    {
      query: address, // 검색할 주소
    },
    (status, response) =&gt; {
      // 응답 상태가 OK인 경우에만 처리
      if (
        status === naver.maps.Service.Status.OK &amp;&amp;
        response.v2.addresses.length &gt; 0
      ) {
        const result = response.v2.addresses[0]; // 첫 번째 검색 결과
        const lat = result.y; // 위도
        const lng = result.x; // 경도

        // 좌표로 지도와 마커 표시
        const latlng = new naver.maps.LatLng(lat, lng);

        // 지도 중심을 검색된 위치로 이동 (기존 지도 객체 사용)
        if (map) {
          map.setCenter(latlng); // 기존 지도에서 중심만 변경
        } else {
          // 지도 객체가 없다면 새로 생성
          map = new naver.maps.Map(&quot;map&quot;, {
            center: latlng,
            zoom: 17,
          });
        }

        // 기존 마커가 있으면 삭제
        if (currentMarker) {
          currentMarker.setMap(null);
        }

        // 새 마커 생성
        currentMarker = new naver.maps.Marker({
          position: latlng, // 마커 위치
          map: map, // 마커가 표시될 지도
          title: address, // 마커의 타이틀
        });

        // 화면에 주소 표시
        addAddress(address);
      } else {
        alert(&quot;주소 검색에 실패했습니다.&quot;);
      }
    }
  );
};</code></pre>
<ul>
<li>네이버 지도 API를 사용해 주소를 검색하고, 검색된 주소를 지도에 표시<ul>
<li>다음 우편번호 서비스 API와 네이버 지도 API 연동</li>
</ul>
</li>
<li>입력한 주소를 naver.maps.Service.geocode로 전달해 좌표 검색</li>
<li>검색 결과(response.v2.addresses[0])에서 첫 번째 주소 데이터 가져와 검색 성공시 위도(lat)와 경도(lng) 정보를 추출</li>
<li>지도 객체가 있으면 중심 좌표를 업데이트(map.setCenter(latlng))</li>
<li>기존 지도 객체(map)가 없다면 new naver.maps.Map(&quot;map&quot;, {...})새로 생성<ul>
<li>&quot;map&quot;은 HTML에서 지도를 표시할 <code>&lt;div&gt;</code>의 id</li>
</ul>
</li>
<li>기존 마커가 있으면 setMap(null)로 삭제 후 new naver.maps.Marker({...})로 새 마커 생성</li>
<li>addAddress 함수를 통해 주소 검색창에 해당 주소 한글로 표시</li>
</ul>
<blockquote>
<ol>
<li>중복 방지: 입력된 주소를 기반으로 좌표 검색 후, 이전 마커를 갱신하여 지도에 표시</li>
<li>사용자 경험 향상: 검색 결과가 없을 경우 오류 메시지를 표시</li>
</ol>
</blockquote>
<br>
<br>


<h3 id="현재-위치를-지도에-표시-및-클릭한-위치의-주소-얻어오는-기능">현재 위치를 지도에 표시 및 클릭한 위치의 주소 얻어오는 기능</h3>
<pre><code class="language-javascript">// 지도 부분
let map; // 전역 변수로 map 객체 관리(재사용)
let currentMarker = null; // 전역 변수로 마커 관리

// 지도 생성 및 현재 위치로 이동
const moveCurrnet = () =&gt; {
  // 위치 정보를 얻기 위해 Geolocation API 사용
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      (position) =&gt; {
        // 사용자의 현재 위치 가져오기
        const lat = position.coords.latitude;
        const lon = position.coords.longitude;

        // 지도 생성 (처음 한 번만 생성)
        if (!map) {
          map = new naver.maps.Map(&quot;map&quot;, {
            center: new naver.maps.LatLng(lat, lon), // 현재 위치로 지도의 중심 설정
            zoom: 17, // 초기 줌 레벨 설정
            minZoom: 7, // 최소 줌 레벨
            zoomControl: true, // 줌 컨트롤 표시
            zoomControlOptions: {
              position: naver.maps.Position.TOP_RIGHT,
            },
          });

          // 지도 클릭 시 마커를 찍고 주소를 받아오기
          naver.maps.Event.addListener(map, &quot;click&quot;, (e) =&gt; {
            const latlng = e.coord; // 클릭한 좌표

            // 기존 마커가 있으면 삭제
            if (currentMarker) {
              currentMarker.setMap(null);
            }

            // 새로운 마커 생성
            currentMarker = new naver.maps.Marker({
              position: latlng, // 클릭한 위치에 마커 생성
              map: map, // 마커가 표시될 지도
              title: &quot;클릭한 위치&quot;, // 마커의 제목
            });

            map.panTo(latlng); // 지도 이동

            // 클릭한 위치의 주소를 받아오기
            getAddressFromLatLng(latlng, addAddress);
          });
        } else {
          map.setCenter(new naver.maps.LatLng(lat, lon)); // 기존 지도 중심 재설정
        }

        // 현재 위치에 마커 표시 (기존 마커가 있으면 삭제 후 갱신)
        if (currentMarker) {
          currentMarker.setMap(null); // 이전 마커 제거
        }

        currentMarker = new naver.maps.Marker({
          position: new naver.maps.LatLng(lat, lon),
          map: map,
          title: &quot;현재 위치&quot;,
        });

        // 현재 위치의 주소를 받아오기
        getAddressFromLatLng(new naver.maps.LatLng(lat, lon), addAddress);
      },
      (error) =&gt; {
        let errorMessage = &quot;&quot;;
        switch (error.code) {
          case error.PERMISSION_DENIED:
            errorMessage =
              &quot;위치 정보를 허용해야 합니다. 위치 서비스 권한을 부여해 주세요.&quot;;
            break;
          case error.POSITION_UNAVAILABLE:
            errorMessage =
              &quot;위치 정보를 사용할 수 없습니다. 네트워크 상태나 GPS 신호를 확인해 주세요.&quot;;
            break;
          case error.TIMEOUT:
            errorMessage =
              &quot;위치 정보를 가져오는데 시간이 너무 걸렸습니다. 다시 시도해 주세요.&quot;;
            break;
          default:
            errorMessage = &quot;알 수 없는 오류가 발생했습니다.&quot;;
            break;
        }
        alert(errorMessage);
      }
    );
  } else {
    alert(&quot;이 브라우저는 위치 정보를 지원하지 않습니다.&quot;);
  }
};</code></pre>
<ul>
<li>네이버 지도 API와 Geolocation API를 사용</li>
<li>navigator.geolocation.getCurrentPosition를 사용하여 현재 위치(위도/경도) 가져옴<ul>
<li>가져오면 처음에는 지도를 생성하고, 이후엔 지도 중심만 갱신</li>
<li>기존 마커 제거 후 현재 위치에 새로운 마커 표시</li>
<li>getAddressFromLatLng 함수로 위도/경도를 이용해 해당 위치의 주소를 가져옴
  -네이버 지도 API에서 Reverse Geocoding 기능을 통해 수행</li>
</ul>
</li>
<li>지도 클릭시<ul>
<li>클릭한 좌표에 새로운 마커 생성 및 기존 마커 제거</li>
<li>클릭한 좌표로 지도 중심 이동</li>
<li>클릭한 위치의 주소를 얻어 화면에 표시</li>
</ul>
</li>
</ul>
<blockquote>
<ol>
<li>동적 지도 관리: 지도 객체와 마커를 효율적으로 관리하여 불필요한 리소스 낭비 방지</li>
<li>현재 위치 기반 지도 표시: 사용자 경험 향상을 위해 현재 위치를 기반으로 지도 초기화</li>
<li>사용자 인터랙션 지원: 사용자가 클릭한 위치에 마커 생성 및 해당 위치의 주소 확인</li>
<li>사용자 경험 개선: 클릭 이벤트를 활용해 실시간으로 지도와 주소 업데이트</li>
<li>포괄적인 오류 처리: 다양한 오류 상황에 대한 구체적인 메세지 제공</li>
</ol>
</blockquote>
<br>
<br>

<h3 id="주소로부터-현재-위치-얻어오기">주소로부터 현재 위치 얻어오기</h3>
<pre><code class="language-javascript">// 주소로부터 현재 위치를 얻어오기 (Reverse Geocoding)
const getAddressFromLatLng = (latlng, callback) =&gt; {
  naver.maps.Service.reverseGeocode(
    {
      location: latlng,
    },
    (status, response) =&gt; {
      if (status === naver.maps.Service.Status.OK) {
        const address = response.result.items[0].address; // 가장 가까운 주소
        callback(address);
      } else {
        callback(&quot;위치를 찾지 못했습니다.&quot;);
      }
    }
  );
};</code></pre>
<ul>
<li>네이버 지도 API의 Reverse Geocoding 기능을 사용하여 주어진 좌표로부터 주소를 추출<ul>
<li>좌표로부터 사람이 읽을 수 있는 주소를 얻기 위한 기능</li>
</ul>
</li>
<li>naver.maps.Service.reverseGeocode를 사용하여 좌표(latlng)로부터 주소 검색</li>
<li>상태 코드가 OK인 경우<ul>
<li>response.result.items[0].address를 통해 가장 가까운 주소를 가져옴</li>
<li>해당 주소를 callback함수에 전달</li>
</ul>
</li>
<li>상태 코드가 OK가 아닌 경우<ul>
<li>메세지를 callback함수로 전달<blockquote>
<p>콜백 함수 활용: 주소를 받아 화면에 표시하거나 다른 작업을 처리할 수 있도록 유연성 제공</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<br>
<br>

<h3 id="주소를-주소-검색창에-표시-및-로컬스토리지에-저장">주소를 주소 검색창에 표시 및 로컬스토리지에 저장</h3>
<pre><code class="language-javascript">// 주소를 화면에 표시하고 로컬 스토리지에 저장
const addAddress = (address) =&gt; {
  document.getElementById(&quot;address&quot;).value = address;
  window.localStorage.setItem(&quot;name&quot;, address);
};</code></pre>
<ul>
<li>지도에서 클릭하거나 현재 위치를 검색한 경우 해당 주소를 UI와 로컬스토리지에 동기화</li>
</ul>
<blockquote>
<ol>
<li>피드백: 선택한 주소를 UI에 바로 반영</li>
<li>데이터 지속성: 브라우저 세션 간 데이터 유지 -&gt; 이전에 선택한 주소를 새 페이지에 자동으로 불러와 입력 필드에 표시</li>
</ol>
</blockquote>
<br>
<br>


<h3 id="현재-위치로-이동-버튼">현재 위치로 이동 버튼</h3>
<pre><code class="language-javascript">// 현재 위치 버튼 클릭 시
document.getElementById(&quot;showMap&quot;).addEventListener(&quot;click&quot;, function (e) {
  e.preventDefault();
  moveCurrnet();
});</code></pre>
<ul>
<li>현재 위치 버튼에 리스너를 추가해 클릭하면 현재 위치로 이동하는 moveCurrent 함수 호출</li>
</ul>
<br>
<br>

<h2 id="결과">결과</h2>
<p><img src="https://velog.velcdn.com/images/tae_1/post/39750ad5-ee14-4669-82db-cfa01f963c6c/image.gif" alt=""></p>
<br>
<br>

<h2 id="직면한-문제와-해결방안">직면한 문제와 해결방안</h2>
<blockquote>
<ul>
<li>지도에서 현재 위치 버튼을 누르거나 주소 검색시 지도에서 이동하지 않음(map 객체가 두 번 생성되어 문제 발생)
  -&gt; 이미 존재하는 map객체를 재사용하여 객체의 중심만 변경</li>
</ul>
</blockquote>
<ul>
<li>map 객체가 처음 생성되거나 이미 존재할 때 모두 지도 클릭 이벤트가 제대로 처리되도록 하고 싶음
  -&gt; moveCurrnet 함수에서 처음 지도 객체를 생성할 때, 지도 클릭 이벤트 리스너를 추가하고 지도 객체가 이미 존재한다면 기존 객체에 이벤트 리스너를 추가
  -&gt; 클릭한 위치에 마커를 추가하는 로직을 클릭 이벤트 리스너 안에서 처리</li>
<li>주소를 검색해서 추가한 마커도 기존 마커와 함께 남아있음
  -&gt; 기존에 표시된 마커가 있다면 setMap(null)을 호출하여 삭제하고, 새로운 마커만 currentMarker에 추가</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 자바스크립트란]]></title>
            <link>https://velog.io/@tae_1/JavaScript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%9E%80</link>
            <guid>https://velog.io/@tae_1/JavaScript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%9E%80</guid>
            <pubDate>Wed, 12 Feb 2025 14:15:56 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트는 싱글쓰레드이다.
그럼 여기서 싱글쓰레드가 무엇인가?</p>
<blockquote>
<p>동시에 뭔가를 실행하지 않고 무조건 하나의 행동이 끝나야 다음거를 실행하는 것을 싱글쓰레드라고 할 수 있다.</p>
</blockquote>
<h4 id="1-싱글쓰레드">1. 싱글쓰레드</h4>
<ul>
<li>한번에 하나만 실행(하나가 끝난 뒤에 다음 거 실행)</li>
<li>자바스크립트는 위에서부터 순차적으로 실행하기 때문에 도중에 에러가 생기면 다음거를 실행하지 않음</li>
<li>그렇기 때문에 에러를 처리하지 못하는 경우 바로 에러가 나 멈춤</li>
<li>보완점으로 타입스크립트가 나왔고 타입스크립트에서 예외처리의 중요성이 커짐</li>
</ul>
<blockquote>
<p>자바스크립트는 싱글쓰레드에 비동기방식
동기방식은 멀티쓰레드</p>
</blockquote>
<h4 id="2-동기비동기">2. 동기/비동기</h4>
<p>js는 싱글쓰레드고 비동기방식으로 처리, node.js는 js로 만들었음</p>
<ul>
<li>동기: 다같이 처리</li>
<li>비동기: 동시에 일어나지 않음</li>
</ul>
<p>쉽게 말하면</p>
<blockquote>
<p>동기는 내가 해당하는 화면에서 다 로드됨(무조건 새로고침해야 적용됨-&gt;은행, 메일서비스)
비동기는 새로고침 안 해도 변화된 데이터가 적용됨(실시간으로 뜸)</p>
</blockquote>
<h4 id="3-call-stack호출-스택">3. Call Stack(호출 스택)</h4>
<ul>
<li>LIFO 방식</li>
<li>함수의 호출을 기록하는 자료구조</li>
<li>자바스크립트는 하나만 실행<ul>
<li>함수가 있으면 따로 빼놓고 console 먼저 찍고 그 다음 함수를 실행시킴</li>
</ul>
</li>
<li>onclick에서 이벤트를 발생시키고 만들어놓은 함수 호출(가져와서 실행)<ul>
<li>onclick 호출 스택은 call stack에 함수가 쌓여 실행되는 것을 의미</li>
</ul>
</li>
</ul>
<h4 id="4-event-loop">4. Event Loop</h4>
<p><img src="https://velog.velcdn.com/images/tae_1/post/62ffe4b1-4400-4ef5-a885-496fc5d0e7be/image.png" alt=""></p>
<ul>
<li>console은 함수가 아니기 때문에 바로 실행</li>
<li>무조건 함수로 만든 것은 백그라운드로 빠져 태스크 큐라는 곳으로 보내짐</li>
<li>순서대로 빠지고 순서대로 실행</li>
</ul>
<p>그렇다면 자바스크립트는 어떻게 실행될까?</p>
<blockquote>
<p>싱글쓰레드라서 순서대로 실행된다.</p>
</blockquote>
<p>함수는 어떤 과정을 거쳐서 실행되는 걸까?</p>
<blockquote>
<p>함수가 실행되면 호출 스택에서 빠져서 백그라운드에 간다. 그리고 순차적으로 태스크 큐에 빠지고 순서대로 보여준다.</p>
</blockquote>
<ul>
<li>내장 함수를 썼을때도 당연히 백그라운드로 빠짐</li>
<li>그렇다면 이런경우에는?<pre><code class="language-javascript">const run =()=&gt;{
  return 3;
}    
</code></pre>
</li>
</ul>
<p>console.log(&quot;1&quot;);
console.log(run);
console.log(&quot;2&quot;);</p>
<pre><code>다음과 같은 순서로 실행됨
&gt; 1
const run =()=&gt;{
    return 3;
}
2



for문은?
&gt; 자바스크립트로 찍는 친구
&gt; 내장 함수는 한번 빠졌다가 나옴 -&gt; 즉, 행동을 3번함(리소스를 잡아먹는다고 표현)

console은?
&gt; 호출 스택에 한번만 딱 함 -&gt; 행동을 한번만에 함
&gt; for문으로 1부터 10까지 돌려서 찍는 것보다 console.log를 1부터 10까지 찍는게 리소스를 덜 잡아먹음
&gt; 물론 100까지면 for로 돌리는게 빠름


참고. forEach 런타임에러 -&gt; map이나 filter로 쓰면 빨라짐


싱글쓰레드
- 호출 스택에 하나만
- 태스크 큐에는 쌓여있음(대기)

이벤트 루프는 언어마다 다 다름


#### 5. node.js
- 비동기 방식(하나씩 처리)에 어울리는 서비스(스트리밍서비스, 채팅서비스 등)
    - 넷플릭스를 한 화면에 여러 개 띄우면 다 볼 수 없음


참고. 가장 빠른 언어는 c언어, 가장 느린 언어는 파이썬

#### 6. 모듈
- express 같은 거
- 캡슐화(자바할때 중요), 추상화

리액트에서 class를 지원하지 않고 함수형식으로…

그래서 프론트엔드는 추상화로 복잡한 시스템이나 객체를 단순화하여 핵심적인 부분에 집중하는 프로그래밍 원칙만 알고 있으면 됨


객체지향 - 외부에서 못 건들도록 함
재사용 - 두개를 하나의 함로 처리(리액트에서 매우 중요)


&gt; 모듈 만들기
&gt; - 해당하는 함수를 내보냄(export로 내보낼 수 있구나 이런식으로 내보낼 수 있구나만 알고 있기(그렇게 중요하진 않음))
&gt; - 모듈을 불러올때는 구조분해해서 가져옴({})
&gt;     - 구조분해란 원래 있는 데이터를 분해한다.
```javascript
const data=[

{
    name:”김김김”,
    age: 14,
},

{
    name:”이이이”,
    age: 15,
},
];
const names = data.map((x,i)⇒{
    return x.name;
});//이름만 뽑아서 새로운 데이터만들기
const name2=data[1].name; //두번째객체의 name 데이터가져오기
console.log(names, “이름”);</code></pre><p>map(리액트문에서는 forEach말고 map을 많이 사용)
여기서 map을 돌릴때 x는 객체</p>
<blockquote>
<p>{
    name:”김김김”,
    age: 14,
},</p>
</blockquote>
<p>를 지칭함
i는 인덱스</p>
<h4 id="7-원하는-데이터-뽑아보기js로-데이터-뽑는-연습">7. 원하는 데이터 뽑아보기(js로 데이터 뽑는 연습)</h4>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<style>
  table,
  th,
  td {
    margin: 0 auto;
    max-width: 1000px;
    padding: 2px 10px;
    text-align: center;
    border: 1px solid black;
    border-collapse: collapse;
  }

<p>  .box {
    margin: 0 auto;
    max-width: 1000px;
    margin-top: 5px;
    border: 1px solid;
  }
</style></p>
<body>
  <div class="main-wrap">
    <!-- js로 동적 추가 -->
    <!-- 1번째 테이블 2번째 테이블 row 클릭시에 -->
  </div>
  <!-- br태그 -->
  <!-- 1번째 -->
  <!-- 이름 나이 커리어 별명 -->

  <!-- 2번째 -->
  <!-- 해당하는 줄을 눌렀을때 alert 혹은 모달을 사용해서
  해당하는 사람의 이름은 name이고 나이는 age이며 미성년자||성인 입니다.
  커리어에는 놀기, 먹기, 자기, 숨쉬기가 있고 별명으로는 ,로 나와야함 -->

  <!-- 3번째 -->
  <!-- 1. 미성년자는 000이 있고 그 사람의 커리어는 careers가 있으며 별명은 nickName입니다.
  2. 성인은 name,name이 있고 name의 커리어는 career가 있으며 별명은 nickName입니다.name의 커리어는 career가 있으며 별명은 nickName입니다.
  3. 별명중 가장 별명이 긴사람을 찾는 알고리즘을 만들어주세요.(광철)
  div든 span이든 3개가 나옴 -->
</body>
<script>
  // 데이터
  const data = [
    {
      id: 1,
      name: '김철수',
      age: 14,
      careers: [
        { title: "놀기" },
        { title: "먹기" },
        { title: "자기" },
        { title: "숨쉬기" },
      ],
      nickName: [
        { name: "김안철수" },
        { name: "김김안철수" },
        { name: "박터짐" },
      ],
    },
    {
      id: 2,
      name: '영희',
      age: 35,
      careers: [
        { title: "놀기" },
        { title: "자전거타기" },
        { title: "오렌지먹기" },
        { title: "사과부시기" },
      ],
      nickName: [
        { name: "김영희" },
        { name: "야생사자" },
        { name: "오올이" },
      ],
    },
    {
      id: 3,
      name: '박광철',
      age: 20,
      careers: [
        { title: "일수나가기" },
        { title: "돈빌려주기" },
        { title: "공무집행방해하기" },
        { title: "무면허운전하기" },
      ],
      nickName: [
        { name: "대흥역호랑이와사자두마리" },
        { name: "마포불주먹" },
        { name: "전설" },
        { name: "경찰의적" },
      ],
    },
  ];
  // 1번째
  const peopletable = document.querySelector(".main-wrap");
  peopletable.innerHTML = `
    <table>
    <thead>
      <tr>
        <td>이름</td>
        <td>나이</td>
        <td>커리어</td>
        <td>별명</td>
      </tr>
      </thead>
      <tbody class="tablebody">

<pre><code>  &lt;/tbody&gt;
  &lt;/table&gt;`;</code></pre><p>  const userinfo = data.map((x, i) =&gt; {
    const careerr = x.careers.map((a, b) =&gt; {
      return <code>${a.title}&lt;/br&gt;</code>;
    });
    const nickNamee = x.nickName.map((a, b) =&gt; {
      return <code>${a.name}&lt;/br&gt;</code>;
    });</p>
<pre><code>return `&lt;tr id=${x.id} onclick=&quot;readyalert(${x.id})&quot;&gt;
    &lt;td&gt;${x.name}&lt;/td&gt;
    &lt;td&gt;${x.age}&lt;/td&gt;
    &lt;td&gt;${careerr.join(&#39;&#39;)}&lt;/td&gt;
    &lt;td&gt;${nickNamee.join(&#39;&#39;)}&lt;/td&gt;
  &lt;/tr &gt;`;</code></pre><p>  });
  const tbody = document.querySelector(&#39;.tablebody&#39;);
  tbody.innerHTML = userinfo.join(&#39;&#39;);</p>
<p>  // 2번째
  const readyalert = (id) =&gt; {
    data.forEach((x, i) =&gt; {
      const ca = x.careers.map((a, b) =&gt; {
        return a.title;
      })
      const ni = x.nickName.map((a, b) =&gt; {
        return a.name;
      })
      let adult = &quot;성인&quot;;
      if (id === x.id) {
        if (x.age &lt; 20) {
          adult = &quot;미성년자&quot;;
        }
        alert(<code>이름은 ${x.name}이고 나이는 ${x.age}이며 ${adult}입니다. 커리어에는 ${ca}가 있고 별명으로는 ${ni}이 있습니다.</code>);
      }
    })
  }</p>
<p>  // 3번째
  // 성인 이름
  const ainfo = data.filter((data) =&gt; data.age &gt;= 20);
  const adultname = ainfo.map((x, i) =&gt; x.name);</p>
<p>  const car1 = ainfo.map((x, i) =&gt; {
    const ca = x.careers.map((a, b) =&gt; {
      return a.title;
    })
    return ca;
  });</p>
<p>  const ni1 = ainfo.map((x, i) =&gt; {
    const ni = x.nickName.map((a, b) =&gt; {
      return a.name;
    })
    return ni;
  });</p>
<p>  // 미성년자 이름
  const binfo = data.filter((data) =&gt; data.age &lt; 20);
  const babyname = binfo.map((x, i) =&gt; x.name);</p>
<p>  const car2 = binfo.map((x, i) =&gt; {
    const ca = x.careers.map((a, b) =&gt; {
      return a.title;
    })
    return ca;
  });</p>
<p>  const ni2 = binfo.map((x, i) =&gt; {
    const ni = x.nickName.map((a, b) =&gt; {
      return a.name;
    })
    return ni;
  });</p>
<p>  // 가장 별명이 긴사람
  const longnick = () =&gt; {
    let max = 0;
    let rrr;
    const nick = data.map((x, i) =&gt; {
      return x.nickName
    });
    nick.map((a, b) =&gt; {
      a.map((c, d) =&gt; {
        if (c.name.length &gt; max) {
          max = c.name.length;
          rrr = c.name;
        }
      })</p>
<pre><code>})
return rrr;</code></pre><p>  }</p>
<p>  let longperson = longnick();</p>
<p>  peopletable.innerHTML += <code>&lt;div class=&quot;box&quot;&gt;1. 미성년자는 ${babyname}가 있고 그 사람의 커리어는 ${car2}가 있으며 별명은 ${ni2}입니다.&lt;/br&gt;
  2. 성인은 ${adultname}이 있고 ${adultname[0]}의 커리어는 ${car1[0]}가 있으며 별명은 ${ni1[0]}입니다. ${adultname[1]}의 커리어는 ${car1[1]}가 있으며 별명은 ${ni1[1]}입니다.&lt;/br&gt;
  3. ${longperson}&lt;/div&gt;</code>;</p>
<p></script></p>
</html>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 페이지네이션(Pagination) 구현하기]]></title>
            <link>https://velog.io/@tae_1/JavaScript-%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98Pagination-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@tae_1/JavaScript-%ED%8E%98%EC%9D%B4%EC%A7%80%EB%84%A4%EC%9D%B4%EC%85%98Pagination-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 09 Feb 2025 15:18:43 GMT</pubDate>
            <description><![CDATA[<h1 id="🔎pagination이란">🔎Pagination이란?</h1>
<hr>
<p>페이징(Paging)이라고도 불리며, 콘텐츠나 데이터를 여러 페이지로 나누어서 특정 페이지로 이동하거나 이전/다음 페이지로 이동할 수 있는 기능을 말한다.</p>
<br>

<h1 id="📋페이지네이션-개발에-필요한-4가지-값">📋페이지네이션 개발에 필요한 4가지 값</h1>
<hr>
<h3 id="--총-페이지수">- 총 페이지수</h3>
<p>총페이지수 = Math.ceil(데이터의 전체 길이/한 페이지에 보여줄 데이터 개수)</p>
<br>

<h3 id="--화면에-보여질-페이지-그룹">- 화면에 보여질 페이지 그룹</h3>
<p>화면에 보여질 페이지 그룹 = Math.ceil(현재 페이지 번호/한 화면에 보여질 페이지 개수)</p>
<blockquote>
<p>총 데이터 개수가 10개, 한 화면에 보여질 페이지 개수가 3개라면? <br> 그룹 1 -&gt; 1<del>3 <br> 그룹 2 -&gt; 4</del>6 <br> 그룹 3 -&gt; 7~9 <br> 그룹 4 -&gt; 10</p>
</blockquote>
<br>

<h3 id="--화면에-보여질-페이지의-첫번째-페이지-번호">- 화면에 보여질 페이지의 첫번째 페이지 번호</h3>
<p>어떤 페이지 그룹의 첫번째 페이지 번호 = ((페이지 그룹 - 1) * 한 화면에 보여질 페이지 개수) + 1</p>
<br>

<h3 id="--화면에-보여질-페이지의-마지막-페이지-번호">- 화면에 보여질 페이지의 마지막 페이지 번호</h3>
<p>어떤 페이지 그룹의 마지막 페이지 번호 = 페이지 그룹 * 한 화면에 보여질 페이지 개수
-&gt; 만약, 페이지 그룹 * 한 화면에 보여질 페이지 개수의 값이 전체 페이지보다 크다면 전체 페이지가 마지막 페이지 번호</p>
<br>

<h1 id="💻페이지네이션-구현">💻페이지네이션 구현</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/tae_1/post/89dfdfb2-9a35-423c-9254-a6a05888d2ec/image.png" alt=""></p>
<p>만들어놓은 쇼핑몰 홈페이지의 메인페이지에 페이지네이션을 추가해야한다.
쇼핑몰의 메인페이지에는 카테고리 메뉴가 존재하여 클릭한 메뉴에 해당하는 타입인 상품들이 나온다.</p>
<h3 id="1-필요한-4가지값-개산">1. 필요한 4가지값 개산</h3>
<p>한페이지에 보여줄 데이터 개수(conCount): 6
한 화면에 보여질 페이지 개수(PAGE): 5</p>
<pre><code class="language-javascript">// 총 페이지 수
totalPage = Math.ceil(datalength / conCount);

// 화면에 보여질 페이지 그룹
pageGroup = Math.ceil(nowPage / PAGE);

// 화면에 보여질 첫번째 페이지
first = (pageGroup - 1) * PAGE + 1;

// 화면에 보여질 마지막 페이지
last = pageGroup * PAGE;
if (last &gt; totalPage) {
  last = totalPage;
};</code></pre>
<h3 id="2-페이지네이션-버튼-생성">2. 페이지네이션 버튼 생성</h3>
<p>4가지 값을 이용해 페이지네이션을 DOM에 그려준다.
함수로 만들어서 페이지네이션 버튼을 만들어 줄 것이다.</p>
<br>

<p>출력할 상품 데이터의 인덱스 번호는 다음과 같은 조건으로 계산한다.
<img src="https://velog.velcdn.com/images/tae_1/post/063d668a-8aca-4e91-8146-73073d6bba43/image.png" alt=""></p>
<blockquote>
<ul>
<li>현재페이지 번호에서 한페이지에 보여줄 데이터 개수를 곱하면 그 다음 페이지 번호의 첫번째 데이터의 인덱스를 알 수 있음 -&gt; 해당 인덱스보다 작아야 함</li>
</ul>
</blockquote>
<ul>
<li>현재페이지 번호에서 1을 빼고 난 후 한페이지에 보여줄 데이터 개수를 곱하면 현재 페이지의 첫번째 데이터의 인덱스를 알 수 있음 -&gt; 해당 인덱스보다 크거나 같아야 함</li>
</ul>
<p>setPageBtn 함수</p>
<p>1) 4가지 값을 계산한다
2) 페이지네이션 넣어줄 요소를 document로 가져온다
3) 가져온 요소에 first부터 last까지 페이지번호 버튼을 삽입한다
 3-1) 만약 nowPage와 같은 숫자라면 class로 active를 추가해준다(클래스 active의 css로 활성화되었다는 것을 표시함)
4) 각 숫자 버튼에 click 리스너를 추가한다
 4-1) nowPage를 해당 페이지로 업데이트한다
 4-2) 이전 숫자 버튼에는 active 클래스를 제거하고 현재 활성화된 숫자 버튼에는 active 클래스를 추가한다
 4-3) 만약 현재 카테고리 타입이 &#39;all&#39;이라면 전체 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputMenu 함수)
 4-4) 만약 나머지 타입이라면 type을 매개변수로 전달해 type에 해당하는 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputCateMenu 함수)</p>
<p>inputMenu 함수</p>
<p>1) 상품을 넣을 요소를 document로 가져온다
2) 전체 데이터를 돌면서 nowPage<em>conCount보다는 작고, (nowPage-1)</em>conCount보다는 크고 같은 인덱스에 해당하는 데이터들을 요소에 추가한다</p>
<p>inputCateMenu 함수</p>
<p>1) 상품을 넣을 요소를 document로 가져온다
2) filter를 이용해 카테고리 타입과 같은 타입을 가진 데이터들을 가져온다
3) 가져온 데이터를 돌면서 nowPage<em>conCount보다는 작고, (nowPage-1)</em>conCount보다는 크고 같은 인덱스에 해당하는 데이터들을 요소에 추가한다</p>
<h3 id="3-이전이후-버튼">3. 이전/이후 버튼</h3>
<p><img src="https://velog.velcdn.com/images/tae_1/post/4d0a2015-944e-4588-ad5e-4356efce4024/image.png" alt="">
위의 그림과 같이 첫번째 페이지 그룹이면 이전 버튼이 없어야 하고 </p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/9eace5cf-4039-4611-b638-81c3a0f9f3cd/image.png" alt="">
마지막 페이지 그룹이면 이후 버튼이 없어야 한다.</p>
<p>이전/이후 버튼은 동적으로 추가하는 것이 아니라 html에 넣어 이미 생성해놨기 때문에 특정 조건에서 숨기고 보이게 만들도록 하겠다.</p>
<p>또한, 이전/이후 버튼을 누르면 해당 그룹의 첫번째 숫자가 활성화되도록 한다.</p>
<h4 id="3-1-이전이후-버튼-숨기기보이기">3-1. 이전/이후 버튼 숨기기/보이기</h4>
<p>setPageBtn 함수에 추가</p>
<p>1) 만약 totalPage가 last보다 작거나 같다면 그 다음 페이지는 없다는 뜻이기 때문에 해당 버튼의 요소를 가져와 숨긴다
2) 만약 totalPage가 last보다 크다면 해당 버튼의 요소를 가져와 보이게 한다
3) 만약 first가 1보다 작거나 같다면 이전 페이지는 없다는 뜻이기 때문에 해당 버튼의 요소를 가져와 숨긴다
4) 만약 first가 1보다 크다면 해당 버튼의 요소를 가져와 보이게 한다</p>
<h4 id="3-2-이전-버튼-기능-넣기">3-2. 이전 버튼 기능 넣기</h4>
<p><img src="https://velog.velcdn.com/images/tae_1/post/62359b21-62e8-4ef9-a2aa-305042852457/image.png" alt=""></p>
<p>1) 이전 버튼의 요소를 가져와 click 리스너를 추가해준다
2) 만약 first - PAGE가 0보다 크다면 first - PAGE로 업데이트해준다
3) 이전 그룹의 숫자 버튼들을 생성하기 위해 setPageBtn 함수를 실행한다
4) 만약 type이 &#39;all&#39;이라면 전체 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputMenu 함수)
5) 만약 나머지 타입이라면 type을 매개변수로 전달해 type에 해당하는 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputCateMenu 함수)</p>
<h4 id="3-3-다음-버튼-기능-넣기">3-3. 다음 버튼 기능 넣기</h4>
<p><img src="https://velog.velcdn.com/images/tae_1/post/9201cbc4-4e7d-4f42-86ef-1a0fd721b2de/image.png" alt=""></p>
<p>1) 다음 버튼의 요소를 가져와 click 리스너를 추가해준다
2) 만약 first + PAGE가 totalPage보다 작거나 같다면 nowPage를 first + PAGE로 업데이트해준다
3) 다음 그룹의 숫자 버튼들을 생성하기 위해 setPageBtn 함수를 실행한다
4) 만약 type이 &#39;all&#39;이라면 전체 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputMenu 함수)
5) 만약 나머지 타입이라면 type을 매개변수로 전달해 type에 해당하는 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputCateMenu 함수)</p>
<h3 id="4-맨-앞맨-뒤-이동-버튼">4. 맨 앞/맨 뒤 이동 버튼</h3>
<p>데이터가 많아지면 맨 앞이나 맨 뒤로 페이지 이동시키는 것은 어려워진다.
이때 필요한 것이 맨 앞/맨 뒤로 이동하는 버튼이다.</p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/3b06b196-713d-4e65-affe-f47dcbc0f1ae/image.png" alt=""></p>
<p>이전/이후 버튼과 마찬가지로 첫번째 페이지 그룹이면 맨 앞 버튼을 없애고,</p>
<p><img src="https://velog.velcdn.com/images/tae_1/post/72eb0b46-46b9-4f2d-a747-433d8fcd5dbc/image.png" alt=""></p>
<p>마지막 페이지 그룹이라면 맨 뒤 버튼을 없애겠다.</p>
<p>맨 앞/맨 뒤 버튼은 동적으로 추가하는 것이 아니라 html에 넣어 이미 생성해놨기 때문에 특정 조건에서 숨기고 보이게 만들도록 하겠다.</p>
<h4 id="4-1-맨-앞-버튼-기능-넣기">4-1. 맨 앞 버튼 기능 넣기</h4>
<p>1) 맨앞 버튼의 요소를 가져와 click 리스너를 추가해준다
2) 맨앞으로 이동하니 nowPage=1로 업데이트해준다
3) 첫번째 그룹의 숫자 버튼들을 생성하기 위해 setPageBtn 함수를 실행한다
4) 만약 type이 &#39;all&#39;이라면 전체 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputMenu 함수)
5) 만약 나머지 타입이라면 type을 매개변수로 전달해 type에 해당하는 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputCateMenu 함수)</p>
<h4 id="4-2-맨-뒤-버튼-기능-넣기">4-2. 맨 뒤 버튼 기능 넣기</h4>
<p>1) 맨뒤 버튼의 요소를 가져와 click 리스너를 추가해준다
2) 맨뒤로 이동하니 nowPage=totalPage로 업데이트해준다
3) 마지막 그룹의 숫자 버튼들을 생성하기 위해 setPageBtn 함수를 실행한다
4) 만약 type이 &#39;all&#39;이라면 전체 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputMenu 함수)
5) 만약 나머지 타입이라면 type을 매개변수로 전달해 type에 해당하는 데이터에서 원하는 데이터만 동적으로 DOM에 그려준다(inputCateMenu 함수)</p>
<br>

<h1 id="🌕최종-결과">🌕최종 결과</h1>
<p><img src="https://velog.velcdn.com/images/tae_1/post/c1824a65-3d5f-42fb-bf61-54df723575f4/image.gif" alt=""></p>
<br>


<h1 id="📝참고자료">📝참고자료</h1>
<p><a href="https://yonghwankim-dev.tistory.com/578">https://yonghwankim-dev.tistory.com/578</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 올림/반올림/내림]]></title>
            <link>https://velog.io/@tae_1/JavaScript-%EC%98%AC%EB%A6%BC%EB%B0%98%EC%98%AC%EB%A6%BC%EB%82%B4%EB%A6%BC</link>
            <guid>https://velog.io/@tae_1/JavaScript-%EC%98%AC%EB%A6%BC%EB%B0%98%EC%98%AC%EB%A6%BC%EB%82%B4%EB%A6%BC</guid>
            <pubDate>Thu, 06 Feb 2025 15:10:45 GMT</pubDate>
            <description><![CDATA[<p>Pagination 구현하려고 공부하다가 Math.ceil함수를 발견했다.
오랜만에 보니까 헷갈려서 정리를 해놔야겠다...</p>
<p>숫자를 올림, 반올림, 내림을 수행하는 함수는 각각 Math.ceil(x), Math.round(x), Math.floor(x)가 있다(여기서 x는 숫자).</p>
<h3 id="mathceil-함수">Math.ceil 함수</h3>
<p>소수값이 있을때 값을 올림</p>
<pre><code class="language-javascript">Math.ceil(0.8); //결과값: 1
Math.ceil(2); //결과값: 2
Math.ceil(3.0002); //결과값: 4
Math.ceil(-3.0002); //결과값: -3</code></pre>
<br>

<h3 id="mathround-함수">Math.round 함수</h3>
<p>소수값이 있을때 가장 가까운 정수로 반올림</p>
<pre><code class="language-javascript">Math.round(5.5); //결과값: 6
Math.round(5.05); //결과값: 5
Math.round(-5.05); //결과값: -5
Math.round(-5.5); //결과값: -5
 Math.round(-5.95); //결과값: -6</code></pre>
<br>

<h3 id="mathfloor-함수">Math.floor 함수</h3>
<p>소수값이 있을때 값을 내림</p>
<pre><code class="language-javascript">Math.round(5.94); //결과값: 5
Math.round(5.05); //결과값: 5
Math.round(-5.05); //결과값: -6
Math.round(5); //결과값: 5</code></pre>
<br>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MySQL] MySQL WorkBench 8.0 CE 에러(could not acquire management access for administration)]]></title>
            <link>https://velog.io/@tae_1/MySQL-WorkBench-8.0-CE-%EC%97%90%EB%9F%ACcould-not-acquire-management-access-for-administration</link>
            <guid>https://velog.io/@tae_1/MySQL-WorkBench-8.0-CE-%EC%97%90%EB%9F%ACcould-not-acquire-management-access-for-administration</guid>
            <pubDate>Thu, 06 Feb 2025 14:31:28 GMT</pubDate>
            <description><![CDATA[<br>
MySQL 워크벤치를 실행하니 위와 같은 에러가 생겼다.

<p>뭔지 검색해보니 MySQL에서 한국어를 인코딩하지 못해서 생기는 문제라고 한다.
(참고로 <strong>window</strong>를 사용하고 있다...)
<br><br></p>
<h2 id="해결-방안">해결 방안</h2>
<h3 id="1-기본-언어-설정">1. 기본 언어 설정</h3>
<p><img src="https://velog.velcdn.com/images/tae_1/post/f4862f88-e1d0-4278-8955-2ac083421c44/image.png" alt=""></p>
<p>설정 &gt; 시간 및 언어 &gt; 기본 언어 설정에 들어가준다.</p>
<br>
<br>

<h3 id="2-시스템-로캘-변경">2. 시스템 로캘 변경</h3>
<p><img src="https://velog.velcdn.com/images/tae_1/post/bed45414-4658-45df-9858-4bf2b9600334/image.png" alt="">
시스템 로캘 변경 클릭</p>
<br>

<p><img src="https://velog.velcdn.com/images/tae_1/post/fca55aba-0b63-41bc-9c69-837728b45298/image.png" alt="">
Beta 체크 후 확인을 누른다음 재부팅!</p>
<p>다시 MySQL 워크벤치에 들어가 Server Status를 보면 잘 뜨는 것을 확인할 수 있다 ㅎㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] CSV파일 다운받기]]></title>
            <link>https://velog.io/@tae_1/JavaScript-CSV%ED%8C%8C%EC%9D%BC-%EB%8B%A4%EC%9A%B4%EB%B0%9B%EA%B8%B0</link>
            <guid>https://velog.io/@tae_1/JavaScript-CSV%ED%8C%8C%EC%9D%BC-%EB%8B%A4%EC%9A%B4%EB%B0%9B%EA%B8%B0</guid>
            <pubDate>Wed, 05 Feb 2025 17:13:39 GMT</pubDate>
            <description><![CDATA[<p>웹페이지에서 버튼을 누르면 JavaScript로 csv파일을 다운받을 수 있게 만들려고 한다.</p>
<p>여기서 csv는 콤마로 구분된 데이터인데 각 데이터 항목은 콤마로 구분되고 행은 줄 바꿈으로 구분된다.
<br>
<br></p>
<h2 id="csv-생성">csv 생성</h2>
<hr>
<p>현재 가지고 있는 data 배열의 각 항목을 csv로 변환해 줄 것이다.
첫번째 행은 미리 추가해주고 다른 행들은 데이터 배열을 처리하여 csv형식으로 바꿔준다.</p>
<pre><code class="language-javascript">const getCSV = (filename) =&gt; {
  let row = [];
  let csvdata = [];

  row.push(&quot;상품명&quot;, &quot;가격&quot;, &quot;상세 내용&quot;);
  csvdata.push(row.join(&quot;,&quot;));

  //데이터 배열
  data.map((x) =&gt; {
    row = [];
    row.push(x.name, x.price, x.content);
    csvdata.push(row.join(&quot;,&quot;));
  });

  downCSV(csvdata.join(&quot;\n&quot;), filename);
};</code></pre>
<p>이때 각 데이터는 콤마로 연결해서 push해준다.
그리고 csv를 다운로드하는 downCSV 함수에 파라미터를 넘길때는 줄바꿈으로 연결해서 보내준다.</p>
<p>그 이유는 위에서 설명했으니 이해했을거라 믿고...</p>
<br>
<br>

<h2 id="csv-다운">csv 다운</h2>
<hr>
<p>csv형식으로 바꿔놨으니 이제 다운만 해주면 된다.
<br></p>
<pre><code class="language-javascript">  // 한글 처리
  const BOM = &quot;\uFEFF&quot;;
  const csvBOM = BOM + csv;
</code></pre>
<p>csv에서는 한글이 깨지기 때문에 BOM(Byte Order Mark, \uFEFF)을 추가해준다.
<br></p>
<pre><code class="language-javascript">const csvFile = new Blob([csvBOM], { type: &quot;text/csv&quot; });</code></pre>
<p>이제 웹 브라우저에서 직접 파일을 만들거나 조작하려면 Blob(Binary Large Object)이 필요하다. 데이터를 파일처럼 다룰 수 있게 해주는 객체인데 Blob을 만들때 type을 지정하면 csv형식이라는 것을 알릴 수 있다.
<br></p>
<pre><code class="language-javascript">const downlink = document.createElement(&quot;a&quot;);
downlink.download = filename;
downlink.href = window.URL.createObjectURL(csvFile);</code></pre>
<p><code>&lt;a&gt;</code>태그를 동적으로 생성하여 download 속성에서 파일 이름을 설정하고 Blob데이터를 실제 파일 URL로 변환하면 브라우저가 이 파일을 다운로드할 수 있게 해준다.
<br></p>
<pre><code class="language-javascript">downlink.click();</code></pre>
<p>마지막으로 다운로드 링크를 클릭해준다.
a태그가 DOM에 추가되지 않아도 click() 함수가 잘 작동하기 때문에 DOM에 추가는 불필요하다고 느껴서 안 해줬다.(해줘야한다면... 저에게 알려주세요)
<br></p>
<pre><code class="language-javascript">window.URL.revokeObjectURL(url);</code></pre>
<p>위 코드는 Blob 객체를 사용하여 URL을 생성했으니 메모리 누수가 발생하는 것을 방지하기 위해 추가해주었다. 필수는 아니지만, 메모리 관리 측면에서...</p>
<br>
<br>


<h3 id="그런데">그런데!!!!!!!!!!!!</h3>
<p>여기서 문제가 생겼다.
<img src="https://velog.velcdn.com/images/tae_1/post/2f479272-cb06-47ef-8e2f-d38c1475ee45/image.png" alt=""></p>
<h4 id="오-마이-갓">오 마이 갓</h4>
<p>데이터 자체에 콤마가 포함되어있어서 그 데이터들도 구분되어 들어간 것이다...</p>
<p>그렇지만 걱정 노노
모르면 검색하면 되지 ㅎㅎ</p>
<p>값에 콤마가 있으면 따옴표(&quot;)로 감싸면 되는군!
음음 새로운 사실을 알아간다 ^^</p>
<p>그럼 바로 적용!</p>
<pre><code class="language-javascript">row.push(`&quot;${x.name}&quot;`, `&quot;${x.price}&quot;`, `&quot;${x.content}&quot;`);</code></pre>
<p><img src="https://velog.velcdn.com/images/tae_1/post/4c7263d4-da16-4bf0-9db7-0360e4395f0c/image.png" alt=""></p>
<p>좋아좋아</p>
]]></description>
        </item>
    </channel>
</rss>