TIL : JS Intermediate 02

박지후·2022년 3월 9일
0

TIL

목록 보기
2/3
post-thumbnail

TIL / 2022-01-03

자바스크립트 중급 강좌 : 140분 완성

Javascript 를 복습하기 위해, 위 youtube 강의를 듣고 정리한 내용입니다.

Spread syntax

인자 전달

function showName(name) {
    console.log(name);
}

showName('Mike'); // 'Mike'
showName('Mike', 'Tom'); // 'Mike'
showName(); // undefined
  • 함수에 전달하는 인자의 개수에는 제한이 없다.
  • 함수의 인자에 접근하는 방법에는 2가지가 있다.
    • arguments
    • 나머지 매개 변수

arguments

  • 함수로 넘어 온 모든 인수에 접근 가능
  • 함수 내에서만 이용 가능한 지역 변수
  • length / index
  • Array 형태의 객체지만, 배열의 내장 메서드는 없다 (forEach, map)
function showName(name) {
    console.log(arguments.length);
    console.log(arguments.length);
    console.log(arguments.length);
}

showName('Mike', 'Tom');
// 2
// 'Mike'
// 'Tom'

Rest parameters

ES6 환경에서는 나머지 매개 변수 사용을 권장한다.

function showName(...names) {
    console.log(names);
}

showName('Mike'); // ['Mike']
showName('Mike', 'Tom'); // ['Mike', 'Tom']
showName(); // []
function add(...numbers) {
    let result = numbers.reduce((prev, cur) => prev + cur);
    console.log(result)
}

add(1, 2, 3);
add(1, 2, 3, 4, 5, 6, 7, 8, 9);
function User(name, age, ...skills) {
    this.name = name;
    this.age = age;
    this.skills = skills;
}

const user1 = new User('Mike', 30, 'html', 'css');
const user2 = new User('Mike', 30, 'JS', 'React', 'Vue');
const user3 = new User('Mike', 30, 'English');

Spread syntax

배열

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

let result = [0, ...arr1, ...arr2, 7, 8, 9];

객체

let user = {name: 'Mike', age: 30};
let user2 = {...user};
let user3 = {
    ...user,
    skill: "JS"
}

user2.name = "Tom";
console.log(user.name); // 'Mike'
console.log(user2.name); // 'Tom'

Closure

Lexical environment

// 코드가 실행되면, 선언한 변수들이 lexical 환경 위로 올라간다. (전역 lexical 환경)
// one : 초기화 X (사용 불가)
// addOne : function 

let one; // one : undefined (사용 가능)
one = 1; // one : 1 (할당)

function addOne(num) {
    console.log(one + num);
}

addOne(5); 
// 함수 실행 시 새로운 lexical 환경 생성 (매개변수와 지역변수들이 저장) (함수 내부 lexical 환경)
// num : 5
  • 함수 실행 시 생성되는 내부 Lexical 환경은 전역 Lexical 환경을 참조한다.

add 함수를 만들어주는 makeAdder 함수가 있다고 해보자.

// 코드 실행 시  `makeAdder` 와 `add3` 가 전역 Lexical 환경에 올라간다. 
// makeAdder: function, add3: 초기화 X
function makeAdder(x) {
    return funtion(y) {
        return x + y;
    }
}

const add3 = makeAdder(3); 
// makeAdder Lexical 환경이 생성된다. 
// x : 3
// 전역 Lexical 환경의 add3 이 function 으로 초기화됨 
console.log(add3(2));
// 위 코드 실행 시, add3에 대한 익명함수 Lexical 환경이 생성
// y : 2

const add10 = makeAdder(10);
console.log(add10(5)); // 15
console.log(add3(1)); // 4
  • makeAdder 에 의해 add3 함수가 생성된 "이후" 에도, 상위 함수인 makeAdder 의 x 에 접근이 가능하다.

    • 그러나 add10add3 은 서로 다른 환경을 가지게 된다!
  • 이런 경우를 "Closure" 라고 부른다.

    • 함수와 렉시컬 환경의 조합
    • 함수가 생성될 당시의 외부 변수를 기억
    • 생성 이후에도 계속 접근 가능
function makeCounter() {
    let num = 0; // 은닉화
    
    return function () {
        return num++;
    }
}

let counter = makeCounter();

