하루에 한 문제

함수 호출과 this 본문

Dev/JavaScript

함수 호출과 this

dkwjdi 2021. 4. 9. 01:39

함수의기본적인 기능은 당연하게도 함수를 호출해 코드를 실행하는 것이다.

자바스크립트는 C/C++ 처럼 엄격한 문법 체크를 하지 않는 느슨한 타입 체크 언어이므로 자유롭게 호출이 가능하다.

 

 

arguments 객체

자바스크립트에서는 함수를 호출할 때 함수 형식에 맞춰 인자를 넘기지 않아도 에러가 발생하지 않는다.

function func(arg1, arg2) {
    console.log(arg1, arg2);
}

func(); // undefined undefined
func(1); // 1 undefined
func(1, 2); // 1 2
func(1, 2, 3); // 1 2
  • func()함수를 인자 개수를 바꿔 호출해보았다.
  • 정의된 인자 개수보다 많은 인자를 통해 호출을 했을 경우에는 무시한다
  • 정의된 인자 개수보다 적은 인자를 통해 호출을 했을 경우 넘겨지지 않은 인자는 undefined 값이 할당된다!

 

이러한 특성 때문에 함수 코드를 작성할 때, 런타임 시에 호출된 인자의 개수를 확인하고 이에 따라 다른 동작을 해야할 때가 있다.

이것을 가능케 하는 것이 바로 arguments 객체다.

 

  • argumetns 객체는 함수를 호출할 때 자바스크립트에 의해 암묵적으로 같이 들어간다.
  • argumetns 객체는 함수를 호출할 때 넘긴 인자들이 배열형태로 저장된 객체를 의미한다.
  • 특이한 점은 실제 배열이 아닌 유사배열객체(객체에 length프로퍼티를 할당)라는 점이다.
//add 함수

function add(a, b) {
    //arguments 객체 출력
    console.dir(arguments);
    return a + b;
}

console.log(add(1));
console.log(add(1,2));
console.log(add(1,2,3));

  • 여기서 length 프로퍼티는 호출할 때 넘겨진 인자의 개수를 의미한다.
  • arguments는 객체이지 배열이 아니다. 즉 length프로퍼티가 있으므로 배열과 유사하게 동작하지만 배열은 아니므로 배열 메서드를 사용하면 에러가 발생한다!

 

arguments 객체를 통해 매개변수 개수가 정확하게 정해지지 않은 함수를 구현하거나, 전달된 인자의 개수에 따라 서로 다른 처리를 해줘야 하는 함수를 구현하는데 유용하게 사용가능하다.

function sum() {
    var result = 0;

    for (var i = 0; arguments.length; i++){
        result += arguments[i];
    }

    return result;
}

console.log(sum(1, 2, 3)); //6
console.log(sum(1, 2, 3, 4,5,6,7,8,9)); //45

이런 코딩이 가능하다.

 

 

호출 패턴과 this바인딩

  • 자바스크립트에서 함수를 호출 할 때 매개변수로 전달되는 인자값, arguments가 전달된다고 앞에서 설명했다.
  • 여기에 더해서 this인자 또한 함수 내부로 암묵적으로 전달된다!

 

 

객체의 메서드를 호출할 때 this 바인딩

  • 객체의 프로퍼티가 함수일 경우 이 함수를 메서드라고 부른다.
  • 이러한 메서드를 호출할 때, 메서드 내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩된다.
//myObject 객체생성
var myObject = {
    name: 'foo',
    sayName: function () {
        console.log(this.name);
    }
}

//otherObject 객체 생성
var otherObject = {
    name : 'bar'
};

// otherObject.sayName()메서드
otherObject.sayName = myObject.sayName;

//sayName() 메서드 호출
myObject.sayName(); //foo
otherObject.sayName(); // bar
  • myObject 객체와 otehrObject 객체는 name 프로퍼티와 sayNmae() 메서드를 가지고 있다.
  • sayName()메서드는 객체의 this.name값을 출력하는 함수이다.
  • 이 때 사용된 this는 자신의 호출한 객체에 바인딩된다!

 

