2019-08-17 arrow function
arrows
- 화살표 함수는 항상 익명이다. 이 함수는 메소드 함수가 아닌 곳에 가장 적합하다. 그래서 생성자로 사용할 수 없다.
Syntax
//Basic Syntax
(param1, param2 ...) => { statements }
(param1, parma2...) =? expression
// 매개변수가 하나뿐인 경우 괄호는 선택사항:
(singleParam) => { statements }
singleParam => { statements }
// 매개변수가 없는 함수는 괄호가 필요:
() => { statements }
// 고급문법
바인딩 되지 않는 this
- 화살표 함수전까지는 모든 새로운 함수는 자신의 this값을 정의했다(이 함수가 생성자이면 새로운 객체, strict모드이면 undefied, 객체메서드로 호출된 경우 문맥 객체 등). 매우 불편했다.
function Person() {
// Person() 생성자는 'this'를 자신의 인스턴스로 정의
this.age = 0;
console.log('First this : ', this);
setInterval(function growUp() {
// 비엄격 모드에서, growUp() 함수는 'this'를 전역객체로 정의하고, 이는 Person()생성자에 정의된 this와 다름.
this.age++;
console.log('this.age : ', this.age);
}, 1000);
}
var p = new Person();
function Person2() {
var that = this;
that.age = 0;
console.log('Person2 first this : ', that);
setInterval(function growUp() {
// 콜백은 'that' 변수를 참조한다.
that.age++;
console.log('that age : ', that.age);
}, 1000);
}
var p2 = new Person2();
function Person3() {
this.age = 0;
setInterval(() => {
this.age++;
console.log('Person3 this.age : ', this.age);
}, 1000);
}
var p3 = new Person3();
- 위의 방법대신 바인딩한 함수는 적절한 this값이 함수 growUp에 전달될 수 있게 해줬다.
- 화살표 함수의 전역 컨텍스트에서 실행할 때, this를 새로 정의하지 않는다. 코드에서 바로 바깥함수(혹은 class)의 this값이 사용된다. this를 클로저로 처리하는 것과 같은 맥락.
- setInterval에 전달 된 함수의 this는 setInterval을 포함한 function의 this와 같다!!!!
this와 strict모드의 관계
function Person4() {
this.age = 0;
console.log(this);
var closure = '123';
console.log('Person first this : ', this.age);
setInterval(function growUp() {
this.age++;
console.log('this.age : ', this.age);
console.log('closure value : ', closure);
}, 2000);
}
var p4 = new Person4();
function PersonX() {
'use strict'
this.age = 0;
var closure2 = '123';
console.log('PersonX first this : ', this.age);
setInterval(() => {
this.age++;
console.log('PersonX this.age : ', this.age);
console.log('PersonX closure value : ', closure2);
}, 2000)
}
var px = new PersonX();
//result
Person first this : 0
PersonX first this : 0
this.age : NaN
closure value : 123
PersonX this.age : 1
PersonX closure value : 123
this.age : NaN
closure value : 123
PersonX this.age : 2
PersonX closure value : 123
call 또는 apply통한 피호출
var adder = {
base : 1,
add : function(a) {
var f = v => v + this.base;
return f(a);
},
addThruCall: function(a) {
var f = v => v + this.base;
var b = {
base : 2
};
return f.call(b, a);
}
};
console.log(adder.add(1)); // 이는 2가 콘솔에 출력될 것임
console.log(adder.addThruCall(1)); // 이도 2가 콘솔에 출력될 것임
JS call
fun.call(thisArg[, arg1[, arg2[, ...]]])
fun.call(지정할 객체명, 전달할 매개변수)
- 서로 다른 this 객체가 기존 함수를 호출 될 때 할당 될 수 있다. this는 현재 객체(호출하는 객체)를 참조한다.
- call 메소드는 다른 객체 대신 메소드를 호출하는데 사용됩니다. 이 메서드를 사용하여 함수의 this 객체를 원래 컨텍스트에서 thisObj로 지정된 새객체로 변경할 수 있습니다.
- call()를 사용하게 되면 this는 thisArg객체를 가리키게 되는데 전달하지않으면 전역객체를 가리킨다.
function Product(name, price) {
this.name = name;
this.price = price;
if (price < 0)
throw RangeError('Cannot create product ' + this.name + ' with a negative price');
}
function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
console.log(this);
}
function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
console.log(this);
}
var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);
var animals = [
{ species: 'Lion', name: 'King'},
{ species: 'Whale', name: 'Fail' }
];
for (var i = 0; i < animals.length; i++) {
(function(i) {
this.print = function() {
console.log('#' + i + ' ' + this.species + ' : ' + this.name);
}
this.print();
}).call(animals[i], i);
}
//result
Food { name: 'feta', price: 5, category: 'food' }
Toy { name: 'robot', price: 40, category: 'toy' }
#0 Lion : King
#1 Whale : Fail
call eehsms apply를 통한 피호출
var adder = {
base : 2,
add : function(a) {
var f = (v) => { return v + this.base; }
return f(a);
},
addThruCall: function(a) {
var f = (v) => { return v + this.base; }
var b = {
base : 33
};
return f.call(b, a);
// b는 현재 객체로 사용될 객체, args는 메소드에 전달할 인자의 목록
}
};
console.log(adder.add(1)); // 이는 2가 콘솔에 출력될 것임
console.log(adder.addThruCall(1)); // 이도 2가 콘솔에 출력될 것임
- 화살표 함수에서는 this가 바인딩되지 않았기 때문에, call() 또는 apply() 메서드는 인자만 전달할 수 있다. this는 무시된다.
- 왜 f.call(b, a)에서 this가 b가 아닌 2일까?????
바인딩 되지 않는 arguments
var arguments = [1, 2, 3];
var arr = () => arguments[0];
console.log('arr : ', arr());
function foo(n) {
console.log(arguments, ' in foo');
var f = () => arguments[0] + n; // foo's implicit arguments bingind. arguments[0] is n
return f();
}
console.log(foo(3));
//result
arr : 1
[Arguments] { '0': 3 } ' in foo'
6
- 왜 [Arguments]가 { ‘0’, 3 }일까???…
메소드로 사용되는 화살표 함수
'use strict';
var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log(this.i, this);
}
}
obj.b();
obj.c();
//result
undefined {}
10 { i: 10, b: [Function: b], c: [Function: c] }
- 화살표 함수는 자신의 this를 가지고(‘bind’)있지 않다.
new 연산자 사용
var Foo = () => {}
var foo = new Foo(); // TypeError
- 화살표 함수는 생성자로서 사용될 수 없다!!! new와 함께 사용하면 오류가 난다.
함수본문
- 화살표함수는 중괄호 {}로 묶이지 않은 한줄짜리 바디와 중괄호로 묶이는 block바디로 쓸 수 있다.
var func = x => x*x; // concise 바디, 암시된 return x*x;
var func = (x, y) => { return x + y }; // block바디, return이 필요
객체 리터럴 반환
- params => {object:literal}을 사용한 객체 리터럴 반환은 예상대로 작동하지 않는다!!
js this
- js는 java와 달리 함수 호출 방식에 따라 this에 바인딩되는 객체가 다르다. 대부분은 4가지 함수 호출로 this를 바인딩한다.
var foo = function () {
console.dir(this);
};
// 1. 함수 호출
foo(); // window
// chrome에서 실행을 시키면 this가 window, 콘솔에서 실행시키면 this가 global
// 2. 메소드 호출
var obj = { foo: foo };
obj.foo(); // obj
// 3. Explicit binding
var bar = { name: 'bar' };
foo.call(bar); // bar
foo.apply(bar); // bar
foo.bind(bar)(); // bar
// 4. 'new' keyword
var instance = new foo(); // instance
1) 일반함수호출
- 함수 일반 호출 this는 global object에 바인딩 된다. 전역 함수는 물론이고 내부함수의 경우도 this는 외부 함수가 아닌 전역객체에 바인딩 된다.
function foo() {
console.log('foo this : ', this);
function bar() {
console.log('bar this : ', this);
}
bar();
}
foo();
// foo this is Object[global]콘솔, 크롬에서 window
// bar this is Object[global]콘솔, 크롬에서 window
- 콜백함수의 경우에도 this는 global에 바인딩 된다.
// callback ex
var value = 1;
var obj = {
value: 100,
foo: function() {
setTimeout(function() {
console.log('callback this ', this);
console.log('callback this value : ', this.value);
}, 100);
}
};
obj.foo();
// callback this Window
// callback this.value 1
- 내부함수는 일반함수, 메소드, 콜백함수 어디에서 선언되었든 this는 전역객체를 바인딩한다.
내부함수의 this가 전역객체를 피하는 방법은 다음과 같다.
Arrow Function
// 내부함수의 this 전역객체 바인딩 피하기
function Person() {
this.age = 0;
setInterval(() => {
this.age++;
console.log(this.age);
}, 1000);
}
var p = new Person();
//result
1 2 3 4 ...
- 화살표 함수는 전역 컨텍스트에서 실행될때 this를 새로 정의하지 않는다. 대신 코드 바로 바깥함수(혹은 class)의 this가 사용된다. 이것은 this를 클로저 처리하는 것과 같다. 그러므로 setInterval에 전달된 함수의 this는 setInterval을 포함한 function의 this와 동일한 값을 가진다.
in strict mode
- ‘use strict’를 선언하면 this가 전역객체를 가리키지 않는다. ‘undefined’가 된다.
메소드호출방식 (dot donation: .방식)
- 함수가 객체의 프로퍼티 값이면 메소드로서 호출된다. 이때 메소드 내부의 this는 메소드를 소유한 객체를 가리킨다.
- 즉 메소드를 할당한 변수를 실행하면 글로벌 객체를 가리킨다. 함수가 생성된 곳이 중요한 것이 아니라, 메소드 호출로 실행된 위치와 연관된다
var obj1 = {
name: 'Lee',
sayName: function() {
console.log(this);
console.log(this.name);
}
}
var obj2 = {
name: 'Kim',
}
obj2.sayName = obj1.sayName;
obj1.sayName();
obj2.sayName();
var obj3 = {
name: 'obj3',
sayName: () => {
console.log(this);
console.log(this.name);
}
}
obj3.sayName();
// result
{ name: 'Lee', sayName: [Function: sayName] }
Lee
{ name: 'Kim', sayName: [Function: sayName] }
Kim
{}
undefined
- 위의 예시에서는 sayName()에 있는 this에서 sayName을 호출한게 obj1, obj2이기때문에 각각 this가 된다.
Explicit binding(call, apply, bind)
- call, bind, apply는 첫번째 인자값으로 this를 지정해줄 수 있다. call은 인자값을 첫번째 값 이후로 무한대로 받을 수 있다.
call
const madeline = { name : 'Madelin'};
const bruce = { name : 'Bruce' };
// 이 함수는 어떤 객체에도 연결되지 않았지만 this를 사용한다.
function greate() {
return `Hello I'm ${this.name}`;
}
let greet2 = () => {
return `Hello I'm ${this.name} greet2`;
}
console.log(greate());
console.log(greate.call(bruce));
console.log(greate.call(madeline));
console.log(greet2.call(bruce));
console.log(greet2.call(madeline));
// result
Hello I'm undefined
Hello I'm Bruce
Hello I'm Madelin
Hello I'm undefined greet2
Hello I'm undefined greet2
apply
let Person = function(name) {
this.name = name;
}
let Person2 = (name) => {
this.name = name;
}
let foo = {};
let foo2 = {};
Person.apply(foo, ['jimmy']);
console.log(foo);
Person2.apply(foo2, ['jimmy2']);
console.log(foo2);
//result
{ name: 'jimmy'}
{}
bind
var module = {
x: 42,
getX: function() {
return this.x;
}
}
var boundGetX = module.getX.bind(module);
console.log(boundGetX());
let module2 = {
x: 42,
getX: function() {
return this.x;
}
}
let boundGetX = module2.getX.bind(module2);
console.log(boundGetX());
// result
42
let boundGetX = module2.getX.bind(module2);
^
SyntaxError: Identifier 'boundGetX' has already been declared
new 생성자함수
function Person(name) {
// 생성자 함수 코드 실행 전 .. 1
this.name = name; // 2
// 생성된 함수 리턴 .. 3
}
let me = new Person('jimmy');
console.log(me.name);
// result
42
- 화살표 함수는 생성자 함수가 안된다.
this, arguments의 바인딩이 다르다
- Arrow Function은 this 바인딩을 갖지 않는다. 기존의 function에서 this의 탐색범위가 함수의 {} 안에서 찾은 반면 Arrow Function에서 this는 일반적인 인자/변수와 동일하게 취급된다. 밑의 문제점이 발생을 한다.
function objFunction() {
console.log('Inside `objFunction` :', this.foo);
return {
foo: 25,
bar: function() {
console.log('Inside `bar` : ', this.foo);
},
};
}
objFunction.call({foo: 11});
objFunction.call({foo: 13}).bar();
function objFunction2() {
console.log('Inside `objFunction`:', this.foo);
return {
foo: 25,
bar: () => console.log('Inside `bar`:', this.foo),
};
}
console.log("=======");
console.log("Using Arrow Function");
objFunction2.call({foo: 13}).bar();
//result
Inside `objFunction` : 11
Inside `objFunction` : 13
Inside `bar` : 25
=======
Using Arrow Function
Inside `objFunction`: 13
Inside `bar`: 13
- Arrow Function안의 this는 objFunction2의 this가 된다. 그리고 Arrow Function의 this는 Scope를 바꾸고 싶지 않을 때 유용하다.
- Arrow Function에서는 .bind, .call method를 사용할 수 없다.
- ES6에서 함수는 callable한것과 contructable 한것의 차이를 둔다. 만약 함수가 consturctable하다면 new로 만들고, callable하다면 일반적인 함수()로 호출한다.
- function newFunc() {}와 const newFunc = function() {}와 같은 방식으로 만든 함수는 callable하며 동시에 constructable합니다. 하지만 Arrow Function(() => {})은 callable하지만 constructable하지 않기때문에 호출만 가능합니다. ps. ES6의 class는 constructable하지만 callable하지 않습니다.
Arrow Function을 사용하면 안될때
1. 객체 메서드를 정의할때
// 안된다.
const obj1 = {
name: 'Lee',
sayName: () => { console.log(`hi, ${this.name}`); }
}
// 올바른 방법
const obj = {
name: 'Lee',
sayName() {
console.log(`Hi ${this.name}`;
}
}
2. arrow Function을 생성자로 쓰면 안된다.
Written on August 17, 2019