poiemaweb / javascript스터디 5

김정빈·2021년 9월 19일
0

스터디

목록 보기
5/8

1. 보다 안정적인 자바스크립트 개발 환경을 위한 Strict mode

strict mode란?

  • strict mode는 자바스크립트 언어의 문법을 보다 엄격히 적용하여 기존에는 무시되던 오류를 발생시킬 가능성이 높거나 자바스크립트 엔진의 최적화 작업에 문제를 일으킬 수 있는 코드에 대해 명시적인 에러를 발생시킨다. ES5부터 strict mode가 추가되었다.
  • ESLint와 같은 린트 도구를 사용하여도 strict mode와 유사한 효과를 얻을 수 있다. 린트 도구는 정적 분석(static analysis) 기능을 통해 소스 코드를 실행하기 전에 소스 코드를 스캔하여 문법적 오류만이 아니라 잠재적 오류까지 찾아내고 오류의 이유를 리포팅해주는 유용한 도구이다.
  • IE9이하는 지원하지 않습니다.

strict mode의 적용

  • strict mode를 적용하려면 전역의 선두 또는 함수 몸체의 선두에 'use strict';를 추가한다.
    • 전역의 선두에 추가하면 스크립트 전체에 strict mode가 적용된다.
    • 함수 몸체의 선두에 추가하면 해당 함수와 중첩된 내부 함수에 strict mode가 적용된다.
    • 코드의 선두에 strict mode를 위치시키지 않으면 제대로 동작하지 않는다.
  • 즉시 실행함수로 감싼 스크립트 단위로 strict mode를 적용하면 좋다.
    • 전역에 strict mode를 적용하는 것을 피하자
    • 함수에 strict mode를 적용하는 것을 피하자.

암묵적 전역 변수

  • 선언하지 않은 변수를 참조하면 ReferenceError가 발생한다.
(function () {
  'use strict';

  x = 1;
  console.log(x); // ReferenceError: x is not defined
}());
  • 변수, 함수, 매개변수의 삭제
(function () {
  'use strict';

  var x = 1;
  delete x;
  // SyntaxError: Delete of an unqualified identifier in strict mode.

  function foo(a) {
    delete a;
    // SyntaxError: Delete of an unqualified identifier in strict mode.
  }
  delete foo;
  // SyntaxError: Delete of an unqualified identifier in strict mode.
}());
  • 매개변수 이름의 중복
(function () {
  'use strict';

  //SyntaxError: Duplicate parameter name not allowed in this context
  function foo(x, x) {
    return x + x;
  }
  console.log(foo(1, 2));
}());
  • with 문의 사용

    (function () {
      'use strict';
    
      // SyntaxError: Strict mode code may not include a with statement
      with({ x: 1 }) {
        console.log(x);
      }
    }());
    • with는 뭐하는 함수일까?

      • with의 매개변수로 주어진 객체의 프로퍼티를 프로퍼티 이름만으로 접근 가능하게 하는 함수입니다.
        with (document.getElementById("myDiv").style) { 
        background = "yellow"; 
        color = "red"; 
        border = "1px solid black"; 
      } 
      
      // 또는 
      
      var r = 10, a, x, y; 
        with (Math) { 
        a = PI * r * r; 
        x = r * cos(PI); 
        y = r * sin(PI / 2); 
      }
    • with를 사용하면 왜 안될까?

      • with를 통해 생겨나는 모호성
        • with문 안의 value가 doSomething매개변수의 value인지 obj안의 property value인지 알 수 없다.
      function doSomething(value, obj) { 
        with (obj) { 
          value = "which scope is this?"; 
          console.log(value); 
        } 
      }
  • 일반 함수의 this

    • strict mode 에서 함수를 일반 함수로서 호출하면 this에 undefined가 바인딩된다. 생성자 함수가 아닌 일반 함수 내부에서는 this를 사용할 필요가 없기 때문이다. 이때 에러는 발생하지 않는다.
    (function () {
      'use strict';
    
      function foo() {
        console.log(this); // undefined
      }
      foo();
    
      function Foo() {
        console.log(this); // Foo
      }
      new Foo();
    }());
    ``~

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

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

  console.log(arguments);
  console.log(this); //크롬브라우저에서 실행 시 window

  return number * number;
}

square(2);
  • Java에서의 this는 인스턴스 자신(self)을 가리키는 참조변수이다.