함수를 호출할 때 this바인딩

  • 자바스크립트에서는 함수를 호출하면, 해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩된다.
  • 브라우저에서 자바스크립트를 실행하는 경우 전역 객체는 window객체가 된다.
var foo = "I'm foo"; //전역변수

console.log(foo);
console.log(window.foo);

//함수 호출 할 때 this바인딩

var test = "This is test";
console.log(window.test);

var sayFoo = function () {
    console.log(this.test); // sayFoo() 호출 시 this는 전역객체에 바인딩
}

sayFoo();

 

 

 

내부 함수에서 this를 사용할 때는 주의해야한다. 아래의 예를 한번 보자.

//전역변수 value 정의
var value = 100;

//myObject 객체 생성

var myObject = {
    value: 1,
    func1: function () {
        this.value += 1;
        console.log("func1 called", this.value);
        
        func2 = function () {
            this.value += 1;
            console.log("func2 called", this.value);

            func3 = function () {
                this.value += 1;
                console.log("func3 called", this.value);
    
            }
            func3();
        }
        func2();
    }
};

myObject.func1();
  • 복잡해 보이지만 하나하나 보면 별로 복잡하지 않다.
  • 우선 myObject라는 객체안의 func1()이라는 메서드를 만들었다.
  • func1() 메서드 안에서 func2()라는 함수를 만들었고, func2()라는 함수안에서 func3()를 만들었다.
  • 실행순서는 func1()이 불리게 되고 func2()가 불리게 되고 func3()이 불리게 된다.

 

이때 실행결과는 어떻게 될까???

아마 2, 3, 4 라고 생각하는 사람이 많을것이다.

 

 

 

 

하지만 결과는 아래처럼 나온다.

 

왜 이렇게 나올까????

  • 자바스크립트에서는 내부 함수 호출 패턴을 정의해 놓지 않기 때문에 내부 함수는 결국 함수 호출로 취급된다.
  • 즉, 내부함수에서의 this는 전역객체(window)에 바인딩된다.
  • func1()은 메서드 이기 때문에 myObject의 value값을 가진다
  • func2()는 함수로 취급하기 때문에 전역객체의 value에 바인딩된다.
  • func3()은 func2()와 마찬가지로 취급된다.

 

그렇다면 내부 함수에서 자신의 부모 메서드의 this에 접근하기 위해서는 어떻게 해야할까?

//전역변수 value 정의
var value = 100;

//myObject 객체 생성

var myObject = {
    value: 1,
    func1: function () {
        var that = this;

        this.value += 1;
        console.log("func1 called", this.value);
        
        func2 = function () {
            that.value += 1;
            console.log("func2 called", that.value);

            func3 = function () {
                that.value += 1;
                console.log("func3 called", that.value);
    
            }
            func3();
        }
        func2();
    }
};

myObject.func1();
  • func1()메서드 내부에서 that이라는 변수에 this를 할당시켜준다.
  • 내부함수는 부모함수의 프로퍼티를 사용할 수 있다고 설명했다.
  • 내부함수에서 부모함수의 this(myObject의 프로퍼티)를 사용할 수 있는 것이다.

 

 

생성자 함수를 호출할 때 this바인딩

  • 앞서 설명했듯이 자바스크립트 객체를 생성하는 방법은 객체 리터럴방식, 생성자함수 두개를 통해 생성가능하다.
  • 자바스크립트에서 생성자는 말 그대로 자바스크립트의 객체를 생성하는 역할을 한다.
  • 기존 함수에 new 연산자를 붙여 호출하면 해당 함수는 생성자 함수로 동작한다.
  • 이는 반대로 생각하면 일반 함수에 new를 붙여 호출하면 원치 않는 생성자 함수처럼 동작할 수도 있다는 말이다.
  • 따라서 특정 함수가 생성자 함수라면 함수 이름의 첫 문자를 대문자로 쓰기를 권한다.
  • 생성자 함수에서의 this는 앞서 본 메서드와 함수 호출방식에서와는 다르게 동작한다.
  • 이 방식을 이해하기 위해서는 우선 생성자 함수가 호출됐을 때 동작하는 방식을 이해해야한다.

 

