자바스크립트에서는 함수를 호출할 때 함수 형식에 맞춰 인자를 넘기지 않더라도 에러가 발생하지 않는다
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, 3
정의된 함수의 인자보다 적게 함수를 호출했을 경우, 넘겨지지 않은 인자에는 undefined가 할당된다
정의된 함수의 인자보다 많게 함수를 호출했을 경우, 초과된 인자는 무시된다
자바스크립트의 이런 특성 때문에, 런타임 시에 호출된 인자의 개수를 확인하고 이에 따라 동작을 다르게 해줘야 할 경우가 있다
이를 가능케 하는게 argument 객체이다
arguments 객체는 함수를 호출할 때 넘긴 인자들이 배열 형태로 저장된 객체를 의미하며 이는 유사 배열 객체(length 프로퍼티를 가진 객체)이다
function add(a, b) {
console.dir(arguments);
return a + b;
}
console.dir(add); // add.length = 2
console.log(add()); // add.arguments.length = 0
console.log(add(1)); // add.arguments.length = 1
console.log(add(1, 2)); // add.arguments.length = 2
console.log(add(1, 2, 3)); // add.arguments.length = 3
arguments 객체는 매개변수 개수가 정확하게 정해지지 않은 함수를 구현하거나, 전달된 인자의 개수에 따라 서로 다른 처리를 해줘야 하는 함수를 개발할 때 유용하게 사용할 수 있다
function sum() {
var ret = 0;
if (arguments.length < 2)
ret = 'insert more than 1 numbers';
else {
for (index in arguments)
ret += arguments[index];
}
return ret;
}
console.log(sum()); // insert more than 1 numbers
console.log(sum(1)); // insert more than 1 numbers
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 9)); // 45
자바스크립트에서 함수를 호출할 때, 기존 매개변수로 전달되는 인자값에 더해 arguments와 this가 전달된다
this가 이해하기 어려운 이유는 자바스크립트의 여러가지 함수가 호출되는 방식(호출 패턴)에 따라 this가 다른 객체를 참조(this 바인)하기 때문이다
여러가지 함수 호출 패턴과 각 패턴에 따라 this가 어떤 객체에 바인딩 되는지 숙지해야 한다
메서드 내부 코드에서 사용된 this는 해당 메서드를 호출한 객체로 바인딩된다
var myObj = {
name: 'foo',
sayName: function () {
console.log(this.name);
}
};
var otherObj = {
name: 'bar'
};
otherObj.sayName = myObj.sayName;
myObj.sayName();
otherObj.sayName();
myObj와 otherObj는 둘다 각각 name과 sayName이라는 프로퍼티가 있다
sayName 메소드에서 사용된 this는 자신을 호출한 객체에 바인딩된다
자바스크립트에서는 함수를 호출하면, 해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩된다
브라우저에서 자바스크립트를 실행하는 경우 전역 객체는 window 객체가 된다
var foo = "I'm foo";
console.log(foo);
console.log(this.foo);
console.log(window.foo);
함수를 호출할 때 this는 전역 객체에 바인딩된다
var foo = "I'm foo";
console.log(window.foo);
var sayFoo = function() {
console.log(this.foo);
};
sayFoo();
하지만 이러한 함수 호출에서의 this 바인딩 특성은 내부 함수를 호출했을 경우에도 그래돌 적용되므로, 내부 함수에서 this를 이용할 때는 주의해야 한다
var value = 100;
var myObj = {
value: 1,
func1: function () {
this.value += 1;
console.log('func1() called. this.value : ' + this.value); // func1은 myObj의 메소드이므로 메소드를 호출한 객체에 this가 바인딩 되어 myObj의 프로퍼티인 1에서 +1한 2가 출력된다
func2 = function () {
this.value += 1;
console.log('func2() called. this.value : ' + this.value); // func2는 내부 함수로 메소드가 아니다. 메소드가 아닌 일반 함수의 this는 전역 객체에 바인딩되므로 전역객체 value 100에서 +1한 101이 출력된다
func3 = function () {
this.value += 1;
console.log('func3() called. this.value : ' + this.value); // func3도 내부 함수로 메소드가 아니다. 그러므로 this는 전역 객체에 바인딩되므로 전역객체 value 101에서 +1한 102가 출력된다
};
func3();
};
func2();
}
};
myObj.func1();
자바스크립트에서는 내부 함수 호출 패턴을 정의해 놓지 않았다
그러므로 내부 함수를 호출하면 this는 전역 객체에 바인딩된다
이렇게 내부 함수가 this를 전역객체로 참조하는 자바스크립트의 한계를 극복하려면 부모함수의 this를 내부함수가 접근 가능한 다른 변수에 저장하는 방법을 사용한다
부모함수의 this를 저장하는 변수를 관례적으로 that으로 한다
var value = 100;
var myObj = {
value: 1,
func1: function () {
var that = this; // 메소드 호출 패턴에 따라 메소드를 호출한 객체에 바인딩되는 this를 저장할 변수 that
this.value += 1;
console.log('func1() called. this.value : ' + that.value);
func2 = function () {
that.value += 1;
console.log('func2() called. this.value : ' + that.value);
func3 = function () {
that.value += 1;
console.log('func3() called. this.value : ' + that.value);
};
func3();
};
func2();
}
};
myObj.func1();
자바스크립트에선 이와 같은 this 바인딩의 한계를 극복하기 위해 this 바인딩을 명시적으로 할 수 있도록 call과 apply 메소드를 제공한다
자바스크립트 객체를 생성하는 방법은 크게 1. 객체 리터럴 방법 과 2. 생성자 함수를 이용하는 두가지 방법이 있다
자바스크립트의 생성자 함수는 자바스크립트의 객체를 생성하는 역할을 한다
다른 언어와는 다르게 그 형식이 정해져 있는 것이 아니라 기존 함수에 new를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다
생성자 함수와 일반 함수를 구분하기 위해 생성자 함수는 시작을 대문자로 한다
생성자 함수가 동작하는 방식
new 연산자로 자바스크립트 함수를 생성자로 호출하면, 다음과 같은 순서로 동작한다
1. 빈 객체 생성 및 this 바인딩
생성자 함수 코드가 실행되기 전 빈 객체가 생성된다. 이 객체가 바로 생성자 함수가 생성하려는 객체이며, 이 객체는 this로 바인딩된다
따라서 이후 생성자 함수 코드 내부에서 이용된 this는 이 객체를 가리킨다
하지만 엄밀히 말하면 여기서 생성된 객체는 빈 객체는 아니다
자바스크립트의 모든 객체는 자신의 부모인 프로토타입 객체와 연결되어 있으며 이를 통해 부모 객체의 프로퍼티나 메소드를 마치 자신의 것처럼 사용할 수 있다
즉 생성자 함수가 생성한 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체(부모)로 설정한다
2. this를 통한 프로퍼티 생성
이후에는 함수 코드 내부에서 this를 사용해서, 앞에서 생성도니 빈 객체에 동적으로 프로퍼티나 메서드를 생성할 수 있다
3. 생성된 객체 리턴
리턴문이 동작하는 방식은 경우에 따라 다르므로 주의해야 한다
가장 일반적인 경우로 리턴문이 없을 경우, this로 바인딩된 새로 생성한 객체가 리턴된다(주의 : 생성자 함수가 아닌 일반 함수를 호출할 때 리턴값이 명시되어 있지 않으면 undefined가 리턴된다)
하지만 리턴값이 새로 생성한 객체가 아닌 다른 객체를 반환하는 경우는 생성자 함수를 호출했다고 하더라도 this가 아닌 해당 객체가 리턴된다
var Person = function (name) {
// 1. 새로운 객체가 생성됨 (빈 객체 A)
// 2. 새로운 객체 A의 prototype 프로퍼티에 생성자 함수(Person)의 prototype 프로퍼티가 가리키는 Person.prototype 객체 할당 (A.prototype = Person.prototype;)
// 3. this = A; --> this 바인딩
this.name = name
// 생생된 객체 리턴
};
var foo = new Person('foo');
console.log(foo.name);
객체 리터럴 방식과 생성자 함수를 통한 객체 생성 방식의 차이
// 객체 리터럴
var foo = {
name: 'foo',
age: 35,
gender: 'man'
};
console.dir(foo);
// 생성자 함수
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
var bar = new Person('bar', 20, 'female');
var baz = new Person('baz', 30, 'male');
console.dir(bar);
console.dir(baz);
객체 리터럴 방식과 생성자 함수 방식의 차이는 프로토타입 객체(proto 프로퍼티)에 있다
객체 리터럴 방식은 자신의 프로토타입 객체가 Object(Object.prototype)이고 생성자 함수 방식은 Person(Person.prototype) 이다
자바스크립트 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체(Person.prototype)를 자신의 부모 역할을 하는 프로토타입 객체(<>)로 설정한다
객체 리터럴 방식에서 객체 생성자 함수는 Object() 이며 생성자 함수 방식의 경우는 생성자 함수 그 자체(Person())이기 때문에 차이가 발생한다.
생성자 함수를 new를 붙이지 않고 호출할 경우
일반 함수 호출의 경우는 this가 window 전역 객체에 바인딩되는 반면에, 생성자 함수 호출은 this가 생성된 객체에 바인딩 된다
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
var qux = Person('qux', 20, 'man'); // 객체를 생성하는 new 생략(일반 함수 호출문)
console.log(qux); // undefined
console.log(window.name);
console.log(window.age);
console.log(window.gender);
위와 같이 Person을 호출할 때 new를 붙이지 않으면 this는 window 객체에 바인딩되므로 name, age, gender가 전역 객체 window의 프로퍼티로 동적으로 할당된다
생성자 함수에서 리턴값이 없을 경우 생성된 객체가 리턴되는 반면, 일반 함수 호출에서 리턴값이 없을 경우에는 undefined가 리턴되므로 undefined 출력
그러므로 생성자 함수로 사용할 함수는 첫 글자를 대문자로 표기하는 네이밍 규칙을 권장
강제 인스턴스 생성 패턴 (권장)
function A(arg) {
if (!(this instanceof A)) // this가 A의 인스턴스가 아니라면 new로 호출해서 생성
return new A(arg);
this.value = arg? arg : 0; // arg가 유의미하면 arg, 아니면 0 할당
}
var a = new A(100);
var b = A(10);
console.log(a.value);
console.log(b.value);
console.log(window.value);
if (!(this instanceof A)) 이 부분은 if (!(this instanceof arguments.callee))로 대체 가능하다(arguments.callee가 곧 함수를 의미)
boolean에 대한 정리
0 / '' / null / undefined 를 제외한 모든 값은 true이다(객체는 빈 객체라도 true)
프로토타입 정리
객체의 [Prototype] 프로퍼티
함수의 prototype 프로퍼티
즉, 생성자 함수를 통해 생성된 객체의 <> 에 생성자 함수의 prototype 프로퍼티를 할당한다
자바스크립트에서 함수 호출시 각각의 상황에 따라 this 바인딩이 자동으로 된다
또한 자바스크립트는 this를 특정 객체에 명시적으로 바인딩하는 방법도 제공한다
apply()와 call()이 그것이다
이 메소드들은 모든 함수의 부모 객체인 Function.prototype 객체의 메서드이므로 모든 함수에서 호출 가능하다 function.apply(thisArg, argArray)
apply()와 call()은 기능이 같고 단지 넘겨받는 인자의 형식이 다르다
apply() 메서드를 호출하는 주체가 함수고, apply() 메서드도 this를 특정 객체에 바인딩할 뿐 결국 본질적인 기능은 함수 호출이라는 것을 기억해야 한다
Person()이라는 함수가 있고 Person.apply() 이렇게 호출한다면 이것의 기본적인 기능은 Person()함수를 호출하는 것이며, this를 명시적으로 바인딩하는 기능을 추가 제공하는 것이다
apply(thisArg, argArray)를 살펴보자
첫번째 인자 thisArg는 apply()메서드를 호출한 함수 내부에서 사용할 this에 바인딩 할 객체를 가리킨다. 즉 thisArg가 this에 명시적으로 바인딩되는 것이다
두번째 인자 argArray는 함수를 호출할 때 넘길 인자들의 배열을 가리킨다
즉, apply() 메서드를 정리하자면, 두 번째 인장니 argArray 배열을 자신을 호출한 함수의 인자로 사용하되, 이 함수 내부에서 사용될 this에 첫 번째 인자인 thisArg객체로 바인딩해서 함수를 호출하는 것이다
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
var foo = {};
Person.apply(foo, ['foo', 30, 'male']);
var bar = {};
Person.call(bar, 'bar', 25, 'female');
console.dir(foo);
console.dir(bar);
이러한 apply()나 call() 메서드는 this를 원하는 값으로 명시적으로 매핑해서 특정 함수나 메서드를 호출할 수 있는 장점이 있다
이들의 대표적인 용도가 바로 arguments 객체와 같은 유사 배열 객체에서 배열 메서드를 사용하는 경우이다
arguments 객체는 실제 배열 객체가 아니라 유사 배열 객체이므로 pop(), shift() 등의 표준 배열 메서드를 사용할 수 없다
하지만 apply()를 이용하면 가능하다
function myFunction() {
console.dir(arguments);
// arguments.shift(); --> 에러!
var args = Array.prototype.slice.apply(arguments, [1]);
console.dir(args);
}
myFunction(1, 2, 3);
자바스크립트 함수는 항상 리턴값을 반환한다
특히, return문을 사용하지 않았더라도 다음의 규칙에 의해 항상 리턴값을 전달한다
일반 함수나 메서드는 리턴값을 지정하지 않았을 경우, undefined를 리턴한다
var noReturnFunc = function () {
console.log("This function has no return statement");
};
var result = noReturnFunc();
console.log(result);
생성자 함수에서 리턴값을 지저앟지 않을 경우 생성된 객체가 리턴된다
function Person(name) {
this.name = name;
}
console.log(new Person('foo')); // Person(foo)
function Animal(name) {
this.name = name;
return new Person('bar'); // 명시적으로 다른 객체 리턴
}
console.log(new Animal('cat')); // Person(bar) --> Animal 객체가 아닌 명시적으로 리턴한 Person 객체가 리턴된다
이때, 생성자 함수에서 명시적으로 리턴한 값이 객체가 아닌 불린, 숫자, 문자열 등 기본 타입인 경우 이러한 리턴을 무시하고 this로 바인딩 된 객체가 리턴된다
출처: 송형주, 고현준 저, 『인사이드 자바스크립트』, 한빛미디어(2014)