poiemaweb / javascript스터디 4

13916 단어 스터디스터디

1. 타입체크

자바스크립트는 동적 타입(dynamic typed) 언어이므로 변수에 어떤 값이 할당될 지 예측하기 어렵다.
이와 같은 이유로 자바스크립트는 타입 체크가 필요하다.

typeof

  • 타입 연산자(Type Operator) typeof는 피연산자의 데이터 타입을 문자열로 반환한다.
  • null을 제외환 원시 타입은 구별을 하지만 그 외의 경우 function을 제외하고 구분을 못 한다.
 typeof '';              // string
 typeof 1;               // number
 typeof NaN;             // number
 typeof true;            // boolean
 typeof [];              // object
 typeof {};              // object
 typeof new String();    // object
 typeof new Date();      // object
 typeof /test/gi;        // object
 typeof function () {};  // function
 typeof undefined;       // undefined
 typeof null;            // object (설계적 결함)
 typeof undeclared;      // undefined (설계적 결함)

Object.prototype.toString

  • Object.prototype.toString 메소드는 객체를 나타내는 문자열을 반환한다.
var obj = new Object();
obj.toString(); // [object Object]
  • Function.prototype.call 메소드를 사용하면 모든 타입의 값의 타입을 알아낼 수 있다.
Object.prototype.toString.call('');             // [object String]
Object.prototype.toString.call(new String());   // [object String]
Object.prototype.toString.call(1);              // [object Number]
Object.prototype.toString.call(new Number());   // [object Number]
Object.prototype.toString.call(NaN);            // [object Number]
Object.prototype.toString.call(Infinity);       // [object Number]
Object.prototype.toString.call(true);           // [object Boolean]
Object.prototype.toString.call(undefined);      // [object Undefined]
Object.prototype.toString.call();               // [object Undefined]
Object.prototype.toString.call(null);           // [object Null]
Object.prototype.toString.call([]);             // [object Array]
Object.prototype.toString.call({});             // [object Object]
Object.prototype.toString.call(new Date());     // [object Date]
Object.prototype.toString.call(Math);           // [object Math]
Object.prototype.toString.call(/test/i);        // [object RegExp]
Object.prototype.toString.call(function () {}); // [object Function]
Object.prototype.toString.call(document);       // [object HTMLDocument]
Object.prototype.toString.call(argument);       // [object Arguments]
Object.prototype.toString.call(undeclared);     // ReferenceError
  • 이 것을 통하여 type을 체크하는 함수를 만들 수 있다.
function getType(target) {
  return Object.prototype.toString.call(target).slice(8, -1);
}
  • 위에서 만든 getType을 응용하여 타입별로 체크하는 함수들을 만들 수도 있다.

instanceof

  • 타입 연산자(Type Operator)이다.
  • instanceof 연산자는 피연산자인 객체가 우항에 명시한 타입의 인스턴스인지 여부를 알려준다. 이때 타입이란 constructor를 말하며 프로토타입 체인에 존재하는 모든 constructor를 검색하여 일치하는 constructor가 있다면 true를 반환한다.
target instanceof HTMLElement; //true or false

유사 배열 객체

  • 배열인지 체크하기 위해서는 Array.isArray 메소드를 사용한다.
  • 유사 배열 객체(array-like object)은 length 프로퍼티를 갖는 객체로 문자열, arguments, HTMLCollection, NodeList 등은 유사 배열이다.
  • 유사 배열 객체는 length 프로퍼티가 있으므로 순회할 수 있으며 call, apply 함수를 사용하여 배열의 메소드를 사용할 수도 있다.
  • 어떤 객체가 유사 배열인지 체크하려면 우선 length 프로퍼티를 갖는지 length 프로퍼티의 값이 정상적인 값인지 체크한다.
  const isArrayLike = function (collection) {
    // 배열 인덱스: 32bit 정수(2의 32제곱 - 1)
    // 유사 배열 인덱스: 자바스크립트로 표현할 수 있는 양의 정수(2의 53제곱 - 1)
    const MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; 
    // 빈문자열은 유사배열이다. undefined == null => true
    const length = collection == null ? undefined : collection.length;
    return typeof length === 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
   };

2. 프로토타입

프로토타입 객체

  • 자바스크립트는 프로토타입 기반 객체지향 프로그래밍 언어이다.
  • 클래스 기반 객체지향 프로그래밍 언어는 객체 생성 이전에 클래스를 정의하고 이를 통해 객체(인스턴스)를 생성한다.
  • 프로토타입 기반 객체지향 프로그래밍 언어는 클래스 없이(Class-less)도 (ECMAScript 6에서 클래스가 추가되었다) 객체를 생성할 수 있다.
  • 자바스크립트의 모든 객체는 자신의 부모 역할을 담당하는 객체와 연결되어 있고 객체 지향의 상속 개념과 같이 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있다. 이러한 부모 객체를 Prototype(프로토타입) 객체 또는 줄여서 Prototype(프로토타입)이라 한다.
  • Prototype 객체는 생성자 함수에 의해 생성된 각각의 객체에 공유 프로퍼티를 제공하기 위해 사용한다.
