50 | )
51 | }
52 |
53 | export default Contributors
54 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/use-search.js:
--------------------------------------------------------------------------------
1 | import {graphql, useStaticQuery} from 'gatsby'
2 | import path from 'path'
3 | import React from 'react'
4 | import SearchWorker from 'worker-loader!./search.worker.js'
5 |
6 | const ensureAbsolute = uri => uri.startsWith('/') ? uri : `/${uri}`
7 |
8 | function useSearch(query) {
9 | const latestQuery = React.useRef(query)
10 | const workerRef = React.useRef()
11 |
12 | const data = useStaticQuery(graphql`
13 | {
14 | allMdx {
15 | nodes {
16 | fileAbsolutePath
17 | frontmatter {
18 | title
19 | }
20 | rawBody
21 | parent {
22 | ... on File {
23 | relativeDirectory
24 | name
25 | }
26 | }
27 | }
28 | }
29 | }
30 | `)
31 |
32 | const list = React.useMemo(
33 | () =>
34 | data.allMdx.nodes.map(node => ({
35 | path: ensureAbsolute(path.join(
36 | node.parent.relativeDirectory,
37 | node.parent.name === 'index' ? '/' : node.parent.name,
38 | )),
39 | title: node.frontmatter.title,
40 | rawBody: node.rawBody,
41 | })),
42 | [data],
43 | )
44 |
45 | const [results, setResults] = React.useState(list)
46 |
47 | const handleSearchResults = React.useCallback(({data}) => {
48 | if (data.query && data.results && data.query === latestQuery.current) {
49 | setResults(data.results)
50 | }
51 | }, [])
52 |
53 | React.useEffect(() => {
54 | const worker = new SearchWorker()
55 | worker.addEventListener('message', handleSearchResults)
56 | worker.postMessage({list})
57 | workerRef.current = worker
58 |
59 | return () => {
60 | workerRef.current.terminate()
61 | }
62 | }, [list, handleSearchResults])
63 |
64 | React.useEffect(() => {
65 | latestQuery.current = query
66 | if (query && workerRef.current) {
67 | workerRef.current.postMessage({query: query})
68 | } else {
69 | setResults(list)
70 | }
71 | }, [query, list])
72 |
73 | return results
74 | }
75 |
76 | export default useSearch
77 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/components/__tests__/contributors.test.js:
--------------------------------------------------------------------------------
1 | import {render} from '@testing-library/react'
2 | import React from 'react'
3 | import Contributors from '../contributors'
4 |
5 | test('renders contributors', () => {
6 | const {queryByText} = render(
7 | ,
25 | )
26 |
27 | expect(queryByText(/2 contributors/)).toBeInTheDocument()
28 | expect(queryByText(/Last edited by/)).toBeInTheDocument()
29 | expect(queryByText(/colebemis/)).toBeInTheDocument()
30 | expect(queryByText(/August 15, 2019/)).toBeInTheDocument()
31 | })
32 |
33 | test('does not render "last edited by" if latest contributor does not have a latest commit', () => {
34 | const {queryByText} = render(
35 | ,
36 | )
37 |
38 | expect(queryByText(/1 contributor/)).toBeInTheDocument()
39 | expect(queryByText(/Last edited by/)).toBeNull()
40 | })
41 |
42 | // The `Contributors` component is unlikely to be passed an empty array
43 | // but it should be able to handle an empty array gracefully just in case.
44 | test('handles no contributors', () => {
45 | const {queryByText} = render()
46 |
47 | expect(queryByText(/0 contributors/)).toBeInTheDocument()
48 | })
49 |
50 | test('does not render duplicate contributors', () => {
51 | const {queryByText} = render(
52 | ,
70 | )
71 |
72 | expect(queryByText(/1 contributor/)).toBeInTheDocument()
73 | })
74 |
--------------------------------------------------------------------------------
/.claude/commands/plan.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Execute the implementation planning workflow using the plan template to generate design artifacts.
3 | ---
4 |
5 | The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
6 |
7 | User input:
8 |
9 | $ARGUMENTS
10 |
11 | Given the implementation details provided as an argument, do this:
12 |
13 | 1. Run `.specify/scripts/bash/setup-plan.sh --json` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute.
14 | - BEFORE proceeding, inspect FEATURE_SPEC for a `## Clarifications` section with at least one `Session` subheading. If missing or clearly ambiguous areas remain (vague adjectives, unresolved critical choices), PAUSE and instruct the user to run `/clarify` first to reduce rework. Only continue if: (a) Clarifications exist OR (b) an explicit user override is provided (e.g., "proceed without clarification"). Do not attempt to fabricate clarifications yourself.
15 | 2. Read and analyze the feature specification to understand:
16 | - The feature requirements and user stories
17 | - Functional and non-functional requirements
18 | - Success criteria and acceptance criteria
19 | - Any technical constraints or dependencies mentioned
20 |
21 | 3. Read the constitution at `.specify/memory/constitution.md` to understand constitutional requirements.
22 |
23 | 4. Execute the implementation plan template:
24 | - Load `.specify/templates/plan-template.md` (already copied to IMPL_PLAN path)
25 | - Set Input path to FEATURE_SPEC
26 | - Run the Execution Flow (main) function steps 1-9
27 | - The template is self-contained and executable
28 | - Follow error handling and gate checks as specified
29 | - Let the template guide artifact generation in $SPECS_DIR:
30 | * Phase 0 generates research.md
31 | * Phase 1 generates data-model.md, contracts/, quickstart.md
32 | * Phase 2 generates tasks.md
33 | - Incorporate user-provided details from arguments into Technical Context: $ARGUMENTS
34 | - Update Progress Tracking as you complete each phase
35 |
36 | 5. Verify execution completed:
37 | - Check Progress Tracking shows all phases complete
38 | - Ensure all required artifacts were generated
39 | - Confirm no ERROR states in execution
40 |
41 | 6. Report results with branch name, file paths, and generated artifacts.
42 |
43 | Use absolute paths with the repository root for all file operations to avoid path issues.
44 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/components/nav-items.js:
--------------------------------------------------------------------------------
1 | import {BorderBox, Flex, StyledOcticon, Link, themeGet} from '@primer/components'
2 | import {LinkExternalIcon} from '@primer/octicons-react'
3 | import {Link as GatsbyLink} from 'gatsby'
4 | import preval from 'preval.macro'
5 | import React from 'react'
6 | import styled from 'styled-components'
7 |
8 | // This code needs to run at build-time so it can access the file system.
9 | const repositoryUrl = preval`
10 | const readPkgUp = require('read-pkg-up')
11 | const getPkgRepo = require('get-pkg-repo')
12 | try {
13 | const repo = getPkgRepo(readPkgUp.sync().package)
14 | module.exports = \`https://github.com/\${repo.user}/\${repo.project}\`
15 | } catch (error) {
16 | module.exports = ''
17 | }
18 | `
19 |
20 | const NavLink = styled(Link)`
21 | &.active {
22 | font-weight: ${themeGet('fontWeights.bold')};
23 | color: ${themeGet('colors.gray.8')};
24 | }
25 | `
26 |
27 | function NavItems({items}) {
28 | return (
29 | <>
30 | {items.map((item) => (
31 |
38 |
39 |
46 | {item.title}
47 |
48 | {item.children ? (
49 |
50 | {item.children.map((child) => (
51 |
61 | {child.title}
62 |
63 | ))}
64 |
65 | ) : null}
66 |
67 |
68 | ))}
69 | {repositoryUrl ? (
70 |
71 |
72 |
73 | GitHub
74 |
75 |
76 |
77 |
78 | ) : null}
79 | >
80 | )
81 | }
82 |
83 | export default NavItems
84 |
--------------------------------------------------------------------------------
/.claude/commands/tasks.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts.
3 | ---
4 |
5 | The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
6 |
7 | User input:
8 |
9 | $ARGUMENTS
10 |
11 | 1. Run `.specify/scripts/bash/check-prerequisites.sh --json` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
12 | 2. Load and analyze available design documents:
13 | - Always read plan.md for tech stack and libraries
14 | - IF EXISTS: Read data-model.md for entities
15 | - IF EXISTS: Read contracts/ for API endpoints
16 | - IF EXISTS: Read research.md for technical decisions
17 | - IF EXISTS: Read quickstart.md for test scenarios
18 |
19 | Note: Not all projects have all documents. For example:
20 | - CLI tools might not have contracts/
21 | - Simple libraries might not need data-model.md
22 | - Generate tasks based on what's available
23 |
24 | 3. Generate tasks following the template:
25 | - Use `.specify/templates/tasks-template.md` as the base
26 | - Replace example tasks with actual tasks based on:
27 | * **Setup tasks**: Project init, dependencies, linting
28 | * **Test tasks [P]**: One per contract, one per integration scenario
29 | * **Core tasks**: One per entity, service, CLI command, endpoint
30 | * **Integration tasks**: DB connections, middleware, logging
31 | * **Polish tasks [P]**: Unit tests, performance, docs
32 |
33 | 4. Task generation rules:
34 | - Each contract file → contract test task marked [P]
35 | - Each entity in data-model → model creation task marked [P]
36 | - Each endpoint → implementation task (not parallel if shared files)
37 | - Each user story → integration test marked [P]
38 | - Different files = can be parallel [P]
39 | - Same file = sequential (no [P])
40 |
41 | 5. Order tasks by dependencies:
42 | - Setup before everything
43 | - Tests before implementation (TDD)
44 | - Models before services
45 | - Services before endpoints
46 | - Core before integration
47 | - Everything before polish
48 |
49 | 6. Include parallel execution examples:
50 | - Group [P] tasks that can run together
51 | - Show actual Task agent commands
52 |
53 | 7. Create FEATURE_DIR/tasks.md with:
54 | - Correct feature name from implementation plan
55 | - Numbered tasks (T001, T002, etc.)
56 | - Clear file paths for each task
57 | - Dependency notes
58 | - Parallel execution guidance
59 |
60 | Context for task generation: $ARGUMENTS
61 |
62 | The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context.
63 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/components/heading.js:
--------------------------------------------------------------------------------
1 | import {Heading, Link} from '@primer/components'
2 | import {LinkIcon} from '@primer/octicons-react'
3 | import themeGet from '@styled-system/theme-get'
4 | import GithubSlugger from 'github-slugger'
5 | import React from 'react'
6 | import textContent from 'react-addons-text-content'
7 | import styled from 'styled-components'
8 | import {HEADER_HEIGHT} from './header'
9 |
10 | const StyledHeading = styled(Heading)`
11 | margin-top: ${themeGet('space.4')};
12 | margin-bottom: ${themeGet('space.3')};
13 | scroll-margin-top: ${HEADER_HEIGHT + 24}px;
14 |
15 | & .octicon-link {
16 | visibility: hidden;
17 | }
18 |
19 | &:hover .octicon-link,
20 | &:focus-within .octicon-link {
21 | visibility: visible;
22 | }
23 | `
24 |
25 | function MarkdownHeading({children, ...props}) {
26 | const slugger = new GithubSlugger()
27 | const text = children ? textContent(children) : ''
28 | const id = text ? slugger.slug(text) : ''
29 |
30 | return (
31 |
32 |
39 |
40 |
41 | {children}
42 |
43 | )
44 | }
45 |
46 | const StyledH1 = styled(StyledHeading).attrs({as: 'h1'})`
47 | padding-bottom: ${themeGet('space.1')};
48 | font-size: ${themeGet('fontSizes.5')};
49 | border-bottom: 1px solid ${themeGet('colors.gray.2')};
50 | `
51 |
52 | const StyledH2 = styled(StyledHeading).attrs({as: 'h2'})`
53 | padding-bottom: ${themeGet('space.1')};
54 | font-size: ${themeGet('fontSizes.4')};
55 | border-bottom: 1px solid ${themeGet('colors.gray.2')};
56 | `
57 |
58 | const StyledH3 = styled(StyledHeading).attrs({as: 'h3'})`
59 | font-size: ${themeGet('fontSizes.3')};
60 | `
61 |
62 | const StyledH4 = styled(StyledHeading).attrs({as: 'h4'})`
63 | font-size: ${themeGet('fontSizes.2')};
64 | `
65 |
66 | const StyledH5 = styled(StyledHeading).attrs({as: 'h5'})`
67 | font-size: ${themeGet('fontSizes.1')};
68 | `
69 |
70 | const StyledH6 = styled(StyledHeading).attrs({as: 'h6'})`
71 | font-size: ${themeGet('fontSizes.1')};
72 | color: ${themeGet('colors.gray.5')};
73 | `
74 |
75 | export const H1 = (props) =>
76 | export const H2 = (props) =>
77 | export const H3 = (props) =>
78 | export const H4 = (props) =>
79 | export const H5 = (props) =>
80 | export const H6 = (props) =>
81 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/components/search.js:
--------------------------------------------------------------------------------
1 | import {BorderBox, Position} from '@primer/components'
2 | import Downshift from 'downshift'
3 | import {navigate} from 'gatsby'
4 | import React from 'react'
5 | import useSearch from '../use-search'
6 | import useSiteMetadata from '../use-site-metadata'
7 | import DarkTextInput from './dark-text-input'
8 | import SearchResults from './search-results'
9 |
10 | function stateReducer(state, changes) {
11 | switch (changes.type) {
12 | case Downshift.stateChangeTypes.changeInput:
13 | if (!changes.inputValue) {
14 | // Close the menu if the input is empty.
15 | return {...changes, isOpen: false}
16 | }
17 | return changes
18 | default:
19 | return changes
20 | }
21 | }
22 |
23 | function Search() {
24 | const [query, setQuery] = React.useState('')
25 | const results = useSearch(query)
26 | const siteMetadata = useSiteMetadata()
27 |
28 | return (
29 | setQuery(inputValue)}
32 | // We don't need Downshift to keep track of a selected item because as
33 | // soon as an item is selected we navigate to a new page.
34 | // Let's avoid any unexpected states related to the selected item
35 | // by setting it to always be `null`.
36 | selectedItem={null}
37 | onSelect={item => {
38 | if (item) {
39 | navigate(item.path)
40 | setQuery('')
41 | }
42 | }}
43 | itemToString={item => (item ? item.title : '')}
44 | stateReducer={stateReducer}
45 | >
46 | {({
47 | getInputProps,
48 | getItemProps,
49 | getMenuProps,
50 | getRootProps,
51 | isOpen,
52 | highlightedIndex,
53 | }) => (
54 |
55 |
61 | {isOpen ? (
62 |
70 |
78 |
83 |
84 |
85 | ) : null}
86 |
87 | )}
88 |
89 | )
90 | }
91 |
92 | export default Search
93 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/components/live-code.js:
--------------------------------------------------------------------------------
1 | import {Absolute, BorderBox, Flex, Relative, Text} from '@primer/components'
2 | import htmlReactParser from 'html-react-parser'
3 | import githubTheme from '../github'
4 | import React, {useState} from 'react'
5 | import reactElementToJsxString from 'react-element-to-jsx-string'
6 | import {LiveEditor, LiveError, LivePreview, LiveProvider} from 'react-live'
7 | import {ThemeContext} from 'styled-components'
8 | import scope from '../live-code-scope'
9 | import ClipboardCopy from './clipboard-copy'
10 | import LivePreviewWrapper from './live-preview-wrapper'
11 |
12 | const languageTransformers = {
13 | html: (html) => htmlToJsx(html),
14 | jsx: (jsx) => wrapWithFragment(jsx),
15 | }
16 |
17 | function htmlToJsx(html) {
18 | try {
19 | const reactElement = htmlReactParser(removeNewlines(html))
20 | // The output of htmlReactParser could be a single React element
21 | // or an array of React elements. reactElementToJsxString does not accept arrays
22 | // so we have to wrap the output in React fragment.
23 | return reactElementToJsxString(<>{reactElement}>)
24 | } catch (error) {
25 | return wrapWithFragment(html)
26 | }
27 | }
28 |
29 | function removeNewlines(string) {
30 | return string.replace(/(\r\n|\n|\r)/gm, '')
31 | }
32 |
33 | function wrapWithFragment(jsx) {
34 | return `${jsx}`
35 | }
36 |
37 | function LiveCode({code, language, noinline}) {
38 | const theme = React.useContext(ThemeContext)
39 | const [liveCode, setLiveCode] = useState(code)
40 | const handleChange = (updatedLiveCode) => setLiveCode(updatedLiveCode)
41 |
42 | return (
43 |
44 |
50 |
58 |
59 |
60 |
61 |
62 |
63 |
75 |
76 |
77 |
78 |
79 |
88 |
89 |
90 | )
91 | }
92 |
93 | export default LiveCode
94 |
--------------------------------------------------------------------------------
/content/pages/160-null-undefined.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: null과 undefined
3 | ---
4 |
5 | JavaScript에는 '없음'를 나타내는 값이 두 개 있는데, 바로 `null`와 `undefined`입니다. 두 값의 의미는 비슷하지만, 각각이 사용되는 목적과 장소가 다릅니다.
6 |
7 | JavaScript는 값이 대입되지 않은 변수 혹은 속성을 사용하려고 하면 `undefined`를 반환합니다.
8 |
9 | ```js
10 | let foo
11 | foo // undefined
12 |
13 | const obj = {}
14 | obj.prop // undefined
15 | ```
16 |
17 | `null`은 '객체가 없음'을 나타냅니다. 실제로 `typeof` 연산을 해보면 아래와 같은 값을 반환합니다.
18 |
19 | ```js
20 | typeof null // 'object'
21 | typeof undefined // 'undefined'
22 | ```
23 |
24 | 그렇다면 개발자의 입장에서 **'없음'**을 저장하기 위해 둘 중 어떤 것을 써야 할까요? `undefined`를 쓴다고 가정해보면, 아래와 같은 코드는 그 의미가 불분명해집니다.
25 |
26 | ```js
27 | let foo // 값을 대입한 적 없음
28 | let bar = undefined // 값을 대입함
29 | foo // undefined
30 | bar // undefined (??)
31 | let obj1 = {} // 속성을 지정하지 않음
32 | let obj2 = {prop: undefined} // 속성을 지정함
33 | obj1.prop // undefined
34 | obj2.prop // undefined (??)
35 | ```
36 |
37 | 비록 `undefined`가 '없음'을 나타내는 값일지라도, 대입한 적 없는 변수 혹은 속성과, 명시적으로 '없음'을 나타내는 경우를 **구분**을 할 수 있어야 코드의 의미가 명확해 질 것입니다. **프로그래머의 입장에서 명시적으로 부재를 나타내고 싶다면 항상 null을 사용**하는 것이 좋습니다.
38 |
39 | 다만, 객체를 사용할 때 어떤 속성의 부재를 `null`을 통해서 나타내는 쪽보다는, 그냥 그 속성을 정의하지 않는 방식이 간편하므로 더 널리 사용됩니다.
40 |
41 | ```js
42 | // 이렇게 하는 경우는 많지 않습니다.
43 | {
44 | name: 'Seungha',
45 | address: null
46 | }
47 |
48 | // 그냥 이렇게 하는 경우가 많습니다.
49 | {
50 | name: 'Seungha'
51 | }
52 |
53 | // 어쨌든 이렇게 하지는 말아주세요.
54 | {
55 | name: 'Seungha',
56 | address: undefined
57 | }
58 | ```
59 |
60 | ## Null Check
61 |
62 | `null`이나 `undefined`는 어떤 변수에도, 어떤 속성에도 들어있을 수 있기 때문에 우리는 코드를 짤 때 값이 있는 경우와 없는 경우 (즉 `null` 혹은 `undefined`인 경우)를 모두 고려해서 코드를 짜야 할 필요가 있습니다. 어떤 값이 `null` 혹은 `undefined`인지 확인하는 작업을 **null check**라고 합니다. null check는 간단히 등호를 이용해서 할 수 있습니다.
63 |
64 | ```js
65 | function printIfNotNull(input) {
66 | if (input !== null && input !== undefined) {
67 | console.log(input)
68 | }
69 | }
70 | ```
71 |
72 | 그런데 매 번 위처럼 긴 비교를 해야 한다는 것은 골치아픈 일입니다. 사실은 위 `if` 구문 안에 있는 식을 다음과 같이 줄여 쓸 수 있습니다.
73 |
74 | ```js
75 | // 아래 세 개의 식은 완전히 같은 의미입니다.
76 | input !== null && input !== undefined
77 | input != null
78 | input != undefined
79 |
80 | // 아래 세 개의 식은 완전히 같은 의미입니다.
81 | input === null || input === undefined
82 | input == null
83 | input == undefined
84 | ```
85 |
86 | 이제까지 세 글자 짜리 등호만을 소개했는데, 사실 JavaScript에는 **두 글자 짜리 등호**도 있습니다. 각각의 공식적인 명칭은 **strict equality** comparison operator, **abstract equality** comparison operator 입니다. 이름에서도 알 수 있듯이, 대개 `===`는 값이 정확히 같을 때 `true`라는 결과값을 반환하고, `==`는 그렇지 않을 때가 많습니다. 그래서 보통의 경우 `===`를 사용하는 것이 권장되는 편입니다.
87 |
88 | 다만 null check를 할 때 만큼은 `==`를 쓰는 것이 편리합니다. 아래 예제를 통해 설명하겠습니다.
89 |
90 | `==`와 `===` 두 연산자는 `null`과 `undefined`에 대해서 아래와 같이 동작합니다.
91 |
92 | ```js
93 | null === undefined // false
94 | null == undefined // true
95 |
96 | null == 1 // false
97 | null == 'hello' // false
98 | null == false // false
99 |
100 | undefined == 1 // false
101 | undefined == 'hello' // false
102 | undefined == false // false
103 | ```
104 |
105 | 즉, `==` 연산자는 한 쪽 피연산자에 `null` 혹은 `undefined`가 오면, 다른 쪽 피연산자에 `null` 혹은 `undefined`가 왔을 때만 `true`를 반환하고, 다른 모든 경우에 `false`를 반환합니다.
106 |
107 | 따라서 null check를 할 때 만큼은 `==`를 사용하는 것이 편합니다. 다른 모든 경우에는 `===`를 사용하는 것이 좋습니다.
108 |
109 | `===`와 `==`에 대한 자세한 내용은 [연산자 더 알아보기](./245-operator-in-depth.html) 챕터에서 다룹니다.
110 |
--------------------------------------------------------------------------------
/.specify/scripts/bash/create-new-feature.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | JSON_MODE=false
6 | ARGS=()
7 | for arg in "$@"; do
8 | case "$arg" in
9 | --json) JSON_MODE=true ;;
10 | --help|-h) echo "Usage: $0 [--json] "; exit 0 ;;
11 | *) ARGS+=("$arg") ;;
12 | esac
13 | done
14 |
15 | FEATURE_DESCRIPTION="${ARGS[*]}"
16 | if [ -z "$FEATURE_DESCRIPTION" ]; then
17 | echo "Usage: $0 [--json] " >&2
18 | exit 1
19 | fi
20 |
21 | # Function to find the repository root by searching for existing project markers
22 | find_repo_root() {
23 | local dir="$1"
24 | while [ "$dir" != "/" ]; do
25 | if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then
26 | echo "$dir"
27 | return 0
28 | fi
29 | dir="$(dirname "$dir")"
30 | done
31 | return 1
32 | }
33 |
34 | # Resolve repository root. Prefer git information when available, but fall back
35 | # to searching for repository markers so the workflow still functions in repositories that
36 | # were initialised with --no-git.
37 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
38 |
39 | if git rev-parse --show-toplevel >/dev/null 2>&1; then
40 | REPO_ROOT=$(git rev-parse --show-toplevel)
41 | HAS_GIT=true
42 | else
43 | REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")"
44 | if [ -z "$REPO_ROOT" ]; then
45 | echo "Error: Could not determine repository root. Please run this script from within the repository." >&2
46 | exit 1
47 | fi
48 | HAS_GIT=false
49 | fi
50 |
51 | cd "$REPO_ROOT"
52 |
53 | SPECS_DIR="$REPO_ROOT/specs"
54 | mkdir -p "$SPECS_DIR"
55 |
56 | HIGHEST=0
57 | if [ -d "$SPECS_DIR" ]; then
58 | for dir in "$SPECS_DIR"/*; do
59 | [ -d "$dir" ] || continue
60 | dirname=$(basename "$dir")
61 | number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0")
62 | number=$((10#$number))
63 | if [ "$number" -gt "$HIGHEST" ]; then HIGHEST=$number; fi
64 | done
65 | fi
66 |
67 | NEXT=$((HIGHEST + 1))
68 | FEATURE_NUM=$(printf "%03d" "$NEXT")
69 |
70 | BRANCH_NAME=$(echo "$FEATURE_DESCRIPTION" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
71 | WORDS=$(echo "$BRANCH_NAME" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//')
72 | BRANCH_NAME="${FEATURE_NUM}-${WORDS}"
73 |
74 | if [ "$HAS_GIT" = true ]; then
75 | git checkout -b "$BRANCH_NAME"
76 | else
77 | >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME"
78 | fi
79 |
80 | FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME"
81 | mkdir -p "$FEATURE_DIR"
82 |
83 | TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md"
84 | SPEC_FILE="$FEATURE_DIR/spec.md"
85 | if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi
86 |
87 | # Set the SPECIFY_FEATURE environment variable for the current session
88 | export SPECIFY_FEATURE="$BRANCH_NAME"
89 |
90 | if $JSON_MODE; then
91 | printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM"
92 | else
93 | echo "BRANCH_NAME: $BRANCH_NAME"
94 | echo "SPEC_FILE: $SPEC_FILE"
95 | echo "FEATURE_NUM: $FEATURE_NUM"
96 | echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME"
97 | fi
98 |
--------------------------------------------------------------------------------
/.claude/commands/implement.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Execute the implementation plan by processing and executing all tasks defined in tasks.md
3 | ---
4 |
5 | The user input can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
6 |
7 | User input:
8 |
9 | $ARGUMENTS
10 |
11 | 1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
12 |
13 | 2. Load and analyze the implementation context:
14 | - **REQUIRED**: Read tasks.md for the complete task list and execution plan
15 | - **REQUIRED**: Read plan.md for tech stack, architecture, and file structure
16 | - **IF EXISTS**: Read data-model.md for entities and relationships
17 | - **IF EXISTS**: Read contracts/ for API specifications and test requirements
18 | - **IF EXISTS**: Read research.md for technical decisions and constraints
19 | - **IF EXISTS**: Read quickstart.md for integration scenarios
20 |
21 | 3. Parse tasks.md structure and extract:
22 | - **Task phases**: Setup, Tests, Core, Integration, Polish
23 | - **Task dependencies**: Sequential vs parallel execution rules
24 | - **Task details**: ID, description, file paths, parallel markers [P]
25 | - **Execution flow**: Order and dependency requirements
26 |
27 | 4. Execute implementation following the task plan:
28 | - **Phase-by-phase execution**: Complete each phase before moving to the next
29 | - **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together
30 | - **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks
31 | - **File-based coordination**: Tasks affecting the same files must run sequentially
32 | - **Validation checkpoints**: Verify each phase completion before proceeding
33 |
34 | 5. Implementation execution rules:
35 | - **Setup first**: Initialize project structure, dependencies, configuration
36 | - **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios
37 | - **Core development**: Implement models, services, CLI commands, endpoints
38 | - **Integration work**: Database connections, middleware, logging, external services
39 | - **Polish and validation**: Unit tests, performance optimization, documentation
40 |
41 | 6. Progress tracking and error handling:
42 | - Report progress after each completed task
43 | - Halt execution if any non-parallel task fails
44 | - For parallel tasks [P], continue with successful tasks, report failed ones
45 | - Provide clear error messages with context for debugging
46 | - Suggest next steps if implementation cannot proceed
47 | - **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file.
48 |
49 | 7. Completion validation:
50 | - Verify all required tasks are completed
51 | - Check that implemented features match the original specification
52 | - Validate that tests pass and coverage meets requirements
53 | - Confirm the implementation follows the technical plan
54 | - Report final status with summary of completed work
55 |
56 | Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/tasks` first to regenerate the task list.
57 |
--------------------------------------------------------------------------------
/content/keyshape/TreeSVG.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import LazySVG from './LazySVG'
3 |
4 | const svgString = `
5 |
30 | `
31 |
32 | export default function TreeSVG() {
33 | return
34 | }
35 |
--------------------------------------------------------------------------------
/content/pages/150-boolean.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: boolean 타입
3 | ---
4 |
5 | boolean 타입에 해당하는 값은 `true`, `false` 두 가지 밖에 없습니다. 이 값들을 '진리값'이라고 부릅니다. 프로그래밍에서의 진리값은 어떤 조건이 참인지 거짓인지를 나타내기 위해 사용됩니다.
6 |
7 | ```js
8 | 1 < 2 // true
9 | 1 > 2 // false
10 | 3 === 3 // true
11 | 3 !== 3 // false
12 | Number.isFinite(Infinity) // false
13 | Number.isNaN(NaN) // true
14 | 'hello'.includes('ll') // true
15 | ```
16 |
17 | ## 논리 연산자
18 |
19 | JavaScript는 진리값에 대한 여러 연산을 지원합니다.
20 |
21 | ```js
22 | // 논리 부정 (logical NOT)
23 | !true // false
24 | !false // true
25 |
26 | // 논리합 (logical OR)
27 | true || true // true
28 | true || false // true
29 | false || true // true
30 | false || false // false
31 |
32 | // 논리곱 (logical AND)
33 | true && true // true
34 | true && false // false
35 | false && true // false
36 | false && false // false
37 |
38 | // 할당 연산 (assignment operators), ES2021
39 | // ||= 는 변수의 값이 true이면 아무 변화가 일어나지 않고 false이면 우항의 값이 변수에 할당됩니다.
40 | let x = false
41 | x ||= true // true
42 |
43 | // &&= 는 변수의 값이 false이면 아무 변화가 일어나지 않고 true이면 우항의 값이 변수에 할당됩니다.
44 | let y = true
45 | y &&= false // false
46 |
47 | // ||=와 &&=는 각각 아래 연산과 같은 동작을 합니다.
48 | x = x || true
49 | y = y && false
50 |
51 | // 삼항 연산자 (ternary operator)
52 | true ? 1 : 2 // 1
53 | false ? 1 : 2 // 2
54 | ```
55 |
56 | ## 연산자 우선순위 (Operator Precedence)
57 |
58 | 한 구문에 여러 개의 연산자를 이어서 쓴 경우, 어떤 연산자는 먼저 계산되고 어떤 연산자는 나중에 계산됩니다. 이는 연산자 우선순위(operator precedence)에 의해 결정됩니다. 자세한 내용은 [MDN 링크](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/%EC%97%B0%EC%82%B0%EC%9E%90_%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84)를 참고해주세요.
59 |
60 | ```js
61 | true || (true && false) // true
62 | ;(true || true) && false // false
63 | true || (false && false) // true
64 | ;(true || false) && false // false
65 | ```
66 |
67 | ## 논리 연산의 여러 가지 법칙
68 |
69 | 논리 연산에는 여러 가지 법칙이 있습니다. 프로그래밍을 하며 논리 연산을 할 일이 많기 때문에, 이 법칙들을 알아두면 도움이 됩니다. 각 법칙이 성립하는 이유를 잘 생각해 보세요. 경우의 수를 모두 생각해보거나, 벤 다이어그램을 그려보세요.[^1]
70 |
71 | ```js
72 | // a, b, c가 **모두 boolean 타입**이라고 할 때, 다음 식의 결과값은 a, b, c의 값과 관계 없이 모두 true 입니다.
73 |
74 | // 이중 부정
75 | !!a === a
76 |
77 | // 교환 법칙
78 | a || b === b || a
79 | a && b === b && a
80 |
81 | // 결합 법칙
82 | a || b || c === a || b || c
83 | a && b && c === a && b && c
84 |
85 | // 분배 법칙
86 | a || ((b && c) === (a || b) && (a || c))
87 | ;(a && (b || c) === (a && b)) || (a && c)
88 |
89 | // 흡수 법칙
90 | a && (a || b) === a
91 | a || (a && b) === a
92 |
93 | // 드 모르간의 법칙
94 | !(a || b) === !a && !b
95 | !(a && b) === !a || !b
96 |
97 | // 그 밖에...
98 | a || true === true
99 | a || false === a
100 | a && true === a
101 | a && false === false
102 |
103 | a || !a === true
104 | a && !a === false
105 |
106 | a || a === a
107 | a && a === a
108 | ```
109 |
110 | ## truthy & falsy
111 |
112 | JavaScript에서는 boolean 타입이 와야 하는 자리에 다른 타입의 값이 와도 에러가 나지 않고 실행됩니다.
113 |
114 | ```js
115 | if (1) {
116 | console.log('이 코드는 실행됩니다.')
117 | }
118 |
119 | if (0) {
120 | console.log('이 코드는 실행되지 않습니다.')
121 | }
122 | ```
123 |
124 | 이렇게 어떤 값들은 `true`로, 어떤 값들은 `false`로 취급되는데, 전자를 **truthy**, 후자를 **falsy**라고 부릅니다. JavaScript에서는 아래의 값들은 모두 falsy이고, 이를 제외한 모든 값들은 truthy입니다.
125 |
126 | - `false`
127 | - `null`
128 | - `undefined`
129 | - `0`
130 | - `NaN`
131 | - `''`
132 |
133 | truthy와 falsy를 활용하면 짧은 코드를 작성할 수 있지만, 코드의 의미가 불분명해지거나 논리적으로 놓치는 부분이 생길 수 있기 때문에 주의해서 사용해야 합니다.
134 |
135 | ## 다른 타입의 값을 진리값으로 변환하기
136 |
137 | 어떤 값을 명시적으로 boolean 타입으로 변환해야 할 때가 있는데, 그 때에는 두 가지 방법을 사용할 수 있습니다.
138 |
139 | ```js
140 | !!'hello' // true
141 | Boolean('hello') // true
142 | ```
143 |
144 | 부정 연산자(`!`) 뒤의 값이 truthy이면 `false`, falsy이면 `true`를 반환하는 성질을 이용해서 이중 부정을 통해 값을 boolean 타입으로 변환할 수 있습니다. 혹은 `Boolean` 함수를 사용해도 됩니다. 전자가 간편하기 때문에 많이 사용되는 편입니다.
145 |
146 | [^1]: `!`은 여집합, `||`는 합집합, `&&`는 교집합, `true`는 전체집합, `false`는 공집합으로 생각하면 됩니다.
147 |
--------------------------------------------------------------------------------
/.specify/scripts/bash/common.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Common functions and variables for all scripts
3 |
4 | # Get repository root, with fallback for non-git repositories
5 | get_repo_root() {
6 | if git rev-parse --show-toplevel >/dev/null 2>&1; then
7 | git rev-parse --show-toplevel
8 | else
9 | # Fall back to script location for non-git repos
10 | local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11 | (cd "$script_dir/../../.." && pwd)
12 | fi
13 | }
14 |
15 | # Get current branch, with fallback for non-git repositories
16 | get_current_branch() {
17 | # First check if SPECIFY_FEATURE environment variable is set
18 | if [[ -n "${SPECIFY_FEATURE:-}" ]]; then
19 | echo "$SPECIFY_FEATURE"
20 | return
21 | fi
22 |
23 | # Then check git if available
24 | if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
25 | git rev-parse --abbrev-ref HEAD
26 | return
27 | fi
28 |
29 | # For non-git repos, try to find the latest feature directory
30 | local repo_root=$(get_repo_root)
31 | local specs_dir="$repo_root/specs"
32 |
33 | if [[ -d "$specs_dir" ]]; then
34 | local latest_feature=""
35 | local highest=0
36 |
37 | for dir in "$specs_dir"/*; do
38 | if [[ -d "$dir" ]]; then
39 | local dirname=$(basename "$dir")
40 | if [[ "$dirname" =~ ^([0-9]{3})- ]]; then
41 | local number=${BASH_REMATCH[1]}
42 | number=$((10#$number))
43 | if [[ "$number" -gt "$highest" ]]; then
44 | highest=$number
45 | latest_feature=$dirname
46 | fi
47 | fi
48 | fi
49 | done
50 |
51 | if [[ -n "$latest_feature" ]]; then
52 | echo "$latest_feature"
53 | return
54 | fi
55 | fi
56 |
57 | echo "main" # Final fallback
58 | }
59 |
60 | # Check if we have git available
61 | has_git() {
62 | git rev-parse --show-toplevel >/dev/null 2>&1
63 | }
64 |
65 | check_feature_branch() {
66 | local branch="$1"
67 | local has_git_repo="$2"
68 |
69 | # For non-git repos, we can't enforce branch naming but still provide output
70 | if [[ "$has_git_repo" != "true" ]]; then
71 | echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2
72 | return 0
73 | fi
74 |
75 | if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
76 | echo "ERROR: Not on a feature branch. Current branch: $branch" >&2
77 | echo "Feature branches should be named like: 001-feature-name" >&2
78 | return 1
79 | fi
80 |
81 | return 0
82 | }
83 |
84 | get_feature_dir() { echo "$1/specs/$2"; }
85 |
86 | get_feature_paths() {
87 | local repo_root=$(get_repo_root)
88 | local current_branch=$(get_current_branch)
89 | local has_git_repo="false"
90 |
91 | if has_git; then
92 | has_git_repo="true"
93 | fi
94 |
95 | local feature_dir=$(get_feature_dir "$repo_root" "$current_branch")
96 |
97 | cat </dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; }
114 |
--------------------------------------------------------------------------------
/content/pages/120-value-variable-type.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 값 다루기
3 | ---
4 |
5 | ## 값과 리터럴
6 |
7 | 프로그래밍을 하며 가장 많이 하는 일은 값(value)을 다루는 것입니다.
8 |
9 | 프로그래밍 언어에서 값을 생성하는 가장 쉬운 방법은 리터럴(literal)을 사용하는 것입니다. 리터럴은 **값의 표기법**으로, 프로그래밍 언어마다 값을 표현하는 여러 가지 리터럴을 가지고 있습니다.
10 |
11 | ```js
12 | 1 // 정수 리터럴
13 | 2.5 // 부동 소수점 리터럴
14 | ;('hello') // 문자열 리터럴
15 | true // 진리값 리터럴
16 | ```
17 |
18 | 리터럴과 연산자(operator)를 이용해 REPL에서 간단한 계산을 할 수 있습니다.
19 |
20 | ```js
21 | 1 + 2 // 3
22 | 3 * 4 // 12
23 | 'hello' + 'world' // 'helloworld'
24 | true || false // true
25 | ```
26 |
27 | 여러 연산자에 대한 자세한 사용법은 이어지는 챕터에서 다룹니다.
28 |
29 | ## 변수 (Variable)
30 |
31 | 값을 한 번 생성한 뒤에 다시 쓰지 못한다면 아주 간단한 프로그램밖에 만들지 못할 것입니다. 그래서 프로그래밍 언어에는 대개 **값에 이름을 붙여서 다시 쓸 수 있게 만드는 기능**이 존재합니다. JavaScript에서는 여러 가지 방법을 통해 값에 이름을 붙일 수 있는데, 그 중에 두 가지[^1]를 여기서 다룹니다.
32 |
33 | `let`은 변수(variable)를 선언(declare)할 때 쓰는 키워드로, ES2015에서 도입되었습니다. 변수의 사용법은 다음과 같습니다.
34 |
35 | ```js
36 | let seven = 7
37 | ```
38 |
39 | 위에서는 `7`이라는 값에 `seven`이라는 이름을 붙이기 위해서 다음과 같이 선언과 동시에 대입(assign)을 했습니다. 물론 변수의 선언이 끝난 이후에 대입을 하거나, 이미 값이 대입되어 있는 변수에 다른 값을 대입할 수도 있습니다.
40 |
41 | ```js
42 | let eight
43 | eight = 8
44 | let seven = 7
45 | seven = 77
46 | seven = 777
47 | ```
48 |
49 | `const`는 재대입(reassign)이 불가능한 변수를 선언할 때 쓰는 키워드로, 역시 ES2015에 도입되었습니다.
50 |
51 | ```js
52 | const myConstant = 7
53 | ```
54 |
55 | `const`로 변수를 선언할 때는 반드시 선언 시에 값을 대입해주어야 합니다. 값 없이 선언만 하게 되면 에러가 발생합니다. 또한 추후에 다른 값을 대입할 수 없습니다.
56 |
57 | ```js
58 | const notAssigned // Uncaught SyntaxError: Missing initializer in const declaration
59 | ```
60 |
61 | ```js
62 | const assigned; = 1
63 | assigned = 2; // Uncaught TypeError: Assignment to constant variable.
64 | ```
65 |
66 | `let`과 `const` 모두 한꺼번에 여러 개의 변수를 선언하는 문법을 지원합니다.
67 |
68 | ```js
69 | let one = 1,
70 | two = 2,
71 | nothing
72 | const three = 3,
73 | four = 4
74 | ```
75 |
76 | `let`과 `const`로 선언한 이름은 다시 선언될 수 없습니다.
77 |
78 | ```js
79 | let seven = 7
80 | let seven = 77 // Uncaught SyntaxError: Identifier 'seven' has already been declared
81 | ```
82 |
83 | ## `let`과 `const` 중 무엇을 쓸 것인가?
84 |
85 | 항상 **let 보다 const**를 사용하는 것이 좋습니다. `let`을 사용하면 의도치 않게 다른 값이 대입되어 버리는 일이 생길 수 있기 때문입니다. 정말로 재대입이 필요한 경우에만 `let`을 사용하는 것이 좋은 습관입니다.
86 |
87 | ## 식별자
88 |
89 | 위에서 사용한 변수의 이름은 모두 **식별자(Identifier)**입니다. 프로그래밍 언어에서 식별자는 어떤 개체를 유일하게 식별하기 위해 사용됩니다. JavaScript 식별자는 아래와 같이 정해진 규칙에 따라 지어져야 합니다.
90 |
91 | - 숫자, 알파벳, 달러 문자($), 언더스코어(\_)가 포함될 수 있다.
92 | - 단, 숫자로 시작되어서는 안 된다.
93 | - 예약어는 식별자가 될 수 없다.
94 |
95 | ```js
96 | const foo; // O
97 | const _bar123; // O
98 | const $; // O - jQuery가 이 식별자를 사용합니다.
99 | const 7seven; // X
100 | const const; // X - 예약어는 식별자가 될 수 없습니다.
101 | ```
102 |
103 | 여기서 예약어(reserved word)란 JavaScript 언어의 기능을 위해 미리 예약해둔 단어들을 말합니다. JavaScript의 예약어 목록을 [이 링크](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words)에서 확인할 수 있습니다.
104 |
105 | `let 패스트 = '캠퍼스'`와 같이 한글도 식별자에 포함될 수 있지만, 좋은 습관은 아닙니다.
106 |
107 | ### Camel Case
108 |
109 | 식별자 이름을 지을 때 JavaScript에서 널리 사용되는 관례(convention)가 있는데, 이 관례를 Camel case라고 부릅니다. 식별자에 들어가는 각 단어의 첫 글자를 대문자로 써 주는 방식입니다.
110 |
111 | ```js
112 | // Camel case
113 | let fastCampus
114 | let fooBar
115 | ```
116 |
117 | 이와는 다르게 대문자를 사용하지 않고 단어 사이에 언더스코어(\_)를 사용해서 단어를 구분해주는 관례도 있는데, 이를 Snake case라고 부릅니다. 이 관례는 JavaScript에서는 잘 사용되지 않고, Python 등의 프로그래밍 언어에서 많이 사용됩니다.
118 |
119 | ```js
120 | // Snake case
121 | let fast_campus
122 | let foo_bar
123 | ```
124 |
125 | 이 밖에 식별자 이름을 지을 때 사용하는 다른 관례들도 있는데, 그에 대해서는 [객체](./180-object) 챕터에서 자세히 다룹니다.
126 |
127 | ## 타입
128 |
129 | JavaScript를 비롯한 대부분의 프로그래밍 언어는 여러 가지 종류의 값을 지원하는데, 이러한 **값의 종류**를 가지고 자료형(data type)이라고 부릅니다. 줄여서 **타입**이라고 부르기도 합니다.
130 |
131 | 값의 타입을 알아보기 위해 `typeof` 연산자를 사용할 수 있습니다.
132 |
133 | ```js
134 | typeof 1 // 'number'
135 | typeof 'hello' // 'string'
136 | ```
137 |
138 | 이어지는 챕터에서는 JavaScript가 지원하는 여러 가지 타입의 값의 사용법에 대해 다룰 것입니다.
139 |
140 | [^1]: 독자 중에 `var` 키워드에 대해 알고 있는 분도 계실 것입니다. `var`는 `let`과 비슷한 역할을 하지만 동작하는 방식과 제약 사항이 다릅니다. 자세한 차이점은 [값 더 알아보기](./220-value-in-depth) 챕터에서 다룹니다.
141 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/components/header.tsx:
--------------------------------------------------------------------------------
1 | import {Box, Flex, Link, StyledOcticon, Sticky} from '@primer/components'
2 | import {
3 | ChevronRightIcon,
4 | MarkGithubIcon,
5 | SearchIcon,
6 | ThreeBarsIcon,
7 | } from '@primer/octicons-react'
8 | import {Link as GatsbyLink} from 'gatsby'
9 | import React from 'react'
10 | import {ThemeContext} from 'styled-components'
11 | import primerNavItems from '../primer-nav.yml'
12 | import useSiteMetadata from '../use-site-metadata'
13 | import DarkButton from './dark-button'
14 | import MobileSearch from './mobile-search'
15 | import NavDrawer, {useNavDrawerState} from './nav-drawer'
16 | import NavDropdown, {NavDropdownItem} from './nav-dropdown'
17 | import Search from './search'
18 |
19 | export const HEADER_HEIGHT = 66
20 |
21 | function Header({isSearchEnabled = true}) {
22 | const theme = React.useContext(ThemeContext)
23 | const [isNavDrawerOpen, setIsNavDrawerOpen] = useNavDrawerState(
24 | theme.breakpoints[2],
25 | )
26 | const [isMobileSearchOpen, setIsMobileSearchOpen] = React.useState(false)
27 | const siteMetadata = useSiteMetadata()
28 | return (
29 |
30 |
37 |
38 | {siteMetadata.shortName ? (
39 |
40 | {siteMetadata.shortName}
41 |
42 | ) : null}
43 |
44 | {isSearchEnabled ? (
45 |
46 |
47 |
48 | ) : null}
49 |
50 |
51 |
52 |
53 |
54 |
55 | {isSearchEnabled ? (
56 | <>
57 | setIsMobileSearchOpen(true)}
61 | >
62 |
63 |
64 | setIsMobileSearchOpen(false)}
67 | />
68 | >
69 | ) : null}
70 | setIsNavDrawerOpen(true)}
74 | ml={3}
75 | >
76 |
77 |
78 | setIsNavDrawerOpen(false)}
81 | />
82 |
83 |
84 |
85 |
86 | )
87 | }
88 |
89 | function PrimerNavItems({items}) {
90 | return (
91 |
92 | {items.map((item, index) => {
93 | if (item.children) {
94 | return (
95 |
96 |
97 | {item.children.map((child) => (
98 |
99 | {child.title}
100 |
101 | ))}
102 |
103 |
104 | )
105 | }
106 |
107 | return (
108 |
115 | {item.title}
116 |
117 | )
118 | })}
119 |
120 | )
121 | }
122 |
123 | export default Header
124 |
--------------------------------------------------------------------------------
/content/pages/100-javascript.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: JavaScript 소개
3 | ---
4 |
5 | JavaScript는 웹의 초창기였던 1995년에 Netscape Navigator라는 웹 브라우저에 처음으로 탑재되어 세상에 공개됐습니다. JavaScript는 Java와 많은 부분에서 다르지만, 마케팅 상의 이유로 그 문법과 이름이 Java와 유사하게 만들어진 덕분에 아직도 Java와 JavaScript를 혼동하는 사람들이 많습니다. **"Java와 JavaScript 사이의 관계는 햄과 햄스터 사이의 관계와 같다"**와 같이 그 미묘한 관계를 풍자하는 많은 [농담](http://javascriptisnotjava.io/)들이 있습니다.
6 |
7 | ## 언어와 구동 환경
8 |
9 | JavaScript 언어 자체에는 다른 범용 프로그래밍 언어(general-purpose programming language)에 비해 적은 양의 기능(주로 **코드의 실행**과 관련된 것)을 포함하고 있습니다. 하지만 이 JavaScript를 구동할 수 있는 **구동 환경**에 여러 기능(주로 **입출력**과 관련된 것)이 포함되어 있어서, 우리는 이 기능을 이용해 쓸모있는 응용 프로그램을 만들 수 있게 됩니다. JavaScript 구동 환경에는 웹 브라우저, 웹 서버 (Node.js), 게임 엔진, 포토샵(!) 등 많은 프로그램들이 있습니다.
10 |
11 | ## ECMAScript와 브라우저 잔혹사
12 |
13 | 몇몇 유명한 프로그래밍 언어와 마찬가지로, JavaScript라는 언어에는 표준 명세(standard specification)라는 것이 존재합니다. 여러 브라우저 개발사에서 통일된 JavaScript 기능을 구현할 수 있도록, 언어의 문법과 기능을 세부적으로 정의해놓은 설계도라고 생각하면 됩니다. JavaScript의 표준 명세는 **ECMAScript**라는 이름을 갖고 있는데, Netscape에 탑재되었던 JavaScript 구현체(implementation)를 ECMA(European Computer Manufacturer’s Association)라는 단체에서 표준화한 것입니다. 이 표준은 1997년에 처음 제정되어 계속 발전하고 있는 중입니다.
14 |
15 | ### 브라우저 간 호환성
16 |
17 | 1999년에 ECMAScript 3판(줄여서 ES3)이 공개될 때까지만 해도 여러 웹 브라우저들의 독자 표준이 난립하는 상황이었습니다. 예를 들어, Internet Explorer에서는 잘 동작하는 기능이 Firefox에서는 동작하지 않는다거나, 그 반대와 같은 경우가 많았습니다. 그래서 이 시절에는 어느 브라우저를 사용하든 잘 동작하는 JavaScript 코드를 짜는 것, 즉 **브라우저 간 호환성(cross-browser compatibility)**를 확보하는 것이 웹 개발자들에게 큰 고민거리였습니다. (물론 "이 웹 사이트는 IE에서 잘 동작합니다"라는 문구를 웹 사이트에 집어넣고 다른 브라우저를 무시하는 경우도 많았습니다...)
18 |
19 | 이런 호환성 문제를 해소하기 위해, 한 편으로는 Dojo, Prototype, jQuery와 같이 개발자 대신 브라우저 간 호환성 문제를 해결해주는 **라이브러리**들이 개발되었고, 또 한 편으로는 각 브라우저 개발사들이 **ECMAScript 표준**을 따라 브라우저를 구현하기 시작했습니다.
20 |
21 | ES5가 공개된 2009년 경부터는 브라우저 간 호환성 문제가 조금씩 해결되기 시작했고, 그 전까지는 천덕꾸러기 취급을 받던 JavaScript에 여러 가지 기능이 추가되어 쓸만한 범용 프로그래밍 언어로서의 면모를 갖추기 시작했습니다. 현재 모든 주요 브라우저는 ES2015 이후의 대부분의 기능을 지원하며, 브라우저 간 호환성 문제는 크게 개선되었습니다.
22 |
23 | ### ES2015, 그 이후
24 |
25 | ES5의 다음 버전부터는 해당 버전이 공개된 연도를 버전 번호로 사용하고 있습니다. 즉, ES5의 다음 버전의 이름은 ES6가 아니라 **ES2015** 입니다. 다만 ES2015라는 이름이 확정되기 전까지는 ES5의 다음 버전이라는 의미에서 ES6라는 이름이 널리 사용되었고, 아직까지도 ES6라는 이름이 사용되는 경우가 있습니다. 하지만 정식 명칭은 ES2015라는 사실을 기억하세요.
26 |
27 | ES2015에서 엄청나게 많은 문법과 기능(클래스, 모듈, 분해대입, 템플릿 문자열, 블록 스코프, 반복자, 프록시 등등...)이 추가되고, Node.js 등 웹 브라우저 외에도 JavaScript를 구동할 수 있는 구동 환경의 종류가 많아지면서, 이제 JavaScript는 Python 등 다른 범용 프로그래밍 언어와 비교해도 전혀 뒤쳐지지 않는 범용 프로그래밍 언어가 되었습니다.
28 |
29 | ES2015부터는 매년 새로운 버전의 ECMAScript가 공개되고 있습니다. ES2022에는 클래스 필드와 `at()` 메소드가, ES2023에는 배열의 불변 메소드(`toSorted()`, `toReversed()` 등)가, ES2024에는 `Object.groupBy()`와 Set 메소드가 추가되었습니다. 최신 명세는 [이 곳](https://262.ecma-international.org/)에서, 브라우저 별 기능 개발 현황은 [이 곳](https://compat-table.github.io/compat-table/es2016plus/)에서 확인해볼 수 있습니다.
30 |
31 | ## 빠르게 발전하는 언어, 따라가는 개발자
32 |
33 | 이렇게 나날이 발전하는 JavaScript에 새롭게 추가된 우아한 기능을 이용해서 개발을 할 수 있다면 행복할 테지만, 한 가지 문제가 있습니다. 최신 버전의 JavaScript를 지원하지 않는 브라우저가 **항상 존재**한다는 점입니다. 구형 브라우저를 업데이트하지 않고 사용하는 사용자는 굉장히 많습니다. 최신 버전의 브라우저를 사용한다고 하더라도, 각 브라우저마다 업데이트 주기가 다르므로 브라우저마다 지원하는 JavaScript 기능이 조금씩 다릅니다.
34 |
35 | 이런 문제를 해결하기 위해, 현재 두 가지 도구를 사용하는데 바로 **트랜스파일러(transpiler)**와 **폴리필(polyfill)**입니다. 트랜스파일러와 폴리필이 없는 웹 개발을 상상하기 어려울 정도로, 이런 도구들이 널리 활용되고 있습니다.
36 |
37 | ### 트랜스파일러 (Transpiler)
38 |
39 | 트랜스파일러는 최신 버전 JavaScript의 **문법**을 **똑같이 동작하는 이전 버전 JavaScript의 문법**으로 바꾸어주는 도구입니다. 마법같은 도구죠! 이를 이용해 개발자는 최신 버전으로 코딩을 하고, 실제로 사용자에게 배포할 코드는 구형 브라우저에서도 잘 동작하도록 변환해줄 수 있습니다. 많이 사용되는 트랜스파일러에는 [Babel](https://babeljs.io/), [TypeScript](https://www.typescriptlang.org/) 같은 것들이 있습니다. 또한 [Vite](https://vite.dev/) 같은 빌드 도구는 개발 속도가 매우 빠르며, 트랜스파일과 번들링을 함께 처리해줍니다.
40 |
41 | ### 폴리필 (Polyfill)
42 |
43 | JavaScript를 실행하는 구동 환경에는 여러 가지 **문법과 기능**이 추가됩니다. 이를 구형 환경에서도 사용할 수 있도록 똑같이 구현해놓은 라이브러리를 폴리필(polyfill) 혹은 심(shim)이라고 부릅니다. 대부분의 주요 브라우저가 ES2015 이후의 기능을 지원하므로, 폴리필의 필요성이 많이 줄어들었습니다. 본인이 개발하는 프로그램이 어느 정도까지의 구형 환경을 지원해야 하는지를 결정한 후, 필요한 경우에만 적절한 폴리필을 프로젝트에 추가하세요.
44 |
45 | ### 이용중인 환경의 JavaScript 기능 지원 여부 확인하기
46 |
47 | 다만 최신 버전에 포함된 문법과 기능을 전부 이전 버전에서 구현할 수 있는 것은 아니고, 또 트랜스파일러마다 지원하는 기능의 종류가 달라서, 항상 모든 최신 기능을 사용할 수 있는 것은 아닙니다. 최신 기능을 사용하고 싶다면, 본인이 사용중인 트랜스파일러가 해당 기능을 지원하도록 설정되어 있는지, 프로젝트에 적절한 폴리필이 포함되어 있는지 확인하세요. JavaScript 최신 버전에 관심이 있다면, 페이스북이나 트위터 등의 매체를 통해 JavaScript 최신 버전에 대한 뉴스를 수집하고,
48 | [브라우저별 지원 기능 일람](https://compat-table.github.io/compat-table/es6/)을 제공하는 웹사이트를 주기적으로 확인해보세요.
49 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/components/nav-drawer.js:
--------------------------------------------------------------------------------
1 | import {BorderBox, Flex, Link, Text} from '@primer/components'
2 | import {ChevronDownIcon, ChevronUpIcon, XIcon} from '@primer/octicons-react'
3 | import {Link as GatsbyLink} from 'gatsby'
4 | import debounce from 'lodash.debounce'
5 | import React from 'react'
6 | import navItems from '../nav.yml'
7 | import primerNavItems from '../primer-nav.yml'
8 | import useSiteMetadata from '../use-site-metadata'
9 | import DarkButton from './dark-button'
10 | import Details from './details'
11 | import Drawer from './drawer'
12 | import NavItems from './nav-items'
13 |
14 | export function useNavDrawerState(breakpoint) {
15 | // Handle string values from themes with units at the end
16 | if (typeof breakpoint === 'string') {
17 | breakpoint = parseInt(breakpoint, 10)
18 | }
19 | const [isOpen, setOpen] = React.useState(false)
20 |
21 | const onResize = React.useCallback(() => {
22 | if (window.innerWidth >= breakpoint) {
23 | setOpen(false)
24 | }
25 | }, [setOpen])
26 |
27 | const debouncedOnResize = React.useCallback(debounce(onResize, 250), [
28 | onResize,
29 | ])
30 |
31 | React.useEffect(() => {
32 | if (isOpen) {
33 | window.addEventListener('resize', debouncedOnResize)
34 | return () => {
35 | // cancel any debounced invocation of the resize handler
36 | debouncedOnResize.cancel()
37 | window.removeEventListener('resize', debouncedOnResize)
38 | }
39 | }
40 | }, [isOpen, debouncedOnResize])
41 |
42 | return [isOpen, setOpen]
43 | }
44 |
45 | function NavDrawer({isOpen, onDismiss}) {
46 | const siteMetadata = useSiteMetadata()
47 | return (
48 |
49 |
55 |
56 |
57 |
58 |
59 |
60 | {navItems.length > 0 ? (
61 |
67 |
76 | {siteMetadata.title}
77 |
78 |
79 |
80 | ) : null}
81 |
82 |
83 | )
84 | }
85 |
86 | function PrimerNavItems({items}) {
87 | return items.map((item, index) => {
88 | return (
89 |
97 | {item.children ? (
98 |
99 | {({open, toggle}) => (
100 | <>
101 |
102 |
103 | {item.title}
104 | {open ? : }
105 |
106 |
107 |
108 | {item.children.map((child) => (
109 |
117 | {child.title}
118 |
119 | ))}
120 |
121 | >
122 | )}
123 |
124 | ) : (
125 |
126 | {item.title}
127 |
128 | )}
129 |
130 | )
131 | })
132 | }
133 |
134 | export default NavDrawer
135 |
--------------------------------------------------------------------------------
/.specify/templates/spec-template.md:
--------------------------------------------------------------------------------
1 | # Feature Specification: [FEATURE NAME]
2 |
3 | **Feature Branch**: `[###-feature-name]`
4 | **Created**: [DATE]
5 | **Status**: Draft
6 | **Input**: User description: "$ARGUMENTS"
7 |
8 | ## Execution Flow (main)
9 | ```
10 | 1. Parse user description from Input
11 | → If empty: ERROR "No feature description provided"
12 | 2. Extract key concepts from description
13 | → Identify: actors, actions, data, constraints
14 | 3. For each unclear aspect:
15 | → Mark with [NEEDS CLARIFICATION: specific question]
16 | 4. Fill User Scenarios & Testing section
17 | → If no clear user flow: ERROR "Cannot determine user scenarios"
18 | 5. Generate Functional Requirements
19 | → Each requirement must be testable
20 | → Mark ambiguous requirements
21 | 6. Identify Key Entities (if data involved)
22 | 7. Run Review Checklist
23 | → If any [NEEDS CLARIFICATION]: WARN "Spec has uncertainties"
24 | → If implementation details found: ERROR "Remove tech details"
25 | 8. Return: SUCCESS (spec ready for planning)
26 | ```
27 |
28 | ---
29 |
30 | ## ⚡ Quick Guidelines
31 | - ✅ Focus on WHAT users need and WHY
32 | - ❌ Avoid HOW to implement (no tech stack, APIs, code structure)
33 | - 👥 Written for business stakeholders, not developers
34 |
35 | ### Section Requirements
36 | - **Mandatory sections**: Must be completed for every feature
37 | - **Optional sections**: Include only when relevant to the feature
38 | - When a section doesn't apply, remove it entirely (don't leave as "N/A")
39 |
40 | ### For AI Generation
41 | When creating this spec from a user prompt:
42 | 1. **Mark all ambiguities**: Use [NEEDS CLARIFICATION: specific question] for any assumption you'd need to make
43 | 2. **Don't guess**: If the prompt doesn't specify something (e.g., "login system" without auth method), mark it
44 | 3. **Think like a tester**: Every vague requirement should fail the "testable and unambiguous" checklist item
45 | 4. **Common underspecified areas**:
46 | - User types and permissions
47 | - Data retention/deletion policies
48 | - Performance targets and scale
49 | - Error handling behaviors
50 | - Integration requirements
51 | - Security/compliance needs
52 |
53 | ---
54 |
55 | ## User Scenarios & Testing *(mandatory)*
56 |
57 | ### Primary User Story
58 | [Describe the main user journey in plain language]
59 |
60 | ### Acceptance Scenarios
61 | 1. **Given** [initial state], **When** [action], **Then** [expected outcome]
62 | 2. **Given** [initial state], **When** [action], **Then** [expected outcome]
63 |
64 | ### Edge Cases
65 | - What happens when [boundary condition]?
66 | - How does system handle [error scenario]?
67 |
68 | ## Requirements *(mandatory)*
69 |
70 | ### Functional Requirements
71 | - **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"]
72 | - **FR-002**: System MUST [specific capability, e.g., "validate email addresses"]
73 | - **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"]
74 | - **FR-004**: System MUST [data requirement, e.g., "persist user preferences"]
75 | - **FR-005**: System MUST [behavior, e.g., "log all security events"]
76 |
77 | *Example of marking unclear requirements:*
78 | - **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?]
79 | - **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified]
80 |
81 | ### Key Entities *(include if feature involves data)*
82 | - **[Entity 1]**: [What it represents, key attributes without implementation]
83 | - **[Entity 2]**: [What it represents, relationships to other entities]
84 |
85 | ---
86 |
87 | ## Review & Acceptance Checklist
88 | *GATE: Automated checks run during main() execution*
89 |
90 | ### Content Quality
91 | - [ ] No implementation details (languages, frameworks, APIs)
92 | - [ ] Focused on user value and business needs
93 | - [ ] Written for non-technical stakeholders
94 | - [ ] All mandatory sections completed
95 |
96 | ### Requirement Completeness
97 | - [ ] No [NEEDS CLARIFICATION] markers remain
98 | - [ ] Requirements are testable and unambiguous
99 | - [ ] Success criteria are measurable
100 | - [ ] Scope is clearly bounded
101 | - [ ] Dependencies and assumptions identified
102 |
103 | ---
104 |
105 | ## Execution Status
106 | *Updated by main() during processing*
107 |
108 | - [ ] User description parsed
109 | - [ ] Key concepts extracted
110 | - [ ] Ambiguities marked
111 | - [ ] User scenarios defined
112 | - [ ] Requirements generated
113 | - [ ] Entities identified
114 | - [ ] Review checklist passed
115 |
116 | ---
117 |
--------------------------------------------------------------------------------
/content/pages/293-module.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 모듈
3 | ---
4 |
5 | 최근들어 프론트엔드 프로젝트의 규모가 커짐에 따라, JavaScript 코드를 **여러 파일과 폴더에 나누어 작성**하고 **서로가 서로를 효율적으로 불러올 수 있도록** 해주는 시스템의 필요성이 절실해졌습니다. 이에 따라 모듈 시스템이 ES2015에 추가되었습니다.
6 |
7 | ES2015 모듈(ESM)은 이제 **모든 주요 브라우저와 Node.js에서 표준으로 지원**됩니다. `script` 태그에 `type="module"` 어트리뷰트를 추가해주면, 이 파일은 모듈로서 동작합니다. 파일 확장자로는 `.js` 또는 `.mjs`가 사용됩니다.
8 |
9 | ```html
10 |
11 | ```
12 |
13 | Node.js에서는 `package.json`에 `"type": "module"`을 추가하거나 `.mjs` 확장자를 사용하여 ES 모듈을 활성화할 수 있습니다:
14 |
15 | ```json
16 | {
17 | "type": "module"
18 | }
19 | ```
20 |
21 | 현재는 대부분의 프로젝트에서 **ES 모듈이 표준**으로 자리 잡았으며, Vite, Webpack 등의 빌드 도구를 통해 개발 환경에서 효율적으로 모듈을 관리하고 있습니다.
22 |
23 | ## 모듈이란?
24 |
25 | ES2015 모듈은 기본적으로 JavaScript 코드를 담고 있는 **파일**입니다. 다만 일반적인 JavaScript 파일과는 다른 여러가지 차이점을 갖고 있습니다.
26 |
27 | - `import` 혹은 `export` 구문을 사용할 수 있습니다.
28 | - 별다른 처리를 해주지 않아도 엄격 모드(strict mode)로 동작합니다.
29 | - 모듈의 가장 바깥쪽에서 선언된 이름은 전역 스코프가 아니라 **모듈 스코프**에서 선언됩니다.
30 |
31 | ## 모듈 스코프
32 |
33 | 모듈 내부의 가장 바깥 스코프에서 이름을 선언하더라도, 전역 스코프가 아니라 **모듈 스코프**에서 선언됩니다. 모듈 스코프에 선언된 이름은 (export 해주지 않는다면) 해당 모듈 내부에서만 접근할 수 있습니다.
34 |
35 | ```js
36 | // variables.js
37 |
38 | const foo = 'bar'
39 |
40 | // 이 파일이 모듈로서 사용되고 있다면, `undefined`가 출력됩니다.
41 | console.log(window.foo)
42 | ```
43 |
44 | 따라서 여러 모듈의 가장 바깥쪽에서 같은 이름으로 변수, 함수, 클래스를 선언하더라도, 서로 다른 스코프에서 선언되기 때문에 이름의 충돌이 생길 일이 없습니다.
45 |
46 | ## export & import
47 |
48 | 모듈 스코프에서 정의된 이름은 `export` 구문을 통해 다른 파일에서 사용할 수 있습니다. 이를 **'이름이 지정된 export'**라는 뜻에서 **named export**라 부릅니다.
49 |
50 | ```js
51 | // variables.js
52 | const foo = 'bar'
53 | const spam = 'eggs'
54 |
55 | // foo, spam을 다른 파일에서 사용할 수 있도록 export 해주었습니다.
56 | export {foo, spam}
57 | ```
58 |
59 | 위에서 `export`된 이름을 다른 파일에서 `import` 구문을 통해 가져온 뒤 사용할 수 있습니다.
60 |
61 | ```js
62 | // main.js
63 |
64 | // variables 모듈에 선언된 이름을 사용하기 위해 import 해주었습니다.
65 | import {foo, spam} from './variables.js'
66 |
67 | console.log(foo)
68 | console.log(spam)
69 | ```
70 |
71 | 단순히 값을 저장하고 있는 변수뿐만 아니라, 함수나 클래스도 `export`를 통해 여러 모듈에서 재사용할 수 있습니다.
72 |
73 | ```js
74 | // functions.js
75 |
76 | function add(x, y) {
77 | return x + y
78 | }
79 |
80 | class Person {
81 | // ...
82 | }
83 |
84 | export {add, Person}
85 | ```
86 |
87 | 다른 모듈에 있는 이름을 사용하려면, 반드시 해당 모듈에서 **이름**을 `export` 해주어야 합니다. `export` 해주지 않은 이름을 다른 모듈에서 `import` 하면 의도대로 동작하지 않습니다. (모듈 실행 환경에 따라 에러가 날 수도 있고, 이름에 `undefined`가 들어있을 수도 있습니다.)
88 |
89 | ```js
90 | // variables.js
91 |
92 | const foo = 'bar'
93 | ```
94 |
95 | ```js
96 | // main.js
97 | import {foo} from './variables.js'
98 |
99 | console.log(foo) // 에러가 나거나, `undefined`가 출력됨
100 | ```
101 |
102 | ## 선언과 동시에 export 하기
103 |
104 | 이름을 선언하는 구문 앞에 `export`를 붙여주면, 선언과 `export`를 한꺼번에 할 수 있습니다.
105 |
106 | ```js
107 | // common.js
108 | export const foo = 'bar'
109 | export const spam = 'eggs'
110 | export function add(x, y) {
111 | return x + y
112 | }
113 | export class Person {
114 | // ...
115 | }
116 | ```
117 |
118 | ## default export
119 |
120 | `export default` 구문을 통해, 모듈을 대표하는 하나의 **값**을 지정하고 그 값을 다른 모듈에서 편하게 불러와서 사용할 수 있습니다. 이렇게 사용하는 값을 **default export**라고 부릅니다.
121 |
122 | ```js
123 | // foo.js
124 |
125 | export default 'bar'
126 | ```
127 |
128 | `import` 구문에서 이름을 적어주는 부분에 중괄호를 생략하면, 모듈의 default export를 가져옵니다.
129 |
130 | ```js
131 | // main.js
132 |
133 | import foo from './foo.js'
134 |
135 | console.log(foo) // bar
136 | ```
137 |
138 | `export default` 뒤에는 임의의 표현식이 올 수 있습니다. 즉, 함수 표현식이나 클래스 표현식도 올 수 있습니다.
139 |
140 | ```js
141 | // add.js
142 |
143 | export default function (x, y) {
144 | return x + y
145 | }
146 | ```
147 |
148 | ```js
149 | import add from './add.js'
150 |
151 | console.log(add(1, 2)) // 3
152 | ```
153 |
154 | `import` 구문에서 default export와 일반적인 export를 동시에 가져올 수 있습니다.
155 |
156 | ```js
157 | // `React`라는 이름의 default export와,
158 | // Component, Fragment라는 일반적인 export를 동시에 가져오기
159 | import React, {Component, Fragment} from 'react'
160 | ```
161 |
162 | ## 다른 이름으로 export & import 하기
163 |
164 | `export` 혹은 `import` 하는 이름의 뒤에 `as`를 붙여서, 다른 이름이 대신 사용되게 할 수 있습니다.
165 |
166 | ```js
167 | const foo = 'bar'
168 |
169 | export {foo as FOO} // FOO 라는 이름으로 export 됩니다.
170 | ```
171 |
172 | ```js
173 | import {Component as Comp} from 'react' // Comp라는 이름으로 import 됩니다.
174 | ```
175 |
176 | ## 모듈 사용 시 주의할 점
177 |
178 | 이제까지 모듈 시스템의 문법을 배워봤습니다. 여기서 주의할 점이 한 가지 있습니다. `import` 구문과 `export` 구문은 모듈 간 의존 관계를 나타내는 것일 뿐, 코드를 실행시키라는 명령이 아니라는 것입니다.
179 |
180 | - 같은 모듈을 여러 다른 모듈에서 불러와도, 모듈 내부의 코드는 단 한 번만 실행됩니다.
181 | - `import` 구문과 `export` 구문은 모듈의 가장 바깥쪽 스코프에서만 사용할 수 있습니다.
182 | - ECMAScript 공식 명세에는 모듈을 불러오는 방법에 대한 내용이 포함되어있지 않고, 이와 관련된 내용을 전적으로 모듈 구현체에 맡겼습니다. 따라서, 모듈을 어떤 환경에서 실행하느냐에 따라서 구체적인 로딩 순서나 동작방식이 조금씩 달라질 수 있습니다.
183 |
184 | ## ES2015 이전의 모듈들
185 |
186 | 사실 ES2015가 JavaScript 생태계에서 사용된 첫 모듈 시스템은 아닙니다. ES2015 모듈 이전에 CommonJS, AMD 등의 모듈 시스템이 있었고, 이 모듈 시스템들도 실제로 널리 사용되었던 시기가 있습니다. 다른 모듈 시스템의 특징과 역사가 궁금하시다면 [이 글](https://d2.naver.com/helloworld/12864)을 읽어보세요.
187 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/components/mobile-search.js:
--------------------------------------------------------------------------------
1 | import {Absolute, Fixed, Flex} from '@primer/components'
2 | import {XIcon} from '@primer/octicons-react'
3 | import Downshift from 'downshift'
4 | import {AnimatePresence, motion} from 'framer-motion'
5 | import {navigate} from 'gatsby'
6 | import React from 'react'
7 | import {FocusOn} from 'react-focus-on'
8 | import useSearch from '../use-search'
9 | import DarkButton from './dark-button'
10 | import DarkTextInput from './dark-text-input'
11 | import SearchResults from './search-results'
12 |
13 | function stateReducer(state, changes) {
14 | switch (changes.type) {
15 | case Downshift.stateChangeTypes.changeInput:
16 | if (!changes.inputValue) {
17 | // Close the menu if the input is empty.
18 | return {...changes, isOpen: false}
19 | }
20 | return changes
21 | case Downshift.stateChangeTypes.blurInput:
22 | // Don't let a blur event change the state of `inputValue` or `isOpen`.
23 | return {...changes, inputValue: state.inputValue, isOpen: state.isOpen}
24 | default:
25 | return changes
26 | }
27 | }
28 |
29 | function MobileSearch({isOpen, onDismiss}) {
30 | const [query, setQuery] = React.useState('')
31 | const results = useSearch(query)
32 |
33 | function handleDismiss() {
34 | setQuery('')
35 | onDismiss()
36 | }
37 |
38 | return (
39 |
40 | {isOpen ? (
41 | handleDismiss()}>
42 |
43 |
56 | setQuery(inputValue)}
59 | selectedItem={null}
60 | onSelect={(item) => {
61 | if (item) {
62 | navigate(item.path)
63 | handleDismiss()
64 | }
65 | }}
66 | itemToString={(item) => (item ? item.title : '')}
67 | stateReducer={stateReducer}
68 | >
69 | {({
70 | getInputProps,
71 | getItemProps,
72 | getMenuProps,
73 | getRootProps,
74 | isOpen: isMenuOpen,
75 | highlightedIndex,
76 | }) => (
77 |
83 |
84 |
91 |
97 |
98 |
103 |
104 |
105 |
106 | {isMenuOpen ? (
107 |
119 |
124 |
125 | ) : null}
126 |
127 | )}
128 |
129 |
130 |
131 | ) : null}
132 |
133 | )
134 | }
135 |
136 | export default MobileSearch
137 |
--------------------------------------------------------------------------------
/.specify/templates/tasks-template.md:
--------------------------------------------------------------------------------
1 | # Tasks: [FEATURE NAME]
2 |
3 | **Input**: Design documents from `/specs/[###-feature-name]/`
4 | **Prerequisites**: plan.md (required), research.md, data-model.md, contracts/
5 |
6 | ## Execution Flow (main)
7 | ```
8 | 1. Load plan.md from feature directory
9 | → If not found: ERROR "No implementation plan found"
10 | → Extract: tech stack, libraries, structure
11 | 2. Load optional design documents:
12 | → data-model.md: Extract entities → model tasks
13 | → contracts/: Each file → contract test task
14 | → research.md: Extract decisions → setup tasks
15 | 3. Generate tasks by category:
16 | → Setup: project init, dependencies, linting
17 | → Tests: contract tests, integration tests
18 | → Core: models, services, CLI commands
19 | → Integration: DB, middleware, logging
20 | → Polish: unit tests, performance, docs
21 | 4. Apply task rules:
22 | → Different files = mark [P] for parallel
23 | → Same file = sequential (no [P])
24 | → Tests before implementation (TDD)
25 | 5. Number tasks sequentially (T001, T002...)
26 | 6. Generate dependency graph
27 | 7. Create parallel execution examples
28 | 8. Validate task completeness:
29 | → All contracts have tests?
30 | → All entities have models?
31 | → All endpoints implemented?
32 | 9. Return: SUCCESS (tasks ready for execution)
33 | ```
34 |
35 | ## Format: `[ID] [P?] Description`
36 | - **[P]**: Can run in parallel (different files, no dependencies)
37 | - Include exact file paths in descriptions
38 |
39 | ## Path Conventions
40 | - **Single project**: `src/`, `tests/` at repository root
41 | - **Web app**: `backend/src/`, `frontend/src/`
42 | - **Mobile**: `api/src/`, `ios/src/` or `android/src/`
43 | - Paths shown below assume single project - adjust based on plan.md structure
44 |
45 | ## Phase 3.1: Setup
46 | - [ ] T001 Create project structure per implementation plan
47 | - [ ] T002 Initialize [language] project with [framework] dependencies
48 | - [ ] T003 [P] Configure linting and formatting tools
49 |
50 | ## Phase 3.2: Tests First (TDD) ⚠️ MUST COMPLETE BEFORE 3.3
51 | **CRITICAL: These tests MUST be written and MUST FAIL before ANY implementation**
52 | - [ ] T004 [P] Contract test POST /api/users in tests/contract/test_users_post.py
53 | - [ ] T005 [P] Contract test GET /api/users/{id} in tests/contract/test_users_get.py
54 | - [ ] T006 [P] Integration test user registration in tests/integration/test_registration.py
55 | - [ ] T007 [P] Integration test auth flow in tests/integration/test_auth.py
56 |
57 | ## Phase 3.3: Core Implementation (ONLY after tests are failing)
58 | - [ ] T008 [P] User model in src/models/user.py
59 | - [ ] T009 [P] UserService CRUD in src/services/user_service.py
60 | - [ ] T010 [P] CLI --create-user in src/cli/user_commands.py
61 | - [ ] T011 POST /api/users endpoint
62 | - [ ] T012 GET /api/users/{id} endpoint
63 | - [ ] T013 Input validation
64 | - [ ] T014 Error handling and logging
65 |
66 | ## Phase 3.4: Integration
67 | - [ ] T015 Connect UserService to DB
68 | - [ ] T016 Auth middleware
69 | - [ ] T017 Request/response logging
70 | - [ ] T018 CORS and security headers
71 |
72 | ## Phase 3.5: Polish
73 | - [ ] T019 [P] Unit tests for validation in tests/unit/test_validation.py
74 | - [ ] T020 Performance tests (<200ms)
75 | - [ ] T021 [P] Update docs/api.md
76 | - [ ] T022 Remove duplication
77 | - [ ] T023 Run manual-testing.md
78 |
79 | ## Dependencies
80 | - Tests (T004-T007) before implementation (T008-T014)
81 | - T008 blocks T009, T015
82 | - T016 blocks T018
83 | - Implementation before polish (T019-T023)
84 |
85 | ## Parallel Example
86 | ```
87 | # Launch T004-T007 together:
88 | Task: "Contract test POST /api/users in tests/contract/test_users_post.py"
89 | Task: "Contract test GET /api/users/{id} in tests/contract/test_users_get.py"
90 | Task: "Integration test registration in tests/integration/test_registration.py"
91 | Task: "Integration test auth in tests/integration/test_auth.py"
92 | ```
93 |
94 | ## Notes
95 | - [P] tasks = different files, no dependencies
96 | - Verify tests fail before implementing
97 | - Commit after each task
98 | - Avoid: vague tasks, same file conflicts
99 |
100 | ## Task Generation Rules
101 | *Applied during main() execution*
102 |
103 | 1. **From Contracts**:
104 | - Each contract file → contract test task [P]
105 | - Each endpoint → implementation task
106 |
107 | 2. **From Data Model**:
108 | - Each entity → model creation task [P]
109 | - Relationships → service layer tasks
110 |
111 | 3. **From User Stories**:
112 | - Each story → integration test [P]
113 | - Quickstart scenarios → validation tasks
114 |
115 | 4. **Ordering**:
116 | - Setup → Tests → Models → Services → Endpoints → Polish
117 | - Dependencies block parallel execution
118 |
119 | ## Validation Checklist
120 | *GATE: Checked by main() before returning*
121 |
122 | - [ ] All contracts have corresponding tests
123 | - [ ] All entities have model tasks
124 | - [ ] All tests come before implementation
125 | - [ ] Parallel tasks truly independent
126 | - [ ] Each task specifies exact file path
127 | - [ ] No task modifies same file as another [P] task
--------------------------------------------------------------------------------
/.claude/commands/constitution.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync.
3 | ---
4 |
5 | The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
6 |
7 | User input:
8 |
9 | $ARGUMENTS
10 |
11 | You are updating the project constitution at `.specify/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values, (b) fill the template precisely, and (c) propagate any amendments across dependent artifacts.
12 |
13 | Follow this execution flow:
14 |
15 | 1. Load the existing constitution template at `.specify/memory/constitution.md`.
16 | - Identify every placeholder token of the form `[ALL_CAPS_IDENTIFIER]`.
17 | **IMPORTANT**: The user might require less or more principles than the ones used in the template. If a number is specified, respect that - follow the general template. You will update the doc accordingly.
18 |
19 | 2. Collect/derive values for placeholders:
20 | - If user input (conversation) supplies a value, use it.
21 | - Otherwise infer from existing repo context (README, docs, prior constitution versions if embedded).
22 | - For governance dates: `RATIFICATION_DATE` is the original adoption date (if unknown ask or mark TODO), `LAST_AMENDED_DATE` is today if changes are made, otherwise keep previous.
23 | - `CONSTITUTION_VERSION` must increment according to semantic versioning rules:
24 | * MAJOR: Backward incompatible governance/principle removals or redefinitions.
25 | * MINOR: New principle/section added or materially expanded guidance.
26 | * PATCH: Clarifications, wording, typo fixes, non-semantic refinements.
27 | - If version bump type ambiguous, propose reasoning before finalizing.
28 |
29 | 3. Draft the updated constitution content:
30 | - Replace every placeholder with concrete text (no bracketed tokens left except intentionally retained template slots that the project has chosen not to define yet—explicitly justify any left).
31 | - Preserve heading hierarchy and comments can be removed once replaced unless they still add clarifying guidance.
32 | - Ensure each Principle section: succinct name line, paragraph (or bullet list) capturing non‑negotiable rules, explicit rationale if not obvious.
33 | - Ensure Governance section lists amendment procedure, versioning policy, and compliance review expectations.
34 |
35 | 4. Consistency propagation checklist (convert prior checklist into active validations):
36 | - Read `.specify/templates/plan-template.md` and ensure any "Constitution Check" or rules align with updated principles.
37 | - Read `.specify/templates/spec-template.md` for scope/requirements alignment—update if constitution adds/removes mandatory sections or constraints.
38 | - Read `.specify/templates/tasks-template.md` and ensure task categorization reflects new or removed principle-driven task types (e.g., observability, versioning, testing discipline).
39 | - Read each command file in `.specify/templates/commands/*.md` (including this one) to verify no outdated references (agent-specific names like CLAUDE only) remain when generic guidance is required.
40 | - Read any runtime guidance docs (e.g., `README.md`, `docs/quickstart.md`, or agent-specific guidance files if present). Update references to principles changed.
41 |
42 | 5. Produce a Sync Impact Report (prepend as an HTML comment at top of the constitution file after update):
43 | - Version change: old → new
44 | - List of modified principles (old title → new title if renamed)
45 | - Added sections
46 | - Removed sections
47 | - Templates requiring updates (✅ updated / ⚠ pending) with file paths
48 | - Follow-up TODOs if any placeholders intentionally deferred.
49 |
50 | 6. Validation before final output:
51 | - No remaining unexplained bracket tokens.
52 | - Version line matches report.
53 | - Dates ISO format YYYY-MM-DD.
54 | - Principles are declarative, testable, and free of vague language ("should" → replace with MUST/SHOULD rationale where appropriate).
55 |
56 | 7. Write the completed constitution back to `.specify/memory/constitution.md` (overwrite).
57 |
58 | 8. Output a final summary to the user with:
59 | - New version and bump rationale.
60 | - Any files flagged for manual follow-up.
61 | - Suggested commit message (e.g., `docs: amend constitution to vX.Y.Z (principle additions + governance update)`).
62 |
63 | Formatting & Style Requirements:
64 | - Use Markdown headings exactly as in the template (do not demote/promote levels).
65 | - Wrap long rationale lines to keep readability (<100 chars ideally) but do not hard enforce with awkward breaks.
66 | - Keep a single blank line between sections.
67 | - Avoid trailing whitespace.
68 |
69 | If the user supplies partial updates (e.g., only one principle revision), still perform validation and version decision steps.
70 |
71 | If critical info missing (e.g., ratification date truly unknown), insert `TODO(): explanation` and include in the Sync Impact Report under deferred items.
72 |
73 | Do not create a new template; always operate on the existing `.specify/memory/constitution.md` file.
74 |
--------------------------------------------------------------------------------
/src/@primer/gatsby-theme-doctocat/components/layout.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BorderBox,
3 | Box,
4 | Details,
5 | Flex,
6 | Grid,
7 | Heading,
8 | Position,
9 | StyledOcticon,
10 | Text,
11 | } from '@primer/components'
12 | import {ChevronDownIcon, ChevronUpIcon} from '@primer/octicons-react'
13 | import React from 'react'
14 | import Head from './head'
15 | import Header, {HEADER_HEIGHT} from './header'
16 | import PageFooter from './page-footer'
17 | import Sidebar from './sidebar'
18 | import SourceLink from './source-link'
19 | import StatusLabel from './status-label'
20 | import TableOfContents from './table-of-contents'
21 |
22 | function Layout({children, pageContext}) {
23 | let {
24 | title,
25 | description,
26 | status,
27 | source,
28 | additionalContributors,
29 | } = pageContext.frontmatter
30 |
31 | if (!additionalContributors) {
32 | additionalContributors = []
33 | }
34 |
35 | return (
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
58 |
65 | {title}
66 |
67 | {pageContext.tableOfContents.items ? (
68 |
75 |
76 | Table of contents
77 |
78 |
79 |
80 | ) : null}
81 |
82 | {status || source ? (
83 |
84 | {status ? : null}
85 |
86 | {source ? : null}
87 |
88 | ) : null}
89 | {pageContext.tableOfContents.items ? (
90 |
91 |
92 | {({open}) => (
93 | <>
94 |
95 |
100 | Table of contents
101 | {open ? (
102 |
107 | ) : (
108 |
113 | )}
114 |
115 |
116 |
123 |
126 |
127 | >
128 | )}
129 |
130 |
131 | ) : null}
132 | {children}
133 | ({login})),
137 | )}
138 | />
139 |
140 |
141 |
142 |
143 | )
144 | }
145 |
146 | export default Layout
147 |
--------------------------------------------------------------------------------
/content/pages/282-data-structures.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 큐, 스택, 트리
3 | ---
4 |
5 | import QueueSVG from '../keyshape/QueueSVG.tsx'
6 | import StackSVG from '../keyshape/StackSVG.tsx'
7 | import TreeSVG from '../keyshape/TreeSVG.tsx'
8 |
9 | 어떤 데이터의 구체적인 구현 방식은 생략한 채, **데이터의 추상적 형태**와 **그 데이터를 다루는 방법**만을 정해놓은 것을 가지고 **ADT(Abstract Data Type)** 혹은 **추상 자료형**이라고 합니다. 이 챕터에서는 널리 사용되는 ADT인 큐, 스택, 트리에 대해 배웁니다.
10 |
11 | ## 큐 (Queue)
12 |
13 | 큐(queue)는 다음과 같은 성질을 갖는 자료형입니다.
14 |
15 | - 데이터를 집어넣을 수 있는 선형(linear) 자료형입니다.
16 | - **먼저 집어넣은 데이터가 먼저 나옵니다.** 이 특징을 줄여서 FIFO(First In First Out)라고 부릅니다.
17 | - 데이터를 집어넣는 enqueue, 데이터를 추출하는 dequeue 등의 작업을 할 수 있습니다.
18 |
19 |
20 |
21 | JavaScript에서는 배열을 이용해서 간단하게 큐를 구현할 수 있습니다.
22 |
23 | ```js
24 | class Queue {
25 | constructor() {
26 | this._arr = []
27 | }
28 | enqueue(item) {
29 | this._arr.push(item)
30 | }
31 | dequeue() {
32 | return this._arr.shift()
33 | }
34 | }
35 |
36 | const queue = new Queue()
37 | queue.enqueue(1)
38 | queue.enqueue(2)
39 | queue.enqueue(3)
40 | queue.dequeue() // 1
41 | ```
42 |
43 | 큐는 **순서대로 처리해야 하는 작업을 임시로 저장해두는 버퍼(buffer)**로서 많이 사용됩니다.
44 |
45 | ## 스택 (Stack)
46 |
47 | 스택(stack) 다음과 같은 성질을 갖는 자료형입니다.
48 |
49 | - 데이터를 집어넣을 수 있는 선형(linear) 자료형입니다.
50 | - **나중에 집어넣은 데이터가 먼저 나옵니다.** 이 특징을 줄여서 LIFO(Last In First Out)라고 부릅니다.
51 | - 데이터를 집어넣는 push, 데이터를 추출하는 pop, 맨 나중에 집어넣은 데이터를 확인하는 peek 등의 작업을 할 수 있습니다.
52 |
53 |
54 |
55 | JavaScript에서는 배열을 이용해서 간단하게 스택을 구현할 수 있습니다.
56 |
57 | ```js
58 | class Stack {
59 | constructor() {
60 | this._arr = []
61 | }
62 | push(item) {
63 | this._arr.push(item)
64 | }
65 | pop() {
66 | return this._arr.pop()
67 | }
68 | peek() {
69 | return this._arr[this._arr.length - 1]
70 | }
71 | }
72 |
73 | const stack = new Stack()
74 | stack.push(1)
75 | stack.push(2)
76 | stack.push(3)
77 | stack.pop() // 3
78 | ```
79 |
80 | 스택은 서로 관계가 있는 여러 작업을 연달아 수행하면서 **이전의 작업 내용을 저장해 둘 필요가 있을 때** 널리 사용됩니다.
81 |
82 | ## 트리 (Tree)
83 |
84 | 트리(tree)는 여러 데이터가 **계층 구조** 안에서 서로 연결된 형태를 나타낼 때 사용됩니다.
85 |
86 |
87 |
88 | 트리를 다룰 때 사용되는 몇 가지 용어를 살펴보겠습니다.
89 |
90 | - 노드(node) - 트리 안에 들어있는 각 항목을 말합니다.
91 | - 자식 노드(child node) - 노드는 여러 자식 노드를 가질 수 있습니다.
92 | - 부모 노드(parent node) - 노드 A가 노드 B를 자식으로 갖고 있다면, 노드 A를 노드 B의 '부모 노드'라고 부릅니다.
93 | - 뿌리 노드(root node) - 트리의 가장 상층부에 있는 노드를 말합니다.
94 | - 잎 노드(leaf node) - 자식 노드가 없는 노드를 말합니다.
95 | - 조상 노드(ancestor node) - 노드 A의 자식을 따라 내려갔을 때 노드 B에 도달할 수 있다면, 노드 A를 노드 B의 조상 노드라고 부릅니다.
96 | - 자손 노드(descendant node) - 노드 A가 노드 B의 조상 노드일 때, 노드 B를 노드 A의 자손 노드라고 부릅니다.
97 | - 형제 노드(sibling node) - 같은 부모 노드를 갖는 다른 노드를 보고 형제 노드라고 부릅니다.
98 |
99 | 아래는 아주 간단한 형태의 `Node`를 구현한 예입니다.
100 |
101 | ```js
102 | class Node {
103 | constructor(content, children = []) {
104 | this.content = content
105 | this.children = children
106 | }
107 | }
108 |
109 | const tree = new Node('hello', [
110 | new Node('world'),
111 | new Node('and'),
112 | new Node('fun', [new Node('javascript!')]),
113 | ])
114 |
115 | function traverse(node) {
116 | console.log(node.content)
117 | for (let child of node.children) {
118 | traverse(child)
119 | }
120 | }
121 |
122 | traverse(tree)
123 | // hello world and fun javascript!
124 | ```
125 |
126 | 트리는 계층 구조를 나타내기 위해, 또한 계층 구조를 통해 알고리즘의 효율을 높이고자 할 때 널리 사용됩니다.
127 |
128 | ## Set 집합 연산
129 |
130 | Set은 중복되지 않는 값들의 집합을 나타내는 자료구조입니다. ES2024와 ES2025에서는 Set에 집합 이론의 기본 연산들이 추가되어, 수학적인 집합 연산을 쉽게 수행할 수 있게 되었습니다.
131 |
132 | ### 집합 연산 메소드
133 |
134 | ```js
135 | const setA = new Set([1, 2, 3, 4])
136 | const setB = new Set([3, 4, 5, 6])
137 |
138 | // 합집합 (Union) - 두 집합의 모든 요소
139 | const union = setA.union(setB)
140 | console.log([...union]) // [1, 2, 3, 4, 5, 6]
141 |
142 | // 교집합 (Intersection) - 양쪽 집합 모두에 있는 요소
143 | const intersection = setA.intersection(setB)
144 | console.log([...intersection]) // [3, 4]
145 |
146 | // 차집합 (Difference) - setA에는 있지만 setB에는 없는 요소
147 | const difference = setA.difference(setB)
148 | console.log([...difference]) // [1, 2]
149 |
150 | // 대칭 차집합 (Symmetric Difference) - 한쪽에만 있는 요소
151 | const symDiff = setA.symmetricDifference(setB)
152 | console.log([...symDiff]) // [1, 2, 5, 6]
153 | ```
154 |
155 | ### 집합 비교 메소드
156 |
157 | Set 간의 포함 관계를 확인하는 메소드들도 추가되었습니다.
158 |
159 | ```js
160 | const setA = new Set([1, 2, 3, 4])
161 | const setB = new Set([1, 2])
162 | const setC = new Set([5, 6])
163 |
164 | // 부분집합 확인 (Subset) - setB의 모든 요소가 setA에 포함되는가?
165 | setB.isSubsetOf(setA) // true
166 | setA.isSubsetOf(setB) // false
167 |
168 | // 상위집합 확인 (Superset) - setA가 setB의 모든 요소를 포함하는가?
169 | setA.isSupersetOf(setB) // true
170 | setB.isSupersetOf(setA) // false
171 |
172 | // 서로소 집합 확인 (Disjoint) - 공통 요소가 없는가?
173 | setA.isDisjointFrom(setC) // true (공통 요소 없음)
174 | setA.isDisjointFrom(setB) // false (공통 요소 있음)
175 | ```
176 |
177 | ### 실용적인 예시
178 |
179 | 이러한 Set 연산은 데이터 분석이나 권한 관리 등 다양한 실무 상황에서 유용하게 사용될 수 있습니다.
180 |
181 | ```js
182 | // 예시: 사용자 권한 관리
183 | const adminPermissions = new Set(['read', 'write', 'delete'])
184 | const userPermissions = new Set(['read', 'write'])
185 |
186 | // 사용자에게 없는 권한 찾기
187 | const missingPermissions = adminPermissions.difference(userPermissions)
188 | console.log([...missingPermissions]) // ['delete']
189 |
190 | // 공통 권한 찾기
191 | const commonPermissions = adminPermissions.intersection(userPermissions)
192 | console.log([...commonPermissions]) // ['read', 'write']
193 | ```
194 |
195 | 이러한 Set 메소드들은 코드를 더 간결하고 읽기 쉽게 만들어주며, 집합 관련 로직을 직관적으로 표현할 수 있게 해줍니다.
196 |
--------------------------------------------------------------------------------
/specs/001-it-s-been/spec.md:
--------------------------------------------------------------------------------
1 | # Feature Specification: JavaScript Tutorial Book Content Update
2 |
3 | **Feature Branch**: `001-it-s-been`
4 | **Created**: 2025-10-02
5 | **Status**: Draft
6 | **Input**: User description: "It's been a while from last update of this book. Let's keep up-to-date the contents of this book."
7 |
8 | ---
9 |
10 | ## ⚡ Quick Guidelines
11 | - ✅ Focus on WHAT users need and WHY
12 | - ❌ Avoid HOW to implement (no tech stack, APIs, code structure)
13 | - 👥 Written for business stakeholders, not developers
14 |
15 | ---
16 |
17 | ## User Scenarios & Testing *(mandatory)*
18 |
19 | ### Primary User Story
20 | As a learner studying JavaScript through this tutorial book, I need the content to reflect current JavaScript standards and best practices so that I can learn relevant, up-to-date knowledge applicable to today's development environment. The book's core content was last substantially updated in 2021 (4 years ago), with minor updates in April 2023.
21 |
22 | ### Acceptance Scenarios
23 | 1. **Given** a learner reads the ECMAScript version references, **When** they check the current year and latest ECMAScript version, **Then** the book should reference the most recent stable ECMAScript version (ES2024 or ES2025)
24 | 2. **Given** a learner reads about JavaScript features, **When** they encounter feature descriptions and examples, **Then** the content should reflect modern JavaScript practices widely adopted in the industry as of 2025
25 | 3. **Given** a learner follows external reference links in the book, **When** they click on documentation or resource links, **Then** the links should point to current, accessible resources
26 | 4. **Given** a learner reads about browser compatibility, **When** they see information about which browsers support which features, **Then** the compatibility information should reflect the current state of modern browsers (Chrome, Firefox, Safari, Edge as of 2025)
27 | 5. **Given** a learner encounters deprecated patterns or features, **When** they read the content, **Then** outdated approaches should be clearly marked as legacy with guidance toward modern alternatives
28 |
29 | ### Edge Cases
30 | - What happens when external links (MDN, TC39, compatibility tables) have moved or changed URLs?
31 | - How does the book handle features that were experimental in 2021 but are now stable or standardized?
32 | - How should content address features that were recommended in 2021 but are now considered anti-patterns?
33 | - What about JavaScript features introduced between 2021-2025 (ES2022, ES2023, ES2024, ES2025) that should be included?
34 |
35 | ## Requirements *(mandatory)*
36 |
37 | ### Functional Requirements
38 | - **FR-001**: Content MUST reference the latest stable ECMAScript version as of 2025 (ES2024 or ES2025)
39 | - **FR-002**: All external links (MDN documentation, TC39 specifications, compatibility tables, etc.) MUST be verified and updated to current URLs
40 | - **FR-003**: Browser compatibility information MUST reflect the current state of major browsers as of 2025
41 | - **FR-004**: Code examples MUST use modern JavaScript syntax and patterns that are widely supported in 2025
42 | - **FR-005**: Features and APIs that have become deprecated or obsolete since 2021 MUST be marked as such with alternatives provided
43 | - **FR-006**: New JavaScript features introduced between 2021-2025 (ES2022, ES2023, ES2024, ES2025) that are relevant for beginners MUST be evaluated for inclusion
44 | - **FR-007**: Content about transpilers and polyfills MUST reflect the current state of these tools (Babel, TypeScript, etc.) as of 2025
45 | - **FR-008**: References to browser versions, Node.js versions, and other runtime environments MUST be updated to current stable versions
46 | - **FR-009**: Performance recommendations and best practices MUST align with current industry standards as of 2025
47 | - **FR-010**: All factual claims about JavaScript ecosystem (frameworks, libraries, tools) MUST be verified against current reality
48 |
49 | ### Key Entities *(include if feature involves data)*
50 | - **Content Section**: A chapter or page in the tutorial book containing explanations, code examples, and references about a specific JavaScript topic
51 | - **Code Example**: Executable JavaScript code snippet demonstrating a concept or feature
52 | - **External Reference**: Links to authoritative sources (MDN, ECMAScript spec, compatibility tables, tool documentation)
53 | - **Version Reference**: Mentions of specific versions (ECMAScript editions, browser versions, Node.js versions)
54 | - **Feature Description**: Explanation of a JavaScript language feature, API, or pattern
55 |
56 | ---
57 |
58 | ## Review & Acceptance Checklist
59 | *GATE: Automated checks run during main() execution*
60 |
61 | ### Content Quality
62 | - [x] No implementation details (languages, frameworks, APIs)
63 | - [x] Focused on user value and business needs
64 | - [x] Written for non-technical stakeholders
65 | - [x] All mandatory sections completed
66 |
67 | ### Requirement Completeness
68 | - [x] No [NEEDS CLARIFICATION] markers remain
69 | - [x] Requirements are testable and unambiguous
70 | - [x] Success criteria are measurable
71 | - [x] Scope is clearly bounded
72 | - [x] Dependencies and assumptions identified
73 |
74 | ---
75 |
76 | ## Execution Status
77 | *Updated by main() during processing*
78 |
79 | - [x] User description parsed
80 | - [x] Key concepts extracted
81 | - [x] Ambiguities marked
82 | - [x] User scenarios defined
83 | - [x] Requirements generated
84 | - [x] Entities identified
85 | - [x] Review checklist passed
86 |
87 | ---
88 |
--------------------------------------------------------------------------------
/.specify/scripts/bash/check-prerequisites.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Consolidated prerequisite checking script
4 | #
5 | # This script provides unified prerequisite checking for Spec-Driven Development workflow.
6 | # It replaces the functionality previously spread across multiple scripts.
7 | #
8 | # Usage: ./check-prerequisites.sh [OPTIONS]
9 | #
10 | # OPTIONS:
11 | # --json Output in JSON format
12 | # --require-tasks Require tasks.md to exist (for implementation phase)
13 | # --include-tasks Include tasks.md in AVAILABLE_DOCS list
14 | # --paths-only Only output path variables (no validation)
15 | # --help, -h Show help message
16 | #
17 | # OUTPUTS:
18 | # JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]}
19 | # Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md
20 | # Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc.
21 |
22 | set -e
23 |
24 | # Parse command line arguments
25 | JSON_MODE=false
26 | REQUIRE_TASKS=false
27 | INCLUDE_TASKS=false
28 | PATHS_ONLY=false
29 |
30 | for arg in "$@"; do
31 | case "$arg" in
32 | --json)
33 | JSON_MODE=true
34 | ;;
35 | --require-tasks)
36 | REQUIRE_TASKS=true
37 | ;;
38 | --include-tasks)
39 | INCLUDE_TASKS=true
40 | ;;
41 | --paths-only)
42 | PATHS_ONLY=true
43 | ;;
44 | --help|-h)
45 | cat << 'EOF'
46 | Usage: check-prerequisites.sh [OPTIONS]
47 |
48 | Consolidated prerequisite checking for Spec-Driven Development workflow.
49 |
50 | OPTIONS:
51 | --json Output in JSON format
52 | --require-tasks Require tasks.md to exist (for implementation phase)
53 | --include-tasks Include tasks.md in AVAILABLE_DOCS list
54 | --paths-only Only output path variables (no prerequisite validation)
55 | --help, -h Show this help message
56 |
57 | EXAMPLES:
58 | # Check task prerequisites (plan.md required)
59 | ./check-prerequisites.sh --json
60 |
61 | # Check implementation prerequisites (plan.md + tasks.md required)
62 | ./check-prerequisites.sh --json --require-tasks --include-tasks
63 |
64 | # Get feature paths only (no validation)
65 | ./check-prerequisites.sh --paths-only
66 |
67 | EOF
68 | exit 0
69 | ;;
70 | *)
71 | echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2
72 | exit 1
73 | ;;
74 | esac
75 | done
76 |
77 | # Source common functions
78 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
79 | source "$SCRIPT_DIR/common.sh"
80 |
81 | # Get feature paths and validate branch
82 | eval $(get_feature_paths)
83 | check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1
84 |
85 | # If paths-only mode, output paths and exit (support JSON + paths-only combined)
86 | if $PATHS_ONLY; then
87 | if $JSON_MODE; then
88 | # Minimal JSON paths payload (no validation performed)
89 | printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \
90 | "$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS"
91 | else
92 | echo "REPO_ROOT: $REPO_ROOT"
93 | echo "BRANCH: $CURRENT_BRANCH"
94 | echo "FEATURE_DIR: $FEATURE_DIR"
95 | echo "FEATURE_SPEC: $FEATURE_SPEC"
96 | echo "IMPL_PLAN: $IMPL_PLAN"
97 | echo "TASKS: $TASKS"
98 | fi
99 | exit 0
100 | fi
101 |
102 | # Validate required directories and files
103 | if [[ ! -d "$FEATURE_DIR" ]]; then
104 | echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2
105 | echo "Run /specify first to create the feature structure." >&2
106 | exit 1
107 | fi
108 |
109 | if [[ ! -f "$IMPL_PLAN" ]]; then
110 | echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
111 | echo "Run /plan first to create the implementation plan." >&2
112 | exit 1
113 | fi
114 |
115 | # Check for tasks.md if required
116 | if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then
117 | echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2
118 | echo "Run /tasks first to create the task list." >&2
119 | exit 1
120 | fi
121 |
122 | # Build list of available documents
123 | docs=()
124 |
125 | # Always check these optional docs
126 | [[ -f "$RESEARCH" ]] && docs+=("research.md")
127 | [[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")
128 |
129 | # Check contracts directory (only if it exists and has files)
130 | if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
131 | docs+=("contracts/")
132 | fi
133 |
134 | [[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")
135 |
136 | # Include tasks.md if requested and it exists
137 | if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then
138 | docs+=("tasks.md")
139 | fi
140 |
141 | # Output results
142 | if $JSON_MODE; then
143 | # Build JSON array of documents
144 | if [[ ${#docs[@]} -eq 0 ]]; then
145 | json_docs="[]"
146 | else
147 | json_docs=$(printf '"%s",' "${docs[@]}")
148 | json_docs="[${json_docs%,}]"
149 | fi
150 |
151 | printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
152 | else
153 | # Text output
154 | echo "FEATURE_DIR:$FEATURE_DIR"
155 | echo "AVAILABLE_DOCS:"
156 |
157 | # Show status of each potential document
158 | check_file "$RESEARCH" "research.md"
159 | check_file "$DATA_MODEL" "data-model.md"
160 | check_dir "$CONTRACTS_DIR" "contracts/"
161 | check_file "$QUICKSTART" "quickstart.md"
162 |
163 | if $INCLUDE_TASKS; then
164 | check_file "$TASKS" "tasks.md"
165 | fi
166 | fi
--------------------------------------------------------------------------------
/content/pages/140-string.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: string 타입
3 | ---
4 |
5 | 문자열에 `typeof` 연산을 해보면 다음과 같은 결과가 나옵니다.
6 |
7 | ```js
8 | typeof 'hello' // 'string'
9 | ```
10 |
11 | 컴퓨터 분야에서는 문자의 나열(string)이라는 뜻에서 문자열을 'string'이라 부릅니다. string 타입을 통해 일반적인 텍스트 데이터를 다룰 수 있습니다. JavaScript 문자열은 내부적으로 [유니코드(Unicode)](https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C)를 통해 표현됩니다.[^1]
12 |
13 | ## 문자열 리터럴
14 |
15 | JavaScript는 문자열 값을 표현하기 위한 여러 가지 리터럴을 제공합니다.
16 |
17 | ```js
18 | 'hello'
19 | 'hello 안녕하세요'`hello world` // template literal
20 | ```
21 |
22 | 따옴표는 표기법일 뿐, 실제 저장되는 값에 영향을 미치지는 않습니다. 예를 들어, `hello`라는 문자열을 표현하기 위 셋 중에 어떤 리터럴을 사용해도 실제 저장되는 값이 달라지지는 않습니다.[^2]
23 |
24 | ```js
25 | 'hello' === 'hello' // true
26 | ```
27 |
28 | ## 템플릿 리터럴 (Template Literal)
29 |
30 | ES2015에서 도입된 템플릿 리터럴(template literal)은 문자열 리터럴의 일종으로, 추가적인 기능을 지원합니다. 템플릿 리터럴을 사용하려면 backtick(`)으로 문자열을 둘러싸면 됩니다.
31 |
32 | ```js
33 | ;`hello world`
34 | ```
35 |
36 | 템플릿 리터럴의 내삽(interpolation) 기능을 이용하면, 문자열을 동적으로 생성하는 코드를 쉽게 작성할 수 있습니다.
37 |
38 | ```js
39 | const name1 = 'Foo'
40 | const name2 = 'Bar'
41 | const sentence = `${name1} meets ${name2}!`
42 | console.log(sentence)
43 |
44 | // 일반적인 문자열 리터럴로는 아래와 같이 해야 합니다.
45 | name1 + ' meets ' + name2 + '!'
46 | ```
47 |
48 | 또한, 템플릿 리터럴을 사용하면 **여러 줄로 이루어진 문자열**을 쉽게 표현할 수 있습니다.
49 |
50 | ```js
51 | ;`hello
52 | world
53 | hello
54 | javascript!
55 | `
56 |
57 | // 일반적인 문자열 리터럴로는 아래와 같이 해야 합니다.
58 | ;('hello\nworld\nhello\njavascript!\n')
59 | ```
60 |
61 | 템플릿 리터럴은 아래와 같이 특이한 형태의 함수 호출 기능을 지원하는데. 이를 'tagged template literal'이라고 합니다. 주로 다른 언어를 JavaScript와 통합할 때 사용되고, 라이브러리 제작자가 아니라면 보통은 tagged template literal을 위한 함수를 직접 제작할 일은 없습니다. 자세한 내용을 알고 싶다면 [이 문서](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals)를 참고하세요.
62 |
63 | ```js
64 | styled.div`
65 | display: flex;
66 | border: 1px solid black;
67 | ` // styled-components
68 | gql`
69 | query {
70 | user {
71 | name
72 | }
73 | }
74 | ` // graphql-tag
75 | html`Hello tagged template literal!` // lit-html
76 | ```
77 |
78 | ## Escape Sequence
79 |
80 | JavaScript는 특수 문자를 문자열에 넣거나, 혹은 직접 유니코드 코드포인트를 사용해서 문자를 넣을 수 있도록 해주는 escape sequence를 제공합니다.
81 |
82 | | 표기법 | 문자 |
83 | | -------- | ------------- |
84 | | \' | 홑따옴표 |
85 | | \" | 쌍따옴표 |
86 | | \\\\ | 백슬래시 |
87 | | \n | 라인 피드 |
88 | | \r | 캐리지 리턴 |
89 | | \t | 탭 |
90 | | \uXXXX | 유니코드 문자 |
91 | | \u{X...} | 유니코드 문자 |
92 | | \\$ | 달러 |
93 | | \\` | 백틱 |
94 |
95 | 위 escape sequence를 문자열 안에서 사용할 수 있습니다.
96 |
97 | ```js
98 | console.log("lorem 'ipsum'") // lorem 'ipsum'
99 | console.log('line\nfeed') // line(줄바꿈)feed
100 | console.log('\uD55C\uAE00') // 한글
101 | console.log('\u{1F435}') // 🐵
102 | ```
103 |
104 | 다만 리터럴을 위해 사용한 따옴표와 **다른 종류의 따옴표**들은 문자열 내에서 자유롭게 쓸 수 있으므로 굳이 escape sequence로 표기해주어야 할 필요가 없습니다.
105 |
106 | ```js
107 | "`lorem` 'ipsum'"
108 | '`lorem` "ipsum"'
109 | ;`'lorem' "ipsum"`
110 | ```
111 |
112 | 위 표의 라인 피드(line feed)와 캐리지 리턴(carage return)은 **개행 문자**로, 우리가 보통 엔터를 누를 때 입력되는 문자입니다. 각각을 줄여서 `LF`, `CR` 이라고 표기하고, 맥과 리눅스에서는 `LF`, 윈도우에서는 `CR+LF`가 개행문자로 사용됩니다. 개행 문자에 대해서 자세히 알아보려면 [이 링크](https://ko.wikipedia.org/wiki/%EC%83%88%EC%A4%84_%EB%AC%B8%EC%9E%90)를 참고하세요.
113 |
114 | ## 문자열과 연산자
115 |
116 | 수 타입 뿐 아니라 문자열에 대해서도 여러 가지 연산자를 쓸 수 있습니다.
117 |
118 | ```js
119 | // 문자열 연결하기
120 | 'hello' + 'world' // 'helloworld'
121 |
122 | // 등호 비교
123 | 'hello' === 'hello' // true
124 | 'hello' !== 'hello' // false
125 |
126 | // 유니코드 코드포인트 비교. 앞에서부터 한 글자씩 차례대로 비교합니다.
127 | 'a' < 'b' // true
128 | 'aaa' < 'abc' // true
129 | 'a' < 'Z' // false
130 | '한글' < '한국어' // false
131 | '2' < '10' // false
132 |
133 | // 문자열을 배열로 바꾸기
134 | ;[...'hello'] // ['h', 'e', 'l', 'l', 'o']
135 | ```
136 |
137 | 위에서 알 수 있는 것처럼 유니코드 코드포인트 비교는 사전순 비교가 아니므로 주의해야 합니다. 사전순 비교를 하려면 `localeCompare` 메소드를 사용하세요.
138 |
139 | ```js
140 | 'b'.localeCompare('a') // 1
141 | 'b'.localeCompare('b') // 0
142 | 'b'.localeCompare('z') // -1
143 | 'b'.localeCompare('Z') // -1
144 | '가나다'.localeCompare('마바사') // -1
145 | ```
146 |
147 | ## 속성 및 메소드
148 |
149 | number 타입과 마찬가지로 string 타입도 래퍼 객체의 속성과 메소드를 사용할 수 있습니다. 아래는 자주 쓰이는 몇 개의 속성과 메소드에 대한 예제입니다. 이 밖의 내용을 확인하려면 [MDN 문서](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String#String_instances)를 참고하세요.
150 |
151 | ```js
152 | // 문자열의 길이 알아내기
153 | 'hello'.length // 5
154 |
155 | // 여러 문자열 연결하기
156 | 'hello'.concat('fun', 'javascript') // 'hellofunjavascript'
157 |
158 | // 특정 문자열을 반복하는 새 문자열 생성하기
159 | '*'.repeat(3) // '***'
160 |
161 | // 특정 문자열이 포함되어 있는지 확인하기
162 | 'hello javascript'.includes('hello') // true
163 | 'hello javascript'.startsWith('he') // true
164 | 'hello javascript'.endsWith('ript') // true
165 | 'hello javascript'.indexOf('java') // 6
166 |
167 | // 문자열의 특정 부분을 바꾼 새 문자열 생성하기
168 | 'hello javascript'.replace('java', 'type') // 'hello typescript'
169 |
170 | // 문자열의 일부를 잘라낸 새 문자열 생성하기
171 | 'hello'.slice(2, 4) // 'll'
172 |
173 | // 좌우 공백문자를 제거한 새 문자열 생성하기
174 | ' hello '.trim() // 'hello'
175 | ' hello '.trimLeft() // 'hello '
176 | ' hello '.trimRight() // ' hello'
177 |
178 | // 좌우 공백문자를 추가한 새 문자열 생성하기
179 | 'hello'.padStart(8) // ' hello'
180 | 'hello'.padEnd(8) // 'hello '
181 |
182 | // 문자열을 특정 문자를 기준으로 잘라 새 배열 생성하기
183 | 'hello!fun!javavscript'.split('!') // ['hello', 'fun', 'javascript']
184 | 'hello'.split('') // ['h', 'e', 'l', 'l', 'o']
185 |
186 | // 모든 문자를 소문자, 혹은 대문자로 변환한 새 문자열 생성하기
187 | 'Hello JavaScript'.toLowerCase() // 'hello javascript'
188 | 'Hello JavaScript'.toUpperCase() // 'HELLO JAVASCRIPT'
189 | ```
190 |
191 | [^1]: 정확히 말하면, 문자열은 JavaScript 내부적으로 UTF-16 형식으로 인코딩된 값으로 다뤄집니다. ([명세](https://tc39.github.io/ecma262/#sec-ecmascript-language-types-string-type))
192 |
193 | [^2]: 그렇다고 해서 따옴표를 마구잡이로 섞어 쓰는 게 좋은 것은 아니며, 꼭 필요한 경우가 아니라면 코드의 일관성을 위해 한 가지 종류의 따옴표만을 사용하는 것이 좋습니다. 주로 홑따옴표(`'`)가 널리 사용됩니다.
194 |
--------------------------------------------------------------------------------
/content/pages/020-tutorial.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 튜토리얼
3 | description: 교재를 읽어나가기 위해 JavaScript 언어의 기본적인 구성요소를 이해하고 넘어갈 필요가 있습니다.
4 | ---
5 |
6 | 교재를 읽어나가기 위해 JavaScript 언어의 기본적인 구성요소를 이해하고 넘어갈 필요가 있습니다. 아래에 나오는 간단한 예제들을 직접 실행해 봅시다. 각 요소들에 대한 자세한 설명은 이어지는 챕터에서 다룹니다.
7 |
8 | ## 코드의 실행
9 |
10 | 기본적으로 JavaScript 코드는 세미콜론(`;`)으로 구분된 구문(statement) 단위로 위에서부터 차례대로 실행됩니다. 그러나 제어 흐름, 예외 처리, 함수 호출 등을 만나면 코드의 실행 흐름이 이리저리 옮겨다니기도 합니다.
11 |
12 | JavaScript 코드는 REPL이라는 도구를 이용해서 조금씩 실행시킬 수도 있고, 혹은 미리 작성해둔 많은 양의 코드를 한 번에 실행시킬 수도 있습니다. 여기서 REPL(Read-Evaluate-Print-Loop)는 코드를 실행시키고 그 결과를 바로 확인할 수 있는 컴퓨터 프로그램을 총칭합니다. 아래에 나오는 코드 예제를 실행시키려면 웹 브라우저의 개발자 도구 콘솔(F12 키 또는 Cmd+Opt+I)을 이용하거나, [Replit](https://replit.com/)과 같은 온라인 REPL을 이용하세요.
13 |
14 | ## 기본 문법
15 |
16 | ### 대소문자의 구분
17 |
18 | JavaScript 언어는 모든 부분에서 대소문자 구분을 하기 때문에 주의해야 합니다. 예를 들어 `function`과 `Function`은 JavaScript에서 완전히 다른 의미를 가집니다.
19 |
20 | ### 세미콜론
21 |
22 | JavaScript는 세미콜론(;)을 이용해서 각 구문을 구분합니다.
23 |
24 | ```js
25 | const a = 1
26 | const b = 2
27 |
28 | // 혹은 이렇게 할 수도 있습니다만, 좋아 보이지는 않습니다.
29 | const c = 1
30 | const d = 2
31 | ```
32 |
33 | 특별한 경우가 아니라면, 세미콜론을 쓰고 나서는 개행을 해서 각각의 구문이 구분되어 보일 수 있도록 하는 것이 좋습니다.
34 |
35 | ### 공백
36 |
37 | JavaScript 언어는 공백에 민감하지 않은 언어입니다. 즉, 다른 문법 요소만 잘 지킨다면 공백의 수가 코드의 실행에 별 영향을 미치지 않습니다.[^1]
38 |
39 | ```js
40 | // 아래 세 구문은 완벽히 똑같은 동작을 합니다.
41 | const x = 1
42 |
43 | const x = 1
44 |
45 | const x = 1
46 | ```
47 |
48 | 또한 코드의 동작 여부와는 별개로, 코드를 읽기 쉽도록 공백을 깔끔하게 유지해야 할 필요가 있습니다.
49 |
50 | ## 주석(comment)
51 |
52 | 주석은 실행되는 코드는 아니지만, 코드에 부가적인 설명을 넣고 싶을 때 사용합니다.
53 |
54 | ```js
55 | // 한 줄 주석
56 |
57 | /* 여러 줄 주석 */
58 |
59 | /*
60 | 여러
61 | 줄
62 | 주석
63 | */
64 | ```
65 |
66 | ## 값(value)과 그 리터럴(literal)
67 |
68 | 프로그래밍은 근본적으로 '값'을 다루는 행위라 할 수 있습니다. JavaScript에도 여러 가지 값을 표현하는 문법이 있으며, 이를 리터럴(literal)이라고 합니다.
69 |
70 | ```js
71 | 1 // 정수
72 | 2.5 // 부동소수점 실수
73 | ;('hello world') // 작은 따옴표 문자열
74 | ;('JavaScript') // 큰 따옴표 문자열
75 | true // 참임을 나타내는 진리값
76 | false // 거짓임을 나타내는 진리값
77 | null // '존재하지 않음'을 나타내는 값
78 | undefined // '정의된 적 없음'을 나타내는 값
79 | ```
80 |
81 | ## 값의 타입(type)
82 |
83 | JavaScript에서 다루어지는 모든 값은 그 '타입'을 가지고 있습니다. 쉽게 '값의 종류'라고 생각하시면 됩니다. `typeof` 연산자는 피연산자의 타입을 가리키는 문자열을 반환합니다.
84 |
85 | ```js
86 | typeof 1 // 'number'
87 | typeof 2.5 // 'number'
88 | typeof 'hello world' // 'string'
89 | typeof true // 'boolean'
90 | typeof null // 'object'
91 | typeof undefined // 'undefined'
92 | // ...
93 | ```
94 |
95 | ## 표현식(expression)과 연산자(operator)
96 |
97 | 표현식이란 JavaScript 문장의 구성요소를 구분하는 하나의 단위로, **값으로 변환될 수 있는 부분**을 가리킵니다.
98 |
99 | JavaScript에서는 수학에서 사용하던 것과 비슷한 여러 가지 종류의 연산자를 통해 연산을 수행할 수 있습니다. 하지만 수학에서 보던 것과는 동작 방식이 달라서 입문자에게는 익숙하지 않은 연산자들도 있습니다.
100 |
101 | ```js
102 | // 사칙 연산
103 | 1 + 3
104 | 2 - 4
105 | 3 * 5
106 | 4 / 6
107 |
108 | // 연산자 우선순위 (operator precedence)
109 | 1 + 2 * 3
110 | ;(1 + 2) * 3
111 |
112 | // 논리 연산
113 | true || false
114 | true && false
115 |
116 | // 진리값으로 취급하기
117 | 1 || 0
118 | 1 && 0
119 | ```
120 |
121 | ## 변수(variable)
122 |
123 | 값을 재사용하기 위해 값에 붙일 이름을 선언하고 그 이름에 값을 대입할 수 있습니다. 이 때 이 이름을 변수(variable)라고 부릅니다. `let` 변수에는 값을 여러 번 다시 대입할 수 있지만, `const` 변수에는 값을 한 번만 대입할 수 있습니다.
124 |
125 | ```js
126 | // 한 변수의 선언
127 | let v
128 |
129 | // 값의 대입
130 | v = 1
131 |
132 | // 변수의 선언과 값의 대입을 동시에
133 | let x = 1
134 |
135 | // 두 변수의 선언과 대입
136 | let y = 2,
137 | z = x + y
138 |
139 | // const 변수
140 | const c = 1
141 | c = 2 // 에러!
142 |
143 | // 선언한 변수를 사용하기
144 | x
145 | typeof x
146 | x * z
147 | ```
148 |
149 | ## 제어 흐름
150 |
151 | JavaScript는 특정 조건을 만족할 때에만 코드를 실행시키거나, 혹은 여러 번 실행시킬 수 있는 구문을 제공합니다.
152 |
153 | ```js
154 | // if 구문
155 | if (2 > 1) {
156 | console.log('괄호 안의 값이 `true`이면 이 영역의 코드가 실행됩니다.')
157 | } else {
158 | console.log('괄호 안의 값이 `false`면 이 영역의 코드가 실행됩니다.')
159 | }
160 |
161 | // while 구문
162 | let i = 0
163 | while (i < 5) {
164 | console.log(`이 코드는 ${i + 1}번 째 실행되고 있습니다.`)
165 | i++
166 | }
167 |
168 | // for 구문
169 | for (let i = 0; i < 5; i++) {
170 | // (초기값; 조건; 갱신)
171 | console.log(`이 코드는 ${i + 1}번 째 실행되고 있습니다.`)
172 | }
173 | ```
174 |
175 | ## 함수 (function)
176 |
177 | 값 뿐만 아니라 **코드 뭉치**에 이름을 붙여서 **재사용**을 할 수도 있는데, 이를 함수라 부릅니다. JavaScript에는 함수를 선언할 수 있는 여러 가지 방법이 있습니다.
178 |
179 | ```js
180 | // `function` 키워드를 이용한 함수 선언
181 | function add(x, y) {
182 | return x + y
183 | }
184 |
185 | // arrow function
186 | const multiply = (x, y) => x * y
187 |
188 | // 함수 호출하기
189 | add(1, 2) // 3
190 | multiply(3, 4) // 12
191 | ```
192 |
193 | JavaScript 및 그 구동 환경에 내장되어 있는 여러 가지 함수를 사용해서 사용자와 상호작용을 하는 코드를 작성할 수 있습니다.
194 |
195 | ```js
196 | // 브라우저 내장 함수인 `prompt`, `console.log`, `alert` 사용하기
197 | const answer = prompt('이름이 무엇인가요?')
198 | console.log(answer)
199 | alert(answer)
200 | ```
201 |
202 | ## 객체
203 |
204 | 대부분의 프로그래밍 언어는 여러 개의 값을 한꺼번에 담아 통(container)처럼 사용할 수 있는 자료구조를 내장하고 있습니다. JavaScript에는 **객체(object)**라는 자료구조가 있습니다.
205 |
206 | 객체에는 **이름(name)**에 **값(value)**이 연결되어 저장됩니다. 이를 이름-값 쌍(name-value pair), 혹은 객체의 **속성(property)**이라고 합니다. 객체를 사용할 때는 속성 이름을 이용해서, 속성 값을 읽거나 쓸 수 있습니다. 또는 아래와 같이 여러 속성을 한꺼번에 정의할 수도 있습니다.
207 |
208 | ```js
209 | // 객체의 생성
210 | const obj = {
211 | x: 0, // 객체의 속성. 속성 이름: x, 속성 값: 0
212 | y: 1, // 객체의 속성. 속성 이름: y, 속성 값: 1
213 | }
214 |
215 | // 객체의 속성에 접근하기
216 | obj.x
217 | obj['y']
218 |
219 | // 객체의 속성 변경하기
220 | obj.x += 1
221 | obj['y'] -= 1
222 |
223 | // 객체의 속성 삭제하기
224 | delete obj.x
225 | ```
226 |
227 | 객체의 속성을 통해 사용하는 함수를 **메소드(method)**라고 부릅니다.
228 |
229 | ```js
230 | const obj = {
231 | x: 0,
232 | increaseX: function () {
233 | this.x = this.x + 1
234 | },
235 | }
236 |
237 | obj.increaseX()
238 | console.log(obj.x) // 1
239 | ```
240 |
241 | ## 배열
242 |
243 | JavaScript에서의 배열은 객체의 일종으로, 다른 객체와는 다르게 특별히 취급됩니다. 배열에 담는 데이터는 객체의 경우와 달리 **요소(element)** 혹은 **항목(item)**이라고 부릅니다. 객체와 마찬가지로 배열 안에 여러 개의 값을 저장할 수 있지만, 배열 요소 간에는 **순서**가 존재하며, 이름 대신에 **인덱스(index)**를 이용해 값에 접근합니다.
244 |
245 | ```js
246 | // 배열의 생성
247 | const arr = ['one', 'two', 'three']
248 |
249 | // 인덱스를 사용해 배열의 요소(element)에 접근할 수 있습니다.
250 | // 배열 인덱스(index)는 0부터 시작합니다.
251 | arr[0] // === 'one'
252 | arr[1] // === 'two'
253 |
254 | // 여러 타입의 값이 들어있는 배열
255 | ;[1, 2, 3, 'a', 'b', {x: 0, y: 0, name: '원점'}]
256 |
257 | // 배열에 요소 추가하기
258 | arr.push('four')
259 |
260 | // 배열의 요소 삭제하기
261 | arr.splice(3, 1) // 인덱스가 3인 요소부터 시작해서 하나를 삭제합니다.
262 | ```
263 |
264 | [^1]: 다만, 정말로 공백을 아무렇게나 넣어도 되는 것은 아닙니다. JavaScript에는 몇몇 조건을 만족하면 개행을 세미콜론처럼 취급하는 세미콜론 자동 삽입(ASI, Auto Semicolon Insertion) 기능이 내장되어 있기 때문에 개행을 할 때는 주의해야 합니다.
265 |
--------------------------------------------------------------------------------
/.specify/memory/constitution.md:
--------------------------------------------------------------------------------
1 |
15 |
16 | # JavaScript로 만나는 세상 Constitution
17 |
18 | ## Core Principles
19 |
20 | ### I. Accuracy & Currency
21 | Content MUST reflect the latest JavaScript standards and best practices. All technical information must be researched and verified before publication.
22 |
23 | **Rationale**: Educational content requires factual accuracy to build correct mental models. Outdated or incorrect information undermines learning outcomes and student trust.
24 |
25 | ### II. Clarity & Accessibility
26 | Use concise, easily understandable expressions appropriate for readers with no programming experience. Avoid jargon without explanation; prefer concrete examples over abstract theory.
27 |
28 | **Rationale**: The target audience (독자) has zero programming experience. Complex explanations create barriers to entry and reduce learning effectiveness.
29 |
30 | ### III. Progressive Learning Structure
31 | Content must be organized in logical progression from fundamentals to advanced topics. Each section builds on previously introduced concepts without forward dependencies.
32 |
33 | **Rationale**: Learners need scaffolded knowledge construction. Breaking the dependency order forces readers to skip around, fragmenting comprehension.
34 |
35 | ### IV. Practical Examples
36 | Every concept MUST include executable code examples demonstrating real-world usage. Examples should be minimal, focused, and immediately testable.
37 |
38 | **Rationale**: Programming is learned through practice. Abstract explanations without concrete examples fail to activate hands-on learning pathways.
39 |
40 | ### V. Modern JavaScript Focus
41 | Prioritize ES6+ syntax and modern JavaScript features. Legacy patterns should be mentioned only for historical context with clear "do not use" guidance.
42 |
43 | **Rationale**: Students entering the field should learn current industry practices. Teaching outdated patterns creates technical debt in learners' skillsets.
44 |
45 | ## Content Standards
46 |
47 | ### Technical Accuracy
48 | - All code examples MUST be syntax-checked and tested
49 | - Feature compatibility MUST specify browser/Node.js version requirements
50 | - Research current MDN documentation and ECMAScript specifications before writing
51 | - Verify examples work in latest stable JavaScript environments
52 |
53 | ### Language & Style
54 | - Write in Korean (한국어) for explanatory text
55 | - Use English for code, technical terms, and identifiers
56 | - Maintain consistent terminology throughout the book
57 | - Keep sentences short (prefer <50 characters per clause)
58 | - Use active voice over passive constructions
59 |
60 | ### Example Quality
61 | - Examples should be self-contained (runnable without external dependencies)
62 | - Prefer console.log output demonstration over abstract concepts
63 | - Include both "correct usage" and "common mistakes" where helpful
64 | - Code snippets MUST follow Prettier formatting rules (.prettierrc)
65 |
66 | ### Link Integrity
67 | - **Internal links MUST NOT include file extensions** (`.md`, `.mdx`)
68 | - Correct: `[배열](./190-array)`
69 | - Incorrect: `[배열](./190-array.mdx)`
70 | - All links MUST be validated before committing via `npm run test:links`
71 | - External links returning 4XX status codes MUST be updated or removed
72 | - Broken links undermine user experience and content credibility
73 |
74 | ## Development Workflow
75 |
76 | ### Content Creation Process
77 | 1. **Research Phase**: Verify feature specifications against authoritative sources (MDN, TC39, ECMAScript spec)
78 | 2. **Draft Phase**: Write content following clarity and accessibility principles
79 | 3. **Example Phase**: Create tested, working code examples
80 | 4. **Review Phase**: Check against constitution compliance (accuracy, clarity, progression)
81 | 5. **Publication Phase**: Commit changes with descriptive messages
82 |
83 | ### Review Checklist (Per Content Section)
84 | - [ ] Code examples tested and working
85 | - [ ] **All hyperlinks verified (internal and external) - MUST run `npm run test:links` before every commit**
86 | - [ ] Technical accuracy verified against current standards
87 | - [ ] Language is accessible to programming beginners
88 | - [ ] Section fits logically in learning progression
89 | - [ ] Modern JavaScript practices demonstrated
90 | - [ ] No unexplained jargon or undefined terms
91 |
92 | ### Change Management
93 | - Content updates MUST maintain backward compatibility with existing learning paths
94 | - Corrections to errors take priority over new content
95 | - User-reported issues and pull requests should be reviewed for accuracy before merging
96 |
97 | ## Governance
98 |
99 | ### Amendment Procedure
100 | 1. Propose change with rationale and impact analysis
101 | 2. Document which principles/standards are affected
102 | 3. Update constitution version following semantic versioning
103 | 4. Propagate changes to affected templates and workflows
104 |
105 | ### Compliance Expectations
106 | - All content contributions must be reviewed against this constitution
107 | - Template files (.specify/templates/*.md) must align with constitutional principles
108 | - Deviations from principles require explicit justification in commit messages
109 | - Constitution supersedes informal practices or undocumented conventions
110 |
111 | ### Version Policy
112 | - **MAJOR** version: Fundamental restructuring of learning approach or removal of core principles
113 | - **MINOR** version: Addition of new principles or significant expansion of standards
114 | - **PATCH** version: Clarifications, wording improvements, non-semantic refinements
115 |
116 | **Version**: 1.1.0 | **Ratified**: 2025-10-02 | **Last Amended**: 2025-10-03
117 |
--------------------------------------------------------------------------------
/.claude/commands/analyze.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation.
3 | ---
4 |
5 | The user input to you can be provided directly by the agent or as a command argument - you **MUST** consider it before proceeding with the prompt (if not empty).
6 |
7 | User input:
8 |
9 | $ARGUMENTS
10 |
11 | Goal: Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/tasks` has successfully produced a complete `tasks.md`.
12 |
13 | STRICTLY READ-ONLY: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually).
14 |
15 | Constitution Authority: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/analyze`.
16 |
17 | Execution steps:
18 |
19 | 1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths:
20 | - SPEC = FEATURE_DIR/spec.md
21 | - PLAN = FEATURE_DIR/plan.md
22 | - TASKS = FEATURE_DIR/tasks.md
23 | Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command).
24 |
25 | 2. Load artifacts:
26 | - Parse spec.md sections: Overview/Context, Functional Requirements, Non-Functional Requirements, User Stories, Edge Cases (if present).
27 | - Parse plan.md: Architecture/stack choices, Data Model references, Phases, Technical constraints.
28 | - Parse tasks.md: Task IDs, descriptions, phase grouping, parallel markers [P], referenced file paths.
29 | - Load constitution `.specify/memory/constitution.md` for principle validation.
30 |
31 | 3. Build internal semantic models:
32 | - Requirements inventory: Each functional + non-functional requirement with a stable key (derive slug based on imperative phrase; e.g., "User can upload file" -> `user-can-upload-file`).
33 | - User story/action inventory.
34 | - Task coverage mapping: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases).
35 | - Constitution rule set: Extract principle names and any MUST/SHOULD normative statements.
36 |
37 | 4. Detection passes:
38 | A. Duplication detection:
39 | - Identify near-duplicate requirements. Mark lower-quality phrasing for consolidation.
40 | B. Ambiguity detection:
41 | - Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria.
42 | - Flag unresolved placeholders (TODO, TKTK, ???, , etc.).
43 | C. Underspecification:
44 | - Requirements with verbs but missing object or measurable outcome.
45 | - User stories missing acceptance criteria alignment.
46 | - Tasks referencing files or components not defined in spec/plan.
47 | D. Constitution alignment:
48 | - Any requirement or plan element conflicting with a MUST principle.
49 | - Missing mandated sections or quality gates from constitution.
50 | E. Coverage gaps:
51 | - Requirements with zero associated tasks.
52 | - Tasks with no mapped requirement/story.
53 | - Non-functional requirements not reflected in tasks (e.g., performance, security).
54 | F. Inconsistency:
55 | - Terminology drift (same concept named differently across files).
56 | - Data entities referenced in plan but absent in spec (or vice versa).
57 | - Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note).
58 | - Conflicting requirements (e.g., one requires to use Next.js while other says to use Vue as the framework).
59 |
60 | 5. Severity assignment heuristic:
61 | - CRITICAL: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality.
62 | - HIGH: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion.
63 | - MEDIUM: Terminology drift, missing non-functional task coverage, underspecified edge case.
64 | - LOW: Style/wording improvements, minor redundancy not affecting execution order.
65 |
66 | 6. Produce a Markdown report (no file writes) with sections:
67 |
68 | ### Specification Analysis Report
69 | | ID | Category | Severity | Location(s) | Summary | Recommendation |
70 | |----|----------|----------|-------------|---------|----------------|
71 | | A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version |
72 | (Add one row per finding; generate stable IDs prefixed by category initial.)
73 |
74 | Additional subsections:
75 | - Coverage Summary Table:
76 | | Requirement Key | Has Task? | Task IDs | Notes |
77 | - Constitution Alignment Issues (if any)
78 | - Unmapped Tasks (if any)
79 | - Metrics:
80 | * Total Requirements
81 | * Total Tasks
82 | * Coverage % (requirements with >=1 task)
83 | * Ambiguity Count
84 | * Duplication Count
85 | * Critical Issues Count
86 |
87 | 7. At end of report, output a concise Next Actions block:
88 | - If CRITICAL issues exist: Recommend resolving before `/implement`.
89 | - If only LOW/MEDIUM: User may proceed, but provide improvement suggestions.
90 | - Provide explicit command suggestions: e.g., "Run /specify with refinement", "Run /plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'".
91 |
92 | 8. Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.)
93 |
94 | Behavior rules:
95 | - NEVER modify files.
96 | - NEVER hallucinate missing sections—if absent, report them.
97 | - KEEP findings deterministic: if rerun without changes, produce consistent IDs and counts.
98 | - LIMIT total findings in the main table to 50; aggregate remainder in a summarized overflow note.
99 | - If zero issues found, emit a success report with coverage statistics and proceed recommendation.
100 |
101 | Context: $ARGUMENTS
102 |
--------------------------------------------------------------------------------
/content/pages/290-exception.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 예외 처리
3 | ---
4 |
5 | JavaScript에는 코드 실행 중에 예기치 못한 에러가 발생했을 때, 이로부터 코드의 실행 흐름을 복구할 수 있는 기능이 내장되어 있습니다. 이런 기능을 일러 **예외 처리(exception handling)**라고 합니다.
6 |
7 | ## 동기식 코드에서의 예외 처리
8 |
9 | JavaScript 코드에서 발생할 수 있는 에러에는 다양한 것들이 있습니다. 문법 에러와 같이 프로그래머의 실수로 인해 에러가 발생하는 경우도 있지만, 네트워크 에러와 같이 코드와는 무관한 이유로 발생하는 에러도 있습니다.
10 |
11 | ```js
12 | new Array(-1) // RangeError: Invalid array length
13 | ```
14 |
15 | ```js
16 | console.log(foo) // ReferenceError: foo is not defined
17 | ```
18 |
19 | ```js
20 | fetch('https://nonexistent-domain.nowhere') // TypeError: Failed to fetch
21 | ```
22 |
23 | 코드 실행 중에 에러가 발생하면, **코드의 실행이 중단되어 그 시점에 실행 중이었던 작업을 완료할 수 없게 됩니다.** JavaScript는 이로부터 **코드의 실행 흐름을 원상복구**할 수 있는 기능을 제공하며, `try...catch...finally` 구문을 사용하면 에러가 나더라도 코드의 실행을 지속할 수 있습니다.
24 |
25 | ```js
26 | try {
27 | console.log('에러가 나기 직전까지의 코드는 잘 실행됩니다.')
28 | new Array(-1) // RangeError: Invalid array length
29 | console.log('에러가 난 이후의 코드는 실행되지 않습니다.')
30 | } catch (e) {
31 | console.log('코드의 실행 흐름이 catch 블록으로 옮겨집니다.')
32 | alert(`다음과 같은 에러가 발생했습니다: ${e.name}: ${e.message}`)
33 | }
34 | ```
35 |
36 | 에러가 났을 때 원상복구를 시도할 코드를 `try` 블록 내부에 작성하면, 에러가 발생했을 때 코드의 실행 흐름이 `try` 블록에서 `catch` 블록으로 옮겨갑니다. 이 때, `catch` 블록 안에서는 에러에 대한 정보를 담고 있는 객체(위 코드의 `e`)를 사용할 수 있습니다. 만약 `e` 객체를 사용하지 않는다면 아래 예제처럼 생략할 수 있습니다.
37 |
38 | ```js
39 | try {
40 | new Array(-1) // RangeError: Invalid array length
41 | } catch {
42 | alert(`에러가 발생했습니다.`)
43 | }
44 | ```
45 |
46 | `try` 블록은 예외 처리를 위해서만 쓰이는 것은 아닙니다. `try` 블록 바로 뒤에 `finally` 블록이 오면, `finally` 블록에 있는 코드는 `try` 블록 안에서의 에러 발생 여부와 관계 없이 **무조건 실행됩니다.** 심지어 `try` 블록 내에서 `return`, `break`, `continue` 등으로 인해 **코드의 실행 흐름이 즉시 이동될 때에도 마찬가지**입니다.
47 |
48 | ```js
49 | for (let i of [1, 2, 3]) {
50 | try {
51 | if (i === 3) {
52 | break
53 | }
54 | } finally {
55 | console.log(`현재 i의 값: ${i}`)
56 | }
57 | }
58 | ```
59 |
60 | `finally` 블록은 `catch` 블록과도 같이 사용됩니다. 이 때 코드의 실행 순서를 정리해 보면 다음과 같습니다.
61 |
62 | - 에러가 안 났을 때: `try` - `finally`
63 | - 에러가 났을 때: `try` - **에러 발생** - `catch` - `finally`
64 |
65 | 아래 코드를 통해 코드의 실행 순서를 시험해보세요.
66 |
67 | ```js
68 | try {
69 | console.log('try')
70 | new Array(-1) // RangeError: Invalid array length
71 | } catch (e) {
72 | console.log('catch')
73 | } finally {
74 | console.log('finally')
75 | }
76 | ```
77 |
78 | ## 직접 에러 발생시키기
79 |
80 | `Error` 생성자와 `throw` 구문을 사용해서 프로그래머가 직접 에러를 발생시킬 수 있습니다.
81 |
82 | ```js
83 | const even = parseInt(prompt('짝수를 입력하세요'))
84 | if (even % 2 !== 0) {
85 | throw new Error('짝수가 아닙니다.')
86 | }
87 | ```
88 |
89 | ### 에러 원인 추적하기 (Error Cause)
90 |
91 | ES2022부터는 에러를 생성할 때 **`cause`** 옵션을 사용하여 에러의 원인이 된 다른 에러를 함께 저장할 수 있습니다. 이를 통해 **에러 체이닝(error chaining)**을 구현하여 디버깅을 더 쉽게 만들 수 있습니다.
92 |
93 | ```js
94 | try {
95 | // 데이터베이스 연결 시도
96 | connectToDatabase()
97 | } catch (originalError) {
98 | // 원본 에러를 cause로 포함하여 새로운 에러 발생
99 | throw new Error('데이터베이스 연결 실패', {cause: originalError})
100 | }
101 | ```
102 |
103 | `cause` 속성을 통해 원본 에러 정보를 추적할 수 있습니다:
104 |
105 | ```js
106 | try {
107 | try {
108 | throw new Error('원본 에러')
109 | } catch (err) {
110 | throw new Error('상위 레벨 에러', {cause: err})
111 | }
112 | } catch (err) {
113 | console.log(err.message) // '상위 레벨 에러'
114 | console.log(err.cause.message) // '원본 에러'
115 | }
116 | ```
117 |
118 | 간혹 프로그램을 작성하면서 **에러의 종류를 구분**해야 하거나 **에러 객체에 기능을 추가**해야 할 필요가 있습니다. 이런 경우에는 **`Error`를 상속받는 클래스**를 만들어서, `throw` 구문에서 이 클래스를 대신 사용할 수 있습니다.
119 |
120 | 예를 들어, 아래 `MyError` 클래스를 통해 에러 객체를 생성할 때 에러에 대한 상세 정보를 포함시키면, `catch` 블록 안에서 원상복구를 위해 이 값을 활용할 수 있습니다.
121 |
122 | ```js
123 | class MyError extends Error {
124 | constructor(value, ...params) {
125 | super(...params)
126 | this.value = value
127 | this.name = 'MyError'
128 | }
129 | }
130 |
131 | try {
132 | const even = parseInt(prompt('짝수를 입력하세요'))
133 | if (even % 2 !== 0) {
134 | throw new MyError(even, '짝수가 아닙니다.')
135 | }
136 | } catch (e) {
137 | if (e instanceof MyError) {
138 | console.log(e.value)
139 | }
140 | }
141 | ```
142 |
143 | ## 비동기식 코드에서의 예외 처리
144 |
145 | ### 비동기 콜백
146 |
147 | **비동기식으로 작동하는 콜백**의 **내부**에서 발생한 에러는, **콜백 바깥**에 있는 `try` 블록으로는 잡아낼 수 없습니다.
148 |
149 | ```js
150 | try {
151 | setTimeout(() => {
152 | throw new Error('에러!')
153 | })
154 | } catch (e) {
155 | console.error(e)
156 | }
157 | ```
158 |
159 | JavaScript 엔진은 에러가 발생하는 순간 **호출 스택을 되감는 과정**을 거칩니다. **이 과정 중에 `try` 블록을 만나야** 코드의 실행 흐름을 원상복구시킬 수 있습니다. 위 예제에서 `setTimeout`에 넘겨진 콜백에서 에러가 발생하면, 호출 스택을 따라 올라가도 `try` 블록을 만나는 것이 아니므로, 코드의 실행 흐름이 `catch` 블록으로 옮겨지지 않는 것입니다.
160 |
161 | 따라서, 위 예제의 `try` 블록을 비동기 콜백 내부에 작성해주어야 합니다.
162 |
163 | ```js
164 | setTimeout(() => {
165 | try {
166 | throw new Error('에러!')
167 | } catch (e) {
168 | console.error(e)
169 | }
170 | })
171 | ```
172 |
173 | ### Promise
174 |
175 | Promise 객체는 세 가지 상태를 가질 수 있습니다.
176 |
177 | - pending - Promise 객체에 결과값이 채워지지 않은 상태
178 | - fulfilled - Promise 객체에 결과값이 채워진 상태
179 | - **rejected - Promise 객체에 결과값을 채우려고 시도하다가 에러가 난 상태**
180 |
181 | Promise 객체가 rejected 상태가 되면, 이 Promise에 대해서는 `then` 메소드에 첫 번째 인수로 넘겨준 콜백이 실행되지 않고, **두 번째 인수로 넘겨준 콜백**이 대신 실행됩니다. 그리고 이 콜백에는 **에러 객체가 첫 번째 인수**로 주어집니다.
182 |
183 | ```js
184 | const p = new Promise((resolve) => {
185 | const even = parseInt(prompt('짝수를 입력하세요'))
186 | if (even % 2 !== 0) {
187 | throw new Error('짝수가 아닙니다.')
188 | } else {
189 | resolve(even)
190 | }
191 | })
192 |
193 | p.then(
194 | (even) => {
195 | return '짝수입니다.'
196 | },
197 | (e) => {
198 | return e.message
199 | },
200 | ).then(alert)
201 | ```
202 |
203 | 혹은, **`catch` 메소드**를 통해 에러 처리 콜백을 지정해줄 수도 있습니다.
204 |
205 | ```js
206 | p.then((even) => {
207 | return '짝수입니다.'
208 | })
209 | .catch((e) => {
210 | return e.message
211 | })
212 | .then(alert)
213 | ```
214 |
215 | 만약, `then` 메소드의 연쇄 안에서 에러가 발생하면, `try...catch` 구문과 유사하게 **처음 만나는 에러 처리 콜백으로 코드의 실행 흐름이 건너뛰는 결과**를 낳게 됩니다.
216 |
217 | ```js
218 | Promise.resolve()
219 | .then(() => {
220 | throw new Error('catch 메소드를 통해 예외 처리를 할 수 있습니다.')
221 | })
222 | .then(() => {
223 | console.log('이 코드는 실행되지 않습니다.')
224 | })
225 | .catch((e) => {
226 | return e.message
227 | })
228 | .then(console.log)
229 | ```
230 |
231 | ### 비동기 함수
232 |
233 | 앞에서 봤던 Promise 객체의 예외 처리 방식은, 일반적인 동기식 예외 처리 방식과 다르게 **콜백**을 사용하고 있어서 코드를 복잡하게 만드는 원인이 됩니다.
234 |
235 | 비동기 함수 내부에서는, **rejected 상태가 된 Promise 객체**를 동기식 예외 처리 방식과 동일하게 **`try...catch...finally` 구문으로 처리할 수 있습니다.**
236 |
237 | ```js
238 | async function func() {
239 | try {
240 | const res = await fetch('https://nonexistent-domain.nowhere')
241 | } catch (e) {
242 | console.log(e.message)
243 | }
244 | }
245 |
246 | func() // 출력 결과: Failed to fetch
247 | ```
248 |
249 | 단, Promise 객체에 대해 **`await` 구문**을 사용하지 않는 경우, 에러가 발생해도 `catch` 블록으로 코드의 실행 흐름이 이동하지 않는다는 사실을 기억하세요.
250 |
251 | ```js
252 | async function func() {
253 | try {
254 | fetch('https://nonexistent-domain.nowhere')
255 | } catch (e) {
256 | console.log(e.message)
257 | }
258 | }
259 |
260 | func() // 아무것도 출력되지 않습니다.
261 | ```
262 |
--------------------------------------------------------------------------------
/tests/check-html-links.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * HTML Link Checker
5 | *
6 | * Checks the built HTML artifacts for broken links by:
7 | * 1. Internal links - verifying file existence
8 | * 2. External links - checking HTTP status codes (not 4xx)
9 | */
10 |
11 | const fs = require('fs');
12 | const path = require('path');
13 | const https = require('https');
14 | const http = require('http');
15 |
16 | const PUBLIC_DIR = path.join(__dirname, '../public');
17 | const errors = [];
18 | const warnings = [];
19 | const checkedUrls = new Map(); // Cache for external URL checks
20 |
21 | // Get all HTML files
22 | function getAllHtmlFiles(dir) {
23 | const files = [];
24 |
25 | if (!fs.existsSync(dir)) {
26 | console.error(`❌ Directory ${dir} does not exist. Run 'yarn build' first.`);
27 | process.exit(1);
28 | }
29 |
30 | const items = fs.readdirSync(dir);
31 |
32 | for (const item of items) {
33 | const fullPath = path.join(dir, item);
34 | const stat = fs.statSync(fullPath);
35 |
36 | if (stat.isDirectory()) {
37 | files.push(...getAllHtmlFiles(fullPath));
38 | } else if (item.endsWith('.html')) {
39 | files.push(fullPath);
40 | }
41 | }
42 |
43 | return files;
44 | }
45 |
46 | // Extract all links from HTML
47 | function extractLinks(content) {
48 | // Match href="..." attributes
49 | const linkRegex = /href="([^"]+)"/g;
50 | const links = [];
51 | let match;
52 |
53 | while ((match = linkRegex.exec(content)) !== null) {
54 | const url = match[1];
55 | // Skip anchors and special protocols
56 | if (url && url !== '#' && !url.startsWith('mailto:') && !url.startsWith('tel:')) {
57 | links.push(url);
58 | }
59 | }
60 |
61 | return [...new Set(links)]; // Remove duplicates
62 | }
63 |
64 | // Check if URL is external
65 | function isExternalLink(url) {
66 | return url.startsWith('http://') || url.startsWith('https://');
67 | }
68 |
69 | // Check external URL with HTTP request
70 | function checkExternalUrl(url) {
71 | return new Promise((resolve) => {
72 | // Check cache first
73 | if (checkedUrls.has(url)) {
74 | resolve(checkedUrls.get(url));
75 | return;
76 | }
77 |
78 | const client = url.startsWith('https://') ? https : http;
79 |
80 | const options = {
81 | method: 'HEAD',
82 | timeout: 10000,
83 | headers: {
84 | 'User-Agent': 'Mozilla/5.0 (compatible; LinkChecker/1.0)'
85 | }
86 | };
87 |
88 | const req = client.request(url, options, (res) => {
89 | const result = {
90 | statusCode: res.statusCode,
91 | ok: res.statusCode !== 404 // Only fail on 404, allow other status codes
92 | };
93 | checkedUrls.set(url, result);
94 | resolve(result);
95 | });
96 |
97 | req.on('error', (err) => {
98 | const result = {
99 | statusCode: null,
100 | error: err.message,
101 | ok: true // Network errors are acceptable (timeouts, connection refused, etc.)
102 | };
103 | checkedUrls.set(url, result);
104 | resolve(result);
105 | });
106 |
107 | req.on('timeout', () => {
108 | req.destroy();
109 | const result = {
110 | statusCode: null,
111 | error: 'Timeout',
112 | ok: true // Timeouts are acceptable
113 | };
114 | checkedUrls.set(url, result);
115 | resolve(result);
116 | });
117 |
118 | req.end();
119 | });
120 | }
121 |
122 | // Resolve internal URL to file path
123 | function resolveInternalUrl(url, fromFile) {
124 | // Remove query strings and anchors
125 | const cleanUrl = url.split('?')[0].split('#')[0];
126 |
127 | if (!cleanUrl || cleanUrl === '/') {
128 | return path.join(PUBLIC_DIR, 'index.html');
129 | }
130 |
131 | // Handle absolute paths
132 | if (cleanUrl.startsWith('/')) {
133 | const filePath = path.join(PUBLIC_DIR, cleanUrl);
134 |
135 | // If it's a directory, check for index.html
136 | if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
137 | return path.join(filePath, 'index.html');
138 | }
139 |
140 | // If no extension, try adding .html
141 | if (!path.extname(filePath)) {
142 | return filePath + '.html';
143 | }
144 |
145 | return filePath;
146 | }
147 |
148 | // Handle relative paths
149 | const dir = path.dirname(fromFile);
150 | const resolvedPath = path.resolve(dir, cleanUrl);
151 |
152 | // If no extension, try adding .html
153 | if (!path.extname(resolvedPath)) {
154 | return resolvedPath + '.html';
155 | }
156 |
157 | return resolvedPath;
158 | }
159 |
160 | // Check a single HTML file
161 | async function checkFile(filePath) {
162 | const content = fs.readFileSync(filePath, 'utf-8');
163 | const links = extractLinks(content);
164 | const relPath = path.relative(PUBLIC_DIR, filePath);
165 |
166 | for (const link of links) {
167 | if (isExternalLink(link)) {
168 | // Check external link
169 | const result = await checkExternalUrl(link);
170 |
171 | if (!result.ok) {
172 | errors.push({
173 | file: relPath,
174 | issue: result.error ? `Failed to fetch: ${result.error}` : `HTTP ${result.statusCode}`,
175 | link: link,
176 | type: 'external'
177 | });
178 | }
179 | } else {
180 | // Check internal link
181 | const targetPath = resolveInternalUrl(link, filePath);
182 |
183 | if (!fs.existsSync(targetPath)) {
184 | errors.push({
185 | file: relPath,
186 | issue: 'File does not exist',
187 | link: link,
188 | resolvedTo: path.relative(PUBLIC_DIR, targetPath),
189 | type: 'internal'
190 | });
191 | }
192 | }
193 | }
194 | }
195 |
196 | // Main execution
197 | async function main() {
198 | console.log('🔍 Checking links in built HTML files...\n');
199 |
200 | const htmlFiles = getAllHtmlFiles(PUBLIC_DIR);
201 | console.log(`Found ${htmlFiles.length} HTML files to check\n`);
202 |
203 | for (const file of htmlFiles) {
204 | process.stdout.write(`Checking ${path.relative(PUBLIC_DIR, file)}...\r`);
205 | await checkFile(file);
206 | }
207 |
208 | console.log('\n');
209 |
210 | // Report results
211 | if (errors.length === 0) {
212 | console.log('✅ All links are valid!\n');
213 | console.log(` Checked ${checkedUrls.size} unique external URLs`);
214 | console.log(` Checked ${htmlFiles.length} HTML files\n`);
215 | process.exit(0);
216 | }
217 |
218 | // Separate internal and external errors
219 | const internalErrors = errors.filter(e => e.type === 'internal');
220 | const externalErrors = errors.filter(e => e.type === 'external');
221 |
222 | if (internalErrors.length > 0) {
223 | console.log(`❌ Found ${internalErrors.length} broken internal link(s):\n`);
224 | internalErrors.forEach((err, i) => {
225 | console.log(`${i + 1}. ${err.file}`);
226 | console.log(` Link: ${err.link}`);
227 | console.log(` Issue: ${err.issue}`);
228 | if (err.resolvedTo) {
229 | console.log(` Resolved to: ${err.resolvedTo}`);
230 | }
231 | console.log('');
232 | });
233 | }
234 |
235 | if (externalErrors.length > 0) {
236 | console.log(`❌ Found ${externalErrors.length} broken external link(s):\n`);
237 | externalErrors.forEach((err, i) => {
238 | console.log(`${i + 1}. ${err.file}`);
239 | console.log(` Link: ${err.link}`);
240 | console.log(` Issue: ${err.issue}`);
241 | console.log('');
242 | });
243 | }
244 |
245 | console.log(`Total errors: ${errors.length}`);
246 | console.log(`Checked ${checkedUrls.size} unique external URLs\n`);
247 |
248 | process.exit(1);
249 | }
250 |
251 | main().catch(err => {
252 | console.error('Unexpected error:', err);
253 | process.exit(1);
254 | });
255 |
--------------------------------------------------------------------------------
/content/pages/170-function.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 함수
3 | ---
4 |
5 | 프로그래밍에서의 함수란, **큰 프로그램을 잘게 쪼개어 특정 코드 뭉치를 반복해서 사용할 수 있도록 묶어놓은 코드 뭉치의 단위**를 말합니다. 함수를 어떻게 작성하느냐에 따라서 코드의 유지보수성과 가독성이 크게 달라집니다. 또 JavaScript의 함수는 굉장히 많은 기능을 갖고 있는데, 사실 함수의 성질을 모두 이해하면 프로그래밍 언어로서의 JavaScript를 전부 이해한거나 마찬가지라고 할 수 있을 정도입니다. 본 챕터에서는 함수의 기본적인 성질만을 다루고, 이어지는 챕터들에서 나머지 내용을 차근차근 다룰 것입니다.
6 |
7 | ## 함수의 구성 요소
8 |
9 | 두 값을 더하는 아주 간단한 함수를 정의해 보겠습니다.
10 |
11 | ```js
12 | function add(x, y) {
13 | const result = x + y
14 | return result
15 | }
16 | ```
17 |
18 | 위에서 `add`라는 **이름**을 갖는 함수를 정의했습니다. 괄호 안의 `x`와 `y`를 **매개변수(parameter)**라 하며, `return` 뒤에 오는 값을 **반환값(return value)**이라고 합니다.
19 |
20 | 함수를 정의했다면, 아래와 같이 함수 이름 뒤에 괄호를 붙여서 이 함수를 실행시킬 수 있습니다. 이를 함수의 **호출(function call)**이라고 합니다.
21 |
22 | ```js
23 | add(2, 3) // 5
24 | ```
25 |
26 | 여기서 괄호 안에 넘겨준 `2`, `3`을 **인수(argument)**라고 부릅니다.
27 |
28 | ### 실행 순서
29 |
30 | JavaScript는 기본적으로는 쓰여진 순서대로 실행되지만, 여러 가지 예외가 존재해서 코드의 실행 순서가 이리저리 옮겨다니기도 합니다. 함수 호출도 그 중 하나로, 함수 호출 코드를 만나면 코드의 실행 흐름이 호출된 함수의 내부로 옮겨갑니다. 함수가 값을 반환하면 다시 이전 위치부터 코드의 실행이 재개됩니다.
31 |
32 | ```js
33 | // 1 - 함수 정의
34 | function add(x, y) {
35 | return x + y // 3 - 함수 실행
36 | }
37 | // 2 - 함수 호출
38 | const result = add(2, 3)
39 | // 4 - 나머지 코드 실행
40 | console.log(result)
41 | ```
42 |
43 | 여기서 유의할 점은, 함수를 정의하는 것만으로는 함수 내부에 있는 코드가 실행되지 않는다는 것입니다. 함수 내부의 코드를 실행하려면, 반드시 함수를 호출해주어야 합니다.
44 |
45 | ### 매개변수와 인수
46 |
47 | 위 코드의 `x`와 `y`를 가지고 **매개변수**라고 합니다. 매개변수는 변수의 일종으로, 함수 호출 시마다 인수가 매개변수에 대입됩니다. 위의 코드에서 `add(2, 3)`과 같이 호출하면 매개변수 `x`에는 `2`가, `y`에는 `3`이 대입된 채로 나머지 코드가 실행됩니다.
48 |
49 | 여기서 주의할 점은 매개변수는 바깥에서 선언된 변수와는 관계없는 독립적인 변수라는 것입니다. 예를 들어, 함수 호출 시 인수가 들어갈 자리에 변수를 써주고, 함수 내부에서 매개변수에 새로운 값을 대입한다고 하더라도 인수로 써준 변수의 값이 변경되지 않습니다.
50 |
51 | ```js
52 | function reassign(x) {
53 | x = 2
54 | return x
55 | }
56 |
57 | const y = 1
58 | const result = reassign(y)
59 |
60 | console.log(y) // 1
61 | console.log(result) // 2
62 | ```
63 |
64 | 매개변수는 `let`으로 선언한 변수와 비슷하게 동작하지만 미묘하게 다른 점이 있습니다. 이에 대해서는 [값 더 알아보기](./220-value-in-depth)에서 자세히 다룹니다.
65 |
66 | ### 반환값
67 |
68 | `return` 구문은 함수의 반환값을 결정합니다. `return` 키워드 바로 다음에 오는 값이 함수 호출의 결과값으로 반환되며, 반환되는 즉시 함수 실행이 끝납니다.
69 |
70 | ```js
71 | function add(x, y) {
72 | return x + y
73 | console.log('이 부분은 실행되지 않습니다.')
74 | }
75 | add(1, 2) // 3
76 | // 3 외에 따로 출력되는 내용이 없습니다.
77 | ```
78 |
79 | 아래와 같이 `return` 뒤에 아무 값도 써주지 않거나, 아예 `return` 구문을 쓰지 않으면 함수는 `undefined`를 반환합니다.
80 |
81 | ```js
82 | function returnUndefined() {
83 | return
84 | }
85 | function noReturn() {}
86 | returnUndefined() // undefined
87 | noReturn() // undefined
88 | ```
89 |
90 | ## 스코프 (Scope)
91 |
92 | 함수의 매개변수를 비롯한, 모든 변수들은 특별한 성질을 갖습니다.
93 |
94 | ```js
95 | function add(x, y) {
96 | // 변수 `x`와 `y`가 정의됨
97 | return x + y
98 | }
99 | add(2, 3)
100 | console.log(x) // 에러!
101 | ```
102 |
103 | 이렇게, 매개변수와 같이 함수 안에서 정의된 변수는 함수 바깥에서는 접근할 수 없기 때문에 함수 안에서만 사용할 수 있습니다. 즉, **변수는 코드의 일정 범위 안에서만 유효하다**는 성질이 있는 것입니다. 이렇게, 특정 변수가 유효한 코드 상의 **유효 범위**를 가지고 **스코프(scope)**라고 합니다.
104 |
105 | 위 예제에서의 `x`와 `y`는 함수 `add`의 내부 코드 안에서만 접근할 수 있습니다. 즉, 매개변수는 **함수 스코프**를 갖습니다.
106 |
107 | 스코프의 성질이 미묘해서, 이를 잘 이해하지 못하면 코드를 작성하거나 읽기 어려울 수 있습니다. 아래에서 스코프의 몇 가지 성질들을 살펴보겠습니다.
108 |
109 | ### 스코프 연쇄 (Scope Chain)
110 |
111 | 함수 내부 코드에서, 매개변수 혹은 그 함수 안에서 정의된 변수만 사용할 수 있는 것은 아닙니다.
112 |
113 | ```js
114 | const five = 5
115 | function add5(x) {
116 | return x + five // 바깥 스코프의 `five` 변수에 접근
117 | }
118 | add5(3) // 8
119 | ```
120 |
121 | `add5` 함수의 `return` 구문에서 함수 바깥에 있는 변수 `five`의 값을 가져와 사용했습니다. 이는 심지어 함수가 여러 겹 중첩(nested)되어 있다고 하더라도 가능합니다.
122 |
123 | ```js
124 | const five = 5
125 | function add5(x) {
126 | function add(y) {
127 | return x + y
128 | }
129 | return add(five)
130 | }
131 | add5(3) // 8
132 | ```
133 |
134 | 코드의 실행 흐름이 식별자에 도달하면, 먼저 그 식별자와 같은 이름을 갖는 변수를 현재 스코프에서 찾아보고, 변수가 존재하면 그것을 그대로 사용합니다. 만약 현재 스코프에서 변수를 찾지 못하면 바로 바깥쪽 스코프에서 변수를 찾아봅니다. 있으면 사용하고 없으면 바깥쪽 스코프로 올라가서 다시 찾아보는, 이 과정이 되풀이됩니다. 이런 과정을 **스코프 연쇄(scope chain)**라 하고, 이 과정은 가장 바깥쪽에 있는 스코프를 만날 때까지 반복됩니다. 가장 바깥쪽 스코프까지 찾아봤는데도 같은 이름의 변수를 찾지 못하면, 그제서야 에러가 발생됩니다.
135 |
136 | 가장 바깥에 있는 스코프를 최상위 스코프(top-level scope) 혹은 **전역 스코프(global scope)**라고 부릅니다. 위 코드에서 `five`가 정의된 스코프가 바로 전역 스코프입니다.[^1]
137 |
138 | ### 변수 가리기 (Variable Shadowing)
139 |
140 | 단일 스코프에서는 같은 이름을 갖는 서로 다른 변수가 존재할 수 없습니다. 하지만 스코프 연쇄가 일어나면 이야기가 달라집니다. 아래의 코드에서는 `x`라는 이름을 갖는 변수가 세 번 정의되었습니다.
141 |
142 | ```js
143 | const x = 3
144 |
145 | function add5(x) {
146 | // `x`라는 변수가 다시 정의됨
147 | function add(x, y) {
148 | // `x`라는 변수가 다시 정의됨
149 | return x + y
150 | }
151 | return add(x, 5)
152 | }
153 |
154 | add5(x)
155 | ```
156 |
157 | 위와 같이, 바깥쪽 스코프에 존재하는 변수와 같은 이름을 갖는 변수를 안쪽 스코프에서 재정의할 수 있습니다. 그렇게 되면 안쪽 스코프에서는 바깥쪽 스코프에 있는 이름이 **무시**됩니다. 이런 현상을 **변수 가리기(variable shadowing)**라고 합니다.
158 |
159 | ### 어휘적 스코핑 (Lexical Scoping)
160 |
161 | 스코프는 **코드가 작성된 구조**에 의해서 결정되는 것이지, **함수 호출의 형태**에 의해 결정되는 것이 아닙니다. 예를 들어 봅시다.
162 |
163 | ```js
164 | function add5(x) {
165 | const five = 5
166 | return add(x)
167 | }
168 |
169 | add5(3) // 8
170 |
171 | function add(x) {
172 | return five + x // ReferenceError: five is not defined
173 | }
174 | ```
175 |
176 | `add`라는 함수가 `add5`라는 함수 안에서 **호출**되었다고 해서, `add` 함수 내부에서 `add5` 함수의 스코프 안에 있는 변수에 접근할 수 있는 것은 아닙니다. 스코프는 코드가 **작성**된 구조에 의해 결정되는 성질입니다. 위 코드를 동작시키려면, 아래와 같이 작성해야 합니다.
177 |
178 | ```js
179 | function add5(x) {
180 | const five = 5
181 | function add(x) {
182 | return five + x
183 | }
184 | return add(x)
185 | }
186 | ```
187 |
188 | ### 스코프의 종류
189 |
190 | 이 챕터에서는 매개변수에 대한 함수 스코프를 중점적으로 다루었는데, 사실 스코프의 종류가 더 있습니다. 특히, `let`과 `const`로 선언된 변수는 함수 스코프가 아니라 조금 다른 종류의 스코프를 가집니다. 이에 대해서는 [값 더 알아보기](./220-value-in-depth)에서 자세히 다룹니다.
191 |
192 | ## 값으로서의 함수
193 |
194 | JavaScript에서는 함수도 값입니다!
195 |
196 | ```js
197 | function add(x, y) {
198 | return x + y
199 | }
200 |
201 | const plus = add
202 | plus(1, 2) // 3
203 | ```
204 |
205 | 다른 값과 마찬가지로, 함수를 선언한 뒤 변수에 대입해서 호출할 수도 있고, 혹은 배열이나 객체에 넣을 수도 있고, 심지어는 함수를 다른 함수에 인수로 넘기거나, 함수에서 함수를 반환할 수도 있습니다.
206 |
207 | ```js
208 | // 함수를 배열이나 객체에 넣기
209 | function add(x, y) {
210 | return x + y
211 | }
212 | ;[add]
213 | {
214 | addFunc: add
215 | }
216 |
217 | // 함수를 인수로 넘기기
218 | function isEven(x) {
219 | return x % 2 === 0
220 | }
221 | ;[1, 2, 3, 4, 5].filter(isEven) // [2, 4]
222 |
223 | // 함수에서 함수 반환하기
224 | function createEmptyFunc() {
225 | function func() {}
226 | return func
227 | }
228 | ```
229 |
230 | 컴퓨터 과학 분야에서 사용되는 용어 중에 [1급 시민(First-Class Citizen)](https://ko.wikipedia.org/wiki/%EC%9D%BC%EA%B8%89_%EA%B0%9D%EC%B2%B4)이라는 특이한 용어가 있습니다. 값으로 사용할 수 있는 JavaScript의 함수는 1급 시민입니다. 1급 시민인 함수를 줄여서 **1급 함수**라 부르기도 합니다.
231 |
232 | JavaScript에서는 1급 함수의 성질을 여러 가지 흥미로운 방식으로 활용할 수 있습니다. 이에 대한 자세한 내용은 [함수형 프로그래밍](./255-fp)에서 다룹니다.
233 |
234 | ## 익명 함수 (Anonymous Function)
235 |
236 | JavaScript에서 함수를 선언할 때 꼭 **이름**을 붙여주어야 하는 것은 아닙니다. 아래와 같이 이름을 붙이지 않은 함수를 가지고 **익명 함수(anonymous function)**, 혹은 함수 리터럴(function literal)이라고 합니다.
237 |
238 | ```js
239 | // 두 수를 더해서 반환하는 익명 함수
240 | function(x, y) {
241 | return x + y;
242 | }
243 | // 위의 익명 함수는 이름이 없어서 이름을 가지고 호출을 할 수 없습니다.
244 |
245 | // 호출을 하려면 변수에 저장한 후에 변수의 이름을 통해 호출해야 합니다.
246 | const add = function(x, y) {
247 | return x + y;
248 | }
249 | add(1, 2); // 3
250 | ```
251 |
252 | 익명 함수는 **함수를 만든 쪽이 아니라 다른 쪽에서 그 함수를 호출할 때** 많이 사용됩니다. 대표적인 경우는 **함수를 인수로 넘겨줄 때**입니다. 예를 들어, 배열의 `filter` 메소드에 필터링할 조건을 표현하는 함수를 넘겨주면, `filter` 메소드쪽에서 배열 각 요소에 대해 함수를 호출한 뒤, `true`를 반환한 요소만을 필터링해서 반환합니다.
253 |
254 | ```js
255 | ;[1, 2, 3, 4, 5].filter(function (x) {
256 | return x % 2 === 0
257 | }) // [2, 4]
258 | ```
259 |
260 | ## 화살표 함수 (Arrow Function)
261 |
262 | 함수 정의를 위한 새로운 표기법인 화살표 함수(arrow function)은 ES2015에서 도입되었습니다.
263 |
264 | ```js
265 | // 여기에서 x + y 는 **바로 반환됩니다.**
266 | const add = (x, y) => x + y
267 | ```
268 |
269 | ```js
270 | // 바로 반환시키지 않고 function 키워드를 통한 함수 정의처럼 여러 구문을 사용하려면 curly braces({...}) 로 둘러싸주어야 합니다.
271 | // `=>` 다음 부분을 중괄호로 둘러싸면, 명시적으로 `return` 하지 않는 한 아무것도 반환되지 않습니다.
272 | const add = (x, y) => {
273 | const result = x + y
274 | return result
275 | }
276 | ```
277 |
278 | ```js
279 | // 매개변수가 하나밖에 없다면, 매개변수 부분의 괄호를 쓰지 않아도 무방합니다.
280 | const negate = (x) => !x
281 | ```
282 |
283 | 화살표 함수는 표기법이 간단하기 때문에 익명 함수를 다른 함수의 인수로 넘길 때 주로 사용됩니다.
284 |
285 | ```js
286 | ;[1, 2, 3, 4, 5].filter((x) => x % 2 === 0)
287 | ```
288 |
289 | 일반적인 함수와 화살표 함수는 표기법에서만 다른 것이 아니고, 몇 가지 미묘한 차이점이 있습니다. 이에 대해서는 [함수 더 알아보기](./230-function-in-depth) 챕터에서 자세히 다룹니다.
290 |
291 | [^1]: 다만, JavaScript 파일의 가장 바깥쪽에서 선언된 변수가 항상 전역 스코프를 갖는 것은 아닙니다. 어떤 스코프를 가질 지는 JavaScript 파일이 사용되는 방식에 따라 달라지는데, 이에 대해서는 [모듈]() 챕터에서 자세히 다룹니다.
292 |
--------------------------------------------------------------------------------