Node.js와 브라우저의 차이점
Node.js는 DOM이 존재하지 않는다는 것이 브라우저와의 가장 큰 차이점 중 하나다. window와 document 객체 역시 존재하지 않는다는 것인데, Node.js에서는 전역 변수를 다룰 때 window 대신 global 키워드를 사용한다. 또한 fetch API도 존재하지 않는데, 서버에 대한 요청 역시 다른 방법을 사용한다.
프론트엔드에서 Node.js가 가지는 의미
대부분 Node.js가 백엔드 환경이라 생각하는 경우가 많다. 이는 서버를 만들 때 Node.js를 사용할 수 있는 것이 사실이고, 또한 대중적으로 알려진 내용들이 많기 때문이다. 하지만 프론트엔드 개발에서도 다양한 Node.js 모듈들과 npm 생태계를 활용하고 있으며, cli에서 배포 등 자동화를 위한 과정을 처리하는 경우가 많기 때문에 이에 대한 학습은 필수적이다.
package.json
package.json은 말 그대로, 어떤 프로젝트에 대해서 해당 프로젝트의 package에 대한 설정을 하는 파일이라 생각하면 된다. 프로젝트의 설명, 특정 배포의 프로젝트 버전, 라이센스의 정보, 구성 데이터와 같은 메타 데이터를 포함한다. 보통은 프로젝트의 루트 디렉토리에 파일이 존재한다. 다음은 Vue.js 애플리케이션에서 추출된 package.json의 예이다.
{
"name": "test-project",
"version": "1.0.0",
"description": "A Vue.js project",
"main": "src/main.js",
"private": true,
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"unit": "jest --config test/unit/jest.conf.js --coverage",
"test": "npm run unit",
"lint": "eslint --ext .js,.vue src test/unit",
"build": "node build/build.js"
},
"dependencies": {
"vue": "^2.5.2"
},
"devDependencies": {
"autoprefixer": "^7.1.2",
"babel-core": "^6.22.1",
"babel-eslint": "^8.2.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-jest": "^21.0.2",
"babel-loader": "^7.1.1",
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-plugin-transform-vue-jsx": "^3.5.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"chalk": "^2.0.1",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"eslint": "^4.15.0",
"eslint-config-airbnb-base": "^11.3.0",
"eslint-friendly-formatter": "^3.0.0",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-loader": "^1.7.1",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-vue": "^4.0.0",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^1.1.4",
"friendly-errors-webpack-plugin": "^1.6.1",
"html-webpack-plugin": "^2.30.1",
"jest": "^22.0.4",
"jest-serializer-vue": "^0.3.0",
"node-notifier": "^5.1.2",
"optimize-css-assets-webpack-plugin": "^3.2.0",
"ora": "^1.2.0",
"portfinder": "^1.0.13",
"postcss-import": "^11.0.0",
"postcss-loader": "^2.0.8",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"uglifyjs-webpack-plugin": "^1.1.1",
"url-loader": "^0.5.8",
"vue-jest": "^1.0.2",
"vue-loader": "^13.3.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.5.2",
"webpack": "^3.6.0",
"webpack-bundle-analyzer": "^2.9.0",
"webpack-dev-server": "^2.9.1",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
},
"browserslist": ["> 1%", "last 2 versions", "not ie <= 8"]
}
위와 같이 프로젝트(패키지)에 대한 전반적인 정보를 가지고 있는데, 이 데이터들 중에는 우리가 프로그램을 실행시키기 위해서 필요한 모듈들에 대해 명시하거나, 실행시키는 방법, 또는 테스트 하는 방법들 역시 명시되어 있다.
- version: 현재 패키지의 버전을 명시
- name: 응용 프로그램 / 패키지 이름을 설정
- description: 응용 프로그램 / 패키지에 대한 간략한 설명
- main: 응용 프로그램의 진입 점 설정
- scripts: 실행할 수 있는 노드 스크립트 세트를 정의. package.json의 여러 기본 제공 스크립트와 이벤트, 임의의 스크립트 등을 지원한다. npm run-script <stage> 또는 npm run <stage>로 실행이 가능하다.
npm run-script <command>
// 예) npm run-script start
- dependencies:
프로그램의 실행과 무관. 오로지 개발을 위해 필요한 의존성 모듈 (설치된 패키지 목록을 종속성으로 설정). 런타임에서 계속 쓰이는 라이브러리 들이라면 dependencies로 들어간다. - devDependencies:
프로젝트를 개발하는 환경에서 필요한 모듈들 (개발 종속성으로 설치된 패키지 목록을 설정). 만약 개발 시에 개발자에게 필요한 라이브러리들이라면 devDependencies로 들어간다.
참고: nodejs.dev/learn/the-package-json-guide
# 화살표 함수
이전에 기본적으로 사용하던 함수 표현식의 형태는 다음과 같다.
const sub = function(x,y){
return x-y;
}
같은 함수를 화살표 함수로 표현하면 다음과 같이 나타낼 수 있다.
const sub = (x,y) => {
return x-y;
}
화살표 함수는 function 키워드를 화살표로 축약시켜 나타낸 것이다. 본문에 return문만 존재한다면, return도 생략 가능하다. 단 return문을 생략하는 경우에는 중괄호를 사용하면 안된다. 화살표함수에서 리턴이 생략된 케이스의 표현에 대해서는 다음과 같이 정리가 가능하다.
// 정상적으로 작동하는 케이스
const sub = (x,y) => x-y;
const sub = (x,y) => (x+y);
// 정상적으로 작동하지 않고 undefined를 리턴
const sub = (x,y) => {x+y}
화살표 함수는 클로저를 표현할 때 더욱 강력하다. 일반적인 함수로 표현된 클로저의 예는 다음과 같다.
const sub = function(x){
return function(y){
return x-y;
}
}
sub(7)(5) // 2
위와 같은 클로저 함수를 다시 화살표 함수로 표현하면 다음과 같이 작성할 수 있다.
// (1) function 키워드를 없앤다.
const sub = (x) => {
return (y) => {
return x-y;
}
}
// (2) return을 생략하고, return만 존재했으므로 중괄호도 없앤다.
const sub = (x) => {
return y => x-y;
}
// (3) 마지막 return도 생략해준다.
const sub = x => y => x-y;
# Spread/Rest
- Spread: 배열을 풀어 인자로 전달 또는 각각의 요소로 넣는 경우 사용.
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));
// expected output: 6
- Rest: 정해지지 않은 인수를 배열로 형태로 나타나게 한다. 파라미터의 수가 가변적인 경우 유용하게 사용될 수 있다.
function sum(...theArgs) {
return theArgs.reduce((previous, current) => {
return previous + current;
});
}
console.log(sum(1, 2, 3));
// expected output: 6
console.log(sum(1, 2, 3, 4));
// expected output: 10
참고: developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Spread_syntax.
developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/rest_parameters
# 구조 분해 할당
- 배열을 분해
const array = [ 'never','really','over' ];
const [first,second] = array;
console.log(first); // 'never'
console.log(second); // 'really'
const arr = [];
function newArr([first,second]){
arr.push(second);
arr.push(first);
}
console.log(arr); // ['really','never']
- spread 또는 rest를 배열 / 객체의 분해에 적용
// 배열
const array = ['la', 'la', 'la', 'how', 'it', 'goes'];
const [first,second,third, ...rest] = array;
console.log(first); // 'la'
console.log(rest); // ['how', 'it', 'goes'];
// 객체
const user = { name: 'JJY', major: 'EE', gender: 'female' };
function userInfo({name, major, gender}){
return `user ${name} majored in ${major}.`;
}
console.log(userInfo(user));
// `JJY majored in EE.`
=> 구조 분해는 예시나 문제를 많이 보고 풀어봐야 익숙해질 듯 하다.
참고: developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
# CommonJS의 도입배경과 Module, exports, require
자바스크립트를 브라우저만이 아니라 범용적으로 사용하기 위해 도입하게 된 것이 바로 모듈화 작업이다. 이런 모듈화를 이끄는 여러 가지 중의 하나가 바로 CommonJS이다. 기존 브라우저용으로만 취급되던 자바스크립트의 문제는 호환되는 표준 라이브러리가 없고, 데이터베이스에 연결할 표준 인터페이스가 없으며 의존성 문제를 해결하는 공통 패키지의 모듈 저장소가 필요하다는 점 등 핵심적인 문제들이 다수 존재했다. 이러한 문제점들은 모듈화를 통해 해결되었는데, 이 '모듈화'는 관련된 코드들을 하나의 코드 단위로 캡슐화 하는 과정을 말한다.
- 스코프: 모든 모듈은 자신만의 독립적인 실행 영역이 존재해야함. => 전역변수와 지역변수를 분리하는 것이 중요하다.
- 정의: 모듈 정의는 exports 객체를 이용. => 각각의 모듈끼리 정보 교환이 필요할 때, 해당 js 파일의 함수를 외부로 공개한다.
- 사용: 모듈 사용은 require 함수를 이용. => exports를 통해 공개된 함수를 다른 모듈에서 이용하려면 require() 함수가 이용된다.
그런데 모든 파일이 로컬 디스크에 있어 바로 불러올 수 있는 경우에는 크게 문제가 없지만, 브라우저에서는 필요한 모듈에 대해 모두 내려받기 전까지 다른 일을 할 수 없다는 단점이 있다. 이를 해결하기 위해서 동적으로 script 태그를 삽입하는 방식을 사용한다.
# module.exports vs exports
{ } | << | module.exports | << | exports |
module.exports, exports는 모두 같은 object를 바라보지만, 최종적으로 리턴되는 값은 module.exports이다. exports는 call by reference로 module.exports를 바라보고 있다. 위와 같이 서로 같은 객체를 바라보고 있는 경우에는 exports에 우리가 추가한 내용이 module.exports에도 추가된다. 하지만 만약 exports에 다른 객체를 할당하게 된다면, module.exports의 객체와 달라지므로 영향을 줄 수 없게 되므로 주의해야한다.
참고: d2.naver.com/helloworld/12864
공부해야 할 내용들
클래스와 인스턴스
- new 키워드의 사용법
- class 키워드의 사용법
- 현실 모델을 바탕으로 클래스와 메소드의 속성 디자인
this, call, apply, bind와 같은 함수 메소드의 이해