본문 바로가기
Learning

인사이드 자바스크립트

by zsgg 2020. 1. 23.

alt book

서평

이 책이 출시된 년도는 15년도이다. 현재 2020년도의 자바스크립트와 비교하면 빠져있는 내용도 많지만 기초에 대해서는 부족함이 전혀 없다.

요약

자바스크립트 기본 개요

객체

자바스크립트의 거의 모든 것은 객체이다. 여기서 '거의'라는 표현을 쓰는 이유는 몇 가지가 제외되기 때문이다. 바로 기본 테이터 타입인 boolean, number, string이다 그리고 특별한 값인 null, undefined도 해당된다. 이를 제외한 나머지는 모두 객체(참조타입)이다. 함수도 객체로 취급한다.

프로토타입

모든 객체는 숨겨진 링크인 프로토타입을 가진다. 이 링크는 해당 개체를 생성한 생성자의 프로토타입 객체를 가리킨다. 이 링크를 ECMAScript에서는 [[Prototype]] 이라고 표현한다.

자바스크립트 데이터 타입과 연산자

null, undefined

둘다 값이 비어있음을 나타낸다.
undefined는 그 자체가 하나의 값이자 타입 그 자체다.
typeof null //object

숫자

자바스크립트에서는 모든 숫자를 64비트 부동 소수점 형태로 저장한다.

문자열

문자열은 문자 배열처럼 인덱스를 이용해서 접근할 수 있다. 주목해야할 점은 str[0]에 'T'를 넣어서 첫 글자를 대문자로 변경해도 str은 변경되지 않는다. 즉, 자바스클비트에서는 한 번 생성된 문자열은 읽기만 가능하지 수정은 불가능하다.

var str = 'test';
str[0] = 'T';
console.log(str); // test

객체 프로퍼티 삭제

자바스크립트에서는 객체의 프로퍼티를 delete 연산자를 이용해 즉시 삭제할 수 있다. 여기서 주의할 점은 delete 연산자는 객체의 프로퍼티를 삭제할 뿐, 객체 자체를 삭제하지는 못한다.

var foo = {
    name: 'foo',
    nickname: 'babo'
}
delete foo.nickname; // 동작함 nickname은 undefined
delete foo; //아무반응없음. 객체는 삭제하지 못함

객체 비교

(객체일때)동등 연산자(==)를 사용하여 두 객체를 비교할때 객체의 프로퍼티값이 아닌 참조값을 비교한다는 것에 주의해야 한다.
(기본형일때) 값을 비교한다.

프로토타입

자바스크립트의 모든 객체는 자신의 부모 역활을 하는 객체와 연결되어 있다. 그리고 이것은 마치 객체지향의 상속 개념과 같이 부모 객체의 프로퍼티를 마치 자신의 것처럼 쓸 수 있는 것 같은 특징이 있다. 자바스크립트에서는 이러한 부모 객체를 프로토타입 객체(짧게는 프로토타입)라고 부른다.

콘솔을 열어서 ''.toString을 보면 객체에 proto 프로퍼티가 있다는 것을 확인할 수 있다. 이 프로퍼티가 바로 객체의 부모인 프로토타입 객체를 가리킨다. ECMAScript 명세서에는 자바스크립의 모든 객체는 자신의 프로토타입을 가리키는 [[Prototype]] proto 라는 숨겨진 프로퍼티를 가진다고 설명하고 있다.

또한, 객체를 생성할 때 결정된 프로토타입 객체는 임의의 다른 객체로 변경하는 것도 가능하다. 즉, 부모 객체를 동적으로 바꿀 수도 있는 것이다.

배열의 요소생성

length 0인 배열의 100번째에 이의의값을 저장할때 length는 101이 된다. 하지만 실제 메모리는 length 크기처럼 할당되지는 않는다.

배열 표준 메서드와 length 프로퍼티

var arr = [], arr.length = 5로 하고 push('bla')를 하면 [undefined,undefined,undefined,undefined,undefined,'bla']가 된다.

배열의 프로퍼티 동적 생성

//배열생성
var arr = ['zero', 'one', 'two'];
console.log(arr.length);//3
//프로퍼티 동적 추가
arr.color = 'blue';
console.log(arr.length);//3
//배열 원소 추가
arr[3] = 'red';
console.log(arr.length);//4
//배열 개체 출력
console.dir(arr)

배열의 length 프로퍼티는 배열 원소의 가장 큰 인덱스가 변했을 경우만 변경된다. 배열에도 프로퍼티 추가가 가능하다.

배열의 프로퍼티 열거

