함수, 프로토타입 체이닝 / JavaScript

88234 단어 JavaScriptJavaScript

함수 생성

함수 리터럴

<script>
function add(x, y) {
	return x + y;
}
</script>
1) 자바스크립트 함수 리터럴은 function 키워드로 시작
2) 함수명은 함수 몸체의 내부 코드에서 자신을 재귀적으로 호출하거나 또는 자바스크립트 디버거가 해당 함수를 구분하는 식별자로 사용. 함수명은 선택 사항, 함수명이 없는 함수를 익명 함수라고 한다.
3) 매개변수 리스트에 매개변수 타입을 기술하지 않는다.

함수 선언문(function statement)

<script>
function add(x, y) {
	return x + y;
}
console.log(add(3, 4));	// (출력값) 7
</script>
1) 함수 선언문 방식은 함수 리터럴 형태와 같다.
2) 함수 선언문 방식으로 정의된 함수의 경우 반드시 함수명이 정의되어 있어야 한다.
3) 위 함수 선언문의 실제 구조는 자바스크립트 엔진에 의해 함수 변수 add 형태로 변경되어 (let add = function add() {..}) 함수 외부에서 호출이 가능한 형태이다.
4) 일반적으로 함수 선언문 방식으로 선언된 함수의 경우 함수 끝에 세미콜론(;)을 붙이지 않는다.

함수 표현식(function expression)

<script>
// 익명 함수 표현식
let add = function (x, y) {
    return x + y;
};

let plus = add;

console.log(add(3,4));	(출력값) 7
console.log(plus(5,6));	(출력값) 11
1) 함수 리터럴로 하나의 함수를 만들고, 여기서 생성된 함수를 변수에 할당
2) 함수 이름이 선택 사항이며, 보통 사용하지 않는다.
3) 위 예시의 변수 add는 함수 이름이 아니다. 함수가 할당된 함수 변수이다.
4) 일반적으로 함수 표현식 방식으로 선언된 함수의 경우 함수 끝에 세미콜론(;)을 붙이는 것을 권장한다.

// 기명 함수 표현식
let add = function sum(x, y) {
	return x + y;
};

console.log(add(3,4));	// (출력값) 7
console.log(sum(3,4));	// (출력값) Uncaught ReferenceError: sum is not defined 에러 발생
1) 함수 표현식에서 사용된 함수 이름이 외부 코드에서 접근 불가능

// 함수의 재귀적인 호출 처리, 함수 표현식 방식으로 구현한 팩토리얼 함수
let factorialLet = function factorial(n) {
    if(n <= 1) {
        return 1;
    }
    return n * factorial(n-1);
};

console.log(factorialLet(3));	(출력값) 6
console.log(factorial(3));		(출력값) Uncaught ReferenceError: factorial is not defined 에러 발생
1) 함수 외부에서는 함수 변수 factorialLet로 함수를 호출, 함수 내부에서 이뤄지는 재귀 호출은 factorial() 함수 이름으로 처리
</script>

Function() 생성자 함수

<script>
let add = new Function('x', 'y', 'return x + y');
console.log(add(3, 4));	(출력값) 7
</script>
-- 자주 사용되지는 않음

함수 호이스팅

<script>
console.log(add(2,3)); // (출력값) 5

// 함수 선언문 형태로 add() 함수 정의
function add(x, y) {
    return x + y;
}

console.log(add(3, 4)); // (출력값) 7
1) add(2,3) 시점에서 함수가 정의되지 않았음에도 하단에 정의된 add() 함수를 호출하는 것이 가능하다. 이는 함수가 자신이 위치한 코드에 상관없이 함수 선언문 형태로 정의한 함수의 유효 범위는 코드의 맨 처음부터 시작한다는 것을 확인할 수 있다. 이것을 '함수 호이스팅'이라고 부른다.
2) 함수 호이스팅은 함수를 사용하기 전에 반드시 선언해야 한다는 규칙을 무시하므로 코드의 구조를 엉성하게 만들 수도 있다고 지적하며, 함수 표현식 사용을 권장한다.

console.log(add(2,3)); // Uncaught ReferenceError: Cannot access 'add' before initialization

