함수 & 메서드 & 생성자

<aside> 💡 this 차이

JavaScript에서는 다른 언어와 다르게 this가 실행 문맥에 따라 갖는 의미가 달라진다.

해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩 된다. 브라우저에서 자바스크립트를 실행하는 경우에는 전역 객체가 window객체 이므로 window객체에 바인딩 된다.

let numbers = {
  numberA: 5,
  numberB: 10,
  sum: function () {
    console.log(this === numbers); // => true
    function calculate() {
      // this는 window, 엄격 모드였으면 undefined
      console.log(this === numbers); // => false
      return this.numberA + this.numberB;
    }
    return calculate();
  },
};
numbers.sum(); // NaN, 엄격 모드(use strict)였으면 TypeError

메서드 내부함수 ( depth가 2 )에서는 메서드로써 호출이 아닌 함수호출에서의 this로서 전역객체에 바인딩 된다.

let user = {
  firstName: "상민",
  sayHi() {
    let arrow = () => alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // 상민

하지만 위처럼 별개의 this가 만들어지는 것은 원하지 않고, 외부 컨텍스트에 있는 this를 이용하고 싶은 경우 화살표함수를 사용하면 유용하다. 2단 depth를 가져 원래의 함수라면 전역객체에 바인딩 되어야하는 this가 sayHi의 this로써 user객체에 바인딩 된다.

객체의 프로퍼티가 함수일 때 이 함수를 메서드라고 하는데, 메서드 내부 코드에서 사용된 this의 바인딩은 해당 메서드를 호출한 객체로 바인딩 된다.

let obj = {
  name: 'sangmin',
  sayName: function () {
    console.log(this == obj); // true
    console.log(this.name); // sangmin
  },
};

let obj2 = { name: 'joonseo' };
obj2.sayName = obj.sayName;

obj.sayName(); // sangmin
obj2.sayName(); // false, joonseo

new연산자를 붙여서 호출하면 해당 함수는 생성자 함수로써 동작한다.

생성자 함수에서의 this는 생성자 함수를 통해서 새로 생성되어 반환되는 객체에 바인딩 된다. (이는 생성자 함수에서 명시적으로 다른 객체를 반환하지 않는 일반적인 경우에 적용됨)

// Person 생성자 함수
let Person = function (name) {
  this.name = name;
};
// foo 객체 생성
let foo = new Person('foo');
console.log(foo.name); // foo

참고 https://valuefactory.tistory.com/674 https://ko.javascript.info/object-methods


</aside>

<aside> 💡 생성자와 Class

여러 프로그래밍 언어에서는 ‘클래스'와 동의어이다.

생성자 함수는 ES6의 클래스 등장 이전까지는 많이 쓰였지만 현재는 사용에 밀리는 추세이다.

function Person(name) {
    this.name = name;
    this.hi = function () {console.log(`Hi, My name is ${this.name}`)}
}
const hannah = new Person('Hannah Yoo');
hannah.hi(); // Hi, My name is Hannah Yoo

이런식으로 메소드가 정의된다고 하더라도 Person 인스턴스가 생성될 때마다 그 객체의 hi프로퍼티에 새로운 함수가 정의되고 할당된다. 만약 5개의 Person객체를 생성한다면 모두 같은일을 하는 자신만의 hi 메소드를 갖게되는 것이다.

해당 부분에 대해 더 효율적인 설계를 위해 hi 메소드를 단 한번만 정의하는 방법이 선호된다. 그리고 각 Person객체는 같은 함수를 참조하면 된다. 이를 위해 함수의 prototype을 써볼 수 있다.

function Person(name) {
    this.name = name;
}
Person.prototype = {
    constructor: Person,
    hi() {
        console.log('hi my name is' + this.name)
    }
}

ES6 덕분에 우리는 위의 생성자 함수를 class문법으로 쓸 수 있게 되었다.

class Person {
    constructor(name) {
    this.name = name;
    }
    hi() {
        console.log('..')
    }
}

다음은 상속에 대한 차이의 예시이다.

function Animal() {}
Animal.prototype.eat = function () {
    console.log('eating')
}

function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
Cat.prototype.meow = function () {
    console.log('meowing');
}
class Animal {
    eat() {
        console.log('eating')
    }
}

class Cat extend Animal () {
    meow() {
        console.log('meowing')
    }
}

클래스를 사용하지 않을 이유가 없다. (개인적인 생각)

객체를 생성하는 일종의 템플릿이다. 클래스는 데이터를 압축한다. 자바스크립트의 클래스는 prototype에 기반하여 사용자의 편의성을 올려주기위한 하나의 기능으로써 자리하고 있다(Syntax Sugar).

클래스 선언식의 예시는 다음과 같다.

const p = new Rectangle(); // Reference Error // 호이스팅 되지 않음

class Rectangle {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
}

함수 선언식과 클래스 선언식의 중요한 차이점은, 클래스 선언식은 함수 선언식과 달리 호이스팅되지 않는다는 점이다.

클래스를 정의하는 또 다른 방법으로 클래스 표현식이 있다. 클래스 표현식은 익명으로 만들어질 수도 있다. 이 역시 마찬가지로 호이스팅되지 않는다.

// unnamed
let Rectangle = class {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
}
console.log(Rectangle.name); // Rectangle

// named
let Rectangle = class Rectangle2 {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
};
console.log(Rectangle.name); // Rectangle2

클래스 바디( → {}로 감싸진 부분 )는 strict mode안에서 실행된다.

constructor라는 class 키워드로 생성된 객체의 생성과 초기화를 위한 특별 메서드가 존재한다. 하나의 클래스 안에는 constructor라는 이름을 가진 메서드는 하나만 존재할 수 있다.

class Rectangle {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
    // getter
    get area() {
        return this.calcArea();
    }
    // method
    calcArea() {
        return this.height * this.width;
    }
}

const square = new Rectangle(10, 10);
console.log(square.area); // 100

또한 static 키워드는 클래스의 static method와 property를 정의한다. static멤버는 이 클래스를 인스턴스화 하지 않고 호출되며, 클래스 인스턴스를 통해 호출하는 것이 불가능하다.

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    static displayName = 'Point';
    static distance(a, b) {
        const dx = a.x - b.x;
        const dy = a.y - b.y;
    return Math.hypot(dx, dy)
    }
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; // undefined
p1.distance; // undefined
p2.displayName; // undefined
p2.distance; // undefined

console.log(Point.displayName); // 'Point'
console.log(Point.distance(p1, p2)); // 7.0710678118654755

참고 https://uiyoji-journal.tistory.com/101


</aside>

<aside> 💡 Syntax Sugar(문법적 설탕)이란?

JS뿐 아니라 프로그래밍 언어 전반적으로 적용될 수 있는 개념. 달달한 이름에 걸맞게 읽는 사람 혹은 작성하는 사람이 편하게 디자인 된 문법이라는 뜻을 가지고 있다.

기본적으로 번거로운 코드양을 줄여 가독성이 좋아지는 효과가 있다. 모든 상황에서 Syntax Sugar를 적용하는게 옳지는 않지만, 직관적으로 작성자의 의도를 알 수 있는 코드라면 Syntax Sugar를 사용하는게 생산성을 높여준다.

  1. 변수 단축 선언

    같은 타입을 갖는 변수를 한 줄로 선언하는 방법.

    let a,
        b = 2,
        c;
    //a : undefined
    //b : 2
    //c : undefined
    
    const d = 4,
        e = 5;
    //const d,e=5; => Uncaught SyntaxError: Missing initializer in const declaration
    
  2. 단축 평가 값

    논리 연산자에 대하여 왼쪽에서 오른쪽으로 평가를 실행하며, 더 이상 평가가 필요하지 않을 경우 실행을 중단.

    const str = "some text";
    const result1 = str || "default value";
    // result1 = "some text";
    
    const nothing = null;
    const result2 = nothing || "default value";
    // result2 = "default value";
    
    const result3 = str && "If str is truthy return this";
    //result3 = "If str is truthy return this"
    
  3. 널 병합 연산자

    ‘??’기준 왼쪽 표현식이 unll, undefined인지 값을 확인하여 해당할 경우 연산이 넘어감(앞에서 배움)

    let foo = 0;
    const result1 = foo || "default Value"; //"default Value"
    const result2 = foo ?? "default Value"; //0
    
  4. 삼항 조건 연산자

    잘 알다시피 if문의 단축 형태. 반드시 삼항에 해당하는 코드가 모두 작성되어야 함. 누락될 경우 SyntaxError발생.

    condition ? exprIfTrue : exprIfFalse;
    
  5. 단축 속성명

    객체에 속성으로 사용하려는 키 값과 같은 이름의 변수가 있을 경우, key : value 할당을 축약형으로 사용 할 수 있습니다.

    const a = 1,
        b = 2,
        c = 3;
    
    const obj1 = {
        a: a,
        b: b,
        c: c,
    };
    
    const obj2 = { a, b, c }; // {a:1,b:2,c:3}
    
  6. 전개구문

    object앞에 ...을 붙혀서 object의 literal을 복제하는 기능이며, 객체 특성에 따라 다른 상황에 사용할 수 있다.

    const abcArr = ["a", "b", "c"];
    const defArr = ["d", "e", "f"];
    
    const newArr = [...abcArr];
    const abcdefArr = [...abcArr, ...defArr, ...newArr1];
    
    const dog = {
        name: "dudu",
        age: 2,
        leg: 4,
        fur: true,
        hobby: "sleep",
    };
    
    const snake = {
        name: "cucu",
        age: 8,
        leg: 0,
        fur: false,
    };
    
    const newDog = { ...dog };
    const monster = { ...dog, ...snake };
    

    전개구문을 통해 객체 리터럴을 복제하는 더 직관적 표현이 가능해졌다. 중복 키 값이 존재할 때, 뒤에 추가되는 객체 값으로 덮어씌워지는것을 주의해야 함.

  7. 나머지 매개변수(Rest parameters)

    매개변수 이름 앞에 ...을 붙여, 할당되지 않은 매개변수를 배열로 받는 기능을 한다.

    function fn1(name, age, ...args) {
        // name = 'Dick'
        // age = 21
        // args = ['handsome','cool','nice'];
    }
    fn1("Dick", 21, "handsome", "cool", "nice");
    
  8. 디폴트 매개변수

    argument없이 함수가 실행될 경우, undefined 대신 사용될 값을 할당하는 방식. (해당 페이지 바로 아래부분에서 다루고 있는 내용)

    function fn1(name = "John Doe", age = 30) {
        console.log(`${name} is ${age} years old`);
    }
    
    fn1();
    //John Doe is 30 years old
    fn1("Peter", 15);
    //Peter is 15 years old
    
  9. 템플릿 리터럴

    표현식을 허용하는 문자열 리터럴. 작은따옴표 대신 백틱을 이용하는 그것이다.

    const hi = "Good Morning";
    console.log(`${hi}`); //Good Morning
    console.log(`${3 + 6}`); //9
    console.log(`${hi ? hi : "Good Evening"}`); //Good Morning
    
  10. String to Number

    Js의 암시적 형변환을 이용해 String을 Number로 변환하는 방법. 산술 연산자 +는 String을 이어붙이는데 사용 할 수도 있지만 숫자의 계산을 우선한다.

    parseFloat("100.1") + //100.1
        "100.1"; //100.1
    parseFloat("100.1") === +"100.1"; //true
    
  11. Double Bitwise Not

    소수점 뒤의 값을 무시하고 32진수 비트값을 반전하는 것을 이용해 Round(?)처럼 사용하는 방법

    ~~null; // => 0
    ~~undefined; // => 0
    ~~""; // => 0
    ~~0; // => 0
    ~~{}; // => 0
    ~~[]; // => 0
    ~~(1 / 0); // => 0
    ~~false; // => 0
    ~~true; // => 1
    ~~1.2543; // => 1
    ~~4.9; // => 4
    ~~-2.999; // => -2
    
  12. 구조 분해 할당

    구조분해할당은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 표현식.

    let [a, b] = ["1", "2", "3"];
    // a : 1 , b : 2
    let [c, d, ...e] = ["1", "2", "3", "4", "5"];
    // c : 1, d : 2, e : [3, 4, 5]
    
    let { apple, banana, carrot, ...fruits } = {
        kiwi: 4,
        apple: 5,
        banana: 3,
        watermelon: 7,
    };
    // apple: 5 , banana: 3 , carrot:undefined, fruits: {kiwi: 4, watermelon: 7}
    

참고 https://dkje.github.io/2020/09/02/SyntaxSugar/


</aside>

arguments & parameters

arguments와 parameters의 차이는 mdn문서로도 정리되어 있다.

<aside> 💡 parameter(매개변수)

자바스크립트에서 함수를 정의할 때는 매개변수의 타입을 따로 명시하지 않는다. 또한, 함수를 호출할 때에도 인수(argument)로 전달된 값에대해 어떠한 타입검사도 이루어지지 않는다.

함수 호출시에 함수의 정의보다 적은 수의 인자가 전달되더라도, 다른 언어와 달리 오류를 발생시키지 않는다. 이와같은 상황에서 전달되지 않은 다른 매개변수에는 자동으로 undefined가 할당되도록 설정되어있다.

function addNum(x, y, z) { // x, y, z라는 3개의 매개변수를 가지는 함수 addNum()을 정의함.
    return x + y + z;
}

addNum(1, 2, 3); // 인수로 1, 2, 3을 전달하여 함수를 호출함. -> 6
addNum(1, 2);    // 인수로 1, 2을 전달하여 함수를 호출함. -> NaN
addNum(1);       // 인수로 1을 전달하여 함수를 호출함. -> NaN
addNum();        // 인수로 아무것도 전달하지 않고 함수를 호출함. -> NaN

전달되지 않은 값들에 undefined가 할당되어 산술연산을 수행할 수 없기 때문에 NaN이 반환된다.


</aside>

<aside> 💡 arguments(인수) 객체

만약 함수의 정의보다 더 많은 수의 인수가 전달되면, 매개변수에 대입되지 못한 인수들은 참조할 수 있는 방법이 없다. 하지만 이 때 arguments객체를 이용하여 함수로 전달된 인자의 개수를 확인하거나 각각의 인수에 접근할 수 있다.

arguments객체는 함수가 호출될 때 전달된 인수를 배열의 형태로 저장하고 있다. 인수의 총 개수는 arguments객체의 length 프로퍼티에 저장된다.

function addNum() {
    let sum = 0;                                // 합을 저장할 변수 sum을 선언함.
    for(let i = 0; i < arguments.length; i++) { // 전달받은 인수의 총 수만큼 반복함.
        sum += arguments[i];                    // 전달받은 각각의 인수를 sum에 더함.
    }
    return sum;
}

addNum(1, 2, 3); // 6
addNum(1, 2);    // 3
addNum(1);       // 1
addNum();        // 0
addNum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55

</aside>

<aside> 💡 axios와 fetch의 요청인자


</aside>

참고 http://www.tcpschool.com/javascript/js_function_parameterArgument

JavaScript 매개변수에 관하여

javaScript의 함수에 대하여 매개변수의 개수에 대한 이야기를 들어본 적이 있을 것이다. 보통 3,4개가 넘어가면 많다고 볼 수 있지만, 그것보다 더 중요한 것은 매개변수의 존재 ‘맥락'과 ‘흐름’이다.

다음의 두 예시를 보도록 하자.

// 괜찮은 사용
function getSquare(top, right, bottom, left) {
  // ..some code
}
// 나쁜 사용
function createCar(name, brand, color, type){
  // ..some code
}
function createCar({ name, brand, color, type }) {
	// name과 brand없으면 Error를 호출하겠다는 뜻
  if (!name) {
    throw new Error('name is required');
  }
  if (!brand) {
    throw new Error('brand is required');
  }
}

createCar({ name: 'CAR', type: 'SUV' }); // Error: brand is required