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');
});
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)>
클로저는 내부 함수가 외부 함수의 로컬 변수에 액세스할 수 있습니다.
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);
배열 유형은 배열이 아닙니다.
개체입니다.
객체 유형도 객체입니다.
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).길이
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); // 엥? 내부 요소가 원본처럼 삭제되었네?
복사란 무엇입니까? 같은 주소가 없었지만 같은 값을 가진 객체생성하기
한 장밖에 복사할 수 없기 때문에, 얕은 복사이기 때문에, 안쪽은 주소를 공유하므로 안쪽 요소를 지워 버리면, 오리지널에도 변화가 생긴다.
즉 제거하면 어느 쪽도 없어진다!
덧붙여서, JavaScript에서는 진정한 깊은 복사는 불가능합니다.
★ 배열, 객체를 복사하는 좋은 방법 (얕은 복사)
//즉, 원본변화 없이 새로운 배열 변화 가능, 하지만 이것은 모두 얕은 복사에 해당, 한겹만 복사, 나머지는 주소공유
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
})
})
})