// 함수 표현식 형태로 add() 함수 정의
let add = function (x, y) {
    return x + y;
};

console.log(add(3, 4)); // (출력값) 7
1) 함수 표현식 형태로 정의되어 있어 호이스팅이 일어나지 않는다.
2) 함수 호이스팅이 발생하는 원인은 자바스크립트의 변수 생성과 초기화 작업이 분리돼서 진행되기 때문이다.
</script>

함수 객체

함수도 객체

<script>
// 함수 선언 방식으로 add() 함수 정의
function add(x, y) {
    return x + y;
}

// add() 함수 객체에 result, status 프로퍼티 추가
add.result = add(3, 2);
add.status = 'OK';

console.log(add.result); (출력값) 5
console.log(add.status); (출력값) 'OK'
1) 함수도 객체다., 함수의 기본 기능인 코드 실행뿐만 아니라, 함수 자체가 일반 객체처럼 프로퍼티들을 가질 수 있다.
</script>

함수도 값 = 일반 객체처럼 취급

  • 리터럴에 의해 생성
  • 변수나 배열의 요소, 객체의 프로퍼티 등에 할당 가능
<script>
// 변수에 함수 할당
let bar = function () { return 100; };
console.log(bar());		// (출력값) 100

// 프로퍼티에 함수 할당
let obj = {};
obj.baz = function () { return 200; };
console.log(obj.baz());	// (출력값) 200
</script>
  • 함수의 인자로 전달 가능
<script>
// 함수 표현식으로 foo() 함수 생성
let foo = function (func) {
    func();
};

// foo() 함수 실행
foo(function() {
    console.log('Function can be used as the argument.');
});	(출력값) 'Function can be used as the argument.'
</script>
  • 함수의 리턴값으로 리턴 가능
<script>
// 함수를 리턴하는 foo() 함수 정의
let foo = function () {
    return function () {
        console.log('this function is the return value.');
    };
};

let bar = foo();
bar();	(출력값) 'this function is the return value.'
</script>
  • 동적으로 프로퍼티를 생성 및 할당 가능

함수 객체의 기본 프로퍼티

function add(x, y) {
    return x + y;
}
console.dir(add);

  • name 프로퍼티 : 함수의 이름
  • caller 프로퍼티 : 자신을 호출한 함수
  • arguments 프로퍼티 : 함수를 호출할 때 전달된 인자값
  • length 프로퍼티 : 함수가 정상적으로 실행될 때 기대되는 인자의 개수
<script>
function func0() {

}
function func1(x) {
    return x;
}
function func2(x, y) {
    return x + y;
}
function func3(x, y, z) {
    return x + y + z;
}
console.log(func0.length);  (출력값) 0
console.log(func1.length);  (출력값) 1
console.log(func2.length);  (출력값) 2
console.log(func3.length);  (출력값) 3
</script>
  • prototype 프로퍼티 : 모든 함수는 객체로서 prototype 프로퍼티를 가지고 있다. 여기서 주의할 것은 함수 객체의 prototype 프로퍼티는 모든 객체의 부모를 나타내는 내부 프로퍼티인 [[Prototype]]과 혼동되어서는 안 된다.
    -> 두 프로퍼티 모두 프로토타입 객체를 가리킨다는 점에서는 공통점이 있지만, 관점에 차이가 있다. 모든 객체에 있는 내부 프로퍼티인 [[Prototype]]는 객체 입장에서 자신의 부모 역할을 하는 프로토타입 객체를 가리키는 반면에, 함수 객체가 가지는 prototype 프로퍼티는 이 함수가 생성자로 사용될 때 이 함수를 통해 새엇ㅇ된 객체의 부모 역할을 하는 프로토타입 객체를 가리킨다.
    -> prototype 프로퍼티는 함수가 생성될 때 만들어지며, constructor 프로퍼티 하나만 있는 객체를 가리킨다. 그리고 prototype 프로퍼티가 가리키는 프로토타입 객체의 유일한 constructor 프로퍼티는 자신과 연결된 함수를 가리킨다. 즉, 자바스크립트에서는 함수를 생성할 때, 함수 자신과 연결된 프로토타입 객체를 동시에 생성하며, 이 둘은 prototype과 constructor라는 프로퍼티로 서로를 참조하게 된다.
