자바스크립트 함수, 그리고 this의 등장

YEONGHUN KO·2021년 11월 10일
0

JAVASCRIPT - BASICS

목록 보기
2/27
post-thumbnail

1. 객체

나름대로 자신의 코드를 가진 독립적인 아이들이다. 객체안에 있는 함수를 메소드 라고 한다.

// 2. object
const yeongInfo = {
  name: "Yeonghun",
  gender: "male",
  awsomeness: true,
  favMovies: ["Peppermind", "Harry potter"],
  favFood: [
    {
      Foodone: "kimchi",
      calories: 20,
    },
    {
      Foodtwo: "Fried rice",
      calories: 190,
    },
  ],
};
// 이런식으로 yeongInfo(객체,독립적 정체성, 자기나름의 세계가 있음) 안에 array와 변수를 추가할 수 있다. 그리고 object안에 원소를 불러내려면 . 을 사용하자 아래처럼 말이다. 그런 의미에서 console.log 도 함수이다.

console.log(yeongInfo.favFood[0].Foodone) >>> kimchi;

2. 함수와 객체화

// 3. function

function ho(name, name2) {
  console.log("Hello", name, "Nice to meet you I'm", name2);
}

ho("척", "씨베리아");

함수를 정의 할 때 먼저 앞에 fuction 을 쓰고 그 뒤에 함수이름을 정해준다. 그리고 함수안에 코드를 작성한다. 함수이름 옆에 ()가 있다.
여기에 들어가는 값을 argument(인자)라고 한다. 그리고 임시로 정한 인자 이름을 함수안에서 사용하면 나중에 외부에서 input 된 데이터가 함수 안에 있는 인자 값으로 대입이 된다. 그리고 함수를 실행 할때 함수이름을 쓰고 괄호안에 인자를 대입하자. 그리고 함수는 console.log 없이 사용가능하다.

근데 greetings 객체를 더 깔끔하게 만들 수 있다.

function greetings(name, name2){
  return `Hello ${name} Nice to meet you I'm ${name2}`;
}

const hey = greetings("척", 112)

console.log(hey)

>>> Hello 척 Nice to meet you I'm 112

//라고 하면 됨. return 값을 주고 앞뒤로 ``(백틱)를 붙여줌.


// 계산 함수

const calculator = {
  plus: function(a,b){
    return a + b
  },
  multiply: function(a,b){
    return a * b
  },
  divide: function(a,b){
    return a / b
  },
  minus: function(a,b){
    return a - b
  },

  square: function(a,b){
    return a ** b
  }
}

console.log(calculator.divide(4,6))
>>>0.66
// 상수이지만 사실 상수안에 여러가지 코드가 들어있어 함수같은 느낌이 난다.

P.S: 참고로 calcaulator 를 객체 divide를 method라고 한다

2-1. 함수의 다양한 사용법(일급 객체, First Class Object)

일급객체

https://velog.io/@reveloper-1311/%EC%9D%BC%EA%B8%89-%EA%B0%9D%EC%B2%B4First-Class-Object%EB%9E%80 을 보고 정리한 글입니다!

우선 자바스크립트에서 함수는 일급객체이다. 일급객체는 다른객체들에 일반적으로 적용가능한 연산을 모두 지원하는 객체를 가리킨다.

  • 여기서 말하는 일반적인 연산은 인자에 전달되거나 함수의 결과값으로 리턴되거나 변수에 할당되는 것을 뜻한다. [위키백과]

쉽게말해 함수를 데이터(string, number, boolean, array, object) 다루듯이 다룰 수 있다는 점이다.

//리턴값
function cal(mode) {
  var funcs = {
    plus: function (left, right) {
      return left + right;
    },
    minus: function (left, right) {
      return left - right;
    },
  };
  return funcs[mode];
}
alert(cal("plus")(2, 1));
alert(cal("minus")(2, 1));

// 배열값
var process = [
  function (input) {
    return input + 10;
  },
  function (input) {
    return input * input;
  },
  function (input) {
    return input / 2;
  },
];
var input = 1;
for (var i = 0; i < process.length; i++) {
  input = process[i](input);
}
alert(input);