//배열생성
var arr = ['zero', 'one', 'two'];
console.log(arr.length);//3
//프로퍼티 동적 추가
arr.color = 'blue';
console.log(arr.length);//3
//배열 원소 추가
arr[3] = 'red';
console.log(arr.length);//4
//배열 개체 출력
console.dir(arr)

for(var prop in arr){
    console.log(prop, arr[prop])
}

for(var i=0; i<arr.length; i++){
    console.log(i, arr[i])
}

출력결과를 보면 for in문은 배열에서 color 프로퍼티까지 출력되었다.

기본 타입과 표준 메서드

기본 타입의 경우는 객체가 아닌데 어떻게 메서드를 호출할 수 있을까? 다음 예제와 같이 기본 타입의 값들에 대해서 객체 형태로 메서드를 호출할 경우, 이들 기본값은 메서드 처리 순간에 객체로 변환된 다음 각 타입별 표준 메서드를 호출하게 되는 것이다.

이렇듯 숫자와 문자열 등은 기본 타입이지만, 이들을 위해 정의된 표준 메서드들을 객체처럼 호출할 수 있다는 것을 기억하자.

함수와 프로토타입 체이닝

자바스크립트에서는 함수도 하나의 값처럼 취급된다(이러한 특징이 있으므로 자바스크립트의 함수는 일급 객체라고 하며, 이에 관해서는 이 장 뒷부분에서 자세히 다룰 것이다.) 따라서 함수도 숫자나 문자열처럼 변수에 할당하는 것이 가능하다.

자바스크립트의 함수도 Function()이라는 기본 내장 생성자 함수로부터 생성된 객체라고 볼 수 있다.

함수 호이스팅

더글라스 크락포드는 함수표현식만을 사용할 것을 권하고 있따. 그 이유 중의 하나가 바로 함수 호이스팅 때문이다.

//선언 전 이지만 실행됨
add(2, 3);
function add(a, b){ return a + b}
add(3, 4);

아직 add가 선언되지 않았음에도 add()함수를 호출하는 것이 가능하다. 이 것은 함수가 자신이 위치한 코드에 상관없이 함수 선언문 형태로 정의한 함수의 유효 범위는 코드의 맨 처음부터 시작한다는 것을 확인할 수 있다. 이것을 함수 호이스팅이라고 부른다.

이러한 함수 호이스팅은 함수를 사용하기 전에 반드시 선언해야 한다느 규칙을 무시하므로 코드의 구조를 엉성하게 만들 수 있다고 지적하며, 함수 표현식 사용을 권장하고 있다.

자바스크립트에서는 함수도 객체다

즉, 함수의 기본 기능인 코드 실행뿐만 아니라, 함수 자체가 일반 객체처럼 프로퍼티들을 가질 수 있다는 것이다.

function aaa(){}
aaa.result = '1234';
console.log(aaa.result);//1234

함수 객체의 기본 프로퍼티

표준

  • length: 함수를 정의할때 정의한 인자 갯수
  • prototype 프로퍼티

표준 아니지만 보편적으로 지원해줌

  • name
  • callee
  • arguments
  • proto

prototype 프로퍼티

