모던 자바스크립트 (12)

Daon·2023년 4월 11일
0

모던자바스크립트

목록 보기
12/12
post-thumbnail

strict mode란?

function foo() {
  x = 10;
}

console.log(x); // ?

foo함수 내에서 선언하지 않은 변수 x에 값10을 할당한다.
이때 자바스크립트 엔진은 변수 x가 어디에서 선언 되었는지 스코프 체인을 통해 검색한다.
위와 같은 경우는 스코프체인이 전역까지 올라가서 결국 x는 암묵적 전역 변수가 된다.

암묵적 전역변수 같은 경우 오류를 발생시키는 원인이 될 가능성이 크다.
이러한 오류를 잡기 위해 es5부터 strict mode가 추가되었다.

2. strict mode의 적용

// 전역에 strict mode의 적용하는 것은 바람직하지 않다!
'use strict';

function foo() {
  x = 10; // ReferenceError: x is not defined
}
foo();

전역에 추가하면 스크립트 전체에 적용되고
따로 함수 내부에 적용하면 함수 내부에서만 적용된다.

3. 전역에 strict mode를 적용하는 것은 피하자.

<!DOCTYPE html>
<html>
<body>
  <script>
    'use strict';
  </script>
  <script>
    x = 1; // 에러가 발생하지 않는다.
    console.log(x); // 1
  </script>
  <script>
    'use strict';

    y = 1; // ReferenceError: y is not defined
    console.log(y);
  </script>
</body>
</html>

만약 외부 서드 파티 라이브러리를 사용할 경우, 라이브러리가 non-strict mode일 경우도 있기 때문에 전역적으로 strict mode를 선언하는 것은 바람직하지 않다.

이러한 경우 아래 예제 처럼 즉시 실행 함수로 스크립트 전체를 감싸서 스코프를 구분하고 strict mode를 적용한다.

// 즉시실행 함수에 strict mode 적용
(function () {
  'use strict';

  // Do something...
}());

4. 함수 단위로 strict mode를 적용하는 것도 피하자

strict mode가 적용된 함수가 참조할 함수 외부의 컨텍스트에 strict mode를 적용하지 않는다면 이 또한 문제가 발생할 수 있다.

(function () {
 // non-strict mode
 var lеt = 10; // 에러가 발생하지 않는다.

 function foo() {
   'use strict';

   let = 20; // SyntaxError: Unexpected strict mode reserved word
 }
 foo();
}());

따라서 즉시실행함수로 감싼 스크립트 단위로 적용하는 것이 바람직하다.

함수 호출 방식에 의해 결정되는 this

자바스크립트의 함수는 호출 될 때, 매개변수로 전단되는 인자값 이외에, arguments객체와 this를 암묵적으로 전달받는다.

function square(number) {

  console.log(arguments);
  console.log(this);

  return number * number;
}

square(2);

자바스크립트의 this keyword는 java와 같은 익숙한 언어의 개념과 달라 혼란을 준다.

자바스크립트 같은 경우 java와 같이 this에 바인딩 되는 객체가 한가지가 아니라
해당 함수 호출 방식에 따라 this에 바인딩되는 객체가 달라진다.

함수 호출 방식과 this 바인딩

JS의 경우 함수 호출 방식에 의해 this에 바인딩할 객체가 동적으로 결정된다.

함수의 상위 스코프를 결정하는 방식인 렉시컬 스코프는 함수를 선언할 떄 결정된다.
this 바인딩과 혼동하지 않도록 주의 해야 한다.

함수의 호출 방식
1. 함수 호출
2. 메소드 호출
3. 생성자 함수 호출
4. apply/call/bind 호출

var foo = function () {
  console.dir(this);
};

// 1. 함수 호출
foo(); // window
// window.foo();

// 2. 메소드 호출
var obj = { foo: foo };
obj.foo(); // obj

// 3. 생성자 함수 호출
var instance = new foo(); // instance

// 4. apply/call/bind 호출
var bar = { name: 'bar' };
foo.call(bar);   // bar
foo.apply(bar);  // bar
foo.bind(bar)(); // bar

1. 함수 호출

전역객체는 모든 객체의 유일한 최상위 객체를 의미하며 일반적으로 Browser-side에서는 window, Server-side에서는 global객체를 의미한다.

전역객체는 전역 스코프를 갖는 전역변수를 프로퍼티로 소유한다.
글로벌 영역에 선언한 함수는 전역객체의 프로퍼티로 접근할 수 있는 전역 변수 메소드이다.

var ga = 'Global variable';

console.log(ga);
console.log(window.ga);

function foo() {
  console.log('invoked!');
}
window.foo();

기본적으로 this는 전역객체에 바인딩된다. 전역함수는 물론이고 심지어 내부 함수까지도 this는 외부함수가 아닌 전역객체에 바인딩된다.

function foo() {
  console.log("foo's this: ",  this);  // window
  function bar() {
    console.log("bar's this: ", this); // window
  }
  bar();
}
foo();

내부함수는 일반 함수, 메소드, 콜백함수 어디에서 선언되었든 관게없이 this는 전역객체를 바인딩한다.

var value = 1;

var obj = {
  value: 100,
  foo: function() {
    var that = this;  // Workaround : this === obj

    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

      console.log("bar's that: ",  that); // obj
      console.log("bar's that.value: ", that.value); // 100
    }
    bar();
  }
};

obj.foo();