우선 일급객체의 조건에 대해서 알아보자.

  • 변수에 할당 할 수 있다.
  • 다른 함수를 인자로 전달 받는다.
  • 다른 함수의 결과로서 리턴될 수 있다.

그럼 함수가 일급객체이기 때문에 할 수 있는 일은??

  • 고차함수를 만들 수 있다.
  • 콜백을 사용할 수 있다.

고차함수

const createMultiplier = (factor) => (num) => {
    return num * factor
}

const doubler = createMultiplier(2)

doubler(4) // 8

const tripler = createMultiplier(3)

tripler(4) // 12

고차함수를 사용하면 코드 반복을 줄일 수 있다. 관련해서 함수형 프로그래밍으로 넘어갈 수 있을 것같다. 왜냐면 함수형 프로그래밍은 고차함수를 쓰기 때문

new 키워드

그리고 new 라는 키워드가 있다.

만약에 선언할때는 객체 앞에 new 라는 키워드를 붙이게 되면 어떤일이 벌어질까? new는 대상을 생성자 객체로 만들어준다. 객체안에 있는 내용을 재생산하는 생성자가 되는 것이다. 그럼 앞으로 호출할 때마다 객체안에 있는 내용들이 생산되고 이를 사용할 수 있다. 그리고 객체안에 property(변수같은거)와 메소드(함수같은거)를 추가할 수 있다.(객체안에 함수처럼 사용되는게 있는데 method 라고 부르고 method 말고 변수처럼 사용되는게 있는데 그걸 property(또는 field, 변수랑 비슷한 기능) 라고 부른다.)

때문에 var 무엇 = new 대상 으로 선언하고나서 대상을 constructor(생성자) 함수 라고도 한다. 무엇을 호출함으로서 객체 안에 있는 기능을 자유자재로 사용할 수 있게된다.(참고로 생성자함수의 이름은 대문자로 시작하는게 좋다. 구분하기 위해)

마치 var d = new Date('2021-01-02') 처럼 말이다.

무슨말인지 도통 감이 잡히질 않으니 코드로 살펴보자.

function Person(in_name) {
  // this는 아래글에서 설명되어있다
  this.name = in_name; //property
  this.age;
  this.calAge = function () {
    //method
    return document.write("my name is " + this.name + "<br>");
  };
}

var p1 = new Person("yeong");
p1.calAge();
p1.age = 22;

person이라는 함수에 "yeong" 이라는 인자를 대입했다. 그리고 이 함수를 객체로 만들고 p1이라는 변수에 포장하였다. 이제부터는 Person이 생성자 함수가 되고 이 함수안에 있는 name property/age property(아직 정의되지 않음)/calAge함수를 사용할 수 있게 된다.

그리고 p1을 콘솔로그 해보면 이렇게 결과값이 나올 것이다.

person {name: "yeong", age: 22, calAge: ƒ}

person함수를 객체화 시켰다. 만약 new라는 키워드를 사용하지 않고 person("yeong") 이라고 선언하고 로그해보면 뭐라고 나올까? undefined라고 뜬다. return 값이 없기때문이다.

new를 사용하지 않고 그냥 함수로서 사용하면 함수안에 있는 수식을 통해 최종 리턴되는 값이 있을건데 그 리턴값만을 출력하게 될뿐이다. 그 함수에 여러가지 다른 함수,또는 property를 골라서 쓰고 또 심지어는 또 다른 함수나 property를 추가할 수 있는 객체와는 다르다. 그리고 그냥 함수에는 this라는 키워드를 사용하지 않고 일반 변수를 설정하듯 해야한다.

그리고 this 라는 키워드가 있는데 아래 챕터에서 살펴보자.

2-2. this(feat , bind)

this가 존재하는 이유와 용법

생활코딩에서 방금 this에 대한 개념을 완벽하게 정리하고 왔다. this 는 객체가 설정한 property를 유연하게 manage하기 위함이다. 간단하게 예를 들어보자

var kim = {
  first: 10,
  second: 20,
  sum() {
    return kim.first + kim.second;
  },
};

요렇게 kim 과 관련된 property 와 method 가 저장된 kim이라는 객체가 있다고 했을때 sum이라는 method를 사용하려면 kim.sum()을 입력하면 된다. 그러나 sum은 항상 kim이라는 객체 안에 있는 first , second를 가리키게 된다.

