- 함수 호출
- 메서드 호출
- 생성자 함수 호출
- apply / call / bind 호출
const foo = function () {
console.dir(this);
};
// 1. 함수 호출
foo();
// 2. 메서드 호출
const obj = { foo: foo };
obj.foo();
// 3. 생성자 함수 호출
const instance = new foo();
// 4. apply, call, bind 호출
const bar = { name: "bar" };
foo.call(bar);
foo.apply(bar);
foo.bind(bar);
var ga = 'Global variable';
console.log(ga); // Global variable
console.log(window.ga); // Global variable
function foo() {
console.log('invoked!');
}
window.foo(); // invoked!
const constVar = 'Const Global Varable';
console.log(window.constVar); // undefined
const funcTest = function(){
console.log('function invoked!');
}
window.funcTest(); // VM3802:1 Uncaught TypeError: window.funcTest is not a function
window.funcTest // undefined
function foo() {
console.log("foo's this: ", this); // window
function bar() {
console.log("bar's this: ", this); // window
}
bar();
}
foo();
var value = 1;
var obj = {
value: 100,
foo: function() {
console.log("foo's this: ", this); // obj
console.log("foo's this.value: ", this.value); // 100
function bar() {
console.log("bar's this: ", this); // window
console.log("bar's this.value: ", this.value); // 1
}
bar();
}
};
obj.foo();
var value = 1;
var obj = {
value: 100,
foo: function() {
setTimeout(function() {
console.log("callback's this: ", this); // window
console.log("callback's this.value: ", this.value); // 1
}, 100);
}
};
obj.foo();
var value = 1;
var obj = {
value: 100,
foo: function() {
setTimeout(() => {
console.log("callback's this: ", this); // obj
console.log("callback's this.value: ", this.value); // 100
}, 100);
}
};
obj.foo();
apply
, call
, bind
메서드를 사용하는 것이다.var value = 'global value';
undefined
var obj = {
value: 'obj value',
foo: function() {
console.log("foo's this: ", this); // obj
console.log("foo's this.value: ", this.value); // obj value
function bar(a, b) {
console.log("bar's this: ", this); // obj
console.log("bar's this.value: ", this.value); // obj value
console.log("bar's arguments: ", arguments); // Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
bar.apply(obj, [1, 2]);
bar.call(obj, 1, 2);
bar.bind(obj)(1, 2);
}
};
const bear = {
name: "bear",
sayName: function () {
console.log(this.name);
},
};
const tiger = {
name: "tiger",
};
tiger.sayName = bear.sayName;
bear.sayName(); // bear
tiger.sayName(); // tiger
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
console.log(this);
return this.name;
};
Person.prototype.name = "Kim";
console.log(Person.prototype.getName()); // Kim
const me = new Person("Lee");
console.log(me.getName()); // Lee
new
연산자를 붙여서 호출하면, 해당 함수는 생성자 함수로 동작한다.new 연산자로 생성자 함수 호출 시 다음과 같은 순서로 동작한다.
- 1. 빈 객체 생성 및 this 바인딩
- 빈 객체가 생성되고, 생성자 함수 내부의 this는 이 빈 객체를 가리킨다.
- 객체의 __proto__
는 자신의 프로토타입 객체로 생성자 함수의 prototype object를 가리킨다. 이 객체는 생성자 함수의 prototype 프로퍼티가 가리키고 있다.
- 2. this를 통한 프로퍼티 생성
- 빈 객체에 동적으로 프로퍼티나 메서드를 생성한다. 이 때, this는 새로 생성된 객체를 가리키므로, this를 통해 생성한 프로퍼티와 메서드는 새로 생성된 객체에 추가된다.
- 3. 생성된 객체 반환
- 반환문이 없는 경우, this에 바인딩된 새로 생성한 객체가 반환된다. 명시적으로 this를 반환한 것과 같은 결과이다.
```javascript
function Food(name) {
this.name = name;
return this; // 명시적으로 this를 반환하지 않아도, 새로 생성된 객체가 반환된다.
}
const apple = new Food("apple");
console.log(apple.name); // apple
```
- 반환문이 this가 아닌 다른 객체를 명시적으로 반환한다면, this가 아닌 해당 객체가 반환된다. 해당 함수는 생성자 함수로서의 역할을 수행하지 못한다.
```javascript
const lemon = { name: "lemon" };
function Food(name) {
this.name = name;
return lemon;
}
const apple = new Food("apple");
console.log(apple.name); // lemon;
```
// 객체 리터럴 방식
var foo = {
name: 'foo',
gender: 'male'
}
console.dir(foo);
// Object
// gender: "male"
// name: "foo"
// __proto__: Object
// 생성자 함수 방식
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
var me = new Person('Lee', 'male');
console.dir(me);
// Person
// gender: "male"
// name: "Lee"
// __proto__: Object
생성자 함수에 new를 붙이지 않고 호출할 경우, 일반 함수를 호출한 것이기에, this를 반환하지 않는다. 이 때 함수 내의 this는 전역 객체를 가리키게 된다.
function Seat(number) {
this.number = number;
}
// new 키워드 없이 호출할 경우, this는 전역 객체에 바인딩된다.
// 또한, 반환문이 없으므로 undefined를 반환하게 된다.
const myseat = Seat(1);
console.log(myseat);
console.log(window.number);
이처럼 생성자 함수를 new 키워드 없이 호출하게 되면, new 키워드로 생성한 것과 다른 결과를 초래한다. 따라서 그러한 실수를 방지하고자 Scope-Safe Constructor로 생성자 함수를 만들게 된다. 이 패턴은 대부분의 라이브러리에서 광범위하게 사용된다.
function Ssc(arg) {
if (!(this instanceof arguments.callee)) {
return new arguments.callee(arg);
}
this.value = arg ? arg : 0;
}
const ssc1 = new Ssc(10);
const ssc2 = Ssc(30);
console.log(ssc1.value); // 10
console.log(ssc2.value); // 30
arguments.callee는 함수 바디 내에서 현재 실행중인 함수의 이름이다. this가 현재 호출된 함수의 인스턴스가 아니라면, new 키워드 없이 호출된 함수이다. 따라서 new 키워드와 함께 생성자 함수를 호출하여, 새로운 객체에 this가 바인딩된 객체를 반환하도록 한다.
func.apply(thisArg, [argsArray])
func.call(thisArg[, arg1[, arg2[, ...]]])
func.bind(thisArg[, arg1[, arg2[, ...]]])
function Product(name) {
this.name = name;
}
const mouse = {};
const monitor = {};
const keyboard = {};
Product.call(mouse, "mouse");
Product.apply(monitor, ["monitor"]);
Product.bind(keyboard)("keyboard");
console.log(mouse); // { name: 'mouse' }
console.log(monitor); // { name: 'monitor' }
console.log(keyboard); // { name: 'keyboard' }
function Relation(relation) {
this.relation = relation;
}
Relation.prototype.func = function (callback) {
if (typeof callback === "function") {
// (A)
callback(); // undefined
}
};
const brother = new Relation("brother");
function log() {
// (B)
console.log(this.relation);
}
brother.func(log);
(A)는 프로토타입 메서드 내부이므로 this가 해당 메서드를 호출한 인스턴스이지만, (B)는 일반함수이므로 (B)에서의 this는 전역 객체이다. 전역 객체에 relation은 없으므로, undefined를 반환하게 된다.
이렇게 콜백 함수의 this에 콜백함수를 호출한 함수의 this를 바인딩 시켜주어서 의도한대로 동작할 수 있도록 한다.
function Relation(relation) {
this.relation = relation;
}
Relation.prototype.func = function (callback) {
if (typeof callback === "function") {
callback.bind(this)(); // brother
}
};
const brother = new Relation("brother");
function log() {
console.log(this.relation);
}
brother.func(log);