JavaScript

1. this

this의 값은 실행 컨텍스트가 생성될 때(대부분의 경우 함수를 호출할 때) 정해진다.

const obj1 = {
  callThis: function() { return this; }
}

const obj2 = {
  callThis: this
}

console.log(obj1.callThis()); // obj1
console.log(obj2.callThis); // window
// 함수 console.log가 호출될 때 this의 값은 window

객체 안에 있는 함수, 즉 메서드라도 객체의 메서드로 호출하지 않는 경우에는(일반 함수로 호출) this 값이 전역이다.

const obj = {
  outer: function () {
    console.log(this);
    const inner = function () {
      console.log(this);
    };
    inner(); // this : Window
  }
};

obj.outer(); // this : obj

call()을 이용하여 this(아래 예시에서는 obj) 값을 지정하고 호출하면 this 값은 지정한 값이다.

const obj = {
  outer: function () {
    console.log(this);
    const inner = function () {
      console.log(this);
    };
    inner.call(this); // this : obj
  }
};

obj.outer(); // this : obj

bind() 역시 이용할 수 있다.

const obj = {
  outer: function () {
    console.log(this);
    const inner = function () {
      console.log(this);
    }.bind(this);
    inner(); // this : obj
  }
};

obj.outer(); // this : obj

화살표 함수의 this

화살표 함수는 실행 컨텍스트를 생성할 때 this 값이 무엇인지 정하는 - this 값을 바인딩하는 과정이 없어서, 실행 시 자바스크립트 엔진이 스코프 체인 상에서 찾을 수 있는 가장 가까운 this 값을 사용한다.

const obj = {
  outer: function () {
    console.log(this);
    const inner = () => {
      console.log(this);
    };
    inner(); // this : obj
  }
};

obj.outer(); // this : obj

setTimeout의 this

[ WindowTimers.setTimeout() this 문제 | MDN ]
'...setTimeout()에 의해 실행된 코드는 별도의 실행 컨텍스트에서 setTimeout이 호출된 함수로 호출됩니다. 호출된 함수에 대해서는 this 키워드를 설정하는 일반적인 규칙이 적용되며, this를 설정 혹은 할당하지 않은 경우, non-strict 모드에서 전역(혹은 window) 객체, strict모드에서 undefined를 기본 값으로 합니다. ..'

setTimeout()은 기본 this 값이 전역이다. 원하는 this 값을 지정하려면 함수를 함수로 감싸거나, bind()를 이용한다.

const obj1 = {
  name: 'obj1',
  func: function () {
    console.log(this.name);
  }
}

const obj2 = {
  name: 'obj2',
  func: obj1.func
}

// 문제 - this값이 전역
setTimeout(obj1.func, 100); // this: window
setTimeout(obj2.func, 200);

// 해결책 1 Wrapper Function
setTimeout(() => {obj1.func()}, 300); // this: obj1
setTimeout(() => {obj2.func()}, 400);

// 해결책 2 bind()
setTimeout(obj1.func.bind(obj1), 500); // this: obj1
setTimeout(obj2.func.bind(obj2), 600);

배열 프로토타입 메서드의 this

여러 배열 프로토타입 메서드는 this 값을 지정할 수 있다. Array.prototype.map()this 값을 지정하여 이용해 보았다.

const obj = {
  a: 10
}

const arr = [1, 2, 3, 4, 5];

const result = arr.map(function (val) { 
  return val + this.a 
}, obj); // callback의 다음 인자로 this값을 전달한다

console.log(result);
// [11, 12, 13, 14, 15]

하지만 이때 callback을 화살표 함수로 표기하면 this 값을 전달해도 인식하지 않는다.

const obj = {
  a: 10
}

const arr = [1, 2, 3, 4, 5];

// 화살표 함수를 쓸 경우 this를 obj로 인식하지 않고 전역으로 인식한다
// this(window)에 a라는 변수가 없으므로 undiefined
const result = arr.map(val => val + this.a, obj);

console.log(result);
// [NaN, NaN, NaN, NaN, NaN]

2. 유사 배열

[ Array-Like Objects and Generic Methods | Dr. Axel Rauschmayer ]
key가 0 또는 양의 정수인 프로퍼티가 존재하고 length 프로퍼티의 값이 0 또는 양의 정수인 객체를 유사 배열이라고 한다.

