1.
//error 발생, 원인은 ? function Cat(name, age){ this.name = name; this.age = age; } const tabbt1 = Cat('nana', 7); console.log(tabby1.name);
JS는 this가 일정하지 않다.(동적으로 결합) ⇒ 선언한 곳이 아닌 호출한 곳을 가리킨다.
- [문제1] 객체 인스턴스를 생성하지 않고, 그러니까 new 생성자를 사용하지 않고 함수를 호출하면 this가 window를 가리키게 됨.
- window를 가리키면, Cat 함수는 반환 값이 없는 이상 tabbit1은 undefined가 됨. 그리고 undefined의 name을 참조하니 error 발생
- [해결]
new.target
으로 함수 또는 생성자가 new 연산자를 사용하여 호출됐는지를 감지할 수 있음- 인스턴스화된 생성자 및 함수에서,
new.target
은 생성자 또는 함수 참조를 반환. - 일반 함수 호출에서는,
undefined
을 반환 new.target
은new
에 의해 호출된 생성자(여기서는 생성자 함수)를 가리킴.- new.target은 new 라는 객체의 프로퍼티가 아님.
- 함수 생성 시 생성되는 것으로 new.target 자체가 내부에서만 작동하는 하나의 변수
new Cat()으로 객체를 생성하면, 함수가 return하는게 없어도 객체를 반환, this는 그 생성된 객체가 되어 변수에 접근해도 입력을 넣어준대로 올바른 값이 나옴
- 객체 생성 방법
- 객체 초기자 사용
- const a = {} 형식
- key : 식별자, 속성이름, value: 속성 이름에 할당할 표현식
- 생성자 함수 사용 (위 코드와 같이)
- 생성자 함수를 작성해서 객체 타입을 정의하고(내부에는 this 사용)
- 자신을 프로토타입으로 지정하고 그 객체를 리턴
new
연산자를 사용해 객체 인스턴스를 생성- 일반 함수와 차별점을 두기 위해 함수 이름의 첫글자를 대문자로 권고
- Object.create 메서드 사용
- 2에서 new 대신 Object.create(객체 이름)을 사용
- 1처럼 객체가 선언되어도, 즉 생성자 함수 정의가 없어도 객체의 프로토타입을 지정할 수 있다
2.
(function(name) { console.log(`hello ${name}) })('roto')
- IIFE(Immediately-invoked function expression: 즉시 작동하는 함수식)
- ()로 함수를 감싸서 실행하는 문법
- 호출하지 않아도 바로 실행됨
- IIFE안에 사용된 변수, 함수들은 모두 블럭 바깥에 영향을 줄 수 없다, 밖에서 호출도 불가
- 즉 블록 안에서만 사용 가능하여 다른 코드와 변수나 함수명 등이 충돌하지 않도록 예방이 가능
- window를 침범하지 않음, 즉 전역을 오염시키지 않음
- 변수에 IIFE를 할당하면 함수의 리턴 값이 할당된다. (일반 함수는 함수 자체가 변수에 할당)
3.
//앞의 변수가 undefined로 출력, 원인은 ? var idiots = { name: 'idiots', genre: 'punk rock', members: { roto: { memberName: 'roto', play: function() { console.log(`band ${this.name} ${this.memberName} play start`) } } } }
- 일반 객체에서 this는 자신이 속한 객체를 가리킴
- 여기 play 값 function에서 this는 play가 속한 roto 객체를 가리킴
- 따라서 roto가 가지고 있지 않은 name은 undefined, 가지고 있는 memberName은 제대로 출력
- [해결]
${this.name} → ${idiots.name} 으로 수정
4.
//error 발생, 원인은 ? function RockBand(members) { this.members = members; this.perform = function() { setTimeout(function() { this.members.forEach(function(member) { //여기 this에서 오류! member.perform(); }) },1000) } } var theOralCigarettes = new RockBand([ { name: 'takuya', perform: function() { console.log('Sing: a e u i a e u i') } } ]); theOralCigarettes.perform();
- this가 function을 호출시킨 setTimeout의 this를 가리키게 되어, this.members는 undefined가 된다.
- 'setTimeout 함수는 명시적으로 항상 전역 객체(window)를 this 바인딩한다
- [해결 - 방법3가지]
- arrow function
- setTimeout 안의 함수를 일반 함수에서 arrow function으로 변경한다.
- arrow function
- setTieout(()⇒{})
- ES6에서 탄생한 것
- 정적(lexically)으로 결합
- 항상 상위 스코프에 bind된다.(주의! 화살표 함수는 자신의 this가 없다!)
- 현재 스코프 : setTimeout, 상위 스코프 : RockBand
- +) 객체의 값, 즉 메소드로 바로 쓸 순 없다. 반드시 외부 함수로 감싸줘야 한다. IIFE로 대응 ㄱㄴ
- bind 사용
- setTimeout(function().bind(this))
- bind 함수
this
값 및 초기 인수를 사용하여 변경한 원본 함수의 복제본. 즉 새로운 함수를 생성- 결국 함수가 가리키는 this를 명시해주어 만든 새로운 함수
- this 다음에 인자를 차례대로 주면 바인딩된 함수에 인자값을 차례로 배당
- 클로저 사용
- var that = this;를 객체안에 선언하고, setTimeout의 콜백함수에서 this대신 이 that을 써서 명시를 해준다.
- setTimeout은 webApI가 타이머를 실행시키고, 종료되면 매개변수인 콜백함수가 매크로 태스크 큐에 들어간다.
- 메서드 내부에서
this
키워드를 사용하면 객체에 접근할 수 있다 - 여기서 객체는 theOralCigarettes가 아니라 RockBand이다. 즉, this = RockBand이다.
5.
//5 number undefined turn! 이 다섯번 반복, 원인은 ? const numbers = [0,1,2,3,4] for(var i = 0; i<numbers.length; i++) { setTimeout(function() { console.log(`[${i}] number ${numbers[i]} turn!`) }, i*1000) }
답 : setTimeout의 콜백함수가 실행될 때는 이미 for문이 종료된 상태인데(콜스택도 빈 상태), 이 때 var의 호이스팅으로 i는 5번 모두 같은 i를 공유하기 때문에, 마지막 i값인 5가 되는 것.
- [해결 - 방법3가지]
- setTimeout을 function(idx){}(i)으로 감싸서, 중첩함수를 만든다.
- for문 돌 때마다 새로운 function scope 발생, 즉 외부 함수의 매개변수가 매번 달라지므로 내부함수는 마찬가지로 달라진 매개변수를 이용
- var → let 으로 변경
- 블록 스코프가 되므로 for문 돌때마다 i는 매번 달라짐
- for문 → forEach(함수)로 변경
- forEach는 for와 다르게 매번 새로운 함수를 만들기 때문에, 각자 다른 i를 이용
6.
var, let, const의 차이
var : function scope, 변수 재할당 가능 ⇒ 호이스팅 발생
let : block scope, 변수 재할당 가능
const : block scope, 변수 재할당 불가능
- var는 호이스팅 문제 때문에, ES6부터는 쓰지 않는걸 권장하고 있다.
호이스팅?
실행할 때 유효 범위의 맨 위로 선언이 끌어올려지는 것
- function scope의 특징이다.
- 즉, 함수 내에 블록에서 var로 변수 등을 선언했다고 해도, 블록 밖에 함수 범위에서 선언이 되어 지는 것이다.
- 이 때문에 값 할당 전에 호출될 수 있다(값은 undefined)
- const, let는 할당 전에 먼저 호출되면 에러가 발생
- 함수 내에 블록들끼리 값이 다 공유 가능하다
따라서 생각지도 못한 버그를 만날 수 있다.
- 변수의 호이스팅(var, const/let 키워드)
- 함수 범위 내에서 선언은 위로 올라가지만, 초기 값 할당은 할당된 그 라인에서 이루어짐. 따라서 초기화 전에 사용하면 var는 undefined 할당, const, let는
not defined
에러가 난다. - const, let은 범위 밖에 있는 변수 이름과 같은 이름으로 범위 안에 변수를 선언해도, 밖에 있는건 상관하지 않고 안에 있는 변수들만 생각한다.
- +) const, let은 블록범위가 함수 일수도, if, for 등이 다 될 수 있다.
- 함수의 호이스팅(function 키워드)
- function 함수가 맨 위로 올라가는 것
- 같은 범위 내라면, 함수 선언전에 호출해도 올바른 값이 나온다.
- but, 함수를 변수에 등록(표현식)하거나 화살표 함수는 호이스팅이 되지 않는다. (이때 선언 전에 호출하면
not defined
에러가 난다.)
- 클래스의 호이스팅(class 키워드)
- 클래스 선언후 객체 인스턴스화 해야한다.
- 선언 전에 인스턴스화 시도하면
ReferenceError : not defined
에러가 난다. - 클래스 역시 변수에 저장하고 인스턴스화 시도하면
not defined
에러가 난다.
결론 : 선언 -> 초기화 -> 사용 순서를 지키는 것이 좋다!