var lee = {}
lee.sum = kim.sum
lee.sum() // 30

라고 했을때 sum을 어디에다 갖다 붙여도 항상 30이라는 숫자를 리턴한다. 즉, 고정되어있고 딱딱하다.

유연하지 못하다. 이때 this 라는 keyword를 사용해준다. this 는 'this가 속한' 객체를 의미하기 때문에 유연하게 형태를 바꿀 수 있는 특성을 부여한다.

따라서 아래와 같이 코드를 바꾸면 훨씬 깔끔하고 효율적으로 객체를 구성하게 되는 것이다. var의 이름이 바뀜에 따라 this가 가리키는 대상도 바뀌기 때문.

var kim = {
  first: 10,
  second: 20,
  sum() {
    return this.first + this.second;
  },
};

this는 다시 말해서 this를 호출했을때 this의 스코프 위치에 따라서 this(호출한 주체)가 바뀐다.

다른예제를 살펴보자.

p.s: 코드가 적용되는 범위를 스코프라고 하는데 그 범위가 상위방향으로 엮여있는걸 스코프체인이라고 말한다. 다시말해 inner func에서 a 라는 변수를 못찾으면 그 위 상위 outer func에서 a라는 변수를 자동으로 찾기 시작한다는거.

var someone = {
  name: "yeong",
  whoAmI: function () {
    console.log(this);
  },
};

someone.whoAmI();

var myWhoAmI = someone.whoAmI;
myWhoAmI();
// Object{}
// Window{}

var btn = document.getElementById("btn");
btn.addeventListener("click", myWhoAmI);

// <button id = 'btn'>...</button>

자 보면은 someone.whoAmI(); 에서 whoAmI() 안에 있는 this는 someone 객체이다. someone이 whoAmI()를 호출했기 때문에.

myWhoAmI(); 는 window가 호출했기 때문에 이때 this는 window가 된다. 앞에 window가 생략되어있다고 봐도 된다.

bind

그리고 btn을 클릭했을때는 this가 button이 된다. button이 호출했기 때문이다. 그럼 더 신기한 기능을 설명하겠다. 뒤에서도 설명하겠지만, 호출자, 즉 this를 다른놈으로 변경할 수 있다. 바로 bind를 이용하면 된다. bind는 호출한 대상을 바꿔버린다. 따라서 this의 주체를 바꿔버릴 수 있다.

(항상 , 이름의 의미를 음미해보자, bind이므로 말그대로 묶어준다가 만들어진 목적일것이다.)

const bindWhoAmI = myWhoAmI.bind(someone);

bindWhoAmI();

요롷게 하면 myWhoAmI의 this는 원래는 window인데 someone으로 바꾸어 주었다.

재밌는 놀이

이번엔 this가 호출한 놈이라는 관점을 가지고 Array 생성자에 새로운 메소드를 추가해 보자.

우선 아래 코드를 보자. 반지름(radius)를 이용해 지름(diameter)을 구하는 로직이다.

const radius = [2,4,1,3]

function calculate(array, logic) {
 const output = []
 for(let i = 0; i < array.length; i++) {
   ouput.push(logic(array[i]))
 }
  
 return output
}

const diameter = (radius) => {
  return radius * 2
}

const diameterOfRadius = caculate(radius,diameter) // [4,8,2,6]

이렇게 지름을 구하는 로직을 만들었다. 여기서 보면 caculate가 Array.map이랑 같은 역할을 할 수 있다는 것을 알 수 있다.

이때 Array.calculate(diameter)처럼 map과 같이 동일하게 사용하게 하려면 어떻게 해야할까??

Array객체에 추가해주면 된다. 잠깐 prototype 개념을 언급해야한다. 간단히 말해 유전자를 조작한다고 보면 됨. Array객체 안에 있는 유전자를 조작해서 이후 만들어진 복제(자식)본 전체에 영향을 끼침.

Array.prototype.calculate = function(logic){
 
 const output = []
 for(let i = 0; i < this.length; i++) {
   output.push(logic(this[i]))
 }
  
 return output
}

radius.calculate(diameter) // [4,8,2,6]

여기서 this 가 유용하게 쓰였다.