const obj1 = {
  0: 10,
  1: 20,
  2: 30,
  length: 3
}

Array.prototype.push.call(obj1, 40); // call()로 배열의 프로토타입 메서드 push를 사용

console.log(obj1); // 값이 배열과 비슷하게 변한다
// {0: 10, 1: 20, 2: 30, 3: 40, length: 4}

유사 배열을 배열로 바꾸는 대표적인 방법은 아래와 같다.

  1. call()로 배열의 프로토타입 메서드 slice()를 사용하기
  2. Array.from() 사용하기
// nodeList는 유사 배열
const nodeList = document.querySelectorAll('div');
const nodeArr = Array.prototype.slice.call(nodeList); // 1번
// const nodeArr = Array.from(nodeList); // 2번

console.log(nodeArr);
// 1~2번 모두 같은 결과를 가져온다
// [div, div, div]

3. EventTarget.addEventListener()

[ Overview of Events and Handlers | MDN ]
[ 이벤트 참조 | MDN ]
addEventListener()callback을 호출할 때 첫 번째 인자로 '이벤트 객체'를 전달한다.
이벤트 객체란, 특정 타입의 이벤트와 관련이 있는 객체로 type, target 등 여러 속성을 가지고 있다. 우선 속성을 자세히 들여다보기 위해 색이름을 텍스트로 표시한 리스트 엘리먼트를 만들고, 텍스트를 클릭했을 때 이벤트 객체가 무엇인지 확인하는 코드를 작성했다.

const colors = ['red', 'orange', 'yellow', 'green', 'blue'];
const ul = document.createElement('ul');

colors.forEach(function(color) {
  const li = document.createElement('li');
  li.textContent = color;
  li.addEventListener('click', function(event) {
    console.log(event.type, event.target, event.target.value); 
  });
  ul.appendChild(li);
})

document.body.appendChild(ul);
event.type : click // 이벤트 이름은 무엇인가?
event.target : <li>​red​</li>​ // 어디서 이벤트가 발생했는가?
event.target.value : 0 // 이벤트가 발생한 곳의 value값은 무엇인가?

위 코드를, 텍스트를 클릭했을 때 'your choice is (색이름)'라는 문장이 콘솔창에 뜨는 코드로 변경했다. 이때 색이름을 콘솔창에 띄우는 함수 noticeChoiceforEach()메서드의 바깥에 따로 작성하고 callback으로 사용하면, 인자로 이벤트 객체가 전달되며 'your choice is [object MouseEvent]'가 출력된다.

const colors = ['red', 'orange', 'yellow', 'green', 'blue'];
const ul = document.createElement('ul');

const noticeChoice1 = function(color) { 
  console.log(`your choice is ${color}`);
}

colors.forEach(function (color) {
  const li = document.createElement('li');
  li.textContent = color;
  li.addEventListener('click', noticeChoice);
  ul.appendChild(li);
})

document.body.appendChild(ul);

// your choice is [object MouseEvent]

코드를 어떻게 바꾸면 'your choic is red'가 출력될까?? 함수 noticeChoice에 내부함수를 만들고, 내부함수에 원하는 코드를 넣어 클로저로 이용하면 해결할 수 있다.

변경한 코드는 사용자가 엘리먼트를 클릭 시 아래 순서로 동작한다.

  1. 이벤트 객체가 noticeChoice(color)의 첫번째 인자로 전달된다.
  2. addEventListener()noticeChoice(color)(이벤트 객체) 를 실행한다.
  3. noticeChoice의 내부함수에서 전달된 이벤트 객체를 받지만, 인자를 이용하는 코드가 없기에 별도 동작도 없다.
  4. 외부함수에 전달된 인자인 color를 내부 함수가 이용해 'your choice is ${color}'를 출력한다.
const colors = ['red', 'orange', 'yellow', 'green', 'blue'];
const ul = document.createElement('ul');

const noticeChoice = function (color) {
  return function () {
    console.log(`your choice is ${color}`);
  }
}

colors.forEach(function (color) {
  const li = document.createElement('li');
  li.textContent = color;
  li.addEventListener('click', noticeChoice(color));
  ul.appendChild(li);
})

document.body.appendChild(ul);
your choice is red
your choice is orange
your choice is yellow
your choice is green
your choice is blue