seoyyyy-dev
1주차 - 자바스크립트 기본지식 본문
객체가 아닌 다른 모든 타입을 의미, 원시타입은 메서드를 갖지 않는다.
- boolean
- 참(true)과 거짓(false)만을 가질 수 있는 데이터 타입이며 주로 조건문에서 많이 쓰인다.
- true, false 같은 boolean 형의 값 외에도 조건문에서 마치 true와 false 처럼 취급되는 truthy, falsy한 값이 존재한다.
값 타입 설명 false Boolean false는 대표적인 falsy한 값 0, -0, 0n, 0x0n Number, BigInt 0은 부호나 소수점 유무에 상관 없이 falsy한 값 NaN Number Not a Number 라는 뜻으로 falsy한 값 '', "", `` String 공백인 빈 문자열은 falsy한 값 null null null은 falsy한 값 undefined undefined undefined는 falsy한 값 {},[] 객체,배열 객체, 배열은 값의 존재와 상관없이 truthy한 값
- null
- 아직 값이 없거나 비어 있는 값을 표현할 때 사용한다.
- null은 다른 원시값과 다르게 typeof로 확인했을 때 해당 타입이 아닌 'object'로 결과가 반환된다.
typeof null === 'object' // true ?
- undefined
- 선언한 후 값을 할당하지 않은 변수 또는 값이 주어지지 않은 인수에 자동으로 할당되는 값이다.
let foo typeof foo === 'undefined' // true function bar(hello) { return hello } typeof bar() === 'undefined' // true
- number
- −(2^53−1) 과 2^53−1 사이의 값을 저장할 수 있다.
- bigint가 등장하기 전까지는 이 범위 외의 값들을 다루기 어려웠다.
const maxInteger = Math.pow(2,53); maxInteger - 1 === Number.MAX_SAFE_INTEGER // true const minInteger = -(Math.pow(2,53) - 1); minInteger === Number.MIN_SAFE_INTEGER // true
- 2진수, 8진수, 16진수 등 별도의 타입을 제공하지 않아 각 진수별로 값을 표현해도 모두 10진수로 해석되어 동일한 값으로 표시된다.
const 이진수_2 = 0b10 // 2진수(binary) 2 이진수_2 === 2 // true const 팔진수_8 = 0o10 // 8진수(octal) 8 팔진수_8 === 8 // true const 십육진수_16 = 0x10 // 16진수(hexadecimal) 16 십육진수_16 === 16 // true
- bigint
- number가 다룰 수 있는 숫자 크기의 제한을 극복하기 위해 ES2020에서 새롭게 나옴
9007199254740992 === 9007199254740993 // 더이상 다룰 수 없는 크기이기 때문에 서로 다른 숫자지만 true가 나옴 const maxInteger2 = Number.MAX_SAFE_INTEGER maxInteger2 + 5 === maxInteger2 + 6 // true const bigInt1 = 9007199254740995n // 끝에 n을 붙이면 bigInt const bigInt2 = BigInt('9007199254740995') // BigInt 함수 사용시에도 bigInt const number = 9007199254740992 const bigint = 9007199254740992n typeof number // number typeof bigint // bigint number == bigint // true number === bigint // false(타입이 달라 false 반환)
- number가 다룰 수 있는 숫자 크기의 제한을 극복하기 위해 ES2020에서 새롭게 나옴
- string
- 텍스트 타입의 데이터를 저장하기 위해 사용
- '', "", ``로 표현할 수 있다.
- ``(백틱)을 사용한 문자열을 템플릿 리터럴(template literal)이라고 하며 같은 문자열을 반환하지만 줄바꿈이 가능하고 문자열 내부에 표현식을 쓸 수 있다.
const longText = ` 안녕하세요. `
- 자바스크립트의 문자열은 원시타입이며 한번 문자열이 생성되면 변경이 불가능하다.
const foo = 'bar' console.log(foo[0]) // b foo[0] = 'a' console.log(foo[0]) // b -> 변경되지 않음
- symbol
- ES6 에서 새롭게 추가된 7번째 타입으로 중복되지 않는 어떤 고유한 값을 나타내기 위해 만들어짐
- 심벌은 심벌함수(Symbol())를 이용해서만 만들 수 있다.
const key = Symbol('key') const key2 = Symbol('key') key === key2 // false <- 같은 값이어도 동일한 값으로 인정하지 않음! // 동일한 값을 사용하기 위해서는 Symbol.for 을 활용 Symbol.for('hello') === Symbol.for('hello') // true
7가지 원시타입 이외의 모든 것. 즉 자바스크립트를 이루고 있는 대부분의 타입이며 여기에 배열, 함수, 정규식, 클래스 등이 포함된다. 객체 타입(object type)은 참조를 전달한다고 해 참조 타입(reference type)이라고도 불린다.
typeof [] === 'object' // true
typeof {} === 'object' // true
function hello() {}
typeof hello === 'function' // true
const hello1 = function() {
}
const hello2 = function() {
}
hello1 === hello2 // false <- 함수의 내용이 같아보여도 참조가 다르기 때문에 false가 반환된다.
원시타입
let hello = 'hello world'
let hi = hello
hello === hi // true
let hello2 = 'hello world'
hello === hello2 // true
객체타입
var hello = {
greet: 'hello, world',
}
var hi = {
greet: 'hello, world',
}
hello === hi // false <- 객체 동등 비교시 false
hello.greet === hi.greet // true <- 원시값인 내부 속성 비교 시 동일
hi = hello
hi === hello // true <- 객체의 담긴 주소가 같아져 true를 반환
1.3 자바스크립트의 또 다른 비교 공식, Ojbect.is
Object.is는 두 개의 인 수를 받아 동일한지 확인하고 반환하는 메서드이다.
Object.is는 ES6에서 새로 도입된 비교 문법으로 동등 비교(===) 가 가지는 한계를 극복하기 위해 만들어 졌다.
//원시타입 비교 시
-0 === +0 // true
Object.is(-0, +0) // false
Number.NaN === NaN // false
Object.is(Number.NaN, NaN) // true
NaN === 0 / 0 // false
Object.is(NaN, 0 / 0) // true
// 객체타입 비교 시
Object.is({}, {}) // false
const a = {
hello: 'hi',
}
const b = a
Object.is(a,b) // true
a === b // true
1.4 리액트에서의 동등 비교
리액트에서 사용하는 동등 비교는 ==나 ===가 아닌 Object.is 이다. 리액트에서는 이 Object.is를 기반으로 동등 비교를 하는 shallowEqual 이라는 함수를 만들어서 사용한다.
// Object.is는 참조가 다른 객체에 대한 비교가 불가능
Object.is({ hello: 'world' }, { hello: 'world' }) // false
// 리액트에서 구현한 shallowEqual은 객체의 1 depth는 비교가 가능
shallowEqual({ hello: 'world' }, { hello: 'world' }) // true
// 그러나 2 depth 까지 가면 이를 비교할 방법이 없어 false를 반환
shallowEqual({ hello: { hi: 'world' } }, { hello: { hi: 'world' } }) // false
shallowEqual는 1 depth의 얕은 비교까지만 가능한데 이는 리액트에서 사용하는 JSX props는 객체이고, 여기 있는 props만 일차적으로 비교하면 되기 때문이다.
type Props = {
hello: string
}
function HelloComponent(props: Props) {
return <h1>{props.hello}</h1>
}
function App() {
return <HelloComponent hello="hi!">
}
위 코드의 props는 객체이고, 기본적으로 리액트는 props에서 꺼내온 값을 기준으로 렌더링을 수행하기 때문에 일반적인 케이스에서는 얕은 비교만으로 충분하다.
작업을 수행하거나 값을 계산하는 등의 과정을 표현하고 이를 하나의 블록으로 감싸서 실행 단위로 만들어 놓은 것
function sum(a, b) {
return a + b
}
sum(10, 24) // 34
리액트에서 컴포넌트를 만드는 함수도 이러한 기초적인 형태를 따른다.
function Component(props) {
return <div>{props.hello}</div>
}
function add(a,b) {
return a + b
}
add(10, 24) // 34
const sum = function (a, b) {
return a + b
}
sum(10, 24) // 34
✔️ 함수 표현식과 함수 선언식의 차이는 호이스팅(hoisting) 여부이다. 호이스팅은 함수 선언문이 마치 코드 맨 앞단에 작성된 것처럼 작동하는 자바스크립트의 특징을 의미한다. 함수가 선언되고 나서 함수가 호출되게 하고 싶다면 표현식을, 함수를 자유롭게 선언하고 어디서든 자유롭게 호출하고 싶다면 선언식을 사용한다.
const add = new Function('a', 'b', 'return a + b')
add(10, 24) // 34
const add = (a, b) => {
return a + b
}
const add = (a, b) => a + b
✔️ 화살표 함수와 기존 함수 생성 방식의 차이점
- 화살표 함수에서는 constructor를 사용할 수 없다.
const Car = (name) => {
this.name = name
}
const myCar = new Car('하이') // TypeError: Car is not a constructor
- 화살표 함수에는 arguments가 존재하지 않는다.
function hello() {
console.log(arguments)
}
// arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
hello(1, 2, 3)
const hi = () => {
console.log(arguments)
}
// Uncaught ReferenceError: arguments is not defined
hi(1, 2, 3)
- this 바인딩이 일반 함수와 다르게 작동한다. 화살표 함수 내부에서 this 참조 시 상위 스코프의 this를 그대로 따르게 된다.
- 즉시 실행 함수는 한 번 선언하고 호출된 이후부터는 더 이상 재호출이 불가능하며 일반적으로 함수에 이름을 붙이지 않는다.
(function (a, b) {
return a + b
})(10 + 24) //34
((a,b) => {
return a + b
},)(10,24) //34
// 함수를 매개변수로 받는 고차 함수 Array.prototype.map
const doubledArray = [1, 2, 3].map((item) => item * 2)
doubledArray // [2, 4, 6]
// 함수를 반환하는 고차 함수
const add = function (a) {
// a가 존재하는 클로저를 생성
return function (b) {
// b를 인수로 받아 두 합을 반환하는 또 다른 함수를 생성
return a + b
}
}
add(1)(3) // 4
- 함수 내의 작동으로 인해 함수 외부에 최대한 영향을 미치지 않도록 한다.
- 하나의 함수에서 너무나 많은 일을 하지 않게 해라.(너무 길어지지 않도록)
- 함수는 하나의 일만 잘하면 된다.(Do One Thing and Do It Well)
- 나중에 배울 useEffect와 같은 부수효과를 일으키는 함수에서 콜백 함수에 이름을 붙여주면 굳이 코드를 유심히 보지 않더라도 어떤 일을 하는지, 어떻게 작동하는지 알아채는데 도움이 될 것이다.
useEffect(function apiRequest() {
/// ... do something
}, [])
- 특정한 객체를 만들기 위한 일종의 템플릿과 같은 개념
- 특정한 형태의 객체를 반복적으로 만들기 위해 사용되는 것
class Car {
// 생성자 - 단 하나만 존재할 수 있으며 생략 가능
constructor(name) {
this.name = name // 프로퍼티 - 클래스로 인스턴스를 생성할 때 내부에 정의할 수 있는 속성 값
}
// (인스턴스)메서드
honk() {
console.log(`${this.name}이 경적을 울립니다!`)
}
// 정적 메서드
static hello() {
console.log('저는 자동차 입니다.')
}
// setter
set age(value) {
this.carAge = value
}
// getter
get age() {
return this.carAge
}
...
}
// car 객체 생성
const myCar = new Car('자동차')
// 메서드 호출
myCar.honk()
// 정적 메서드 직접 호출
Car.hello()
// 정적 메서드는 클래스로 만든 객체에서는 호출 불가
myCar.hello() // TypeError: myCar.hello is not a function
// setter를 만들면 값 할당 가능
myCar.age = 32
// getter로 값 가져오기 가능
console.log(myCar.age, myCar.name) // 32 자동차
✔️ 상속(extends)
- 기존 클래스를 상속받아서 자식 클래스에서 이 상속받은 클래스를 기반으로 확장하는 개념
class Car {
constructor(name) {
this.name = name
}
honk() {
console.log(`${this.name} 경적을 울립니다!`)
}
}
class Truck extends Car {
constructor(name) {
// 부모 클래스의 constructor를 호출
super(name)
}
load() {
console.log('짐을 싣습니다.')
}
}
const myCar = new Car('자동차')
myCar.honk() // 자동차 경적을 울립니다!
const truck = new Truck('트럭')
truck.honk() // 트럭 경적을 울립니다!
truck.load() // 짐을 싣습니다!
함수와 함수가 선언된 어휘적 환경(LexicalScope)의 조합이며 "선언된 어휘적 환경"이란 변수가 코드 내부에서 어디서 선언되었는지를 뜻함
- 이 스코프에서 변수를 선언하면 어디서든 호출할 수 있다.
- 브라우저 환경에서는 window, Node.js 환경에서 global 이 객체에 전역 레벨에서 선언한 스코프가 바인딩 된다.
var global = 'global scope' // 전역 스코프에 선언함
function hello() {
console.log(global)
}
console.log(global)
hello()
console.log(global === window.global) // true
- 다른 언어와 다르게 자바스크립트는 기본적으로 함수 레벨 스코프를 따르는데 즉 {} 블록이 스코프 범위를 결정하지 않는다.
if(true) {
var global = 'global scope'
}
console.log(global) // 'global scope' <- global은 {} 내부에서 선언되었는데 {} 밖에서도 접근이 가능함
console.log(global === window.global) // true
- 단순한 if 블록과는 달리 함수 블록 내부에서는 스코프가 결정된다.
function hello() {
var local = 'local variable'
console.log(local) // local variable
}
hello()
console.log(local) // Uncaught ReferenceError: local is not defined
- 스코프가 중첩된 경우엔 가장 가까운 스코프에서 변수가 존재하는지를 먼저 확인한다.
var x = 10
function foo() {
var x = 100
console.log(x) // 100
function bar() {
var x = 1000
console.log(x) // 1000
}
bar()
}
console.log(x) // 10
foo()
전역 스코프는 어디서든 원하는 값을 꺼내올 수 있는 장점이 있지만 반대로는 누구든 접근할 수 있고 수정할 수 있다는 것이 단점이다. 클로저를 활용해 변수를 직접 사용자에게 노출하지 않고 수정하지 못하게 막을 수 있으며, 접근하는 경우 로그를 남기는 등의 부차적인 작업도 수행할 수 있다.
function Counter() {
var counter = 0
return {
increase: function() {
return ++ counter
},
decrease: function() {
return -- counter
},
counter: function() {
console.log('counter에 접근!')
return counter
},
}
}
var c = Counter()
console.log(c.increase()) // 1
console.log(c.increase()) // 2
console.log(c.decrease()) // 1
console.log(c.counter()) // 1
리액트 함수 컴포넌트의 훅에서 클로저 사용시엔 대표적으로 useState가 사용된다.
function Component() {
const [state, setState] = useState()
function handleClick() {
// setState는 클로저를 활용했기 때문에 state의 최신 값(prev)을 알고 있다.
setState((prev) => prev + 1)
}
// ...
}
for(var i = 0; i < 5; i ++) {
setTimeout(function () {
console.log(i)
}, i * 1000)
}
위 코드를 봤을 때 0부터 시작해 1초 간격으로 console.log로 0, 1, 2, 3, 4 차례대로 출력될 것 같지만 실제 실행 시 4초 뒤에 5만 출력된다.
그 이유는 i가 전역 변수로 작동하기 때문이며 var는 for문의 존재와 상관 없이 해당 구문에 선언된 함수 레벨 스코프를 바라보고 있으므로 for문을 다 순회한 후 태스크 큐에 있는 setTimeout을 실행하려고 했을 때 이미 전역 레벨에 있는 i는 5로 업데이트가 완료되어 있다.
이를 올바르게 수정하기 위해 블록 레벨 스코프를 갖는 let으로 수정해주면 된다.
for(let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, i * 1000)
}
또는 for문 내부에 즉시 실행 익명 함수를 선언해준다.
for(var i = 0; i < 5; i ++) {
setTimeout((function (sec) {
return function() {
console.log(sec)
}
})(i), i * 1000)
}
마지막으로 클로저는 사용하는 데 비용이 들기 때문에 주의가 필요하다.