생성자 함수가 동작하는 방식

1. 빈 객체 생성 및 this바인딩

  • 생성자 함수 코드가 실행되기 전 빈 객체가 생성된다.(이 객체는 this로 바인딩된다.)
  • 따라서 이후 생성자 함수의 코드 내부에서 사용된 this는 이 빈 객체를 가리키게 된다.
  • 하지만 엄밀하게 얘기하면 빈객체는 아니다!
  • 앞서 설명했지만 자바스크립트의 모든 객체는 자신의 부모인 프로토타입 객체와 연결되어 있으며, 이를 통해 부모 객체의 프로퍼티나 메서드를 자신의 것처럼 사용할 수 있기 때문이다.
  • 생성자 함수가 생성한 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다.

2. this를 통한 프로퍼티 생성

  • 이후에는 함수 코드 내부에서 this를 사용해서, 앞에서 생성된 빈 객체에 동적으로 프로퍼티나 메서드를 생성할 수 있다.

3. 생성된 객체 리턴

  • 리턴문이 동작하는 방식은 경우에 따라 다르다.
  • 우선 가장 일반적인 경우로 특별하게 리턴문이 없을 경우, this로 바인딩된 새로 생성한 객체가 리턴된다.

 

위의 내용을 코드로 한번 살펴보자.

//Person() 생성자 함수
var Person = function (name) {
    this.name = name;
}

//foo객체 생성
var foo = new Person('foo');
console.log(foo.name); //foo

Person이라는 생성자 함수를 정의하고, 이를 통해  foo객체를 만드는 과정이다.

 

  1. Person() 함수가 생성자로 호출되면, 함수 코드가 실행되기 전 빈 객체가 생성된다.
  2. 생성된 빈 객체는 Person()생성자 함수의 prototype 프로퍼티가 가르키는 객체(Person.prototype객체)를 [[Prototype]링크로 연결해서 자신의 프로토타입으로 설정한다.
  3. 이렇게생성된 객체는 생저 함수 코드에서 사용되는 this로 바인딩 된다.
  4. this가 카리키는 빈 객체에 name이라는 동적 프로퍼티 생성
  5. 리턴값이 특별하게 없으니 this로 바인딩한 객체가 반환, foo변수에 저장

 

 

객체 리터럴 방식과 생성자 함수를 통한 객체 생성 방식의 차이

간단한 예제를 통해 확인해보자

//객체 리터럴 방식으로 foo 객체 생성
var foo = {
    name: 'foo',
    age: 35,
    gender : 'man',
}
console.dir(foo);

//생성자 함수
function Person(name, age, gender, position) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}

//Person 생성자 함수를 이용해 bar 객체, baz객체 생성
var bar = new Person('bar', 33, 'woman');
console.dir(bar);

var baz = new Person('baz', 25, 'woman');
console.dir(baz)

  • 콘솔창에서 볼 수 있듯이 프로토타입 객체(_proto_프로퍼티)에 있음을 알 수 있다.
  • 객체 리터럴 방식은 자신의 프로토타입 객체가 Object(Object.prototype)이다.
  • 반면 생성자 함수 방식은 Person(Person.prototype)이다.

 

 

 

 

 

 

 

참고

book.naver.com/bookdb/book_detail.nhn?bid=7400243

'Dev > JavaScript' 카테고리의 다른 글

프로토타입 체이닝  (0) 2021.04.13
함수 리턴  (0) 2021.04.09
함수 객체의 기본 프로퍼티  (0) 2021.04.07
함수객체  (0) 2021.04.07
함수정의  (0) 2021.04.07
Comments