Tech/JavaScript

[js/자바스크립트] js 기초 2

닝닝깅 2022. 10. 6. 19:04

참고

https://www.youtube.com/watch?v=4_WLS9Lj6n4

들었던 js 강의 중에 가장 핵심만 설명해주면서도 이해하기 쉬운 강의였다.!!


변수 , 호이스팅

const

1. 선언, 초기화, 할당 단계

 

let

1. 선언단계

2. 초기화 단계

3. 할당단계

 

var

1. 선언 및 초기화 단계

2. 할당단계

- 한번 선언된 변수를 다시 선언할 수 있음

- 선언하기 전에 사용할 수 있음

 

호이스팅(hoisting)

var를 사용하면 변수를 먼저 호출하고 나중에 선언해도 에러가 발생하지 않는다.

console.log(name);
var name = 'Mike';
//undefined

위와 같은 코드는 아래와 같이 동작하는데 이를 호이스팅이라고 한다.

호이스팅은 스코프 내부 어디서든 변수 선언은 최상위에 선언된 것처럼 행동하는 것이다.

var name;
console.log(name);
name = "Mike";

이때 변수 선언은 호이스팅이 되지만 할당은 호이스팅 되지 않아 콘솔 창에 undefined라고 찍히는 것이다.

 

호이스팅은 스코프 단위로 일어난다. 

const, let = 블록 스코프

var = 함수 스코프 : 코드블록 안에서 선언하였어도 밖에서 사용할 수도 있음 (함수에서는 적용 불가능)

 

생성자 함수

객체리터럴

let user = {
	name: 'Mike',
    age: 30,
}

 

생성자 함수를 사용하면 여러개의 객체리터럴을 편리하게 생성할 수 있다.

생성자 함수를 만든 후 new 연산자를 사용해서 호출한다.

function User (name, age){
	this.name = name;
    this.age = age;
    this.sayName = () => {
    	console.log(this.name);
    },
}

let user1 = new User('Mike', 30);

 

계산된 프로퍼티 ( Computed Property)

function makeObj(key, val){
	return{
		[key]: val,
    }
}

const obj = makeObj("나이", 33);
//{나이: 33}

 

객체 메소드

Object.assign() - 객체 복제

하나의 객체를 두개의 변수로 접근하는 것이 아니라 동일하게 복제할 때 사용한다.

const user = {name, age을 가진 두개의 프로퍼티}

const newUser = Object.assign({gender: 'Male'},user);
//name, age, gender 세개의 프로퍼티를 가진 객체 생성

Object.values() - 값 배열 반환

Object.entries() - 키/값 배열 반환  ex) [['name','Mike'],['age',30]]

Object.fromEntries() - 키/값 배열을 객체로

 

심볼 ( Symbol )

객체의 프로퍼티 키는 문자형과 심볼형으로 쓸 수 있다.

앞서 사용한 방식은 모두 문자형이다.

 

심볼형은 유일성을 보장한다.

다른 사람이 만들어 놓은 객체에 자신만의 특성을 추가하고 싶은 경우에 주로 사용된다.

심볼을 키로 사용할 경우 키와 값 모두 노출되지 않는다.

const id = Symbol('id');
const id2 = Symbol('id2');
//id != id2

숨겨진 심볼 키를 보기 위해서는 아래와 같은 방법을 사용한다. 

Object.getOwnPropertySymbols(user);

 

Symbol.for() : 전역심볼

- 하나의 심볼 값을 생성한 뒤 키를 통해 같은 심볼 값을 공유한다.

 

숫자, 수학 메소드 ( Number, Math )

toString() 

let num = 10;

num.toString(); //"10"
num.toString(2); // "1010" - 2진수로 바꾼 후 문자로

Math.ceil() : 올림

Math.floor(): 내림

Math.round(): 반올림

toFixed(): 소숫점 자리를 특정하고 싶을 때 사용한다.

let userRate = 30.1234;
userRate.toFixed(2);
//30.12

isNaN(): NaN인지 아닌지 확인한다.

parseInt(): 문자열을 숫자로 바꿔준다.

Math.random(): 0~1사이의 랜덤한 수

//1~100사이 임의의 숫자를 뽑고 싶을 때
Math.floor(Math.random()*100)+1