<script>
// myFunction() 함수 정의
function myFunction() {
    return true;
}
console.dir(myFunction.prototype);
console.dir(myFunction.prototype.constructor);
</script>


다양한 함수 형태

콜백 함수

  • 콜백 함수는 코드를 통해 명시적으로 호출하는 함수가 아니라, 개발자는 단지 함수를 등록하기만 하고, 어떤 이벤트가 발생했거나 특정 시점에 도달했을때 시스템에서 호출되는 함수를 말한다. 또한, 특정 함수의 인자로 넘겨서, 코드 내부에서 호출되는 함수 또한 콜백 함수가 될 수 있다.
  • 대표적인 콜백 함수의 사용 예는 자바스크립트에서의 이벤트 핸들러 처리이다. 웹 페이지가 로드되거나 키보드가 입력되는 등의 DOM 이벤트가 발생할 경우, 브라우저는 정의된 DOM 이벤트에 해당하는 이벤트 핸들러를 실행시킨다. 만약 이러한 이벤트 핸들러에 콜백 함수가 등록했다면, 콜백 함수는 이벤트가 발생할 때마다 브라우저에 의해 실행되게 된다.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script type="text/javascript" src="hello_javascript.js"></script>
    <script>
        // 페이지 로드 시 호출될 콜백 함수
        window.onload = function () {
            alert('This is the callback function.');
        };
    </script>
</body>
</html>

즉시 실행 함수

  • 함수를 정의함과 동시에 바로 실행하는 함수
  • 최초 한 번의 실행만을 필요로 하는 초기화 코드 부분 등에 사용
  • jQuery와 같은 자바스크립트 라이브러니나 프레임워크 소스들에서 사용
    + 함수 내부에서 정의된 매개변수와 변수들은 함수 코드 내부에서만 유효하기 때문에 라이브러리 코드를 즉시 실행 함수 내부에 정의해두게 되면, 라이브러리 내의 변수들은 함수 외부에서 접근할 수 없다. 따라서 이렇게 즉시 실행 함수 내에 라이브러리 코드를 추가하면 전역 네임스페이스를 더럽히지 않으므로, 이후 다른 자바스크립트 라이브러리들이 동시에 로드가 되더라도 라이브러리 간 변수 이름 충돌 같은 문제를 방지할 수 있다.
<script>
(function (name) {
    console.log('This is the immediate function -> ' + name);
})('foo');
</script>

내부 함수

  • 함수 내부에서 정의된 함수
  • 클로저 생성, 부모 함수 코드에서 외부에서의 접근을 막고 독립적인 헬퍼 함수를 구현하는 용도 등으로 사용
  • 내부 함수에서는 자신을 둘러싼 부모 함수의 변수에 접근이 가능하다.
  • 내부 함수는 일반적으로 자신이 정의된 부모 함수 내부에서만 호출이 가능하다.
<script>
// parent() 함수 정의
function parent() {
    var a = 100;
    var b = 200;

    // child() 함수 정의
    function child() {
        var b = 300;

        console.log(a);
        console.log(b);
    }
    child();
}
parent();
child();
</script>

  • 특정 함수 스코프 안에 선언된 내부 함수를 호출할 수 있다. 부모 함수에서 내부 함수를 외부로 리턴하면, 부모 함수 밖에서도 내부 함수를 호출하는 것이 가능하다.
<script>
function parent() {
    var a = 100;
    // child() 내부 함수
    var child = function () {
        console.log(a);
    }

    // child() 함수 반환
    return child;
}
var inner = parent();
inner(); -> 클로저
</script>

<script>
// self() 함수
var self = function () {
    console.log('a');
    return function () {
        console.log('b');
    }
}
self = self();  // a -> self() 함수가 출력된 후, self 함수 변수에 self() 함수 호출 리턴값으로 내보낸 함수가 저장
self();         // b -> self 함수 변수에 저장된(self() 함수에서 리턴된) 내부 함수가 호출 된다.
</script>

