본 글은 코드스테이츠 프론트앤드 부트캠프의 koans 페어 프로그래밍을 하면서 기존에 알고 있던 사실에 추가로 알게된 사실들 + 까먹기 쉬운 사실들 위주로 정리하였다. 몇몇은 자료들을 찾아서 덧붙이기도 했다.
시간이 허락할 때 하나씩 주제별 글에 붙여넣거나, 따로 새로운 글을 작성하는 것..이 목표...이긴하다..🥴
1. 자바스크립트의 이상한 현상들만 모아놓은 저장소가 있다.
wtfjs(what the f*ck javaScript)의 약자로, 예상치 못한 띠용! 스러운 자바스크립트 현상들을 모아 놓고, 왜 그런지 해석해보는 저장소가 있다. 재미로 보고, 되도록 값을 정확하게 예측할 수 있는 방향으로 사용하는 걸 지향해야한다.
2. 자바스크립트 호이스팅
호이스팅 : 변수의 선언과 초기화를 분리한 후, 선언만 코드의 최상단으로 옮기는 것
예시로 mdn의 코드를 보자면 아래와 같다.
catName("클로이"); //선언하기 전에 호출 먼저함
function catName(name) { //여기서 선언
console.log("제 고양이의 이름은 " + name + "입니다");
}
/*
결과: "제 고양이의 이름은 클로이입니다" // 그래도 잘 나옴
*/
이렇게 함수를 선언도 하기 전에 호출했음에도 결과가 정상적으로 찍히는 것이, 변수의 선언을 최상단으로 올리는 자바스크립트의 'hoisting'습성때문이다.
함수 뿐 아니라 변수에서도 호이스팅을 발생하는데, var로 선언한 변수의 경우 호이스팅 시 undefined로 변수를 초기화하지만, let과 const로 선언한 변수의 경우 호이스팅 시 변수를 초기화하지 않는다.
console.log(num); // 호이스팅한 var 선언으로 인해 undefined 출력
var num; // 선언
num = 6; // 초기화
그리고 let은 초기화를 하지 않으면 변수를 읽거나 쓸 수없기 때문에 같은 코드에서 var만 let으로 바꾸면 에러가 난다. undefined가 아니라 아예 에러코드!
console.log(num); // 콘솔창에 uncaught referenceError: num is not defined 발생
let num; // 선언
num = 6; // 초기화
이 둘을 한번에 보여주는 mdn의 예시코드는 아래와 같다.
function do_something() {
console.log(bar); // undefined
console.log(foo); // ReferenceError
var bar = 1;
let foo = 2;
}
쨌든 이런 이유로, let과 const는 호이스팅이 발생하지 않는것과 다름 없다.
그렇다면 함수를 선언하는 방식인 "함수 선언문" 과 "함수 표현식에선"?
아래의 코드를 보면 함수 선언문 에서는 함수를 선언하기전에 호출부터 했지만, 작동이 잘 된 반면, 함수 표현식은(호이스팅이 되는 var로 선언을 했어도) 작동이 되지 않은 것을 확인할 수 있다.
foo(); //hello출력
foo2(); //TypeError: foo2 is not a function 발생
//호이스팅으로 foo2가 선언은 되었지만 undefined 상태이므로
function foo() { // 함수선언문
console.log("hello");
}
var foo2 = function() { // 함수표현식
console.log("world");
}
즉! 함수 선언문은 호이스팅이 되고, 함수 표현식은 호이스팅이 안된다(그게 비록 var를 사용했을지라도)
호이스팅에 대해선 더 다룰 내용이 있을 것 같은데, 일단은 아래의 두가지 규칙만 알아두기로 했다.
호이스팅 규칙
-"var 변수 선언"이나 "함수선언문"에서만 호이스팅이 일어난다.
-var 변수나 함수가 호이스팅이 되었다고 초기값 세팅까지 호이스팅 되는 것은 아니다.
koans에서 함수 호이스팅에 관한 문제는 아래와 같았다.
it('함수 선언식(declaration)과 함수 표현식(expression)의 차이를 확인합니다.', function () {
let funcExpressed = 'to be a function';
expect(typeof funcDeclared).to.equal('function');
expect(typeof funcExpressed).to.equal('string');
function funcDeclared() {
return 'this is a function declaration';
}
funcExpressed = function () {
return 'this is a function expression';
};
const funcContainer = { func: funcExpressed };
expect(funcContainer.func()).to.equal('this is a function expression');
funcContainer.func = funcDeclared;
expect(funcContainer.func()).to.equal('this is a function declaration');
});
3. 구글이 추천하는 자바스크립트 스타일 가이드 문서가 있다.
cosnt와 let을 언제 쓰면 좋을지에 대해 설명하면서 알려준 사이튼인데, 그 밖에도 한번씩 읽어보면 도움이 많이 될것 같다. <링크>
4. 화살표 함수의 여러가지 사용법
화살표함수를 축약해서 사용하는 방법에 대해 여러가지를 배웠다.
//방법1 : 기본
const add = (x, y) => {
return x + y
}
//방법2: 리턴 생략
const subtract = (x, y) => x - y
//방법3: 실행문 소괄호 붙일 수도 있음
const multiply = (x, y) => (x * y)
//방법4: 파라미터가 하나일 경우 인자 전달하는 소괄호 생략 가능
const divideBy10 = x => x / 10
5. 화살표함수로 클로저 함수 나타내기
이건 새로 배운건 아닌데, 볼때마다 좀 눈에 안익게 생겨서 한번 되새겨 보자는 의미로 작성.
아래의 두 코드 모두 클로저를 함수이다.
//조금 더 친절한 표기
const adder = x => {
return y => {
return x + y
}
}
//조금 덜 친절한 표기
const subtractor = x => y => {
return x - y
}
6. Lexcial scope은 함수및 변수가 선언된 곳을 기준으로 결정된다.
함수를 호출했을 때 기준이 아니라, 선언한 위치 기준으로 변수가 결정된다. 다음의 문제를 참고해 보자
//mocha, chai framework을 사용한 문제로, 해당 문법이 섞여있음
let age = 27;
let name = 'jin';
let height = 179;
function outerFn() {
let age = 24;
name = 'jimin'; //전역에 있는 name 값을 바꾼다.
let height = 178;
function innerFn() {
age = 26; //외부함수의 age값을 재할당한다.
let name = 'suga'; //innerFn()내부 함수로, 바깥의 name값엔 영향을 주지 않는다.
return height; //innerFn()이 실행되었을때, 178을 리턴한다.
}
innerFn();
expect(age).to.equal(26);
expect(name).to.equal('jimin');
return innerFn;
//🚨 헷갈렸던 부분!!
//return innerFn() 하면 178이 리턴되었을 것이지만,
//return innerFn으로 함수를 실행하지 않고, 함수 이름만 할당해줬으므로
//리턴값은 innerFn함수 그자체이며, innerFn의 리턴값이 아니다.
}
const innerFn = outerFn();//리턴된 innerFn함수가 innerFn이라는 변수에 할당된다.
//여기까지 outerFn은 실행완료, 아직 innerFn은 실행이 안된 상태이다.
expect(age).to.equal(27); //innerFn이 실행이 안되었으므로 값은 그대로
expect(name).to.equal('jimin'); //outerFn은 실행이 되었으므로 이름이 바뀌었다.
expect(innerFn()).to.equal(178);//이제 innerFn()을 실행시켜준다.그리고 178이 리턴된다.
7.pop과 shift는 지운 요소를 리턴, push와 unshift는 합쳐진 총 길이를 리턴
이것도 배열 자체의 변화에만 신경쓰느라 놓치기가 쉬운데 한번 짚고 넘어갈 필요가 있을것 같아서 다시 적어본다.
const arr = [1,2,3,4]
let popedElem = arr.pop();
let pushedLength = arr.push(5);
popedElem; //4
pushedLength //4
arr; //[1,2,3,4,5]
8.method와 this의 기본 개념
둘다, 특히 this는, 더 많은 내용이 있지만, 지금 알고 있는 수준에선
객체안의 함수를 method라고 부르고 이 method에서 this를 쓰면 함수(method)를 품고있는 객체를 가르킨다.
참고로 화살표 함수는 자신의 this가 없으며, 화살표 함수의 this는 자신을 감싼 정적범위 (전역에선 전역 객체)를 의미한다.
일반 변수 조회 규칙(normal variable lookup rules)을 따르기 때문에, 현재 범위에서 존재하지 않는 this를 찾을 때, 화살표 함수의 바깥 범위에서 this를 찾는다. 화살표 함수를 사용할 때에는 이걸 주의해야한다.
9. length는 배열에서만 사용가능하다.
뭔가 될거 같았다.. 괜히...하지만! 객체의 길이를 알아내기 위해선 아래와 같이 key나 value를 나열해 배열을 리턴하는 매소드를 사용해야한다.
let obj = {a:1,b:2,c:3}
Object.keys(obj) // ['a', 'b', 'c']
Object.keys(obj).length // 3
Object.values(obj) // [1, 2, 3]
Object.values(obj).length // 3
10. obj의 키에 해당되는 값을 읽어오는 방법은 두가지가 있다.
아래를 참고하자
let obj = {a:1,b:2,c:3}
obj[a] // ⛔️ Uncaught ReferenceError: a is not defined
obj['a'] // 1
obj.a // 1
언뜻봐선 ['키값']이렇게 번거롭게 쓸 필요가 있나 싶었는데 있었다....ㅋ
아래와 같이 띄어쓰기가 포함된 문자열의 경우 arr.['']로만 접근이 가능하다.
let twins = {
//애초에 첨 만들때부터
//기본적으로 key값엔 띄어쓰기가 못오고
//쓰려면 ''안에 넣어줘야한다.
'Jared Leto': 'Suicide Squad',
'Joaquin Phoenix': 'Joker',
'Heath Ledger': 'The Dark Knight',
'Jack Nicholson': 'Tim Burton Batman'
}
twins.Jared Leto //⛔️ Uncaught SyntaxError: Unexpected identifier
twins.'Jared Leto' //⛔️ Uncaught SyntaxError: Unexpected identifier
twins[Jared Leto] //⛔️ Uncaught SyntaxError: Unexpected identifier //꼭 ''안에 key적기
twins['Jared Leto'] // 'Suicide Squad'
11. key in obj는 키가 obj에 있는지 체크하여 boolean을 반환한다.
이건 그냥 생각이 안났다... 외우자..
const megalomaniac = { mastermind: 'Agent Smith', henchman: 'Agent Smith' };
'mastermind' in megalomaniac //true
'secretary' in megalomaniac //false
12. new Date().getFullYear()로 올해 연도를 따온다
var today = new Date();
var year = today.getFullYear();
today //Wed Jul 13 2022 20:35:42 GMT+0900 (한국 표준시)
year //2022
13. 'string'.repeat()은 같은 문자열을 n번 반복한 하나의 문자열을 내준다.
let string = 'chillDev'
string.repeat(3) //'chillDevchillDevchillDev'
'chillDev'.repeat(3) //'chillDevchillDevchillDev'
' chillDev'.repeat(3) //' chillDev chillDev chillDev'
14. 원시자료형은 '깊은 복사', 참고자료형은 기본적으로 '얕은 복사'
깊은 복사 | 원시자료형(String, Number, Boolean, undefined, symbol, null) 의 복사 |
-복사된 값을 변경해도, 원본의 값은 변화하지 않음 |
얕은 복사 | 참고자료형의 Object.assign({},obj), spread, slice() 복사 | -1차원의 객체는 깊은 복사가 가능하지만, 2차원의 객체(객체 안의 객체)는 깊은 복사가 불가능함. -따라서 얖은 복사 까지만 가능함 -이차원 객체까지 완벽하게 깊은 복사를 하려면 아래의 방법들이 있을 수 있음 -stringify(), parse()와 같은 json 객체 메소드 이용 -커스텀 재귀함수 -lodash 모듈의 cloneDeep() |
아래는 얖은 복사의 한 방법인 Object.assign()을 사용한 코드를 테스트하는 문제를 정리한 것이다.
const obj = {
mastermind: 'Joker',
henchwoman: 'Harley',
relations: ['Anarky', 'Duela Dent', 'Lucy'],
twins: {
'Jared Leto': 'Suicide Squad',
'Joaquin Phoenix': 'Joker',
'Heath Ledger': 'The Dark Knight',
'Jack Nicholson': 'Tim Burton Batman',
},
};
function passedByReference(refObj) {
refObj.henchwoman = 'Adam West';
}
passedByReference(obj);
//passedByReference함수 통과하면서 obj의 henchwoman 값이 변함
const assignedObj = obj;
//obj와 동일한 주소값을 갖고 있는 오브젝트 변수 복사
assignedObj['relations'] = [1, 2, 3];
//obj.relations와 assignedObj.relations 값 모두 변화
obj['relations']; //[1, 2, 3]
const copiedObj = Object.assign({}, obj);
//주소값 공유가 아니라 얕은 복사로 아예 새로운 객체 비슷하게 만듬
copiedObj.mastermind = 'James Wood';
//2차원 객체가 아닌 부분들은 주소값만 복사하는게 아니라 그냥 완벽하게 복사되었으므로,
//copiedObj를 변경했다고, obj가 변하지는 않음
obj.mastermind // 'Joker'
obj.henchwoman = 'Harley';
//마찬가지로 obj를 변경했다고 copiedObj가 변경되지는 않음
copiedObj.henchwoman //'Adam West' passedByReference(obj)로 변경되었던 값
delete obj.twins['Jared Leto']; //객체안의 객체의 Jared Leto라는 키와 그의 vlaue를 삭제
'Jared Leto' in copiedObj.twins //false
//⭐️2차원 객체까지 완벽하게 복사된것이 아니라,
//객체 안의 객체는 여전히 주소값만 복사된 것이므로
//복사된 객체에서도 해당 값이 지워짐
아래의 그림을 보면 더 이해하기 쉽다.
코드스테이츠가 제공한 참고자료 링크들
15. arguments를 통한 유사배열문법
유사배열이란 말그대로 배열은 아니지만 배열같은 '객체'를 일컫는 말이다. 좀 더 상세히 말하자면, 배열은 아니지만, 인덱스로 접근이 가능한 객체를 말한다.
이는 자바스크립트에서 인자를 넣어 함수를 호출할때 자동으로 arguments라는 객체에 넣었던 인자를 저장하게 설계되어 있는데, 바로 그 객체를 빼내어 배열처럼 [index]로 호출하여 사용할 수 있다.
다음의 예를 보자.
function returnArrLike(){
return arguments
}
returnArrLike('a','b','c','d')
// Arguments(4) ['a', 'b', 'c', 'd', callee: ƒ, Symbol(Symbol.iterator): ƒ]
// Arguments라고 배열처럼 생긴 값을 리턴해줌
returnArrLike('a','b','c','d')[0]
//배열처럼 index로 조회해도 값이 나온다. 'a'
returnArrLike('a','b','c','d').length
//배열처럼 length를 조회 가능하다. 4
16. rest parameter는 전달인자의 수가 정해져 있지 않은 경우에 유용하다.
function sum(...nums) {
let sum = 0;
for (let i = 0; i < nums.length; i++) {
sum = sum + nums[i];
}
return sum;
}
sum(1, 2, 3) //6
sum(1, 2, 3, 4) //10
아주 유용한것 같다. 갯수가 정해져 있지 않을때 무조건 묶어서 배열로 만들어준다음 처리한다!
17. rest parameter는 전달인자의 일부에만 적용할 수도 있다.
따로따로인 인자를 묶어서 배열로 전달하지만, 경우에 따라서 다음과 같이 원시자료형과 배열로 따로따로 전달될 수도 있다.
function getAllParams(required1, required2, ...args) {
return [required1, required2, args];
}
getAllParams(123) //[123, undefined, Array(0)]
getAllParams(123,456,7,8,9) //[123, 456, Array(3)]
getAllParams(123,456,7,8,9)[2] //[7, 8, 9]
여기서 Array(0), Array(3)은 크롬에서 배열을 간략하게 길이로만 나타내는 표현법이다.
여기서 주목할 점은 전달받은 인자가 미리 설정해놓은 인자보다 적다면 이런식으로 해당변수에 값이 들어가지 않은것으로 치부된다는 점이다.
18. 배열을 선언하고 배열을 이용해서 할당할 수 있다.
배열로 선언하고 배열로 할당하는 경우가 뭔소리인가 생소할 수 있지만, 아래와 같은 경우가 있다.
const array = ['chill', 'dev', 'tistory', 'blog']
const [first,second] = array
first // chiill
second // dev
이렇게 사실상 first, second라는 두가지 변수를 선언하고 할당하는데, 이 두가지를 한번에 배열의 형태로 선언하고 배열을 할당해서 한큐에 끝내는 것이다.
19. rest/spread 문법을 배열 분해에 적용할 수 있다.
이렇게 분해해서 일부는 원시자료형, 일부는 참조자료형인 배열로 저장하는 것도 가능하다.
const array = ['chill', 'dev', 'say', 'hello']
const [start, ...rest] = array
start // chill
rest //['dev', 'say', 'hello']
그런데 이때! 주의 사항이있다.
할당하기 전 왼쪽에는, rest 문법 이후에 쉼표가 올 수 없습니다
const [first, ...middle, last] = array 🚫
그니까, 나머지들을 묶어서 배열로 만드는거지, 중간에 묶어서 배열로 만들수는 없다.
20. 객체의 단축(shorthand) 문법
이미 선언되어 있는 변수를 객체의 key와 value로 가져올 수도 있다.
const name = 'chilldev'
const age = 29
const person = {
name,
age,
level: 'Junior'
}
person // {name:'chilldev', age:29, level: 'Junior'}
naem //chilldev
반대로 객체의 key와 value를 변수와 값으로 가져올 수도 있다.
const student = { name: 'chilldev', major: 'FE' }
const { name } = student //해당 변수에 obj에서 변수와 동일한 키값에 해당되는 값을 할당한다.
name // chilldev
'개념 > javaScript' 카테고리의 다른 글
[javaScript]객체 메서드 정리 & 객체의 반복문과 복사 (0) | 2022.07.20 |
---|---|
[javaScript] 배열 메서드 정리 & 배열의 반복문과 복사 (0) | 2022.07.20 |
[javaScript] spread와 rest, 그리고 구조분해할당 (0) | 2022.07.12 |
[javaScript] 클로저(closure)란? 어휘적 환경(lexcial environment)이란? (0) | 2022.07.12 |
[javaScript] 스코프와 let,const,var (0) | 2022.07.12 |