Math.max() / Math.max() : 괄호 안의 인수들 중 최대/최솟값

Math.abs() : 절댓값

Math.pow(n, m) : n의 m승

Math.sqrt() : 제곱근

Number.Integer(num) : num이 정수인지 아닌지 판별 

 

# 숫자 진수 변환

https://ithub.tistory.com/290

 

자바스크립트(Javascript) 진수변환 방법 (2진수, 8진수, 10진수, 16진수)

문제 자바스크립트의 Number객체의 내장 함수인 toString() 함수와 전역 함수인 parseInt() 함수를 사용하면, 진수변환을 간단하게 처리할 수 있습니다. 사용 방법 /** * 진수 변환 * * 10진수를 진수 변환

ithub.tistory.com

문자열 메소드

백틱(`)을 사용하게 되면 문자열 안에 변수를 포함할 수 있고 줄바꿈으로 여러줄을 작성할 수 있다.

length() - 문자열 길이

toUpperCase() / toLowerCase() - 모든 문자를 대/소문자로 변경

str.indexOf(text) - 문자열 내에 text위치 반환 ( 없으면 -1 반환 ) , 정규식 쓸 수 없다

str.slice(n,m) - n ~ m - 1 까지의 문자열 반환

str.substring(n,m) - n과 m 사이 문자열 반환 ( 순서 바뀌어도 작동)

str.substr(n,m) - n부터 m개의 문자를 반환

str.trim() - 문자열 앞뒤 공백 제거

str.repeat(n) - 문자열 n번 반복

str.match("string") - 문자열내에서 특정 문자열 찾고 배열로 반환

str.search(text) - indexOf와 동일, 정규식 쓸 수 있다.

* 문자열에 사칙연산을 사용하게 되면 자동으로 숫자형으로 형변환된다.

 

 

배열 메소드

arr.splice(n,m) - n부터 m개의 요소 삭제

arr.splice(n,m,x..) - n부터 m개의 요소 지우고 그 자리에 x.. 추가

arr.slice(n,m) - n부터 까지 반환

arr.concat(arr2, arr3 ..) - 합쳐서 새 배열 반환

arr.forEach(fn) - 배열 반복

arr.forEach((name, index) => {
	console.log(name)
});

arr.indexOf / arr.lastIndexOf - 배열의 인덱스 값 반환

arr.includes() - 포함하는 지 확인

arr.find(fn) / arr.findIndex(fn) - 조건에 맞는 요소 / 인덱스 값 반환

const arr = [1,2,3,4,5,6];

const result = arr.find((item => {
	return item % 2 ==0;
});
//2

arr.filter(fn) - 만족하는 모든 요소를 배열로 반환

const arr = [1,2,3,4,5,6];

const result = arr.filter((item => {
	return item % 2 ==0;
});
//2,4,6

arr.reverse() - 배열을 역순으로 재정렬

arr.map(fn) - 함수를 받아 특정 기능을 수행하고 새로운 배열을 반환

let userList = {
	{ name: "Mike", age: 30},
    { name: "Jane", age: 27},
};

let newUserList = userList.map((user, index) => {
	return Object.aggign({}, user, {
    	id: index + 1,
    	isAdult: user.age > 19,
    });
});
//[{name: "Mike", age: 30, id: 1, isAdult: true}, {...}, {...}]

arr.join() - 배열을 합쳐서 문자열 리턴

arr.split() - 문자열을 나눠서 배열로 리턴

 

arr.sort() - 배열을 정렬

인수로 함수를 받음

let arr = [27, 8, 5, 13];

arr.sort((a, b) => {
	return a - b;
})
//[5, 8, 13, 27]

정렬을 할 때 Lodash 같은 라이브러리를 사용하여 더 쉽게 할 수 있다.

Lodash : _.sortBy(arr);

 

arr.reduce() - 배열의 값을 계산

인수로 함수를 받음

(누적 계산값, 현재값) => {  return 계산값 }; 

let userList = [
	{name: "Mike", age: 30},
   	...
]

let result = userList.reduce((prev, cur) => {
	if ( cur.age > 19) {
    	prev.push(cur.name);
    }
    return prev;
}, 0);

 

구조분해할당 (Destructuring assignment)

: 배열이나 객체의 속성을 분해해서 그 값을 변수에 담을 수 있게 하는 표현식

let str = "Mike-Tom-Jane";
let [user1, user2, user3] = str.split("-");

let[user1, , user2] = ['Mike', 'Tom', 'Jane', 'Tony'];
//user1 = Mike, user2 = Jane

 

나머지 매개변수, 전개 구문

나머지 매개변수

js에서 함수에 넘겨주는 인수의 개수는 제한이 없다.

인수를 초과하면 초과한 인수는 무시되고 아무 인수도 넘겨주지 않을 경우에는 undefined를 반환한다.

 

함수의 인수를 얻는 방법은 두가지가 있다

1. arguments로 접근 (화살표 함수에서는 사용 불가능)

- 함수로 넘어온 모든 인수에 접근

- 함수 내에서 이용 가능한 지역 변수

- length / index

- 배열 형태의 객체

function showName(name) {
    console.log(arguments.length);
    console.log(arguments[0]);
    console.log(arguments[1]);
}
showName('Mike', 'Tom');
//2
//Mike
//Tom

 

2. 나머지 매개변수 사용

- 정해지지 않은 개수의 인수를 배열로 나타낸다. 

function add(...numbers) {
    let result = 0;
    numbers.forEach(num => (result += num));
    console.log(result);
}

add(1, 2, 3);
add(1, 2, 3, 4, 5, 6, 7, 8, 9);
//6
//45
//user 객체를 만들어 주는 생성자 함수
function User(name, age, ...skill){
    this.name = name;
    this.age = age;
    this.skills = skills;
}
const user1 = new User("Mike", 30, "html", "css", "js");
console.log(user1);
//{name: "Mike", age: 30, skills: ["html", "css", "js"];

 

전개구문

배열에서의 사용

let arr1 = [1,2,3];
let arr2 = [4,5,6];

let result = [0, ...arr1, ...arr2, 7, 8, 9];
//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

 

복제를 위한 사용

let user = {name: "Mike", age: 30};
let user2 = {...user};

 

클로저 (Closure)

js는 어휘적(Lexical) 환경을 갖는다.

function makeAdder(x){
	return function(y){
    	return x + y;
    }
};
//전역 Lexical 환경
//makeAdder: function
//add3: function

const add3 = makeAdder(3);
//makeAdder Lexical 환경
//x: 3

console.log(add3(2));
//익명함수 Lexical 환경
//y: 2

y를 가지고 있고 상위 함수인 makeAdder의 x에 접근이 가능하다.

 

클로저는 함수와 렉시컬 환경의 조합이다.

함수가 생성될 당시의 외부변수를 기억하고 생성이후에도 계속 접근 가능하다.

 

예를 들어 위의 코드를 보면,

makeAdder이 리턴하는 함수는 y를 가지고 있고 상위함수인 makeAdder의 x에 접근이 가능하다.

따라서 x와 y의 값을 리턴할 수 있는 것이다.

 add3 함수가 생성된 이후에도 상위함수인 makeAdder의 x에 접근이 가능하다.

 

setTimeout / setInterval

setTimeout

일정 시간이 지난 후 함수를 실행한다.

const tId = function showName(name){
                 console.log(name);
            }
setTimeout(showName, 3000, 'Mike'); //함수, 시간, 인수

clearTimeout(tId); //예정된 작업을 없앤다.

딜레이 타임을 0으로 줘도 바로 실행되는 것이 아니다. 

 

setInterval

일정 시간 간격으로 함수를 반복한다.

setTimeout과 동일하게 사용된다.

 

call, apply, bind

: 함수 호출 방식과 관계없이 this를 지정할 수 있다.

 

call

모든 함수에서 사용할 수 있으며 this를 특정값으로 지정할 수 있다.

const mike = { name: "Mike", };

function update(birthYear, occupation) {
    this.birthYear = birthYear;
    this.occupation = occupation;
}

update.call(mike, 1999, "singer"); //this로 설정될 매개변수, birthYear, occupation

 

apply

함수 매개변수를 처리하는 방법을 제외하면 call과 완전히 같다.

call은 일반적인 함수와 마찬가지로 매개변수를 직접 받지만 apply는 매개변수를 배열로 받는다.

update.call(mike, [1999, "singer"])

 

bind

함수의 this값을 영구히 바꾼다.

const mike = { name: "Mike", };

const updateMike = update.bind(mike); //항상 mike를 this로 받는 함수

 

상속, 프로토타입

객체 안에 특정 프로퍼티가 있는지 확인하기 위해서 hasOwnProperty()라는 함수를 사용할 수 있다.

이 함수는 객체에 프로퍼티로 존재하는 함수이다.

const user = { name: "Mike" };
user.hasOwnProperty(name);
//true

 

객체가 상위 객체의 상속을 받기 위해서는 __proto__ 를 사용한다.

const car = { wheels: 4 };
const bmw = { color: "red" };

bmw.__proto__ = car;

상속을 받은 객체는 hasOwnProperty() 함수 사용 시 실제 갖고 있는 프로퍼티만 true로 반환한다.

 

객체마다 상속을 명시해주지 않고 생성자 함수에 프로퍼티를 직접 추가하면 객체 생성이 더 편리하다.

const BMW = function(color) {
	this.color = color;
};

//방법1
BMW.prototype.wheels = 4;
BMW.prototype.drive = function () {
    console.log("drive...");
}

//방법2
BMW.prototype = {
    constructor: BMW,
    navigation: 1,
    stop() {
        console.log("STOP");
    },
}

방법2에서는 생성자 명시를 꼭 해주어야 한다.

 

클래스

: ES6에서 등장한 개념이다.

 

User은 객체 내부에 showName()이 있고 User2는 프로토타입 내부에 showName()이 있다.

const User = function (name, age) {
    this.name = name;
    this.age = age;
    this.showName = function () {
        console.log(this.name);
    };
};

class User2 {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    showName() {
        console.log(this.name);
    }
}

클래스는 선언할 때 반드시 new를 사용하여 선언해야 한다.

const tom = new User2("Tom", 20);

 

클래스의 상속은 extends를 사용한다.

class Car {
    constructor(color){
        this.color = color;
        this.wheels = 4;
    }
    drive() {
        console.log("drive");
    }
}

class BMW extends Car {
    park() {
        console.log("park");
    }
}

 

위의 예시에서 BMW클래스에서 부모클래스와 동일한 이름의 메소드를 갖고 있을 경우 메소드 덮어쓰기가 된다.

부모클래스의 메소드 내용을 그대로 갖고 오고 싶을 경우에는 아래와 같이 하면 된다.

class BMW extends Car {
    consturctor(color) {  //생성자에서 color인수가 필요하므로 그대로 가져온다
        super(color);     //super()함수를 사용하여 오버라이딩을 한다.
        this.navigation = 1;
    }
    park() {
    	super.park();      //super.<프로퍼티이름>()
        console.log("park");
    }
}

 

Promise

:  특정 작업이 완료될 때까지 기다리지 않고 다음 작업을 시작하는 비동기 처리에 사용되는 객체이다.

const pr = new Promise((resolve, reject) => {
    //code
});

resolve와 reject는 콜백함수로 resolve는 성공했을 경우, reject는 실패했을 경우에 실행된다.

 

//선언부
const pr = new Promise((resolve, reject) => {
    setTimeout (() => {
        resolve("OK");
    }, 1000);
});

//실행부
pr.then((result) => {
    console.log(result);
})
  .catch((err) => {
      console.log(err);
    })
  .catch(() => {
      console.log("끝");
    })

pr은 생성된 프로미스 객체이고 1초후 성공함수를 실행한다.

then() 메소드는 프로미스를 리턴하고 두개의 콜백 함수를 인수로 받는다.

 

const f1 = (message) => {
	console.log(message);
    return new Promise((res, rej) => {
        setTimeout(() => {
            res("1번 주문 완료");
        }, 1000);
    });
};

const f2 = (message) => {
	console.log(message);
    return new Promise((res, rej) => {
        setTimeout(() => {
            res("2번 주문 완료");
        }, 3000);
    });
};

const f3 = (message) => {
	console.log(message);
    return new Promise((res, rej) => {
        setTimeout(() => {
            res("3번 주문 완료");
        }, 2000);
    });
};


//프로미스 체이닝
f1()
  .then((res) => f2(res))
  .then((res) => f3(res))
  .then((res) => console.log(res));
// 1번 주문완료
// 2번 주문완료
// 3번 주문완료

then() 메소드에서 인수로 들어온 콜백함수의 리턴값이 promise객체 일 때 then()이 콜백함수의 리턴 값인 프로미스 객체를 받는다.

 

Promise.all([f1(), f2(), f3()]).then((res) => {
    console.log(res); 
});
//["1번 주문완료", "2번 주문완료", "3번 주문완료"]

Promise.all()은 프로미스가 동시에 실행되고 모두 이행되면 값을 사용할 수 있도록 한다.

동시에 실행되기 때문에 시간이 단축된다. 위의 경우에는 실행시간이 6초정도인 반면 아래의 경우에는 3초정도 된다.

 

Promise.race()는 하나의 프로미스라도 완료되면 작업이 실행이 종료된다.

 

async, await

: 프라미스를 체인형식보다 가독성 좋게 호출하기 위한 방법이다.

 

async

: 함수 앞에 async를 붙이면 무조건 프라미스를 반환한다. 프라미스가 아닌 것도 프라미스로 감싸 반환한다 .

async function getName(){
    return Promise.resolve("Tom");
}

getName().then((name) => {
    console.log(name);
});

 

await

: 무조건 async 함수내에서만 작동한다. 프라미스가 처리될 때까지 기다리고 이후 결과를 반환한다.

function getName(name){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(name);
        },1000);
    });
}

async function showName(){
    const result = await getName('Mike');
    console.log(result);
}

 

앞서 프라미스 체이닝을 통해 구현하였던 실행부를 async, await를 사용하여 구현하면 다음과 같이 된다.

async function order(){
    const result1 = await f1();
    const result2 = await f2(result1);
    const result3 = await f3(result2);
    console.log(result3);
}
order();

에러를 잡기 위해서는 try~catch 구문을 사용한다.

 

Generator

: 함수의 실행을 중간에 멈췄다가 재개할 수 있는 기능이다.

function을 만들 때 *을 붙여 선언하면 된다.

yeild로 작업을 멈추게 할 수 있고

next()해주면 진행이 멈췄던 부분부터 이어서 실행한다.

Generator은 next외에도 return, throw 메소드를 가진다.

function* fn(){

    console.log("가");
    yield 1;
	console.log("나");
    yield 2;
    console.log("다");
    yield 3;
    return "finish";
}

const a = fn();
a.next();
//가
//{value: 1, done: false};

a.next();
//나
//{value: 2, done: false};

a.return('END');
//{value: "END", done: true};
a.next();
//{value: undefined, done: true};

 

Generator는 iterable이면서 iterator이다

 

iterable

- Symbol.iterator 메소드가 있다.

- Symbol.iterator는 iterator를 반환해야 한다.

 

iterator

- next 메소드를 가진다.

- next 메소드는 value와 done 속성을 가진 객체를 반환한다.

- 작업이 끝나면 done은 true가 된다.

 

배열 같은 경우에도 파라미터에 Symbol.iterator 메소드를 가지고 있고 이는 iterator을 반환하며 next메소드를 사용할 수 있기 때문에 반복적인 객체라고 할 수 있다.

 

generator의 next()에 인수를 전달할 수도 있다.

function* fn(){
    const num1 = yeild "첫번째 숫자를 입력해주세요";
    console.log(num1);
    
    const num2 = yeild "두번째 숫자를 입력해주세요";
    console.log(num2);
    
    return num1 + num2;
}

const a = fn();

a.next();
//{value: "첫번째 숫자를 입력해주세요", done: false}

a.next(2);
//2
//{value: "두번째 숫자를 입력해주세요", done: false}

a.next(4);
//4
//{value: 6, done: true}

 

 

또 Generator는 값을 미리 만들어 두지 않고 next로 호출할 때마다 값을 만든다. 

그리고 yield*을 사용하여 다른 generator나 배열 등 반복가능한 객체를 불러올 수 있다.

 


비동기 처리를 들어가고 급격하게 이해가 어려워지기 시작했다. 이후 더 공부할 필요가 있는 것 같다...