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

41836 단어 functionthisbindbind

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. 함수의 다양한 사용법

함수를 리턴값으로도 사용가능하고 배열값으로도 사용가능하다.

//리턴값
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);

이렇게 다양한 용도로 사용가능한 함수를 first-class citizen 이라고도 한다.

그리고 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

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

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

요렇게 kim 과 관련된 property 와 method 가 저장된 kim이라는 객체가 있다고 했을때 sum이라는 method를 사용하려면 kim.sum()을 입력하면 된다. 근데 var 의 이름이 바뀌는 순간 에러가 난다. sum() method 안에 있는 kim.first 가 undefined 인 상태가 되기 때문. 그래서 유연하지 못하다. 이때 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가 생략되어있다고 봐도 된다.

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

const bindWhoAmI = myWhoAmI.bind(someone);

bindWhoAmI();

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

근데 또 질문거리가 생겼다. 우연히 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를 변경해줘야 하기 때문이라고 생각된다. 나중에 정확히 알게되면 다시 포스팅!

2-3. 함수의 prototype

만약에 생성자 함수를 원형으로해서 뒤에 그 함수를 쉴새없이 찍어낸다고 했을때 생성자 함수안에 또 다른 함수가 들어있다면 찍어낼때마다 함수가 만들어지고 그렇게 되면 메모리 손실이 어마어마하게 커지면서 문제가 발생할 수 있다. 그래서 복제된 객체들이 하나씩 그 함수를 가지고 있는게 아니라 함수를 생성자 함수에서 따로 빼내어 공유하게끔 하면 메모리 손실을 획기적으로 줄일 수 있다.

그때 사용하는 키워드가 prototype이다.

function Person(first, second, third) {
  this.first = first;
  this.second = second;
  this.third = third;
}

Person.prototype.sum = function () {
  return `prototype :` + (this.first + this.second + this.third);
};

Person.prototype.fourth = "pro fourth";

var kim = new Person(10, 20, 40);

kim.sum = function () {
  return `kim's sum :` + (this.first + this.second + this.third);
};

var lee = new Person(20, 30, 10);
var gang = new Person(20, 20, 60);

console.log(kim.sum());
console.log(lee.sum());
console.log(gang.sum());
console.log(kim.fourth);

// kim's sum70
// prototype :60
// prototype :100
// pro fourth

또한 kim에 해당하는 sum만 따로 설정가능하다. 그리고 property를 추가할 수 도 있다.

좋은 웹페이지 즐겨찾기