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';
};
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');
});
<lexical scope>
return 값인 메세지가 함수 내부에 있나(지역변수), 외부에 있냐(전역변수)에 따라 달라짐.
함수 내부에 있는 지역변수와 전역변수가 만나면, 지역변수를 우선으로 선언함.
it('lexical scope에 대해서 확인합니다.', function () {
let message = 'Outer';
function getMessage() {
return message;
//message가 함수 내부에 있나요?
// 그럼 외부에서 참조
}
function shadowGlobal() {
let message = 'Inner';
return message;
// 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');
});
<클로저(closure)>
클로저는 내부(inner) 함수가 외부(outer) 함수의 지역 변수에 접근할 수 있습니다.
step1. 함수가 함수를 리턴하고 있다 / 클로져 아냐? 의심...
step2. 리턴되고 있는 함수(내부함수)가 외부에 있는 함수의 변수를 사용해? 클로저
클로져를 사용하는 이유 : 변수를 보호 하기 위해서 (은닉)
변수를 조회하려고 합니다.
"외부함수의 변수에 접근할 수 있는 내부함수"를 클로져 함수로 부르는 이유도 그렇습니다.
it('클로저(closure)에 대해 확인합니다.', function () { function increaseBy(increaseByAmount) { return function (numberToIncrease) { return numberToIncrease + increaseByAmount; }; } /* increaseBy3 = function increaseBy(3) { return function (numberToIncrease) { return numberToIncrease + 3; //increaseByAmount = 3을 넣음, 내부함수에도 전달 }; } increaseBy5 = function increaseBy(5) { return function (numberToIncrease) { return numberToIncrease + 5; //increaseByAmount = 5을 넣음, 내부함수에도 전달 }; } */ //함수가 함수를 리턴하고 있다 / 클로져 아냐? 의심... //리턴되고 있는 함수(내부함수)가 외부에 있는 함수의 변수를 사용해? 클로저 //클로져를 사용하는 이유 : 변수를 보호 하기 위해서 (은닉) 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';//'jimin'으로 재할당
let height = 179;
function outerFn() {
let age = 24; // innerFn의 영향을 받아서 26으로 재할당됨
name = 'jimin';
let height = 178;
function innerFn() {
age = 26; // innerFn에는 age 없음
let name = 'suga';
return height;
}
innerFn();
expect(age).to.equal(26);
expect(name).to.equal('jimin');//외부에서 가져옴
return innerFn;
}
const innerFn = outerFn(); //outerFn의 리턴값을 innerFn에 할당
//변수 innerFn은 outerFn 내부에 있는 innerFn과 동일하게 동작
expect(age).to.equal(27);
expect(name).to.equal('jimin');
expect(innerFn()).to.equal(178);
});
});
아고라 스테이츠에 올려서 어떤 분이 답변해주심 ㅎㅎ 감사함돠
난 이해할 수 있을까....?
types-part2
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);
Array
const emptyArr = [];
expect(typeof emptyArr === 'array').to.equal(false);
expect(emptyArr.length).to.equal(0);
배열의 타입은 배열이 아니다. 객체(object)이다.
객체의 타입도 객체(object)이다.
const multiTypeArr = [
0,
1,
'two',
function () {
return 3;
},
{ value1: 4, value2: 5 },
[6, 7],
];
expect(multiTypeArr[4]['value2']).to.equal(5);
expect(multiTypeArr[5][1]).to.equal(7);
객체의 키의값에 접근할 때는, 키에 대해 먼저 접근한다.
이때, obj['key'] 또는 obj.key 형식으로 접근한다.
배열은 인덱스로 접근한다.
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');
});
한 변수에 배열을 할당하면, 주소를 공유하기 때문에 변수에 변화가 생기면, 원본에도 변화가 생긴다.
하지만, arr.slice() 의 경우 형태가 같은 새로운 배열을 생성하기 때문에(주소공유x), 변수에 변화를 줘도, 원본에는 변화가 없다.
Object
it('Object의 기본을 확인합니다.', function () {
const emptyObj = {};
expect(typeof emptyObj === 'object').to.equal(true);
expect(emptyObj.length).to.equal(undefined);
객체의 길이를 적을때는 Object. keys(obj). length
it('Object의 속성(property)를 다루는 방법을 확인합니다.', function () {
const megalomaniac = { mastermind: 'Agent Smith', henchman: 'Agent Smith' };
expect('mastermind' in megalomaniac).to.equal(true);
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('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');
Object.assign({}, obj); 를 배열로치면? arr.slice()!
즉, 새로운 객체를 만들어내며 형태는 같지만 완전히 새로운 객체를 만들어낸다. 얕은복사/원본이 변하지 않음!!
delete obj.twins['Jared Leto'];
expect('Jared Leto' in copiedObj.twins).to.equal(false); // 엥? 내부 요소가 원본처럼 삭제되었네?
복사란 무엇인가? 같은 주소를 가지지 않았지만 똑같은 값을 가진 객체를 만들어내는 것
한겹밖에 복사가 안되니까,얕은 복사니까, 안쪽은 주소를 공유하기 때문에 안쪽 요소를 지워버리면, 원본에도 변화가 생긴다. 즉 제거하면, 둘다 없어진다!
참고로, 자바스크립트에서는 진정한 깊은 복사 불가능하다.
★배열, 객체를 복사하기 위한 좋은 방법들 (얕은복사)
//즉, 원본변화 없이 새로운 배열 변화 가능, 하지만 이것은 모두 얕은 복사에 해당, 한겹만 복사, 나머지는 주소공유
let slice = arr.slice();
let assign = Object.assign({},obj)
let spreadArr = {...arr};
let spreadObj = {...obj};
SpreadSyntax(...)
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('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);
매개변수보다 전달인자가 많을때, 매개변수 갯수만큼만 뱉어냄.
반대로, 매개변수가 더 많다면 undefined
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']); // 키들을 모두 배열
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);
});
Array.from() 너 배열이 되어라~!
객체에서 배열로 바꿔준다
Destructuring 구조분해할당
it('객체의 단축(shorthand) 문법을 익힙니다', () => {
const name = '김코딩'
const age = 28
const person = {
name, //name 을 외부에서 참조
age, //age 을 외부에서 참조
level: 'Junior',
}
expect(person).to.eql({name: '김코딩', age: 28, level: 'Junior'})
})
it('rest/spread 문법을 객체 분해에 적용할 수 있습니다 #1', () => {
const student = { name: '최초보', major: '물리학과' }
const { name, ...args } = student
//name = student.name
//arg = student.name 빼고 나머지
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 }) {
//lesson: course 에서 lesson을 키로한 값이 course 다.
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,//company 그대로 복사
department: 'Marketing'
//company에서 기존처럼 user.company 로 쓰는데 디파트먼트 마케팅만 바꾸겠다.
}
}
expect(changedUser).to.eql({
name: '박해커',
company: {
name: 'Code States',
department: 'Development',
role: {
name: 'Software Engineer'
}
},
age: 20
})
expect(overwriteChanges).to.eql({name: '김코딩', age: 35, company:{
name: 'Code States',
department: 'Development',
role: {
name: 'Software Engineer'
}
}})
expect(changedDepartment).to.eql({
name: '김코딩',
company: {
name: 'Code States',
department: 'Marketing',
role: {
name: 'Software Engineer'
}
},
age: 35
})
})
})
'Javascript' 카테고리의 다른 글
이벤트 객체에서 문자열 뽑아오기 (0) | 2023.03.08 |
---|---|
DOM 기초개요 (0) | 2023.03.07 |
DOM 다루기 (CRUD + append) (0) | 2023.03.07 |
자바스크립트 입문편: 디지털 시계만들기 (2) | 2023.03.05 |
현재 시간 브라우저에 나타내기 (0) | 2023.03.04 |