var student = {
  name: 'Lee',
  score: 90
};

// student에는 hasOwnProperty 메소드가 없지만 아래 구문은 동작한다.
console.log(student.hasOwnProperty('name')); // true
  • ECMAScript spec에서는 자바스크립트의 모든 객체는 [[Prototype]]이라는 인터널 슬롯(internal slot)를 가진다고 나와 있다.
    • [[Prototype]]의 값은 null 또는 객체이며 상속을 구현하는데 사용된다.
    • [[Prototype]] 객체의 데이터 프로퍼티는 get 액세스를 위해 상속되어 자식 객체의 프로퍼티처럼 사용할 수 있다. 하지만 set 액세스는 허용되지 않는다.
    • [[Prototype]]의 값은 Prototype(프로토타입) 객체이며 __proto__ accessor property로 접근할 수 있다. __proto__ 프로퍼티에 접근하면 내부적으로 Object.getPrototypeOf가 호출되어 프로토타입 객체를 반환한다.
    var student = {
      name: 'Lee',
      score: 90
    }
    console.log(student.__proto__ === Object.prototype); // true
    • 프로토타입 객체는 다른 임의의 객체로 변경할 수 있다. 이것은 부모 객체인 프로토타입을 동적으로 변경할 수 있다는 것을 의미한다. 이러한 특징을 활용하여 객체의 상속을 구현할 수 있다.

[[Prototype]] vs prototype 프로퍼티

  • 모든 객체는 자신의 프로토타입 객체를 가리키는 [[Prototype]] 인터널 슬롯(internal slot) 을 갖으며 상속을 위해 사용된다.

  • 함수도 객체이므로 [[Prototype]] 인터널 슬롯을 갖는다. 그런데 함수 객체는 일반 객체와는 달리 prototype 프로퍼티도 소유하게 된다.

  • prototype 프로퍼티와 [[Prototype]]은 모두 프로토타입 객체를 가리키지만 관점의 차이가 있다.

  • [[Prototype]]

    • 함수를 포함한 모든 객체가 가지고 있는 인터널 슬롯이다.
    • 객체의 입장에서 자신의 부모 역할을 하는 프로토타입 객체를 가리킨다.
    • 함수 객체의 경우 Function.prototype을 가리킨다.
        console.log(Person.__proto__ === Function.prototype);
  • prototype 프로퍼티

    • 함수 객체만 가지고 있는 프로퍼티이다.
    • 함수 객체가 생성자로 사용될 때 이 함수를 통해 생성 될 객체의 부모 역할을 하는 객체(프로토타입 객체)를 가리킨다.
      console.log(Person.prototype === foo.__proto__);

constructor 프로퍼티

  • 프로토타입 객체는 constructor 프로퍼티를 갖는다.
  • constructor 프로퍼티는 자식 객체의 입장에서 자신을 생성한 객체를 가리킨다.
    • Person() 생성자 함수에 의해 생성된 객체를 foo라 하자. 이 foo 객체를 생성한 객체는 Person() 생성자 함수이다. foo 객체의 프로토타입 객체는 Person.prototype이며 person.prototype의 constructor 프로퍼티는 Person() 생성자 함수이다.
function Person(name) {
  this.name = name;
}

var foo = new Person('Lee');

// Person() 생성자 함수에 의해 생성된 객체를 생성한 객체는 Person() 생성자 함수이다.
console.log(Person.prototype.constructor === Person);

// foo 객체를 생성한 객체는 Person() 생성자 함수이다.
console.log(foo.constructor === Person);

// Person() 생성자 함수를 생성한 객체는 Function() 생성자 함수이다.
console.log(Person.constructor === Function);

Prototype chain

  • 자바스크립트는 특정 객체의 프로퍼티나 메소드에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티 또는 메소드가 없다면 [[Prototype]]이 가리키는 링크를 따라 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 차례대로 검색한다. 이것을 프로토타입 체인이라 한다.
var student = {
  name: 'Lee',
  score: 90
}