console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2

setTimeout / setInterval

function fn() {
    console.log(3)
}

setTimeout(fn, 3000); // 3초후 fn 실행

setTimeout(function() {
    console.log(3)
}, 3000);
function showName(name) {
    console.log(name);
}

setTimeout(showName, 3000, 'Mike'); // 매개변후 마지막에 전달
  • setTimeout()tId 를 반환한다.
  • clearTimout(tid) 으로 예정된 작업을 없앨 수 있다.
function showName(name) {
    console.log(name);
}

const tId = setInterval(showName, 3000, 'Mike');
  • clearInterval(tid) 도 존재.
setTimeout(() => console.log(2), 0);

console.log(1);
// 1
// 2
  • 현재 스크립트 종료 후 스케쥴링 함수를 실행한다.
  • 또한 브라우저는 4ms ~ 정도의 delay 시간이 존재한다.
let num = 0;

function showTime() {
    console.log(`접속하진지. ${num++}초가 지났습니다.`);
    if (num > 5) {
        clearInterval(tId);
    }
}
const tId = setInterval(showTime, 1000);

call, apply, bind

JavaScript 에선, 함수 호출 방식과 관계없이 this 를 지정할 수 있다.

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

const tom = {
    name: 'Tom',
};

function showThisName() {
    console.log(this.name);
}

showThisName(); // this 는 window 를 가리키므로, window.name 즉 빈 문자열 출력
showThisName.call(mike); // Mike
showThisName.call(tom); // 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.apply(tom, [2002, 'teacher']); // apply 는 함수 매개변수를 배열로 받는다. 
console.log(tom);

call, apply 를 활용한 min, max

const nums = [3, 10, 1, 6, 4];

const minNum = Math.min(...nums);
const maxNum = Math.max(...nums);

const minNum2 = Math.min.apply(null, nums);
const maxNum2 = Math.max.call(null, ...nums);

bind

const updateMike = update.bind(mike);
updateMike(1990, "police");
console.log(mike); // mike updated !!
const user = {
    name: "Mike",
    showName: function () {
        console.log(`hello, ${this.name}`);
    }
}

user.showName(); // hello, MIke

let fn = user.showName;
fn(); // hello, 
fn.call(user); // hello, Mike
fn.apply(user); // hello, MIke

let boundFn = fn.bind(user);
boundFn(); // hello, Mike

상속 / prototype

const user = {
    name : "Mike"
}

user.name // "Mike"
user.hasOwnProperty('name'); // 'true'
user.hasOwnProperty('age'); // false

user 
// {name: "Mike"}
// __proto__: Object 
// ...
  • hasOwnProperty() 메서드는 우리가 만든게 아닌데, 어디 있는 걸까?
    • __proto__ 객체의 메서드이다.
    • 만약, user 객체가 hasOwnProperty() 라는 메서드를 직접 가지고 있으면, user 객체의 메서드를 사용하고, 없을 경우에만 __proto__ 에서 찾는다.

상속

const bmw = {
    color: "red",
    navigation: 1,
    wheels: 4,
    drive() {
        console.log("drive...");
    }
};

const benz = {
    color: "black",
    wheels: 4,
    drive() {
        console.log("drive...");
    }
};

const bmw = {
    color: "blue",
    wheels: 4,
    drive() {
        console.log("drive...");
    }
};

위 코드를 아래와 같이 변경 가능

const car = {
    wheels: 4,
    drive() {
        console.log("drive...");
    }
}

const bmw = {
    color: "red",
    navigation: 1,
};

const benz = {
    color: "black",
};

const bmw = {
    color: "blue",
};

bmw.__proto__ = car;
benz.__proto__ = car;
audi.__proto__ = car;
  • console 에서 bmw 출력 시엔, color 와 navigation 속성 만이 드러난다.

  • 상속은 계속 이어질 수 있다.

    const x5 = {
        color: "white",
        name: "x5"
    }
    
    x5.__proto__ = bmw;

반복문

for(p in x5) {
    console.log(p);
} // color, name, navigation, wheels, drive

Object.keys(x5) // ["color", "name"]
Object.values(x5) // ["white", "x5"]

for(p in x5) {
    if(x5.hasOwnProperty(p)) {
        console.log('o', p);
    } else {
        console.log('x', p);
    }
}
// o color 
// o name
// x navigation
// x wheels
// x drive

