⚡️ Type - part1
it("비교연산자 '==='는 두 값의 일치 여부를 엄격하게 검사(strict equality)합니다.", function () {
let actualValue = 1 + 1;
let expectedValue = 2;
expect(actualValue === expectedValue).to.be.true;
// 이제 'FILL_ME_IN'을 대신할 수 있는 건 number 타입의 2뿐입니다.
// 문자열 '2'는 테스트를 통과하지 못합니다.
});
it('expect의 전달인자로 들어간 표현식의 평가(evaluation) 결과를 예측해 봅니다.', function () {
expect(1 + '1').to.equal('11');
});
it('expect의 전달인자로 들어간 표현식의 평가(evaluation) 결과를 예측해 봅니다.', function () {
expect(123 - '1').to.equal(122);
});
it('expect의 전달인자로 들어간 표현식의 평가(evaluation) 결과를 예측해 봅니다.', function () {
expect(1 + true).to.equal(2);
});
it('expect의 전달인자로 들어간 표현식의 평가(evaluation) 결과를 예측해 봅니다.', function () {
expect('1' + true).to.equal('1true');
});
⚡️ Let / Const
describe("'const'에 대해서 학습합니다.", function () {
it("'const'로 선언된 변수에는 재할당(reassignment)이 금지됩니다.", function () {
// 아래 코드에서 문제가 되는 부분을 삭제합니다.
const constNum = 0;
//constNum = 0; // const 재할당시 에러메세지
expect(constNum).to.equal(0);
const constString = 'I am a const';
//constString = "which means I'm a constant variable, delete me."; // const 재할당시 에러메세지
expect(constString).to.equal('I am a const');
});
it("'const'로 선언된 배열의 경우 새로운 요소를 추가하거나 삭제할 수 있습니다.", function () {
const arr = [];
const toBePushed = 42;
arr.push(toBePushed);
expect(arr[0]).to.equal(42);
// 여전히 재할당은 금지됩니다.
// arr = [1, 2, 3];
});
it("'const'로 선언된 객체의 경우, 속성을 추가하거나 삭제할 수 있습니다.", function () {
const obj = { x: 1 };
expect(obj.x).to.equal(1);
delete obj.x;
expect(obj.x).to.equal(undefined); //없는값 = undefined
// 여전히 재할당은 금지됩니다.
// obj = { x: 123 };
obj.occupation = 'SW Engineer';
expect(obj['occupation']).to.equal('SW Engineer');
});
⚡️ Scope
describe('scope 대해서 학습합니다.', function () {
// scope는 변수의 값(변수에 담긴 값)을 찾을 때 확인하는 곳을 말합니다. 반드시 기억하시기 바랍니다.
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';
};
// 자바스크립트 함수 호이스팅(hoisting)에 대해서 검색해 봅니다.
// 함수선언문 function 함수명 (){} 호이스팅 됨
// 함수표현식 var 함수명 = funcion(){} 은 호이스팅 X
// var 함수명 부분만 호이스팅됨(선언부만)
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');
});
it('lexical scope에 대해서 확인합니다.', function () {
let message = 'Outer';
function getMessage() {
return message;
}
function shadowGlobal() {
let message = 'Inner';
return message;
}
function shadowGlobal2(message) {
return message;
}
function shadowParameter(message) {
message = 'Do not use parameters like this!';
return message;
}
expect(getMessage()).to.equal('Outer');
expect(shadowGlobal()).to.equal('Inner');
expect(shadowGlobal2('Parameter')).to.equal('Parameter');
expect(shadowParameter('Parameter')).to.equal('Do not use parameters like this!');
expect(message).to.equal('Outer');
});
it('default parameter에 대해 확인합니다.', function () {
function defaultParameter(num = 5) {
return num;
}
expect(defaultParameter()).to.equal(5);
expect(defaultParameter(10)).to.equal(10);
function pushNum(num, arr = []) {
arr.push(num);
return arr;
}
expect(pushNum(10)).to.deep.equal([10]);
expect(pushNum(20)).to.deep.equal([20]);
expect(pushNum(4, [1, 2, 3])).to.deep.equal([1,2,3,4]);
});
it('클로저(closure)에 대해 확인합니다.', function () {
function increaseBy(increaseByAmount) {
return function (numberToIncrease) {
return numberToIncrease + increaseByAmount;
};
}
const increaseBy3 = increaseBy(3);
const increaseBy5 = increaseBy(5);
expect(increaseBy3(10)).to.equal(13);
expect(increaseBy5(10)).to.equal(15);
expect(increaseBy(8)(6) + increaseBy(5)(9)).to.equal(28);
/*
mdn에 따르면 클로저의 정의는 다음과 같습니다. 반드시 기억하시기 바랍니다.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.
클로저는 함수와 함수가 선언된 어휘적 환경의 조합을 말한다.
이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.
여기서의 키워드는 "함수가 선언"된 "어휘적(lexical) 환경"입니다.
특이하게도 자바스크립트는 함수가 호출되는 환경와 별개로, 기존에 선언되어 있던 환경 - 어휘적 환경 - 을 기준으로 변수를 조회하려고 합니다.
유어클레스 영상에서 언급되는 "외부함수의 변수에 접근할 수 있는 내부함수"를 클로져 함수로 부르는 이유도 그렇습니다.
클로저는 내부(inner) 함수가 외부(outer) 함수의 지역 변수에 접근할 수 있습니다.
이를 유념하시고 클로저의 유즈 케이스를 검색해 보시기 바랍니다. 아래 검색 키워드를 활용합니다.
function factories
namespacing private variables/functions
*/
});
it('lexical scope와 closure에 대해 다시 확인합니다.', function () {
let age = 27;
let name = 'jin';
let height = 179;
function outerFn() {
let age = 24;
name = 'jimin';
let height = 178;
function innerFn() {
age = 26; // 재할당 한것 주의
let name = 'suga';
return height;
}
innerFn();
expect(age).to.equal(26);
expect(name).to.equal('jimin');
return innerFn;
}
const innerFn = outerFn();
expect(age).to.equal(27);
expect(name).to.equal('jimin');
expect(innerFn()).to.equal(178);
});
});
❗️ 호이스팅(Hosting) 이란?
- 우선, 자바스크립트에서 함수를 정의 하는 방법에는 함수 선언식과 함수 표현식이 있다.
//* 함수 선언식 *//
function sum1(a,b) {
return a + b;
}
//* 함수 표현식 *//
const sum2 = function(a,b) {
return a + b;
}
- 함수 선언식과 함수 표현식의 차이
- 함수 선언식은 함수 전체를 호이스팅 한다.
- 정의된 범위의 맨 위로 호이스팅되어 함수 선언 전에 함수를 사용 할 수 있다.
- 함수 표현식은 별도의 변수에 할당하게 되는데, 변수는 선언부와 할당부를 나누어 호이스팅하게 된다.
- 함수 표현식은 선언부만 호이스팅 하게 된다.
// 예제 1)
sum(10, 20); // 30
minus(30, 40) // Uncaught TypeError: minus is not a function
function sum(a, b) { // 함수 선언식
return a + b;
}
var minus = function (a,b) { // 함수 표현식
return a - b;
}
- 위의 예제1은 호이스팅이 마치게 되면 다음과 같이 표현 할 수 있다.
// 예제 1) 호이스팅
function sum(a, b) { // 함수 선언식 - 함수 전체가 호이스팅됨.
return a + b;
};
var minus; // 함수표현식 - <선언부>만 호이스팅 된다.
sum(10, 20); // 30
minus(30, 40) // Uncaught TypeError: minus is not a function
function (a,b) { // 함수 표현식 - 할당부는 그대로
return a - b;
}
- 함수 선언식으로 작성한 함수는 함수 전체가 호이스팅 되며, 함수를 어디에 선언하였든지에 관계없이 자유롭게 사용 가능하다.
- 그러나, 이런 호이스팅이 의도치 않은 버그를 생성할 여지가 있다.(동명의 함수 중복 사용, 스코프 꼬임 현상 등)
- 따라서, 호이스팅을 의도적으로 사용하는 경우가 아니라면 호이스팅이 되지 않는 let과 const 변수 및 함수 표현식을 사용하는 것이 권장된다.
⚡️ Arrow Function
describe('화살표 함수에 관해서', function () {
it('함수 표현식 사용법을 복습합니다', function () {
const add = function (x, y) {
return x + y
}
expect(add(5, 8)).to.eql(13)
})
it('화살표 함수 사용법을 익힙니다', function () {
// function 키워드를 생략하고 화살표 => 를 붙입니다
const add = (x, y) => {
return x + y
}
expect(add(10, 20)).to.eql(30)
// 리턴을 생략할 수 있습니다
const subtract = (x, y) => x - y // 중괄호하고 return 동시에 생략
expect(subtract(10, 20)).to.eql(-10)
// 필요에 따라 소괄호를 붙일 수도 있습니다
const multiply = (x, y) => (x * y)
expect(multiply(10, 20)).to.eql(200)
// 파라미터가 하나일 경우 소괄호 생략이 가능합니다
const divideBy10 = x => x / 10
expect(divideBy10(100)).to.eql(10)
})
it('화살표 함수를 이용해 클로저를 표현합니다', function () {
const adder = x => {
return y => {
return x + y
}
}
expect(adder(50)(10)).to.eql(60)
const subtractor = x => y => {
return x - y
}
expect(subtractor(50)(10)).to.eql(40)
const htmlMaker = tag => textContent => `<${tag}>${textContent}</${tag}>`
expect(htmlMaker('div')('code states')).to.eql(`<div>code states</div>`)
const liMaker = htmlMaker('li')
expect(liMaker('1st item')).to.eql(`<li>1st item</li>`)
expect(liMaker('2nd item')).to.eql(`<li>2nd item</li>`)
})
})
⚡️ Type - part2
describe('primitive data type과 reference data type에 대해서 학습합니다.', function () {
/*
* 아래 주석이 이해하기 어렵다면, 유어클래스 Lesson - Primitive & Reference를 복습하세요 :)
자바스크립트에서 원시 자료형(primitive data type 또는 원시값)은 객체가 아니면서 method를 가지지 않는 아래 6가지의 데이터를 말합니다.
string, number, bigint, boolean, undefined, symbol, (null)
*/
it('원시 자료형은 값 자체에 대한 변경이 불가능(immutable)합니다.', function () {
let name = 'codestates';
expect(name).to.equal('codestates');
expect(name.toUpperCase()).to.equal('CODESTATES');
expect(name).to.equal('codestates');
// 새로운 값으로 재할당은 가능합니다.
name = name.toUpperCase();
expect(name).to.equal('CODESTATES');
/*
원시 자료형은 값 자체에 대한 변경이 불가능하다고 하는데, 한 변수에 다른 값을 할당하는 것은 변경이 된 것이 아닌가요?
let num1 = 123;
num2 = 123456;
원시 자료형 그 자체('hello', 123, 456n, true 등)와 원시 자료형이 할당된 변수는 구분되어야 합니다.
사과 박스에 귤을 담았다고 해서, 귤이 갑자기 사과가 되지는 않는 것과 같이 123이 갑자기 123456이 되지 않습니다.
*/
});
it('원시 자료형을 변수에 할당할 경우, 값 자체의 복사가 일어납니다.', function () {
let overTwenty = true;
let allowedToDrink = overTwenty;
overTwenty = false;
expect(overTwenty).to.equal(false);
expect(allowedToDrink).to.equal(true); // 값의 주소값 X 값 자체의 복사임!
let variable = 'variable';
let variableCopy = 'variableCopy';
variableCopy = variable; // 'variable'
variable = variableCopy; // 'variable'
expect(variable).to.equal('variable');
});
it('원시 자료형 또는 원시 자료형의 데이터를 함수의 전달인자로 전달할 경우, 값 자체의 복사가 일어납니다.', function () {
let currentYear = 2020;
function afterTenYears(year) {
year = year + 10;
}
afterTenYears(currentYear);
expect(currentYear).to.equal(2020);
function afterTenYears2(currentYear) {
currentYear = currentYear + 10;
return currentYear;
}
let after10 = afterTenYears2(currentYear);
expect(currentYear).to.equal(2020);
expect(after10).to.equal(2030);
// 사실 함수의 전달인자도 변수에 자료(data)를 할당하는 것입니다.
// 함수를 호출하면서 넘긴 전달인자가 호출된 함수의 지역변수로 (매 호출 시마다) 새롭게 선언됩니다.
});
/*
자바스크립트에서 원시 자료형이 아닌 모든 것은 참조 자료형 입니다. 배열([])과 객체({}), 함수(function(){})가 대표적입니다.
const pi = 3.14
const arr = ["hello", "world", "code", "states"];
위 두 가지 코드에서 어떤 차이를 찾으실 수 있나요? 아쉽게도 보기에는 큰 차이가 없습니다.
하지만 자바스크립트는 보기와는 다르게 작동되는 부분이 있습니다. (under the hood)
여기서 변수 pi에는 3.14라는 원시 자료형 '값'이 할당되고, arr에는 참조 자료형의 '주소'가 할당됩니다.
영어 단어 reference 의미와 연결시켜보면 실제 데이터가 저장된 주소를 가리킨다(refer), 즉, 참조(reference)한다로 이해하면 쉽습니다.
왜 참조 자료형에서는 '주소'를 할당할 수 밖에 없을까요?
원시 자료형은 immutable 하다고 말씀 드렸습니다. 참조 자료형은, 그렇지 않습니다.
우리가 배열에 요소를 추가 및 삭제하고, 객체에 속성을 추가 및 삭제할 수 있었습니다.
이것 자체가, 참조 자료형은 이미 immutable하지 않다는 것을 보여주고 있습니다.
언제든 데이터가 늘어나고 줄어들 수 있죠 (동적으로 변한다.), 그렇기 때문에 특별한 저장공간의 주소를 변수에 할당함으로써 더 잘 관리하고자 합니다.
이런 저장 공간을 heap이라고 부릅니다.
아래와 같이 코드가 작성되어 있다면...
let num = 123;
const msg = "hello";
let arr = [1, 2, 3];
const isOdd = true;
원시 자료형의 데이터가 저장되는 공간 (stack)
1 | num | 123
2 | msg | "hello"
3 | arr | heap의 12번부터 3개 // (실제 데이터가 저장되어 있는 주소)
4 |isOdd| true
=====================================
Object 자료형의 데이터가 저장되는 공간 (heap)
10 ||
11 ||
12 || 1
13 || 2
14 || 3
실제 자바스크립트는 변수를 위와 같이 저장할 것입니다.
*
* 위의 원리가 잘 이해 되셨나요? heap과 stack이라는 용어가 어색하더라도, 위 원리가 잘 이해되었다면 괜찮습니다.
* 이해가 잘 안되시면, 원시 자료형이 할당되는 경우는 값 자체가 할당되고, 참조 자료형은 주소가 할당된다고 암기하셔도 좋습니다.
*
* const hello = "world"; // "world" 그 자체
* const arr = [1, 2, 3]; // [1, 2, 3] 의 메모리 주소 xxxxxx
*
*/
it('참조 자료형의 데이터는 동적(dynamic)으로 변합니다.', function () {
const arr = [1, 2, 3];
expect(arr.length).to.equal(3);
arr.push(4, 5, 6);
expect(arr.length).to.equal(6);
arr.pop();
expect(arr.length).to.equal(5);
const obj = {};
expect(Object.keys(obj).length).to.equal(0);
obj['name'] = 'codestates';
obj.quality = 'best';
obj.product = ['sw engineering', 'product manager', 'growth marketing', 'data science'];
expect(Object.keys(obj).length).to.equal(3);
delete obj.name;
expect(Object.keys(obj).length).to.equal(2);
});
it('참조 자료형을 변수에 할당할 경우, 데이터의 주소가 저장됩니다.', function () {
/*
참조 자료형의 경우, 값 자체의 복사가 일어나지 않는 이유는 어느 정도 납득할만한 이유가 있습니다.
배열이 얼마나 많은 데이터를 가지고 있는지가 프로그램의 실행 중 수시로 변경될 수 있기 때문입니다.
쉽게 생각해서 number 타입 데이터 100만개를 요소로 갖는 배열을 생각해 봅시다.
따로 명시하지 않는 이상 100만개의 데이터를 일일히 복사하는 것은 상당히 비효율적입니다.
따라서 일단은 주소만 복사해서 동일한 데이터를 바라보는 게 만드는 것이 효율적입니다.
배열과 객체의 데이터를 복사하는 방법은 06_Array.js, 07_Object.js에서 다룹니다.
*/
const overTwenty = ['hongsik', 'minchul', 'hoyong'];
let allowedToDrink = overTwenty;
overTwenty.push('san');
expect(allowedToDrink).to.deep.equal(['hongsik', 'minchul', 'hoyong', 'san']);
overTwenty[1] = 'chanyoung';
expect(allowedToDrink[1]).to.deep.equal('chanyoung');
// .deep.equal은 배열의 요소나 객체의 속성이 서로 같은지 확인하는 matcher입니다.
// .equal아닌 .deep.equal을 사용하는 이유는 아래 테스트 코드를 통해 고민하시기 바랍니다.
const ages = [22, 23, 27];
allowedToDrink = ages;
expect(allowedToDrink === ages).to.equal(true);
expect(allowedToDrink === [22, 23, 27]).to.equal(false);
const nums1 = [1, 2, 3];
const nums2 = [1, 2, 3];
expect(nums1 === nums2).to.equal(false);
const person = {
son: {
age: 9,
},
};
const boy = person.son;
boy.age = 20;
expect(person.son.age).to.equal(20);
expect(person.son === boy).to.equal(true);
expect(person.son === { age: 9 }).to.equal(false);
expect(person.son === { age: 20 }).to.equal(false);
/*
아래의 테스트 코드들은 선뜻 받아들이기 힘들 수 있습니다.
const nums1 = [1, 2, 3];
const nums2 = [1, 2, 3];
expect(nums1 === nums2).to.equal(FILL_ME_IN);
배열 nums1과 배열 num2에는 동일한 데이터 [1, 2, 3]이 들어있는 게 분명해 보이는데, 이 둘은 같지가 않습니다.
사실 변수 num1와 num2는 배열이 아닙니다.
참조 타입의 변수에는 (데이터에 대한) 주소만이 저장된다는 것을 떠올려 봅시다.
정확히 말해서 변수 num1은 데이터 [1, 2, 3]이 저장되어 있는 메모리 공간(heap)을 가리키는 주소를 담고 있습니다.
따라서 위의 코드는 각각 다음의 의미를 가지고 있습니다.
const nums1 = [1, 2, 3]; // [1, 2, 3]이 heap에 저장되고, 이 위치의 주소가 변수 num1에 저장된다.
const nums2 = [1, 2, 3]; // [1, 2, 3]이 heap에 저장되고, 이 위치의 주소가 변수 num2에 저장된다.
이제 heap에는 두 개의 [1, 2, 3]이 저장되어 있고, 각각에 대한 주소가 변수 num1, num2에 저장되어 있습니다.
이게 비효율적으로 보일수도 있습니다. 굳이 같은 데이터를 왜 한번 더 저장하는 지 이해하기란 쉽지 않습니다.
하지만 [1, 2, 3]이 아니라 상당히 큰 데이터(예. length가 100,000인 배열)를 가지고 다시 생각해 봅시다.
const nums1 = [10, 2, 71, ..., 987]; // 길이 100,000개인 배열
const nums2 = [10, 2, 71, ..., 987]; // 길이 100,000개인 배열
이 두 배열이 서로 같아서 두 번 저장할 필요가 없다고 말하려면, 일단 두 배열이 같은지 확인해야 합니다.
이런 작업을 Object 자료형을 쓸 때마다 한다고 가정해보면, 이것이 얼마나 비효율적인지를 금방 알 수 있습니다.
이제 아래와 같이 정리할 수 있습니다. 반드시 기억하시기 바랍니다.
Object 자료형은 데이터는 heap에 저장되고, 변수에 할당을 할 경우 변수에는 주소가 저장된다.
1) [1, 2, 3]; // [1, 2, 3]이라는 데이터가 heap에 저장되지만 변수 할당이 되지 않아 주소는 어디에도 저장되지 않는다.
2) const num1 = [1, 2, 3]; // // [1, 2, 3]이라는 데이터가 heap에 저장되고, 그 주소가 변수 num1에 저장된다.
3) const num2 = [1, 2, 3]; // // [1, 2, 3]이라는 데이터가 heap에 저장되고, 그 주소가 변수 num2에 저장된다.
1), 2), 3)에서 말하는 주소는 전부 다른 주소입니다.
아래의 객체 간 비교도 동일한 논리로 이해하시면 됩니다.
expect(person.son === { age: 20 }).to.equal(FILL_ME_IN);
다음 문제를 해결해 보시기 바랍니다.
const num1 = [1, 2, 3]; // [1, 2, 3]이 heap에 저장되고, 그 주소가 변수 num1에 저장된다.
const num2 = num1; // 변수 num1에 저장된 주소가 변수 num2에 저장된다.
// 두 변수 num1, num2는 같은 주소를 저장하고 있습니다. 아래 결과는 어떻게 될까요?
expect(num1 === num2).to.equal(FILL_ME_IN);
*/
});
});
⚡️ Array
describe('Array에 대해서 학습합니다.', function () {
it('Array의 기본을 확인합니다.', function () {
const emptyArr = [];
expect(typeof emptyArr === 'array').to.equal(false);
expect(emptyArr.length).to.equal(0);
const multiTypeArr = [
0,
1,
'two',
function () {
return 3;
},
{ value1: 4, value2: 5 },
[6, 7],
];
expect(multiTypeArr.length).to.equal(6);
expect(multiTypeArr[0]).to.equal(0);
expect(multiTypeArr[2]).to.equal('two');
expect(multiTypeArr[3]()).to.equal(3);
expect(multiTypeArr[4].value1).to.equal(4);
expect(multiTypeArr[4]['value2']).to.equal(5);
expect(multiTypeArr[5][1]).to.equal(7);
});
it('Array의 요소(element)를 다루는 방법을 확인합니다.', function () {
const arr = [];
expect(arr).to.deep.equal([]);
arr[0] = 1;
expect(arr).to.deep.equal([1]);
arr[1] = 2;
expect(arr).to.deep.equal([1, 2]);
arr.push(3);
expect(arr).to.deep.equal([1,2,3]);
const poppedValue = arr.pop();
expect(poppedValue).to.equal(3);
expect(arr).to.deep.equal([1,2]);
});
it('Array 메소드 slice를 확인합니다.', function () {
const arr = ['peanut', 'butter', 'and', 'jelly'];
expect(arr.slice(1)).to.deep.equal(['butter', 'and', 'jelly']);
expect(arr.slice(0, 1)).to.deep.equal(['peanut']);
expect(arr.slice(0, 2)).to.deep.equal(['peanut', 'butter']);
expect(arr.slice(2, 2)).to.deep.equal([]);
expect(arr.slice(2, 20)).to.deep.equal(['and', 'jelly']);
expect(arr.slice(3, 0)).to.deep.equal([]);
expect(arr.slice(3, 100)).to.deep.equal(['jelly']);
expect(arr.slice(5, 1)).to.deep.equal([]);
// arr.slice는 arr의 값을 복사하여 새로운 배열을 리턴합니다.
// 아래의 코드는 arr 전체를 복사합니다. 자주 사용되니 기억하시기 바랍니다.
expect(arr.slice(0)).to.deep.equal(['peanut', 'butter', 'and', 'jelly']);
});
it('Array를 함수의 전달인자로 전달할 경우, reference가 전달됩니다.', function () {
// call(pass) by value와 call(pass) by reference의 차이에 대해서 학습합니다.
const arr = ['zero', 'one', 'two', 'three', 'four', 'five'];
function passedByReference(refArr) {
refArr[1] = 'changed in function';
}
passedByReference(arr);
expect(arr[1]).to.equal('changed in function');
const assignedArr = arr;
assignedArr[5] = 'changed in assignedArr';
expect(arr[5]).to.equal('changed in assignedArr');
const copiedArr = arr.slice();
copiedArr[3] = 'changed in copiedArr';
expect(arr[3]).to.equal('three'); // slice는 원본 변경 x
});
it('Array 메소드 shift와 unshift를 확인합니다.', function () {
const arr = [1, 2];
arr.unshift(3);
expect(arr).to.deep.equal([3,1,2]);
const shiftedValue = arr.shift();
expect(shiftedValue).to.deep.equal(3);
expect(arr).to.deep.equal([1,2]);
});
});
⚡️ Object
describe('Object에 대해서 학습합니다.', function () {
/*
이번 과제에서는 객체의 기본적인 내용을 재확인합니다.
이머시브 과정에서 객체를 보다 자세하게 학습하게 됩니다. (예. prototype)
*/
it('Object의 기본을 확인합니다.', function () {
const emptyObj = {};
expect(typeof emptyObj === 'object').to.equal(true);
expect(emptyObj.length).to.equal(undefined);
const megalomaniac = {
mastermind: 'Joker',
henchwoman: 'Harley',
getMembers: function () {
return [this.mastermind, this.henchwoman];
},
relations: ['Anarky', 'Duela Dent', 'Lucy'],
twins: {
'Jared Leto': 'Suicide Squad',
'Joaquin Phoenix': 'Joker',
'Heath Ledger': 'The Dark Knight',
'Jack Nicholson': 'Tim Burton Batman',
},
};
expect(megalomaniac.length).to.equal(undefined);
expect(megalomaniac.mastermind).to.equal('Joker');
expect(megalomaniac.henchwoman).to.equal('Harley');
expect(megalomaniac.henchWoman).to.equal(undefined);
expect(megalomaniac.getMembers()).to.deep.equal(['Joker','Harley']);
expect(megalomaniac.relations[2]).to.equal('Lucy');
expect(megalomaniac.twins['Heath Ledger']).to.deep.equal('The Dark Knight');
});
it('Object의 속성(property)를 다루는 방법을 확인합니다.', function () {
const megalomaniac = { mastermind: 'Agent Smith', henchman: 'Agent Smith' };
expect('mastermind' in megalomaniac).to.equal(true); // in 은 값은 물어보는게 아니라 있는지 없는지 물어봄
megalomaniac.mastermind = 'Neo';
expect(megalomaniac['mastermind']).to.equal('Neo');
expect('secretary' in megalomaniac).to.equal(false);
megalomaniac.secretary = 'Agent Smith';
expect('secretary' in megalomaniac).to.equal(true);
delete megalomaniac.henchman;
expect('henchman' in megalomaniac).to.equal(false);
});
it("'this'는 method를 호출하는 시점에 결정됩니다.", function () {
const currentYear = new Date().getFullYear(); // new Date() 현재시간을 나타내는 객체를 생성한다
const megalomaniac = {
mastermind: 'James Wood',
henchman: 'Adam West',
birthYear: 1970,
calculateAge: function (currentYear) {
return currentYear - this.birthYear;
},
changeBirthYear: function (newYear) {
this.birthYear = newYear;
},
};
expect(currentYear).to.equal(2023);
expect(megalomaniac.calculateAge(currentYear)).to.equal(53);
megalomaniac.birthYear = 2000;
expect(megalomaniac.calculateAge(currentYear)).to.equal(23);
megalomaniac.changeBirthYear(2010);
expect(megalomaniac.calculateAge(currentYear)).to.equal(13); // 두개가 완전히 동일한지?? 방법이2 개?
/**
* !!Advanced [this.mastermind]? this.birthYear? this가 무엇일까요?
*
* method는 '어떤 객체의 속성으로 정의된 함수'를 말합니다. 위의 megalomaniac 객체를 예로 든다면,
* getMembers는 megalomaniac 객체의 속성으로 정의된 함수인 '메소드'라고 할 수 있습니다. megalomaniac.getMembers()와 같은 형태로 사용(호출)할 수 있죠.
* 사실은, 전역 변수에 선언한 함수도 웹페이지에서 window 객체의 속성으로 정의된 함수라고 할 수 있습니다.
* window. 접두사 없이도 참조가 가능하기 때문에(window.foo()라고 사용해도 됩니다.), 생략하고 쓰는 것뿐입니다. 이렇듯, method는 항상 '어떤 객체'의 method입니다.
* 따라서 호출될 때마다 어떠한 객체의 method일 텐데, 그 '어떠한 객체'를 묻는 것이 this입니다.
* 예시로, obj이라는 객체 안에 foo라는 메서드를 선언하고, this를 반환한다고 했을 때 ( 예: let obj = {foo: function() {return this}}; )
* obj.foo() === obj 이라는 코드에 true라고 반환할 것입니다.
* this는 함수의 호출에 따라서 값이 달라지기도 합니다. (apply나 call, bind에 대해서는 하단의 학습자료를 통해 더 공부해 보세요.)
*
* 그러나 화살표 함수는 다릅니다. 자신의 this가 없습니다.
* 화살표 함수에서의 this는 자신을 감싼 정적 범위(lexical context)입니다. (전역에서는 전역 객체를 가리킵니다.)
* 일반 변수 조회 규칙(normal variable lookup rules)을 따르기 때문에, 현재 범위에서 존재하지 않는 this를 찾을 때, 화살표 함수 바로 바깥 범위에서 this를 찾습니다.
* 그렇기에 화살표 함수를 사용할 때, 이러한 특이점을 생각하고 사용해야 합니다.
*
* 이와 관련하여, this에 대해서 더 깊이 학습하셔도 좋습니다.
* 가이드가 될 만한 학습자료를 첨부합니다.
* https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this
*/
});
it('객체의 method를 정의하는 방법을 확인합니다.', function () {
const megalomaniac = {
mastermind: 'Brain',
henchman: 'Pinky',
getFusion: function () {
return this.henchman + this.mastermind;
},
battleCry(numOfBrains) {
return `They are ${this.henchman} and the` + ` ${this.mastermind}`.repeat(numOfBrains);
},
};
expect(megalomaniac.getFusion()).to.deep.equal('PinkyBrain');
expect(megalomaniac.battleCry(3)).to.deep.equal('They are Pinky and the Brain Brain Brain');
});
it('Object를 함수의 전달인자로 전달할 경우, reference가 전달됩니다.', function () {
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);
expect(obj.henchwoman).to.equal('Adam West');
const assignedObj = obj;
assignedObj['relations'] = [1, 2, 3];
expect(obj['relations']).to.deep.equal([1, 2, 3]);
const copiedObj = Object.assign({}, obj);
copiedObj.mastermind = 'James Wood';
expect(obj.mastermind).to.equal('Joker');
obj.henchwoman = 'Harley';
expect(copiedObj.henchwoman).to.equal('Adam West');
delete obj.twins['Jared Leto'];
expect('Jared Leto' in copiedObj.twins).to.equal(false); // 얕은 복사 객체안에 객체는 <주소값공유> 상태이기때문에 삭제시 같이 삭제
/*
마지막 테스트 코드의 결과가 예상과는 달랐을 수도 있습니다.
'Object.assign'을 통한 복사는 reference variable은 주소만 복사하기 때문입니다.
이와 관련하여 얕은 복사(shallow copy)와 깊은 복사(deep copy)에 대해서 학습하시기 바랍니다.
가이드가 될 만한 학습자료를 첨부합니다.
https://scotch.io/bar-talk/copying-objects-in-javascript
https://medium.com/watcha/깊은-복사와-얕은-복사에-대한-심도있는-이야기-2f7d797e008a
*/
});
});
⚡️ Spread Syntax
describe('Spread syntax에 대해 학습합니다.', function () {
it('전개 문법(spread syntax)을 학습합니다.', function () {
const spread = [1, 2, 3];
// TODO: 전개 문법을 사용해 테스트 코드를 완성합니다. spread를 지우지 않고 해결할 수 있습니다.
const arr = [0, ...spread, 4];
expect(arr).to.deep.equal([0, 1, 2, 3, 4]);
});
it('빈 배열에 전개 문법을 사용할 경우, 아무것도 전달되지 않습니다.', function () {
const spread = [];
// TODO: 전개 문법을 사용해 테스트 코드를 완성합니다. spread를 지우지 않고 해결할 수 있습니다.
const arr = [0, ...spread, 1];
expect(arr).to.deep.equal([0, 1]);
});
it('여러 개의 배열을 이어붙일 수 있습니다.', function () {
const arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
const concatenated = [...arr1, ...arr2];
expect(concatenated).to.deep.equal([0, 1, 2, 3, 4, 5]);
// 아래 코드도 같은 동작을 수행합니다.
// arr1.concat(arr2);
});
it('여러 개의 객체를 병합할 수 있습니다.', function () {
const fullPre = {
cohort: 7,
duration: 4,
mentor: 'hongsik',
};
const me = {
time: '0156',
status: 'sleepy',
todos: ['coplit', 'koans'],
};
const merged = { ...fullPre, ...me };
// 변수 'merged'에 할당된 것은 'obj1'과 'obj2'의 value일까요, reference일까요?
// 만약 값(value, 데이터)이 복사된 것이라면, shallow copy일까요, deep copy일까요?
expect(merged).to.deep.equal({
cohort: 7,
duration: 4,
mentor: 'hongsik',
time: '0156',
status: 'sleepy',
todos: ['coplit', 'koans'],
});
});
it('Rest Parameter는 함수의 전달인자를 배열로 다룰 수 있게 합니다.', function () {
// 자바스크립트는 (named parameter를 지원하지 않기 때문에) 함수 호출 시 전달인자의 순서가 중요합니다.
function returnFirstArg(firstArg) {
return firstArg;
}
expect(returnFirstArg('first', 'second', 'third')).to.equal('first');
function returnSecondArg(firstArg, secondArg) {
return secondArg;
}
expect(returnSecondArg('only give first arg')).to.equal(undefined);
// rest parameter는 spread syntax를 통해 간단하게 구현됩니다.
function getAllParamsByRestParameter(...args) {
return args;
}
// arguments를 통해 '비슷하게' 함수의 전달인자들을 다룰 수 있습니다. (spread syntax 도입 이전)
// arguments는 모든 함수의 실행 시 자동으로 생성되는 '객체'입니다.
function getAllParamsByArgumentsObj() {
return arguments; // 인자들을 담고있는 객체 키를 인덱스로 받고 안에 값을 인자들로 받는다
}
const restParams = getAllParamsByRestParameter('first', 'second', 'third');
const argumentsObj = getAllParamsByArgumentsObj('first', 'second', 'third');
expect(restParams).to.deep.equal(['first', 'second', 'third']); // 배열의 형태로 담김 (중요!)
expect(Object.keys(argumentsObj)).to.deep.equal(['0','1','2']); // Object 메써드 => 배열로 담음
expect(Object.values(argumentsObj)).to.deep.equal(['first', 'second', 'third']);
// arguments와 rest parameter를 통해 배열로 된 전달인자(args)의 차이를 확인하시기 바랍니다.
expect(restParams === argumentsObj).to.deep.equal(false);
expect(typeof restParams).to.deep.equal('object');
expect(typeof argumentsObj).to.deep.equal('object');
expect(Array.isArray(restParams)).to.deep.equal(true);
expect(Array.isArray(argumentsObj)).to.deep.equal(false);
const argsArr = Array.from(argumentsObj);
expect(Array.isArray(argsArr)).to.deep.equal(true);
expect(argsArr).to.deep.equal(['first', 'second', 'third']);
expect(argsArr === restParams).to.deep.equal(false); //형태는 같은데 참조한게 다르기때문에 완전동치x
});
it('Rest Parameter는 전달인자의 수가 정해져 있지 않은 경우에도 유용하게 사용할 수 있습니다.', function () {
function sum(...nums) {
let sum = 0;
for (let i = 0; i < nums.length; i++) {
sum = sum + nums[i];
}
return sum;
}
expect(sum(1, 2, 3)).to.equal(6);
expect(sum(1, 2, 3, 4)).to.equal(10);
});
it('Rest Parameter는 전달인자의 일부에만 적용할 수도 있습니다.', function () {
// rest parameter는 항상 배열입니다.
function getAllParams(required1, required2, ...args) {
return [required1, required2, args];
}
expect(getAllParams(123)).to.deep.equal( [123, undefined, []]);
function makePizza(dough, name, ...toppings) {
const order = `You ordered ${name} pizza with ${dough} dough and ${toppings.length} extra toppings!`;
return order;
}
expect(makePizza('original')).to.equal('You ordered undefined pizza with original dough and 0 extra toppings!');
expect(makePizza('thin', 'pepperoni')).to.equal('You ordered pepperoni pizza with thin dough and 0 extra toppings!');
expect(makePizza('napoli', 'meat', 'extra cheese', 'onion', 'bacon')).to.equal('You ordered meat pizza with napoli dough and 3 extra toppings!');
});
});
⚡️ Destructuring
describe('구조 분해 할당(Destructuring Assignment)에 관해서', () => {
it('배열을 분해합니다', () => {
const array = ['code', 'states', 'im', 'course']
const [first, second] = array
expect(first).to.eql('code')
expect(second).to.eql('states')
const result = []
function foo([first, second]) {
result.push(second)
result.push(first)
}
foo(array)
expect(result).to.eql(['states', 'code'])
})
it('rest/spread 문법을 배열 분해에 적용할 수 있습니다', () => {
const array = ['code', 'states', 'im', 'course']
const [start, ...rest] = array
expect(start).to.eql('code')
expect(rest).to.eql(['states', 'im', 'course'])
// 다음과 같은 문법은 사용할 수 없습니다. 할당하기 전 왼쪽에는, rest 문법 이후에 쉼표가 올 수 없습니다
// const [first, ...middle, last] = array
})
it('객체의 단축(shorthand) 문법을 익힙니다', () => {
const name = '김코딩'
const age = 28
const person = {
name,
age,
level: 'Junior',
}
expect(person).to.eql({name: '김코딩',age: 28, level: 'Junior' })
})
it('객체를 분해합니다', () => {
const student = { name: '박해커', major: '물리학과' }
const { name } = student
expect(name).to.eql('박해커')
/*
var o = {p: 42, q: true};
var {p, q} = o;
console.log(p); // 42
console.log(q); // true 값만 가지고 온다.
*/
})
it('rest/spread 문법을 객체 분해에 적용할 수 있습니다 #1', () => {
const student = { name: '최초보', major: '물리학과' }
const { name, ...args } = student
expect(name).to.eql('최초보')
expect(args).to.eql({major: '물리학과'}) // 주의 객체에 있는걸 가져와서 객체로 담기나?
})
it('rest/spread 문법을 객체 분해에 적용할 수 있습니다 #2', () => {
const student = { name: '최초보', major: '물리학과', lesson: '양자역학', grade: 'B+' }
function getSummary({ name, lesson: course, grade }) {
return `${name}님은 ${grade}의 성적으로 ${course}을 수강했습니다`
}
expect(getSummary(student)).to.eql('최초보님은 B+의 성적으로 양자역학을 수강했습니다')
})
it('rest/spread 문법을 객체 분해에 적용할 수 있습니다 #3', () => {
const user = {
name: '김코딩',
company: {
name: 'Code States',
department: 'Development',
role: {
name: 'Software Engineer'
}
},
age: 35
}
const changedUser = {
...user,
name: '박해커',
age: 20
}
const overwriteChanges = {
name: '박해커',
age: 20,
...user
}
const changedDepartment = {
...user,
company: {
...user.company,
department: 'Marketing'
}
}
////////////////////////////////////////////////
expect(changedUser).to.eql({
name: '박해커',
company: {
name: 'Code States',
department: 'Development',
role: {
name: 'Software Engineer'
}
},
age: 20
})
expect(overwriteChanges).to.eql({
name: '김코딩',
company: {
name: 'Code States',
department: 'Development',
role: {
name: 'Software Engineer'
}
},
age: 35
})
expect(changedDepartment).to.eql({
name: '김코딩',
company: {
name: 'Code States',
department: 'Marketing',
role: {
name: 'Software Engineer'
}
},
age: 35
})
})
})
////////////////////////////////////////////////
'코딩 > 코드스테이츠 45기(FE)' 카테고리의 다른 글
[Solo Project] 나만의 AGROA STATES 만들기🐤 (진행중) (0) | 2023.05.05 |
---|---|
블로깅 챌린지 4주차 - [JS/브라우저]DOM / [실습]유효성 검사 (0) | 2023.05.04 |
블로깅 챌린지 4주차 - [JS] 자료형과 복사 (0) | 2023.05.01 |
블로깅 챌린지 3주차 - [JS] 배열, 객체 (0) | 2023.04.25 |
블로깅 챌린지 10일차 - [Linux/Git] 기초 (0) | 2023.04.24 |