함수 호출 방식과 this 바인딩

자바스크립트의 경우 함수 호출 방식에 의해 this에 바인딩할 어떤 객체가 동적으로 결정된다.

  • 함수 호출

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

      // in browser console
      this === window // true
      
      // in Terminal
      node
      this === global // true
    • 전역객체는 전역 스코프(Global Scope)를 갖는 전역변수(Global variable)를 프로퍼티로 소유한다. 글로벌 영역에 선언한 함수는 전역객체의 프로퍼티로 접근할 수 있는 전역 변수의 메소드이다.

    • 기본적으로 this는 전역객체(Global object)에 바인딩된다. 전역함수는 물론이고 심지어 내부함수의 경우도 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() {
        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();
    • 콜백함수의 경우에도 this는 전역객체에 바인딩된다.
    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();
    • 내부함수는 일반 함수, 메소드, 콜백함수 어디에서 선언되었든 관게없이 this는 전역객체를 바인딩한다. 더글라스 크락포드는 “이것은 설계 단계의 결함으로 메소드가 내부함수를 사용하여 자신의 작업을 돕게 할 수 없다는 것을 의미한다” 라고 말한다.
    • 자바스크립트는 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();
  • 메소드 호출

    • 함수가 객체의 프로퍼티 값이면 메소드로서 호출된다. 이때 메소드 내부의 this는 해당 메소드를 소유한 객체, 즉 해당 메소드를 호출한 객체에 바인딩된다. 프로토타입 메소드도 마찬가지이다.
    var obj1 = {
      name: 'Lee',
      sayName: function() {
        console.log(this.name);
      }
    }
    
    var obj2 = {
      name: 'Kim'
    }
    
    obj2.sayName = obj1.sayName;
    
    obj1.sayName();
    obj2.sayName();
  • 생성자 함수 호출

    • 생성자 함수 동작 방식
        1. 빈 객체 생성 및 this 바인딩
          생성자 함수의 코드가 실행되기 전 빈 객체가 생성된다. 이 빈 객체가 생성자 함수가 새로 생성하는 객체이다. 이후 생성자 함수 내에서 사용되는 this는 이 빈 객체를 가리킨다. 그리고 생성된 빈 객체는 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정한다.
        1. this를 통한 프로퍼티 생성
          생성된 빈 객체에 this를 사용하여 동적으로 프로퍼티나 메소드를 생성할 수 있다.
        1. 생성된 객체 반환
          반환문이 없는 경우, this에 바인딩된 새로 생성한 객체가 반환된다. 명시적으로 this를 반환하여도 결과는 같다. 반환문이 this가 아닌 다른 객체를 명시적으로 반환하는 경우, this가 아닌 해당 객체가 반환된다. 이때 this를 반환하지 않은 함수는 생성자 함수로서의 역할을 수행하지 못한다. 따라서 생성자 함수는 반환문을 명시적으로 사용하지 않는다.
    function Person(name) {
      // 생성자 함수 코드 실행 전 -------- 1
      this.name = name;  // --------- 2
      // 생성된 함수 반환 -------------- 3
    }
    
    var me = new Person('Lee');
    console.log(me.name);
    • 생성자 함수에 new 연산자를 붙이지 않고 호출 하는 것을 방지하기 위한 패턴

      • Scope-Save Constructor

      • callee는 arguments 객체의 프로퍼티로서 함수 바디 내에서 현재 실행 중인 함수를 참조할 때 사용한다. 다시 말해, 함수 바디 내에서 현재 실행 중인 함수의 이름을 반환한다.

        // Scope-Safe Constructor Pattern
        function A(arg) {
          // 생성자 함수가 new 연산자와 함께 호출되면 함수의 선두에서 빈객체를 생성하고 this에 바인딩한다.
        
        /*
        this가 호출된 함수(arguments.callee, 본 예제의 경우 A)의 인스턴스가 아니면 new 연산자를 사용하지 않은 것이므로 이 경우 new와 함께 생성자 함수를 호출하여 인스턴스를 반환한다.
        arguments.callee는 호출된 함수의 이름을 나타낸다. 이 예제의 경우 A로 표기하여도 문제없이 동작하지만 특정함수의 이름과 의존성을 없애기 위해서 arguments.callee를 사용하는 것이 좋다.
        */
        if (!(this instanceof arguments.callee)) {
         return new arguments.callee(arg);
        }
        
        // 프로퍼티 생성과 값의 할당
        this.value = arg ? arg : 0;
        }
        
        var a = new A(100);
        var b = A(10);
        
        console.log(a.value);
        console.log(b.value);
  • apply/call/bind 호출

    • this를 특정 객체에 명시적으로 바인딩하는 방법도 제공된다.

    • Function.prototype.apply

      func.apply(thisArg, [argsArray])
      
      // thisArg: 함수 내부의 this에 바인딩할 객체
      // argsArray: 함수에 전달할 argument의 배열
      • apply() 메소드를 호출하는 주체는 함수이며 apply()메소드의 본질적인 기능은 함수 호출이 다.

      • apply() 메소드의 대표적인 용도는 arguments 객체와 같은 유사 배열 객체에 배열 메소드를 사용하는 경우이다.

        function convertArgsToArray() {
          console.log(arguments);
        
          // arguments 객체를 배열로 변환
          // slice: 배열의 특정 부분에 대한 복사본을 생성한다.
          var arr = Array.prototype.slice.apply(arguments); // arguments.slice
          // var arr = [].slice.apply(arguments);
        
          console.log(arr);
          return arr;
        }
        
        convertArgsToArray(1, 2, 3);
    • Function.prototype.call

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

        	   ```

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

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

      • apply()와 call() 메소드는 콜백 함수의 this를 위해서 사용되기도 한다.

    • Function.prototype.bind

      • ES5에 추가되었다.

      • 함수에 인자로 전달한 this가 바인딩된 새로운 함수를 리턴한다.

        function Person(name) {
          this.name = name;
        }
        
        Person.prototype.doSomething = function (callback) {
          if (typeof callback == 'function') {
            // callback.call(this);
            // this가 바인딩된 새로운 함수를 호출
            callback.bind(this)();
          }
        };
        
        function foo() {
          console.log('#', this.name);
        }
        
        var p = new Person('Lee');
        p.doSomething(foo);  // 'Lee'

    3. 실행컨텍스트와 자바스크립트의 동작 원리

    실행 컨텍스트(Execution Context)

    • 실행 가능한 코드가 실행되기 위해 필요한 환경

    • 실행 가능한 코드

      • 전역 코드 : 전역 영역에 존재하는 코드
      • Eval 코드 : eval 함수로 실행되는 코드
      • 함수 코드 : 함수 내에 존재하는 코드
    • 실행에 필요한 여러가지 정보란 아래와 같은 것들이 있다.

      • 변수 : 전역변수, 지역변수, 매개변수, 객체의 프로퍼티
      • 함수 선언
      • 변수의 유효범위(Scope)
      • this
    • 실행에 필요한 정보를 형상화하고 구분하기 위해 자바스크립트 엔진은 실행 컨텍스트를 물리적 객체 의 형태로 관리한다.

      var x = 'xxx';
      
      function foo () {
        var y = 'yyy';
      
        function bar () {
          var z = 'zzz';
          console.log(x + y + z);
        }
        bar();
      }
      
      foo();

      • 위 코드를 실행하면 위 그림과 같이 실행 컨텍스트 스택(Stack)이 생성하고 소멸한다. 현재 실행 중인 컨텍스트에서 이 컨텍스트와 관련없는 코드(예를 들어 다른 함수)가 실행되면 새로운 컨텍스트가 생성된다. 이 컨텍스트는 스택에 쌓이게 되고 컨트롤(제어권)이 이동한다.
      • 컨트롤이 실행 가능한 코드로 이동하면 논리적 스택 구조를 가지는 새로운 실행 컨텍스트 스택이 생성된다.
      • 전역 코드(Global code)로 컨트롤이 진입하면 전역 실행 컨텍스트가 생성되고
        실행 컨텍스트 스택에 쌓인다. 전역 실행 컨텍스트는 애플리케이션이 종료될 때
        (웹 페이지에서 나가거나 브라우저를 닫을 때)까지 유지된다.
      • 함수를 호출하면 해당 함수의 실행 컨텍스트가 생성되며 직전에 실행된 코드 블록의
        실행 컨텍스트 위에 쌓인다.
      • 함수 실행이 끝나면 해당 함수의 실행 컨텍스트를 파기하고 직전의 실행 컨텍스트에 컨트롤을 반환한다.

실행 컨텍스트의 3가지 객체

실행 컨텍스트는 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이지만 물리적으로는 객체의 형태를 가지며 3가지 프로퍼티를 소유한다.

  • Variable Object (VO / 변수객체)
    • 실행 컨텍스트가 생성되면 자바스크립트 엔진은 실행에 필요한 여러 정보들을 담을 객체를 생성한다. 이를 Variable Object(VO / 변수 객체)라고 한다.
    • Variable Object는 코드가 실행될 때 엔진에 의해 참조되며 코드에서는 접근할 수 없다.
    • Variable Object가 담는 정보
      • 변수
      • 매개변수(parameter)와 인수 정보(arguments)
      • 함수 선언(함수 표현식은 제외)
    • Varicable Object가 가리키는 객체
      • 전역 컨텍스트
        • Variable Object는 전역 객체(Global Object / GO)를 가리킨다.
        • 전역 객체는 전역에 선언된 전역 변수와 전역 함수를 프로퍼티로 소유한다.
      • 함수 컨텍스트
        • Variable Object는 Activation Object(AO / 활성 객체)를 가리킨다.
        • 매개변수와 인수들의 정보를 배열의 형태로 담고 있는 객체인 arguments object가
          추가된다.
  • Scope Chain (SC)
    • 스코프 체인(Scope Chain)은 일종의 리스트
    • 전역 객체와 중첩된 함수의 스코프의 레퍼런스를 차례로 저장하고 있다.
    • 스코프 체인은 해당 전역 또는 함수가 참조할 수 있는 변수, 함수 선언 등의 정보를 담고 있는 전역 객체(GO) 또는 활성 객체(AO)의 리스트를 가리킨다.
    • 스코프 체인은 식별자 중에서 객체(전역 객체 제외)의 프로퍼티가 아닌 식별자, 즉 변수를 검색하는 메커니즘이다.
    • 엔진은 스코프 체인을 통해 렉시컬 스코프를 파악한다.
  • this value
    • this 프로퍼티에는 this 값이 할당된다. this에 할당되는 값은 함수 호출 패턴에 의해 결정된다.

실행 컨텍스트의 생성 과정

  • 전역 코드에의 진입

    • 전역 객체(Global Object)가 생성된다.

      • 전역 객체는 단일 사본으로 존재하며 이 객체의 프로퍼티는 코드의 어떠한 곳에서도 접근할 수 있다.
      • 초기 상태의 전역 객체에는 빌트인 객체(Math, String, Array 등)와 BOM, DOM이 설정되어 있다.
      • 전역 코드로 컨트롤이 진입하면 전역 실행 컨텍스트가 생성되고 실행 컨텍스트 스택에 쌓인다.
    • 스코프 체인의 생성과 초기화

      • 스코프 체인은 전역 객체의 레퍼런스를 포함하는 리스트가 된다.
    • Variable Instantiation(변수 객체화) 실행

      • Variable Object에 변수, 매개변수와 인수 정보(arguments), 함수 선언을 Variable Object에 추가하여 객체화한다.
      • Varaible Instantiation 순서
          1. (Function Code인 경우) 매개변수(parameter)가 Variable Object의 프로퍼티로, 인수(argument)가 값으로 설정된다.
          1. 대상 코드 내의 함수 선언(함수 표현식 제외)을 대상으로 함수명이 Variable Object의 프로퍼티로, 생성된 함수 객체가 값으로 설정된다.(함수 호이스팅)
          1. 대상 코드 내의 변수 선언을 대상으로 변수명이 Variable Object의 프로퍼티로, undefined가 값으로 설정된다.(변수 호이스팅)
    • 함수의 선언 처리

      • 선언된 함수명이 Variable Object(전역 코드인 경우 Global Object)의 프로퍼티로
      • 생성된 함수 객체가 값으로 설정된다.
      • 생성된 함수 객체는 [[Scopes]] 프로퍼티를 가지게 된다. [[Scopes]] 프로퍼티는
        함수 객체만이 소유하는 내부 프로퍼티(Internal Property)로서 함수 객체가 실행되는
        환경을 가리킨다. 따라서 현재 실행 컨텍스트의 스코프 체인이 참조하고 있는 객체를 값으로
        설정한다.
      • 클로저
        내부 함수의 [[Scopes]] 프로퍼티는 자신의 실행 환경(Lexical Enviroment)과 자신을 포함하는 외부 함수의 실행 환경과 전역 객체를 가리키는데 이때 자신을 포함하는 외부 함수의
        실행 컨텍스트가 소멸하여도 [[Scopes]] 프로퍼티가 가리키는 외부 함수의 실행 환경(Activation object)은 소멸하지 않고 참조할 수 있다.
    • 변수의 선언 처리

      • 선언 단계(Declaration phase)
        변수 객체(Variable Object)에 변수를 등록한다. 이 변수 객체는 스코프가 참조할 수 있는 대상이 된다.
      • 초기화 단계(Initialization phase)
        변수 객체(Variable Object)에 등록된 변수를 메모리에 할당한다.
        이 단계에서 변수는 undefined로 초기화된다.
      • 할당 단계(Assignment phase)
        undefined로 초기화된 변수에 실제값을 할당한다.
      • var 키워드로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다.
        이러한 현상을 변수 호이스팅(Variable Hoisting)이라한다.
    • this value 결정
      this value가 결정되기 이전에 this는 전역 객체를 가리키고 있다가 함수 호출 패턴에 의해 this에 할당되는 값이 결정된다. 전역 코드의 경우, this는 전역 객체를 가리킨다.

  • 전역 코드의 실행

    var x = 'xxx';
    
    function foo () {
      var y = 'yyy';
    
      function bar () {
        var z = 'zzz';
        console.log(x + y + z);
      }
      bar();
    }
    
    foo();

    위 예제를 보며 전역 코드의 실행을 살펴보자

    • 변수 값의 할당
      전역 변수 x에 문자열 ‘xxx’를 할당할 때, 현재 실행 컨텍스트의 스코프 체인이 참조하고 있는 Variable Object를 선두(0)부터 검색하여 변수명에 해당하는 프로퍼티가 발견되면 값(‘xxx’)을 할당한다.

    • 함수 foo의 실행

      • 새로운 함수 foo의 실행 컨텍스트가 생성된다.
      • 함수 foo의 실행 컨텍스트로 컨트롤이 이동하면 전역 코드의 경우와 마찬가지로
        스코프 체인의 생성과 초기화, Variable Instantiation 실행, this value 결정이 순차적으로 실행된다.
    • 스코프 체인의 생성과 초기화

      • Activation Object에 대한 레퍼런스를 스코프 체인의 선두에 설정

      • Activation Object는 우선 arguments 프로퍼티의 초기화를 실행하고 그 후, Variable Instantiation가 실행된다.

      • Activation Object는 스펙 상의 개념으로 프로그램이 Activation Object에 직접 접근할 수 없다. (Activation Object의 프로퍼티로의 접근은 가능하다)

      • 그 후, Caller(전역 컨텍스트)의 Scope Chain이 참조하고 있는 객체가 스코프 체인에 push된다. 이 경우 함수 foo를 실행한 직후 실행 컨텍스트의 스코프 체인은 Activation Object(함수 foo의 실행으로 만들어진 AO-1)과 전역 객체를 순차적으로 참조하게 된다.

    • Variable Instantiation 실행
      Function Code의 경우, 스코프 체인의 생성과 초기화에서 생성된 Activation Object를 Variable Object로서 Variable Instantiation가 실행된다. 이것을 제외하면 전역 코드의 경우와 같은 처리가 실행된다.

    • this value 결정
      변수 선언 처리가 끝나면 다음은 this value가 결정된다. this에 할당되는 값은 함수 호출 패턴에 의해 결정된다.

    • foo 함수 코드의 실행
      이제 함수 foo의 코드 블록 내 구문이 실행된다. 위 예제를 보면 변수 y에 문자열 ‘yyy’의 할당과 함수 bar가 실행된다.

    • 변수 값의 할당
      지역 변수 y에 문자열 ‘yyy’를 할당할 때, 현재 실행 컨텍스트의 스코프 체인이 참조하고 있는 Variable Object를 선두(0)부터 검색하여 변수명에 해당하는 프로퍼티가 발견되면 값 ‘yyy’를 할당한다.

    • 함수 bar의 실행
      함수 bar가 실행되기 시작하면 새로운 실행 컨텍스트이 생성된다. 이전 함수 foo의 실행 과정과 동일하게 1. 스코프 체인의 생성과 초기화, 2. Variable Instantiation 실행, 3. this value 결정이 순차적으로 실행된다.

0개의 댓글