자신을 호출하는 array를 인자로 받아오지 않고 this로 받아서 바로 사용한것!


근데 또 질문거리가 생겼다.

우연히 someone.whoAmI 를 arrow func으로 표현해보았다. 그리고 나서 someone.whoAmI()를 출력했는데 this가 window라고 알려준다.

var someone = {
  name: "yeong",
  whoAmI: () => {
    console.log(this);
  },
};
// window{}

그 이유는 arrow function 에는 일반함수에는 없는게 3가지가 있기 때문이다 바로 함수이름, this, arguments 가 없다.

2-2-1. arrow function 에는 없는 것들

우선 함수이름이 없다는거는 따로 설명하지 않아도 self-evident 니깐 넘어가겠다.

1. this가 없다
{: .notice--success}

우선 아래 코드를 보자

const btn = document.querySelector(".btn");

var myObj = {
  count: 0,
  setCount: function () {
    console.log(this.count);
    btn.addEventListener("click", () => {
      console.log(this.count++);
    });
  },
};

myObj.setCount();
//0
//1
//2

이때 btn이 불러온 콜백함수가 arrow function 이다. 근데 arrow function 에 this가 없기 때문에 그 밖의 스코프를 참조하게 된다. 바로 setCount의 스코프이다. setCount의 스코프에서 this는 myObj 이기 때문에 this.count는 myObj의 count:0 가 된다. 또한 arrow function으로 new 생성자를 만들수 없다. new는 this 오브젝트를 불러와야하는데 arrow에는 이게 없어서

2. arguments가 없다
{: .notice--success}

여기서 arguments란 인자를 받지않는 함수에 인자를 parameter로 pass하게 될때 arguments를 쓰면 배열같은 형태로 받아서 사용 할 수 있게 하는 키워드이다

const myFun = function () {
  console.log(arguments);
};

myFun(1, 2, 3, 4);
//Arguments(4) [1, 2, 3, 4]

이렇게 사용가능. 근데 arrow function으로 접근해보자

const myFun = () => {
  console.log(arguments);
};

myFun(1, 2, 3, 4);
//arguments is not defined

그럼 이렇게 사용가능하게 할 수 있다.

function outter() {
  const myFun = () => {
    console.log(arguments);
  };
}

outter(1, 2, 3, 4);
//Arguments(4) [1, 2, 3, 4]

const myFun = function (...args) {
  console.log(args);
};

myFun(1, 2, 3, 4);
//(4) [1, 2, 3, 4]

myFun에 arguments가 없으니 outter의 스코프를 참조하기 시작함. 또는 ...args라고 spread syntax를 사용하면 된다. 그럼 인자를 받아서 사용가능하다. 그리고 ...args를 사용하면 실제 배열이 return된다. 이건 python에서도 배웠다. 이로서 this에 대한 개념은 거의다 잡힌것 같다.

근데 한가지 질문이 있다. class안에서 property를 만들때 const,let,var를 사용하는대신에 왜 꼭 this를 사용해야하는가? this가 무슨뜻인지는 아는데 굳이 this를 사용해야하는 이유는 무엇인가? 아직 그이유를 정확히 알아내지는 못했지만 나름 추측을 해보자면.

car 클래스는 new를 통해 생성자 함수가 되고 그이후에 인자를 다르게 받음으로써 생성자 함수가 담긴 변수가 여러번 바뀔 수 있다. 이때 this가 아니라면 변화할때 마다 property를 변경해줘야 하기 때문이라고 생각된다. 나중에 정확히 알게되면 다시 포스팅!

==> 우선 떠오르는 거는, this를 사용하지 않으면 클래스의 property로 접근 불가능. 즉, p1.calAge 처럼 접근이 불가능하단 말이다. 만약 아래와 같은 클래스가 있다고 하자.

class Test {
  constructor(){
   this.cool = 'cool'
  }
  
  sayCool() {
    console.log(this.cool)
  }
}

이때 cool 변수는 객체에 담기고 sayCool은 prototype에 담긴다. 아래 처럼 말이다.

만약 const , let , var를 쓴다면 함수를 만들어 낼 때마다 메모리가 낭비 될 수 있을 것 같다.

profile
'과연 이게 최선일까?' 끊임없이 생각하기

0개의 댓글