함수 호출과 this

arguments 객체

  • 함수 형식에 맞춰 인자를 넘기지 않더라도 함수 호출이 가능하다.
<script>
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
</script>
  • 자바스크립트의 함수 호출 특성 때문에 함수 코드를 작성할 때, 런타임 시에 호출된 인자의 개수를 확인하고 이에 따라 동작을 다르게 해줘야 할 경우가 있다. 이를 가능케 하는게 바로 'arguments 객체'다.
  • 'arguments 객체'는 함수를 호출할 때 넘긴 인자들이 배열 형태로 저장된 객체를 의미한다. -> 이 객체는 실제 배열이 아닌 '유사 배열 객체'(length 프로퍼티를 가진 객체)
<script>
// 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));
</script>

<script>
function sum() {
    var result = 0;

    for(var i = 0; i < 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
</script>
  • arguments 객체는 매개변수 개수가 정확하게 정해지지 않은 함수를 구현하거나, 전달된 인자의 개수에 따라 서로 다른 처리를 해줘야 하는 함수를 개발하는 데 유용하게 사용할 수 있다.

호출 패턴과 this 바인딩

  • 함수 호출 시 기존 인자와 함께 arguments 객체 및 this 인자가 함수 내부로 암묵적으로 전달된다.
  • 함수가 호출되는 방식(호출 패턴)에 따라 this가 다른 객체를 참조(this 바인딩)할 수 있어서 주의해야 한다.
  • 객체의 프로퍼티가 함수일 경우, 이 함수를 '메서드'라고 부른다. 이러한 메서드를 호출할 때, 메서드 내부 코드에서 사용된 this는 '해당 메서드를 호출한 객체로 바인딩'된다.
<script>
// 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
</script>
  • 자바스크립트에서 '함수를 호출'하면, 해당 함수 내부 코드에서 사용된 'this는 전역 객체에 바인딩'된다. 브라우저에서 자바스크립트를 실행하는 경우 전역 객체는 window 객체가 된다. '내부 함수를 호출'했을 경우에도 그대로 적용된다.
  • 내부 함수 호출 패턴을 정의해 놓지 않으면 예상 결과와 다르게 내부 함수의 this는 전역 객체(window)에 바인딩된다.
<script>
// myObject 객체 생성
var myObject = {
    value: 1,
    func1: function () {
        this.value += 1;
        console.log('func1() called. this.value : ' + this.value);

        // func2() 내부 함수
        func2 = function () {
            this.value += 1;
            console.log('func2() called. this.value : ' + this.value);

            // func3() 내부 함수
            func3 = function () {
                this.value += 1;
                console.log('func3() called. this.value : ' + this.value);
            }
            func3();    // func3() 내부 함수 호출
        }
        func2();    // func2() 내부 함수 호출
    }
};
myObject.func1(); // func1() 메서드 호출
</script>

  • 부모 함수의 this를 내부 함수가 접근 가능한 다른 변수에 저장하는 방법을 사용해서 해결
<script>
// myObject 객체 생성
var myObject = {
    value: 1,
    func1: function () {
        var that = this;

        this.value += 1;
        console.log('func1() called. this.value : ' + this.value);

        // func2() 내부 함수
        func2 = function () {
            that.value += 1;
            console.log('func2() called. this.value : ' + that.value);

            // func3() 내부 함수
            func3 = function () {
                that.value += 1;
                console.log('func3() called. this.value : ' + that.value);
            }
            func3();    // func3() 내부 함수 호출
        }
        func2();    // func2() 내부 함수 호출
    }
};
myObject.func1(); // func1() 메서드 호출
</script>

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

  • 기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.
<script>
// Person() 생성자 함수 정의
var Person = function (name) {
    // 함수 코드 실행 전
    this.name = name;
    // 함수 리턴
};

// foo 객체 생성
var foo = new Person('foo');
console.log(foo.name);  // (출력값) foo
</script>
  • 객체 생성 두가지 방법(객체 리터럴, 생성자 함수)
<script>
// 객체 리터럴 방식으로 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, 'man');
console.dir(baz);
</script>

  • 일반 함수 호출의 경우 this가 window 전역 객체에 바인딩되는 반면에, 생성자 함수 호출의 경우 this는 새로 생성되는 빈 객체에 바인딩된다.
<script>
// 생성자 함수
function Person(name, age, gender, position) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}

var qux = Person('qux', 20, 'man');
console.log(qux);			// (출력값) undefined

console.log(window.name);	// (출력값) qux
console.log(window.age);	// (출력값) 20
console.log(window.gender);	// (출력값) man
</script>
  • call과 apply 메서드를 이용한 명시적인 this 바인딩
  • apply() 메서드에서 첫 번째 인자 thisArg는 apply() 메서드를 호출한 함수 내부에서 사용한 this에 바인딩할 객체를 가리킨다. 즉, 첫 번째 인자로 넘긴 객체가 this로 명시적으로 바인딩되는 것이다. 두 번째 argArray 인자는 함수를 호출할 때 넘길 인자들의 배열을 가리킨다.
  • call() 메서드는 apply() 메서드의 배열 형태의 인자를 각각 하나의 인자로 넘기는 방식만 다르다. ex) Person.apply(foo, ['foo', 30, 'man']); -> Person.call(foo, 'foo', 30, 'man');