// Object.prototype.hasOwnProperty()
console.log(student.hasOwnProperty('name')); // true
  • 객체 리터럴 방식으로 생성된 객체의 프로토타입 체인
    • 객체 리터럴 방식으로 생성된 객체는 결국 내장 함수(Built-in)인 Object() 생성자 함수로 객체를 생성하는 것을 단순화시킨 것이다.
    • 함수 객체인 Object() 생성자 함수는 일반 객체와 달리 prototype 프로퍼티가 있다.
      • prototype 프로퍼티는 함수 객체가 생성자로 사용될 때 이 함수를 통해 생성된 객체의 부모 역할을 하는 객체, 즉 프로토타입 객체를 가리킨다.
var person = {
  name: 'Lee',
  gender: 'male',
  sayHello: function(){
    console.log('Hi! my name is ' + this.name);
  }
};

console.dir(person);

console.log(person.__proto__ === Object.prototype);   // ① true
console.log(Object.prototype.constructor === Object); // ② true
console.log(Object.__proto__ === Function.prototype); // ③ true
console.log(Function.prototype.__proto__ === Object.prototype); // ④ true
Object literal Prototype chaining

  • 생성자 함수로 생성된 객체의 프로토타입 체인

    • 생성자 함수로 객체를 생성하기 위해서는 우선 생성자 함수를 정의하여야 한다.
    • 함수 선언식과 함수표현식은 함수 리터럴 방식을 사용하고 함수 리터럴 방식은 Function()생성자 함수를 사용하는 방식을 단순화 시킨 것이다.
    • 모든 함수 객체의 [[prototype]]는 Function.prototype이다. 생성자 함수도 함수 객체이므로 생성자 함수의 [[prototype]] 객체는 Function.prototype이다.
    function Person(name, gender) {
      this.name = name;
      this.gender = gender;
      this.sayHello = function(){
        console.log('Hi! my name is ' + this.name);
      };
    }
    
    var foo = new Person('Lee', 'male');
    
    console.dir(Person);
    console.dir(foo);
    
    console.log(foo.__proto__ === Person.prototype);                // ① true
    console.log(Person.prototype.__proto__ === Object.prototype);   // ② true
    console.log(Person.prototype.constructor === Person);           // ③ true
    console.log(Person.__proto__ === Function.prototype);           // ④ true
    console.log(Function.prototype.__proto__ === Object.prototype); // ⑤ true

  • 객체 리터럴 방식이나 생성자 함수 방식이나 결국은 모든 객체의 부모 객체인 Object.prototype 객체에서 프로토타입 체인이 끝난다. Object.prototype 객체를 프로토타입 체인의 종점(End of prototype chain)이라 한다.

프로토타입 객체의 확장

  • 프로토타입 객체도 객체이므로 일반 객체와 같이 프로퍼티를 추가/삭제할 수 있다. 그리고 이렇게 추가/삭제된 프로퍼티는 즉시 프로토타입 체인에 반영된다.
function Person(name) {
  this.name = name;
}

var foo = new Person('Lee');

Person.prototype.sayHello = function(){
  console.log('Hi! my name is ' + this.name);
};

foo.sayHello();

원시 타입(Primitive data type)의 확장

  • 자바스크립트에서 원시 타입(숫자, 문자열, boolean, null, undefined)을 제외한 모든것은 객체이다. 그런데 원시 타입인 문자열이 객체와 유사하게 동작한다.
var str = 'test';
console.log(typeof str);                 // string
console.log(str.constructor === String); // true
console.dir(str);                        // test

var strObj = new String('test');
console.log(typeof strObj);                 // object
console.log(strObj.constructor === String); // true
console.dir(strObj);
// {0: "t", 1: "e", 2: "s", 3: "t", length: 4, __proto__: String, [[PrimitiveValue]]: "test" }

console.log(str.toUpperCase());    // TEST
console.log(strObj.toUpperCase()); // TEST
  • 원시 타입으로 프로퍼티나 메소드를 호출할 때 원시 타입과 연관된 객체로 일시적으로 변환되어 프로토타입 객체를 공유하게 된다.
  • 원시 타입은 객체가 아니므로 프로퍼티나 메소드를 직접 추가할 수 없다.
  • String 객체의 String.prototype에 메소드를 추가하면 원시 타입, 객체 모두 메소드를 사용할 수 있다.

프로토타입 객체의 변경

  • 부모 객체의 프로토타입 객체는 다른 임의의 객체로 변경 할 수 있다.
  • 프로토타입 객체 변경 이전에 해당 프로토타입 객체를 부모로 둔 객체는 기존 프로토타입 객체에 연결되어있다.
  • 새롭게 변경한 프로토타입 객체는 변경 이후 생성된 객체에 연결된다.
function Person(name) {
  this.name = name;
}

var foo = new Person('Lee');

// 프로토타입 객체의 변경
Person.prototype = { gender: 'male' };