위의 방법 이외에도 this를 명시적으로 바인딩하는 apply/call/bind 메소드를 제공한다

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(a, b) {
      console.log("bar's this: ",  this); // obj
      console.log("bar's this.value: ", this.value); // 100
      console.log("bar's arguments: ", arguments);
    }
    bar.apply(obj, [1, 2]);
    bar.call(obj, 1, 2);
    bar.bind(obj)(1, 2);
  }
};

obj.foo();

2. 메소드 호출

함수가 객체의 프로퍼티 값이면 메소드로서 호출된다.
이 떄 this는 해당 메소드를 소유한 객체 즉 해당 메소드를 호출한 객체에 바인딩된다.

var obj1 = {
  name: 'Lee',
  sayName: function() {
    console.log(this.name);
  }
}

var obj2 = {
  name: 'Kim'
}

obj2.sayName = obj1.sayName;

obj1.sayName();
obj2.sayName();

3. 생성자 함수 호출

JS의 생성자 함수는 말 그대로 객체를 생성하는 역할을 한다.
기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.

// 생성자 함수
function Person(name) {
  this.name = name;
}

var me = new Person('Lee');
console.log(me); // Person&nbsp;{name: "Lee"}

// new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수로 동작하지 않는다.
var you = Person('Kim');
console.log(you); // undefined

3-1 생성자 함수 동작 방식

  1. 빈 객체 생성 및 this 바인딩
    • 생성자 함수의 코드가 실행되기 전 빈 객체가 생성된다. 이 빈 객체가 생성자 함수가 새로 생성하는 객체이다.
      생성자 함수 내에서 사용되는 this는 이 빈 객체를 가르킨다.
  2. this를 통한 프로퍼티 생성
    • 생성된 빈 객체에 this를 사용하여 동적으로 프로퍼티나 메소드르를 생성할 수 있다. this는 새로 생성된 객체를 가르키므로 this를 통해 생성한 프로퍼티와 메소드는느 새로 생성된 객체에 추가된다.
  3. 생성된 객체 반환
    • 반환문이 없는 경우, this에 바인딩된 새로 생성한 객체가 반환된다. 명시적으로 this를 반환하여도 결과는 같다.
    • 반환문이 this가 아닌 다른 객체를 명시적으로 반환하는 경우 해당 객체가 반환된다.
function Person(name) {
  // 생성자 함수 코드 실행 전 -------- 1
  this.name = name;  // --------- 2
  // 생성된 함수 반환 -------------- 3
}

var me = new Person('Lee');
console.log(me.name);

3-2 객체 리터럴 방식과 생성자 함수 방식의 차이

객체 리터럴 방식과 생성자 함수 방식의 차이를 보면

// 객체 리터럴 방식
var foo = {
  name: 'foo',
  gender: 'male'
}

console.dir(foo);

// 생성자 함수 방식
function Person(name, gender) {
  this.name = name;
  this.gender = gender;
}

var me  = new Person('Lee', 'male');
console.dir(me);

var you = new Person('Kim', 'female');
console.dir(you);

차이는 프로토타입 객체에 있다.

  • 객체 리터럴 방식 : 생성된 객체의 프로토타입 객체는 Object.prototype이다.
  • 생성자 함수 방식 : 생성된 객체의 프로토타입 객체는 Person.prototype이다.

3-3 생성자 함수에 new 연산자를 붙이지 않고 호출한 경우

일반함수와 생성자 함수에 특별한 형식적 차이는 없으며 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다.
일반 함수 같은 경우는 this가 전역객체에 바인딩되지만
new 연산자와 함께 생성자 함수를 호출하면 this는 생성자 함수가 암묵적으로 생성한 빈 객체에 반인딩된다.

function Person(name) {
  // new없이 호출하는 경우, 전역객체에 name 프로퍼티를 추가
  this.name = name;
};

// 일반 함수로서 호출되었기 때문에 객체를 암묵적으로 생성하여 반환하지 않는다.
// 일반 함수의 this는 전역객체를 가리킨다.
var me = Person('Lee');

console.log(me); // undefined
console.log(window.name); // Lee

4. apply/call/bind 호출

Function.prototype.apply, Function.prototype.call 메소드들은 모든 함수 객체의 프로토타입 객체인 Function.prototype 객체의 메소드이다.

func.apply(thisArg, [argsArray])

// thisArg: 함수 내부의 this에 바인딩할 객체
// argsArray: 함수에 전달할 argument의 배열

기억해야 할 것은 apply() 메소드를 호출하는 주체는 함수이며 apply() 메소드는 this를 특정 객체에 바인딩할 뿐 본질적인 기능은 함수 호출이라는 것이다.

var Person = function (name) {
  this.name = name;
};

var foo = {};

// apply 메소드는 생성자함수 Person을 호출한다. 이때 this에 객체 foo를 바인딩한다.
Person.apply(foo, ['name']);

console.log(foo); // { name: 'name' }

apply() 메소드의 대표적인 용도는 arguments 객체와 같은 유사 배열 객체에 배열 메소드를 사용하는 경우이다. arguments 객체는 배열이 아니기 때문에 slice() 같은 배열의 메소드를 사용할 수 없으나 apply() 메소드를 이용하면 가능하다.

Person.apply(foo, [1, 2, 3]);

Person.call(foo, 1, 2, 3);

call() 메소드의 경우, apply()와 기능은 같지만 apply()의 두번째 인자에서 배열 형태로 넘긴 것을 각각 하나의 인자로 넘긴다.

profile
같이 일하고싶은 그런 개발자!

0개의 댓글