<script>
// 생성자 함수
function Person(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;
}

// foo 빈 객체 생성
var foo = {};

// apply() 메서드 호출
Person.apply(foo, ['foo', 30, 'man']);
console.dir(foo);
</script>
-> Person('foo', 30, 'man') 함수를 호출하면서, this를 foo 객체에 명시적으로 바인딩한 것

  • apply() 메서드를 활용한 arguments 객체의 배열 표준 메서드 slice() 활용
<script>
function myFunction() {
    console.dir(arguments);

    // arguments.shift(); 에러 발생

    // arguments 객체를 배열로 변환
    var args = Array.prototype.slice.apply(arguments);
    console.dir(args);
}
myFunction(1,2,3);
</script>
1) arguments 객체는 length 프로퍼티만을 가진 유사 객체 배열이므로, shift()와 같은 표준 배열 메서드를 호출하면 에러가 발생한다.
2) apply() 메서드로 arguments 객체에서 마치 배열 메서드가 있는 것처럼 처리할 수 있다. Array.prototype.slice.apply(arguments)를 'Array.prototype.slice() 메서드를 호출하고, 이때 this는 arguments 객체로 바인딩한다.'로 해석하게 되면 arguments 객체가 Array.prototype.slice() 메서드를 마치 자신의 메서드인 양 arguments.slice()와 같은 형태로 메서드를 호출하라는 것이다.

함수 리턴

  • 자바 스크립트 함수는 항상 리턴값을 반환한다. return 문을 사용하지 않았더라도 규칙에 따라 항상 리턴값을 전달
<script>
// noReturnFunc() 함수
var noReturnFunc = function () {
    console.log('This function has no return statement.');
};

var result = noReturnFunc();
console.log(result);
</script>
-- 리턴문이 없는 함수의 경우, 함수를 호출할 때 undefined 값이 리턴

  • 생성자 함수에서 리턴값을 지정하지 않을 경우 this로 바인딩 된 새로 생성된 객체가 리턴된다. 때문에 생성자 함수에서는 일반적으로 리턴값을 지정하지 않는다.
  • 생성자 함수에서 명시적으로 객체를 리턴할 경우 명시적으로 return된 객체가 출력된다.
<script>
// Person() 생성자 함수
function Person(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;

    return {name: 'bar', age: 20, gender: 'woman'};
}

var foo = new Person('foo', 30, 'man');
console.dir(foo);
</script>

  • 생성자 함수에서 명시적으로 기본 타입(불린, 숫자, 문자열) 값을 리턴했을 경우 리턴값을 무시하고 this로 바인딩된 객체가 리턴된다.
<script>
// Person() 생성자 함수
function Person(name, age, gender) {
    this.name = name;
    this.age = age;
    this.gender = gender;

    return 100;
}

var foo = new Person('foo', 30, 'man');
console.dir(foo);
</script>


프로토타입과 프로토타입 체이닝

좋은 웹페이지 즐겨찾기