var bar = new Person('Kim');

console.log(foo.gender); // undefined
console.log(bar.gender); // 'male'

console.log(foo.constructor); // ① Person(name)
console.log(bar.constructor); // ② Object()
changing prototype

프로토타입 체인 동작 조건

  • 객체의 프로퍼티를 참조하는 경우
  • 해당 객체에 프로퍼티가 없는 경우
  • 객체의 프로퍼티에 값을 할당하는 경우, 프로토타입 체인이 동작하지 않는다.

3. 스코프

스코프란?

  • 스코프는 참조 대상 식별자(identifier, 변수, 함수의 이름과 같이 어떤 대상을 다른 대상과 구분하여 식별할 수 있는 유일한 이름)를 찾아내기 위한 규칙이다. 자바스크립트는 이 규칙대로 식별자를 찾는다.

스코프의 구분

  • 전역 스코프 (Global scope)
    • 코드 어디에서든지 참조할 수 있다.
  • 지역 스코프 (Local scope or Function-level scope)
    • 함수 코드 블록이 만든 스코프로 함수 자신과 하위 함수에서만 참조할 수 있다.
      모든 변수는 스코프를 갖는다.
  • 변수는 선언 위치(전역 또는 지역)에 의해 스코프를 가지게 된다.
    • 전역 변수 (Global variable)
      • 전역 스코프를 갖는 변수로 전역에서 선언된 함수
    • 지역 변수 (Local variable)
      • 지역(함수) 내에서 선언된 변수이며 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.

자바스크립트 스코프의 특징

  • 자바스크립트는 함수 레벨 스코프(function-level scope)를 따른다. 함수 레벨 스코프란 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다(참조할 수 없다)는 것이다.
    • 단, ECMAScript 6에서 도입된 let keyword를 사용하면 블록 레벨 스코프를 사용할 수 있다.

전역 스코프(Global scope)

  • 전역에 변수를 선언하면 이 변수는 어디서든지 참조할 수 있는 전역 스코프를 갖는 전역 변수가 된다. - var 키워드로 선언한 전역 변수는 전역 객체(Global Object) window의 프로퍼티이다.
  • 전역 변수의 사용은 변수 이름이 중복될 수 있고, 의도치 않은 재할당에 의한 상태 변화로 코드를 예측하기 어렵게 만드므로 사용을 억제하여야 한다.

비 블록 레벨 스코프(Non block-level scope)

  • var을 통해 코드 블록 내에 선언된 변수는 전역 스코프를 갖는다.

함수 레벨 스코프(Function-level scope)

  • 함수 내에서 선언된 매개변수와 변수는 함수 외부에서는 유효하지 않다.
  • 전역변수와 지역변수의 이름의 동일한 경우 지역변수를 우선하여 참조한다.
  • 함수 레벨 스코프에서 외부 함수에서 선언된 변수나 전역 변수를 접근 및 변경 가능하다.

렉시컬 스코프

  • 동적 스코프(Dynamic scope)
    • 함수를 어디서 호출하였는지에 따라 함수의 상위 스코프를 결정하는 방식
  • 렉시컬 스코프(Lexical scope)
    • 함수를 어디서 선언하였느냐에 따라 함수의 상위 스코프를 결정하는 방식
    • 자바스크립트는 렉시컬 스코프이다.

암묵적 전역

  • 식별자를 var등 예약어 없이 선언 하면 해당 식별자를 전역 객체의 프로퍼티에 추가한다.
  • 하지만 해당 식별자에 대해 변수 호이스팅이 발생하지는 않는다.
  • 또한 해당 식별자는 변수와 달리 delete 연산자로 삭제할 수 있다.

최소한의 전역변수 사용

  • 전역변수 사용을 최소화하는 방법 중 하나는 애플리케이션에서 전역변수 사용을 위해 다음과 같이 전역변수 객체 하나를 만들어 사용하는 것이다. (더글라스 크락포드의 제안)
var MYAPP = {};

MYAPP.student = {
  name: 'Lee',
  gender: 'male'
};

console.log(MYAPP.student.name);

즉시실행함수를 이용한 전역변수 사용 억제

  • 전역변수 사용을 억제하기 위해, 즉시 실행 함수(IIFE, Immediately-Invoked Function Expression)를 사용할 수 있다.
  • 라이브러리 등에 자주 사용된다. 즉시 실행 함수는 즉시 실행되고 그 후 전역에서 바로 사라진다.
(function () {
  var MYAPP = {};

  MYAPP.student = {
    name: 'Lee',
    gender: 'male'
  };

  console.log(MYAPP.student.name);
}());

console.log(MYAPP.student.name);

좋은 웹페이지 즐겨찾기