prototype 프로퍼티 와 [[Prototype 프로퍼티. 두 프로퍼티 모두 프로토타입 객체를 가리킨다는 점에서는 공통점이 있지만, 관점에 차이가 있다. 모든 객체에 있느 내부 프로퍼티인 [[Prototype 는 객체 입장에서 자신의 부모 역활을 하는 프로토타입 객체를 가리키는 반면에, 함수의 객체가 가지는 prototype 프로퍼티는 이 함수가 생성자로 사용될 때 이 함수를 통해 생성된 객체의 부모 역활을 하는 프로토타입 객체를 가리킨다.

function aaa(){
  this.name = 'this name,';
  this.namee = 'this namee,';
}
console.log(window.name, aaa.namee, window.namee)
/*
<empty string> undefined undefined
*/

var bbb = new aaa();
console.log(bbb.name, bbb.namee, bbb.constructor.name)
/*
this name, this namee, aaa
*/

이 코드에서 bbb의 name namee가 '함수의 객체가 가지는 prototype 프로퍼티는 이 함수가 생성자로 사용될 때 이 함수를 통해 생성된 객체의 부모 역활을 하는 프로토타입 객체를 가리킨다.'이다.

function aaa(){
  this.name = 'this name,';
  this.namee = 'this namee,';
}
aaa.prototype.funcAa = function(){
    console.log('funcAa namee', this.namee);
    return 'print funcAa';
}
aaa.nameeee = 'this nameee,'

//---

var bbb = new aaa();

//funcAa namee this namee,
//pring funcAa
console.log(bbb.funcAa());
//funcAa namee undefined
//pring funcAa
console.log(bbb.__proto__.funcAa());

//this nameeee,
console.log(aaa.nameeee);
//this namee,
console.log(bbb.namee);

this 바인딩

메서드를 호출할 때, 메서드 내부 코드에서 사용된 this는 해당 메서드르 호출한 객체로 바인딩 된다

function aaa(){ this.aa = 'this aa,' }
aaa()
// this aa,
aa

생성자 함수르 호출할 때 this 바인딩

기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작한다. 이는 반대로 생각하면 일반 함수에 new를 붙여 호출하면 원치 않는 생성자 함수처럼 동작할 수 있다. 따라서 대부분의 자바스크립트 스타일 가이드에서는 특정 함수가 생성자 함수로 정의되어 있음을알리려고 함수 이름의 첫 문자를 대문자로 쓰기를 권하고 있다.

생성자 함수가 동작하는 방식

  1. 생성자 함수 코드가 실행되기 전 빈 객체가 생성된다. 바로 이 객체가 생성자 함수가 새로 생성하는 개체이며, 이 객체는 this로 바인딩 된다.
  2. 이후에는 함수 코드 내부에서 this를 사용해서, 앞에서 생성된 빈 객체에 동적으로 프로퍼티나 메서드를 생성할 수 있다.
  3. 리턴문이 없다면 this로 바인딩된 새로 생성한 객체가 리턴된다.

강제로 인스턴스화 하기

function A(arg){
    if(!(this instanceof A)){
        return new A(arg);
    }
    this.value = arg ? arg : 0;
}

함수 리턴

자바스크립트 함수는 항상 리턴값을 반환한다. 특히, return 문을 사용하지 않았더라도 다음의 규칙으로 항상 리턴값을 전달하게 된다.

  • 일반 함수나 메서드는 리턴값을 지정하지 않을 경우, undefined값이 리턴된다.
  • 생성자 함수에 리턴값을 지정하지 않을 경우 지정된 생성자 객체가 리턴된다. / 생성자 함수의 리턴값이 객체가 아닌 불린, 문자, 숫자의 경우는 이러한 리턴값을 무시하고 this로 바인딩된 객체가 리턴된다.

프로토타입 체이닝

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

// foo 객체 생성
var foo = new Person('foo');

Person()생성자 함수는 prototype 프로퍼티로 자신과 링크된 프로토타입 객체(Person.prototype)를 가리킨다. 그리고 앞서 설명한 자바스크립트의 객체 생성 규칙에 의하면 Person() 생성자 함수로 생성된 foo 객체는 Person() 함수의 프로토타입 객체를 [[Prototype 링크로 연결한다. 결국, prototype 프로퍼티나 [[Prototype은 같은 프로토타입 객체를 가리키고 있다.

prototype 프로퍼티는 함수의 입장에서 자신과 링크된 프로토타입 객체를 가리키고 있으며, 이에 반해 [[Prototype 링크는 객체의 입장에서 자신의 부모 객체인 프로토타입 개체를 내부의 숨겨진 링크로 가리키고 있다.

결국, 자바스크립트에서 객체를 생성하는 건 생성자 함수의 역활이지만, 생성된 객체의 실제 부모 역활을 하는건 생성자 자신이 아닌 생성자의 prototype 프로퍼티가 가리키는 프로토타입 객체다.

prototype

프로토타입 체이닝

자바스크립트에서 객체는 자기 자신의 프로퍼티뿐만 아니라, 자신의 부모 역활을 하는 프로토타입 객체의프로퍼티 또한 마치 자신의 것처럼 접근하는 게 가능하다. 이것을 가능케 하는 게 바로 프로토타입 체이닝이다.

자바스크립트에서 특정 객체의 프로퍼티나 메서드에 접근하려고 할 때, 해당 객체에 적븐하려는 프로퍼티 또는 메서드가 없다면 [[Prototype 링크를 따라 자신의 부모 역활을 하는 프로토타입 객체의 프로퍼티르 차례대로 검색하는 것을 프로토타입 체이닝이라고 말한다.

자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체(부모 객체)로 취급한다.

자바스크립트에서 Object.prototype 객체는 프로토타입 체이닝의 종점이다. 이것을 달리 해석하면, 객체 리터럴 방식이나 생성자 함수 방식에 상관없이 모든 자바스크립트 객체는 프로토타입 체이닝으로 Object.prototype 개체가 가진 프로퍼티와 메서드에 접근하고, 서로 공유가 가능하다는 것을 알 수 있다.

함수가 생성될 때, 자신의 prototype 프로퍼티에 연결되는 프로토타입 객체는디폴트로 constructor 프로퍼티만을 가진 객체다. 당연히 프로토타입 객체 역시 자바스크립트의 객체이므로 일반 객체처럼 동적으로 프로퍼티를 추가/삭제하는것이 가능하다. 그리고 이렇게 변경된프로퍼티는 실시간으로 프로토타입 체이닝에 반영된다.

function aaa(){
  this.namee = 'this namee,';
}

aaa.prototype.getNamee = function(){
  return this.namee;
}
aaa.prototype.namee = 'prototype name,';

var bbb = new aaa();

console.log(bbb.getNamee());// this namee,
console.log(bbb.__proto__.getNamee());// prototype name,
console.log(aaa.prototype.getNamee());// prototype name,

prototype

실행 컨텍스트와 클로저

ECMAScript에서는 실행 컨텍스트의 생성을 다음과 같이 설명한다. '현재 실행되는 컨텍스트에서 이 컨텍스트와 관련 없는 실행코드가 실행되면, 새로운 컨텍스트가 생성되어 스택에 들어가고 제어권이 그 컨텍스트로 이동한다.'

스코프 정보 생성

현재 컨텍스트의 유효 범위를 나타내는 스코프 정보를 생성한다. 이 스코프 저어보는 현재 실행 중인 실행 컨텍스트 안에서 연결 리스트와 유사한 형식으로 만들어진다. 현재 컨텍스트에서 특정 변수에 접근해야 할 경우, 이 리스트를 활용한다. 이 리스트르 스코프 체인이라고 하는데, [[scope 프로퍼티로 참조된다.

스코프 체인

각각의 함수는 [[scope 프로퍼티로 자신이 생성된 실행 컨텍스트의 스코프 체인을 참조한다. 함수가 실행되는 순간 실행 컨텍스트가 만들어지고, 이 실행 컨텍스트는 실행된 함수의 [[scope 프로퍼티를 기반으로 새로운 스코프 체인을 만든다.

var value = 'val1';

function printFunc(){
  var value = 'val2';

  function printValue(){
    return value;
  }

  console.log(printValue());
}

// val1
printFunc();
var value = 'val1';

function printValue(){
  return value;
}

function printFunc(){
  var value = 'val2';
  console.log(printValue());
}

// val2
printFunc();

실행되는 함수 call 되는 위치가 아닌, 정의된 위치가 중요하다.


자바스크립트에서는 스코프 체인을 사용자가 임의로 수정하는 키워드가 있느데, 이것이 with이다. with는 eval과 함께, 성능을 높이고자 하는 자바스크립트 프로그래머에게는 사용하지 말아야 할 키워드이다.

클로저

function outerFunc(){
  var x = 10;
  var innerFunc = function(){
    console.log(x);
  }
  return innerFunc;
}
var inner = outerFunc();
inner();

결과를 보면 짐작할 수 있지만, outerFunc 실행 컨텍스트는 여전히 사라졌지만, outerFunc 변수 객체는 여전히 남아있고, innerFunc의 스코프 체인으로 참조되고 있다. 이것이 바로 자바스크립트에서 구현한 클로저라는 개념이다. 조금 더 풀어서 정의하면 이미 생명 주기가 끝난 외부 함수의 변수르 참조하는 함수를 클로저라고 한다.

그리고 크로저로 참조되는 외부변수(var x)를 자유변수라고 한다. closure라는 이름은 함수가 자유변수에 대해 닫혀있다는 의미인데, 우리말로 의역하면 '자유 변수에 엮여있는 함수'라는 표현이 맞을 듯하다.

스코프는 성능문제를 유발시킬 수 있는 여지가 있다. 대부분의 클로저에서는 스코프 체인에서 뒤쪽에 있는 객체에 자주 접근하므로, 성능을 저하시키는 이유로 지목되기도 한다. 대부분의 클로저에서는 스코프 체인에서 뒤쪽에 있는 객체에 자주 접근하므로, 성능을 저하시키는 이유로 지목되기도 한다.게다가 앞에서 알아본 대로 클로저를 사용한 코드가 그렇지 않은 코드보다 메모리 부담이 많아진다. 그렇다고 클로저를 쓰지 않는 것은 자바스크립트의 강력한 기능 하나르 무시하고 사용하는 것과 다름이 없다. 따라서 클로저를 영리하게 사용하는 지혜가 필요하며, 이를 위해선 많은 프로그래밍 경험을 쌓아야 한다.

객체지향 프로그래밍

함수형 프로그래밍

jQuery 소스 코드 분석

'Learning' 카테고리의 다른 글

document, winston.js  (0) 2020.01.21
document, Express.js  (0) 2020.01.21
성공과 실패를 결정하는 1%의 네트워크 원리  (0) 2020.01.21
생각을 넓혀주는 독서법  (0) 2020.01.19
서평 쓰는 법  (0) 2020.01.19

댓글