생성자 함수

const car = {
    wheels: 4,
    drive() {
        console.log("drive...");
    }
}

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

const x5 = new Bmw("red");
const z4 = new Bmw("blue");

x5.__proto__ = car;
z4.__proto__ = car;

or 아래와 같이 중복 코드를 줄일 수 있다.

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

Bmw.prototype.wheels = 4;
Bmw.prototype.drive = function () {
    console.log("drive...");
}

const x5 = new Bmw("red");
const z4 = new Bmw("blue");

console.log(z4 instanceof Bmw); // true
console.log(z4.constructor === Bmw); // true 

or 한번 더

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

Bmw.prototype = {
    wheels: 4,
    drive() {
        console.log("drive...");
    },
    navigation: 1,
    stop() {
        console.log("STOP!");
    },
};

const x5 = new Bmw("red");
const z4 = new Bmw("blue");

console.log(z4 instanceof Bmw); // true
console.log(z4.constructor === Bmw); // false !!! 
  • constructor 비교 시 false 를 반환한다.

    • 이를 방지 하기 위해 하나씩 property 를 작성하는 것이 좋다.
    • 혹은 아래 처럼 수동으로 명시한다.
    Bmw.prototype = {
        constructor: Bmw, 
        wheels: 4,
        drive() {
            console.log("drive...");
        },
        navigation: 1,
        stop() {
            console.log("STOP!");
        },
    };

Closure 를 이용한 은닉화

const Bmw = function (color) {
    const c = color;
    this.getColor = function () {
        console.log(c);
    }
}

Class

비슷한 형태의 객체를 생성하기 위해 생성자 함수를 이용했었다.

ES6 부터 추가된 Class 를 사용해도 된다.

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

const mike = new User("Mike", 30);

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

const tom = new User2("Tom", 19);
  • showName() 과 같이 class 내에 정의한 메서드는 __proto__ 에 저장된다.

생성자 함수를 class 와 동일하게 동작하게 하려면?

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

User.prototype.showName = function () {
    console.log(this.name);
}

그렇다면 왜 class 를 사용하는가?

// 위의 User 와 User2 를 살펴보면
const mike = User("Mike", 30); // new 가 없는 경우 
console.log(mike); // undefined

const tom User2("Tom", 19); 
// TypeError : constructor Users cannot be invoked without 'new'
  • User 와 User2 로 생성된 객체를 비교해보면, class 로 생성된 객체의 경우 constructor 가 class 로 명시되어 있다.
for(const p in mike) {
    console.log(p);
} // name, age, showName 

for(const p in tom) {
    console.log(p);
} // name, age 
  • 또한 반복문에서, class 는 prototype 메서드를 포함하지 않는다.

상속

// extends

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

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

const z4 = new Bmw("blue");
console.log(z4);
// constructor 로 생성된 color 와 wheels 가 객체 속성 (최상단) 에 위치하고, 
// __proto__ 내부에 park 이 위치
// drive, stop 은 그 다음 __proto__ 에 위치 

z4.drive();
//  z4 객체 내에서 drive 를 찾고, __proto__, 그 다음 __proto__ 까지 가서 찾는다. 

메서드 오버라이딩

class Bmw extends Car {
    park() {
        console.log("PARK");
    }
    stop() {
        console.log("OFF"); // car 클래스에도 존재하는 메서드. 오버라이드 가능 
    }
}
class Bmw extends Car {
    park() {
        console.log("PARK");
    }
    stop() {
        super.stop(); // 부모의 stop 메서드 사용
        console.log("OFF"); 
    }
}

z4.stop();
// STOP!
// OFF

생성자 오버라이딩

class Bmw extends Car {
    constructor(color) {
        super(color);
        this.navigation = 1;
    }
    park() {
        console.log("PARK");
    }
}
  • extends 로 생성된 자식 클래스는
    • 빈 객체를 만든 뒤, this 에 이를 할당하는 작업을 건너뛴다.
    • 따라서 항상 super() 로 부모 클래스의 생성자를 실행해야 한다.
  • 자식 클래스에 constructor 가 없는 경우, 자동으로 부모의 생성자를 오버라이딩해 실행한다.

0개의 댓글