자바스크립트 중급 강좌 ③
구조분해할당
배열이나 객체의 속성을 분해해서 그 값을 변수에 담을 수 있게 하는 표현식
let [x,y] = [1,2];
console.log(x); //1
console.log(y); //2
배열 구조 분해
let users=['Mike', 'Tom', 'Jane'];
let [user1, user2, user3] = users; //let user1= users[0]; let user2 = users[1]; let user3 = users[2]; 와 같다.
console.log(user1); //'Mike'
console.log(user2); //'Tom'
console.log(user3); //'Jane'
let str = "Mike-Tom-Jane";
let [user1, user2, user3] = str.split('-'); // str.split('-') 은 ["Mike", "Tom" , "Jane"] 이다.
console.log(user1); //'Mike'
console.log(user2); //'Tom'
console.log(user3); //'Jane'
배열 구조 분해 : 기본값
해당하는 값이 없는 경우?
let [a,b,c] = [1,2]; // c에는 undefined가 들어간다.
let [a=3, b=4, c=5] = [1,2] ; //이렇게 기본값을 주면 에러를 미연에 방지할 수 있다.
console.log(a); //1
console.log(b); //2
console.log(c); //5
배열 구조 분해 : 일부 반환값 무시
let [user1, ,user2] = ['Mike', 'Tom', 'Jane', 'Tony']; //공백과 쉼표를 이용해서 필요하지 않는 요소를 무시할 수 있다.
console.log(user1); //'Mike'
console.log(user2); //'Jane'
배열 구조 분해 : 바꿔치기
원래는 let a = 1; let b =2 ; 에서 a와 b를 바꾸려면 새로운 cup 인 c가 있어야한다.
구조분해할당을 하면 [a, b] = [b, a] 로 바꿀 수 있다. 더이상 임시변수 c가 필요없다
객체 구조 분해
객체도 구조분해가 가능하다.
let user = {name: 'Mike', age: 30};
let {name, age} = user; // 이 코드는 let name = user.name; let age = user.age; 와 같다.배열구조분해할당과 다르지 않다.
//let {age, name} = user; 로 순서를 바꾸어도 동일하게 동작한다.
console.log(name); //'Mike'
console.log(age); // 30
객체구조분해: 새로운 변수 이름으로 할당
프로퍼티의 키값을 무조건 사용하는게 아니라, 변수의 이름을 바꿀수도 있다.
let user = {name: 'Mike' , age:30};
let {name:userName, age:userAge} = user; //name 프로퍼티의 키를 userName으로, age 프로퍼티의 키를 userAge로 바꾸었다.
console.log(userName); //'Mike'
console.log(userAge); //30
객체 구조 분해 : 기본값
배열과 마찬가지로 객체를 분해할 때도 기본값을 줄 수 있다.
let user = {name:'Mike', age:30};
let {name, age, gender} = user; //gender에는 아무것도 안들어있으므로 undefined가 된다.
let {name, age, gender='male'} = user; // user객체에 gender 없다면 기본적으로 초기값으로 male이 할당된다.
let user = {
name:'Jane',
age:18,
gender:'female'
};
let {name, age, gender = 'male'} = user;
console.log(gender); //'female' 출력, user객체에 gender값이 있으면 그 값이 사용된다. 객체로 받은값이 undefined 일때만 기본값이 사용된다.
나머지 매개변수, 전개 구문 (Rest parameters, Spread syntax)
... 점 세개로 사용한다. 인수전달
function showName(name){ //자바스크립트에서 함수에 넘겨주는 인수의 개수는 제약이 없다.
console.log(name);
}
showName('Mike'); //'Mike'
showName('Mike', 'Tom'); // ? 이름을 하나더 전달하면 어떻게 될까? 에러는 발생하지 않는다. Mike만 찍힌다.
showName(); // undefined 가 출력된다. 아무것도 인수로 안보내도 에러는 발생하지 않는다.
함수에 인수를 얻는 방법은
arguments로 접근하는 방법, 나머지 매개변수를 쓰는 방법 2가지가 있다.
과거에는 arguments, 지금은 여러 장점이 있는 나머지 매개변수를 사용하는 추세.
화살표함수에는 argumnet가 없다.
arguments
1. 함수로 넘어 온 모든 인수에 접근할 수 있다.
2. 함수내에서 이용 가능한 지역변수이다.
3. legth/index 속성이 있다.
4. 배열은 아니고 Array 형태의 객체이다.
5. 배열의 내장 메서드 없음 (forEach, map은 사용할 수 없다.)
function showName(name) {
console.log(arguments.length);
console.log(arguments[0]);
console.log(arguments[1]);
}
showName('Mike', 'Tom'); //두개의 인수를 전달, argument로 접근할 수 있다.
//2
//Mike
//Tom
//es6를 사용할수 있는 환경에서는 나머지 매개변수(Rest parameters) 사용을 권장한다.
나머지 매개변수
정해지지 않은 갯수의 인수를 배열로 나타낼수 있게 한다.
function showName(...names){ // 점 세개를 찍고 뒤에 배열이름을 정해준다. names 배열안에 전달된 인수들이 들어간다.
console.log(names);
}
showName(); // [] , 아무것도 전달하지 않으면 undefined가 아니라 빈배열이 나타난다.
showName('Mike'); //['Mike']
showName('Mike', 'Tom'); //['Mike', 'Tom']
실습
전달 받은 모든 수를 더해야함
function add() {} // 어떻게 바꿔야 할까???????????????
add(1, 2, 3);
add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
function add(...numbers) { //numbers는 배열이고 length가 있기 때문에 for문으로 사용해도 된다.
let result = 0;
numbers.forEach((num) =>(result += num));
console.log(result);
}
add(1, 2, 3); // 6
add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55
function add(...numbers){
let result = numbers.reduce((prev,cur)=>prev +cur); // 나머지 매개변수는 reduce같은 배열의 메소드를 사용할 수 있는 장점이 있다.
console.log(result);
}
add(1, 2, 3); // 6
add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55
user객체를 만들 수 있는 생성자 함수를 만든다.
function User(name, age, ...skills){ //생성자 함수는 첫글자 대문자. 사람마다 skills는 다를거므로 ...을 붙여서 나머지매개변수를 사용한다.
//나머지 매개변수는 주의 해야할점은 ...skills 처럼 인수중에 가장 마지막에 위치해야한다.
this.name = name;
this.age = age;
this.skills = skills;
}
const user1 = new User('Mike', 30, 'html', 'css');
const user2 = new User('Tom', 20, 'JS', 'React');
const user3 = new User("Jane", 10, "ENGLISH");
console.log(user1); //User { name: 'Mike', age: 30, skills: [ 'html', 'css' ] }
console.log(user2); //User { name: 'Tom', age: 20, skills: [ 'JS', 'React' ] }
console.log(user3); //User { name: 'Jane', age: 10, skills: [ 'ENGLISH' ] }
전개 구문(Spread syntax) : 배열
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let result = [...arr1, ...arr2]; //...arr1 은 1, 2, 3 을 풀어서 쓴거다. ...arr2는 4, 5 ,6을 풀어서 쓴거다.
console.log(result); //[1, 2, 3, 4, 5, 6]
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let result = [0, ...arr1, ...arr2, 7, 8, 9];
console.log(result); // [0,1,2,3,4,5,6,7,8,9]
배열에 중간에 넣고(arr.push()), 빼고(arr.splice()) , 병합(arr.concat()).. 하는 과정이 번거로우나, 전개구문을 사용하면 쉽게 할 수 있다.
전개 구문(Spread syntax) : 객체
전개구문은 객체도 가능하다.
let user = {name:'Mike'}
let mike = {...user, age:30}
console.log(mike) // {name:"Mike", age:30}
전개 구문(Spread syntax) : 복제
Object.assign()을 사용하지 않아도 된다.
let arr = [1, 2, 3];
let arr2 = [...arr]; //[1, 2, 3]
let user = {name:'Mike', age:30};
let user2 = {...user};
user2.name = "Tom"; //user2의 name을 Tom으로 바꾸어도, user 의 name에는 영향을 미치지 않는다. 별개의 객체user2가 복제되었다.
console.log(user.name); //"Mike"
console.log(user2.name); //"Tom"
실습
arr1 을 [4,5,6,1,2,3] 으로 만드려면 어떻게 해야할까?
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
console.log(arr1);
전개구문 안쓰는 방법으로 해보면
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
arr2.reverse().forEach((num)=>{
arr1.unshift(num);
});
console.log(arr1); //[4,5,6,1,2,3]
전개 구문을 쓰면
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
arr1 = [...arr2, ...arr1]; //간단하게 만들 수 있다.
console.log(arr1);//[4,5,6,1,2,3]
객체에서도 전개구문을 적용하는 실습을해보자
먼저 전개구문 안쓰고 하는 방법
let user = { name: "Mike"};
let info = {age:30};
let fe = ["JS", "React"];
let lang = ["Korean", "English"];
user = Object.assign({}, user, info, {
skills :[]
});
fe.forEach((item)=>{
user.skills.push(item);
});
lang.forEach((item)=>{
user.skills.push(item);
});
console.log(user);//{ name: 'Mike', age: 30, skills: [ 'JS', 'React', 'Korean', 'English' ] } 출력
전개구문을 쓰는 방법
let user = { name: "Mike"};
let info = {age:30};
let fe = ["JS", "React"];
let lang = ["Korean", "English"];
user = {
...user,
...info,
skills:[...fe, ...lang],
}; //전개구문을 쓰면 간단하게 만들 수 있다.
console.log(user);//{ name: 'Mike', age: 30, skills: [ 'JS', 'React', 'Korean', 'English' ] } 출력
클로저 (closure)
자바스크립트는 어휘적 환경(Lexical Environment)을 갇는다.
코드가 실행되면 스크립트 내에서 선언한 변수들이 Lexcial 환경에 올라간다
let 으로 선언된 변수도 호이스팅된다.
let one;
one = 1;
function addOne(num) {
console.log(one + num);
}
addOne(5);
let one; // let one과 function addOne(num)은 (전역) Lexcial 환경에 올라간다. one은 초기화 x 로 사용불가이다.
one = 1;
function addOne(num) { 그에 비해 함수선언문은 변수와 달리 바로 초기화가 되어있어 바로 사용이 가능하다.
console.log(one + num);
}
addOne(5); // 함수가 실행되며 , 새로운 (내부) Lexcial 환경이 만들어진다. 함수가 넘겨받은 매개변수와 지역변수가 저장된다. 함수가 호출되는동안 함수에서 만들어진 내부 Lexical 환경과 외부에서 받은 전역 Lexical 환경 두개를 가진다. 내부 Lexical 환경은 외부 Lexical 환경에 대한 참조를 갖는다.
지금은 저 함수의 외부 Lexical환경이 전역 Lexical 환경이다. 코드에서 변수를 찾을때 내부에 없으면 외부, 외부가 없으면 전역으로 범위를 넓혀서 찾는다. console.log(one + num); 에서 one과 num은 내부 Lexical에서 우선 찾는다. num은 찾았지만 one은 없으므로, 외부로 넓혀서 찾는다. 찾아서 더해줄 수 있다.
어휘적 환경 다른예
add함수를 만들어주는 함수 makeAdder()
function makeAdder(x){
return function(y){
return x + y;
}
}
const add3 = makeAdder(3);
console.log(add3(2));
최초 실행시 makeAdder 함수와 변수 add3는 전역 Lexcial 환경에 들어간다.
전역 Lexical환경에서 add3은 초기화가 안된상태고 사용할수 없다.
function makeAdder(x){
return function(y){
return x + y;
}
}
const add3 = makeAdder(3); // 이라인이 실행될때 ,makeAdder 가 실행되고 makeAdder Lexical 환경이 만들어진다. 여기서 전달받은 x의 값이 makeAdder로 들어가게 된다.
//함수의 Lexcial환경에는 넘겨받는 매개변수와 지역변수가 저장된다. 전역 Lexcical 환경이였던 add3은 makeAdder함수가 실행됬으니 return하는 함수가 된다.
console.log(add3(2)); //add3을 실행하면 return function(y) 함수가 실행되는데, 이 때 또 익명함수 Lexical환경이 만들어지고 y가 2로 들어간다.
x + y를 하면 처음에 x와 y를 찾고, y가 있는데 x가 없으니 참조하는 외부 Lexcical 환경으로 가서 x를 찾았다
다시정리
function makeAdder(x){
return function(y){ // 이 함수는 자신이 y를 가지고 있고, 상위함수인 makeAdder 의 매개변수 x에 접근가능
return x + y;
}
}
const add3 = makeAdder(3);
console.log(add3(2)); //5 , add3 함수가 생성된 이후에도 상위함수인 makeAdder 의 x에 접근 가능, 이런것을 Closure라 한다.
//Closure는 함수와 그 함수의 렉시컬 환경의 조합, 함수가 생성될 당시의 외부변수를 기억하고, 생성된 이후에도 그 변수에 계속해서 접근 가능
//외부함수의 실행이 끝나서, 외부함수가 소멸된 이후에도 내부함수가 외부함수의 변수에 접근할 수 있다.
const add10 = makeAdder(10); // makeAdder(10)이 호출되지만 makeAdder(3)에는 아무런 변화가 없다. makeAdder(10)과 makeAdder(3)은 서로다른 환경을 가지고 있다.
console.log(add10(5)); // 15
console.log(add3(1)); //4
function makeCounter(){
let num = 0; //외부함수의 변수이다 //은닉화
return function() { //이함수는 숫자로 반환하는데
return num++;
};
}
let counter = makeCounter();//counter에 makeCounter를 return하는 함수 즉, return function() 를 넣었다.
console.log(counter()); //0
console.log(counter()); //1 , 내부함수에서 외부함수의 변수 즉, num에 접근한다
console.log(counter()); //2
0, 1, 2 이 숫자들을 수정할수 있을까? 불가능
오직 카운터를 증가시키고 반환받는다. 은닉화에 성공한거다.
함수실행을 시간으로 제어하는 스케줄링을 알아보자
setTimeout
일정시간이 지난 후 함수를 실행한다
setInterval
일정 시간 간격으로 함수를 반복한다
function fn(){
console.log(3)
}
setTimeout(fn, 3000); // 3초후에 3을 찍어준다. setTimeout()은 두개의 매개변수를 받는다. 일정시간이 지난뒤 실행하는 함수 fn, 3000 은 3초를 의미한다.
setTimeout(function(){
console.log(3)
},3000); // 위와 같다. 함수를 전달하지 않고 직접 코드를 작성해도 동일하게 동작한다.
function showName(name){
console.log(name);
}
setTimeout(showName, 3000, 'Mike'); //3초마다 'Mike'출력 , 이렇게 인수가 필요하면 시간뒤에 적어준다. 'Mike'는 showName 함수의 첫번째 인수로 전달된다.
clearTimeout
예정된 작업을 없앤다.setTimeout은 tId를 반환하는데 clearTimeout을 이용하면 스케줄링을 취소할수있다.
function showName(name){
console.log(name);
}
const tId = setTimeout(showName, 3000, 'Mike');
clearTimeout(tId); //3초가 지나기전에 코드가 실행되어 아무일도 일어나지 않는다.
setInterval
setTimeout과 사용법동일하다.
한번 실행하고 끝나는 것과 달리, 계속 반복수행한다.
function showName(name){
console.log(name);
}
const tId = setInterval(showName, 3000, 'Mike'); //'Mike', 'Mike' ... 3초마다 무한반복
주의 사항
delay = 0 으로 줘도 실제 바로 실행되지 않는다.
setTimeout(function(){
console.log(2) // 1이 찍히고 2가 찍힌다. 이유는 현재 실행중인 스크립트가 종료된 이후, 스케줄링 함수가 실행된다. 0이라고 적어도 바로 실행되는게아니다. 브라우저는 기본적으로 4ms 정도의 대기시간이 있다.
}, 0); // delay가 0
console.log(1); // 1이 먼저 찍힌다.
1초에 한번씩 메세지가 나온다.
let num = 0;
function showTime() {
console.log(`안녕하세요. 접속하신지 ${num++}초가 지났습니다.`);
}
setInterval(showTime, 1000);
5초동안 보여주고 싶으면??
let num = 0;
function showTime(){
console.log(`안녕하세요. 접속하신지 ${num++}초가 지났습니다.`);
if(num > 5){
clearInterval(tId);
}
}
const tId = setInterval(showTime, 1000);
call, apply, bind
함수 호출 방식과 관계없이 this를 지정할 수 있다.
call
call 메서드는 모든 함수에서 사용할 수 있으며 ,this를 특정값으로 지정할수있다.
const mike = {
name: "Mike",
};
const tom = {
name : "Tom",
};
function showThisName() {
console.log(this.name); // this는 window를 가리킨다.
}
showThisName.call(mike);//Mike, 함수를 호출하면서 call을 사용하고 this로 사용할 객체를 넘기면 해당함수가 주어진 객체의 메소드인것처럼 사용할수 있다.
//call의 첫번째 매개변수는 this로 사용할 값이고, 매개변수가 더 있으면 그 매개변수를 호출하는 함수로 전달된다.
showThisName.call(tom);
function update(birthYear, occupation){
this.birthYear = birthYear;
this.occupation = occupation;
}
update.call(mike, 1999, 'singer');
첫번째 매개변수는 this로 사용될 값, 두번째 매개변수부터는 함수가 사용할 매개변수들을 순서대로 적은것이다.
console.log(mike); //{ name: 'Mike', birthYear: 1999, occupation: 'singer' }
update.call(tom, 2002, "teacher");
console.log(tom); //{ name: 'Tom', birthYear: 2002, occupation: 'teacher' }
apply
함수 매개변수를 처리하는 방법을 제외하면 call과 완전히 같다.
call은 일반적인 함수와 마찬가지로 매개변수를 직접 받지만, apply는 매개변수를 배열로 받는다.
const mike = {
name: "Mike",
};
const tom = {
name : "Tom",
};
function showThisName() {
console.log(this.name);
}
function update(birthYear, occupation){
this.birthYear = birthYear;
this.occupation = occupation;
}
update.apply(mike, [1999, 'singer']);
console.log(mike); //{ name: 'Mike', birthYear: 1999, occupation: 'singer' }
update.apply(tom, [2002, "teacher"]);
console.log(tom); //{ name: 'Tom', birthYear: 2002, occupation: 'teacher' }
apply는 배열요소를 함수매개변수로 사용할때 유용하다.
const minNum = Math.min(3, 10 , 1, 6, 4);
const maxNum = Math.max(3, 10, 1, 6, 4);
console.log(minNum); // 1
console.log(maxNum); // 10
const minNum = Math.min([3, 10 , 1, 6, 4]);
const maxNum = Math.max(3, 10, 1, 6, 4);
console.log(minNum); // NaN, 배열자체를 넣어버리면 NaN이 출력
console.log(maxNum); // 10
스프레드 연산자를 배웠으니 활용해보자.
const nums = [3, 10, 1, 6, 4];
const minNum = Math.min(...nums);
const maxNum = Math.max(...nums);
console.log(minNum); // 1
console.log(maxNum); // 10
apply를 써보자.
apply는 두번째 매개변수로 배열을 전달하면, 그 요소들을 차례대로 인수로 사용한다.
const nums = [3, 10, 1, 6, 4];
const minNum = Math.min.apply(null, nums); //null은 this로 사용될 값인데, Math.min이나 max는 this가 필요 없으므로 아무 값이나 넣어줘도 된다.
// = Math.min.apply(null, [3, 10, 1, 6, 4])
const maxNum = Math.min.apply(null, nums);
const maxNum = Math.max.call(null, ...nums);
// = Math.min.apply(null, 3, 10, 1, 6, 4)
console.log(minNum); // 1
console.log(maxNum); // 10
bind
함수의 this값을 영구히 바꿀 수 있다.
const mike = {
name: "Mike",
};
function update(birthYear, occupation){
this.birthYear = birthYear;
this.occupation = occupation;
}
const updateMike = update.bind(mike);
updateMike(1980, "police");
console.log(mike); // {name:"Mike", birthYear:1980, occupation:"police"}
실제사용예제>
const user = {
name : "Mike",
showName : function() {
console.log(`hello, ${this.name}`);
},
};
user.showName(); // hello, Mike
let fn = user.showName;
fn() ; // hello, 뒤에 이름이 나오지 않는다.
fn에 할당할때 this를 잃어버렸다.
메소드는 . 앞에 user같은 것들이 this라고 했다.
호출할때 fn만 호출하니까 this가 없다.
//call을 써보자.
fn.call(user); //hello, Mike 잘나온다.
//apply 써도된다.
fn.apply(user); // hello, Mike 잘나온다.
// bind를 이용해서 새함수를 만들어보자
let boundFn = fn.bind(user);
boundFn(); // hello, Mike 잘나온다.
'프론트엔드 > Javascript' 카테고리의 다른 글
자바스크립트 중급 강좌 ④ (0) | 2021.07.28 |
---|---|
자바스크립트 기초 강좌 ② (0) | 2021.07.27 |
자바스크립트 중급 강좌 ② (0) | 2021.05.31 |
자바스크립트 중급 강좌 ① (0) | 2021.05.30 |
자바스크립트 기초 강좌 ① (0) | 2021.05.28 |