인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



함수형 프로그래밍은 프로그래밍의 여러 가지 패러다임 중 하나이다. 함수의 조합으로 작업을 수행하는 프로그래밍 방식이다. 중요한 것은 이 작업이 이루어 지는 동안 작업에 필요한 데이터와 상태는 변하지 않는다는 점이다. 변할 수 있는 건 오직 함수 뿐이다. 


자바스크립트에서의 함수형 프로그래밍은 다음과 같은 자바스크립트의 특징 때문에 가능하다.

- 일급 객체로서의 함수

- 클로저



1.Memoization Pattern


//Memoization이란 계산 결과를 저장해 놓아 이후 다시 계산할 필요 없이 사용할 수 있게 한다는 컴퓨팅 용어이다.
//말 그대로 함수의 키값에 결과를 저장해두고 이후 계산없이 사용할 수 있다.
//왠지 아래 함수보다는 input, func 값으로 key값을 만들어서 하면 이후 똑같은 호출 시 계산없이해서 좋을 거 같다.
//jQuery에서는 data()라는 메서드로 이 메모이제이션 패턴을 사용하였다.

function Calculate(key, input, func){
    Calculate.data = Calculate.data || {};
    if(!Calculate.data[key]){
        var result;
        result = func(input);
        Calculate.data[key] = result;
    }
    return Calculate.data[key];
}

var result;

result = Calculate(1,5,function(input){
    return input * input;
});
console.log(result);

result = Calculate(2, 5, function(input){
    return input * input /4;
});
console.log(result);

console.log(Calculate(1));
console.log(Calculate(2));



1) Memoization을 적용한 Factorial


//클로저를 활용한 앞서 실행한 factorial 값을 저장하는 factorial 함수
var factorial = function(){
    var cache = {'0':1};
    //연산을 수행하는 과정에서 캐시에 저장된 값이 있으면 곧바로 그 값을 반환한다.
    var factorial = function(n){
        var result = 0;
        if(typeof(cache[n]) === 'number'){
            result = cache[n];
        } else {
            result = cache[n] = n * factorial(n-1);
        }
        return result
    };

    return factorial;
}();

console.log(factorial(10));
console.log(factorial(12));



2) Memoization을 적용한 Fibonaci


//메모이제이션 기법을 적용한 피보나치 수열

var fibo = function(){
    var cache = {'0' : 0, '1': 1};

    var fibo = function(n){
        if(typeof(cache[n]) === 'number'){
            result = cache[n];
        } else {
            result = cache[n] = fibo(n-1) + fibo(n-2);
        }
        return result;
    }
    return fibo;
}();

console.log(fibo(10));


2.함수 적용

apply(), bind(), call() 함수는 함수형 프로그래밍에서 중요한 함수들이다.




3.Currying


커링이란 특정 함수에서 정의된 인자의 일부를 넣어 고정시키고, 나머지를 인자로 받는 새로운 함수를 만드는 것을 의미한다.

function calculate(a, b, c){
    return a*b+c;
}

function curry(func){
    var args = Array.prototype.slice.call(arguments, 1);

    return function(){
        return func.apply(null, args.concat(Array.prototype.slice.call(arguments)));
    }
}

var new_func1 = curry(calculate, 1);
console.log(new_func1(2,3)); //5 

var new_func2 = curry(calculate, 1, 2);
console.log(new_func2(3));//5

커링이란 특정 함수에서 정의된 인자의 일부를 넣어 고정시키고, 나머지를 인자로 받는 새로운 함수를 만드는 것을 의미한다.


bind() 함수가 커링과 매우 유사하다.


//bind() 함수는 고정시키고자 하는 인자를 함수 호출시 전달하고, 반환받은 함수를 호출하면서 나머지 가변 인자를 넣어줄 수 있다.

Function.prototype.bind = function(thisArg){
    var fn = this;
    slice = Array.prototype.slice;
    args = slice.call(arguments, 1);
    return function(){
        return fn.apply(thisArg, args.concat(slice.call(arguments)));
    };
}


4.wrapper


특정 함수를 자신의 함수로 덮어쓰는 것을 말한다. 물론 여기서 사용자는 원래 함수 기능을 잃어버리지 않은 상태로 자신의 로직을 수행할 수 있어야 한다.

기존 제공하는 함수에 기능을 추가하고 싶거나, 버그를 피하고자 할 때 많이 사용된다.


function wrap(object, method, wrapper){ var fn = object[method]; return object[method] = function(){ //original에서의 this와 익명함수 wrapper에서의 this가 다르므로 동일하게 만든다. return wrapper.apply(this, [fn.bind(this)].concat(Array.prototype.slice.call(arguments))); }; }

//original 함수가 있고, 이는 인자로 넘어온 값을 value에 할당하고 출력하는 기능을 한다. Function.prototype.original = function(value){ this.value = value; console.log("value : " + this.value); } //이를 사용자가 덮어쓰기 위해 wrap 함수를 호출하였다. 세 번째 인자로 넘긴 자신의 익명 함수를 original에 덮어쓰는 것이다. //여기서 사용자는 자신의 익명 함수의 첫 번째 인자로 원래 함수의 참조를 받을 수 있다. 이로 인해 원래 로직을 수행 할 수 있다. var mywrap = wrap(Function.prototype, "original", function(orig_func, value){ orig_func(value); console.log("wrapper value : " + this.value); }); var obj = new mywrap("song");



인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



1.클래스, 생성자, 메서드


일반적으로 C++, Java와 같은 언어에서는 class라는 키워드를 제공하여 프로그래머가 클래스를 만들 수 있다.

그리고 클래스와 같은 이름의 메서드로 생성자를 구현한다. 그러나 자바스크립트에는 이러한 개념이 없다.(ES5에서는)

자바스크립트에서는 함수객체로 클래스, 생성자, 메서드도 구현 가능하다.



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

Person.prototype.getName = function(){
    return this.name;
}

Person.prototype.setName = function(name){
    this.name = name;
}

var me = new Person("song");
console.log(me.getName());



2.상속


자바스크립트는 클래스를 기반으로 하는 전통적인 상속을 지원하지는 않는다.(ES5기준) 하지만 자바스크립트 특성 중 객체 프로토타입 체인을 이용하여 상속을 구현할 수 있다.


1) 클래스 개념 없이 객체의 프로토타입으로 상속 구현하기.

아래 예제는 더글라스 크락포드가 자바스크립트 객체를 상속하는 방법으로 소개한 코드이다.

var create_object = function(o){ function F() {} F.prototype = o; return new F(); };


생성된 F객체는 object o에 프로토타입 링크가 연결되어 있고, 

또, 이 object o는 Object prototype에 프로토타입 링크가 연결되어 있다.


그리고 프로토타입으로 상속을 구현하기에 중심이되는 또 다른 메서드가 있다. 

바로 extend() 메서드이다. 자바스크립트에서는 범용적으로 extend() 메서드로 자신이 원하는 객체 혹은 함수를 추가시킨다.

다음 코드는 jQuery 1.0의 extend 함수이다.


function extend(obj, prop){
    if(!prop){prop = obj; obj = this;}
    for(var i in prop) obj[i] = prop[i];
    return obj;
}


위와 같은 extend() 함수의 약점은 얕은 복사를 하고 있다는 것이다. 프로퍼티 중 객체가 있는 경우 깊은 복사를 하는 것이 일반적이다.



create_object(), extend() 메서드를 이용하여 상속을 하는 예제를 구현해 보자.

//클래스 개념 없이 프로토타입 특성으로만 상속 구현하기

//더글라스 크락포드가 자바스크립트 객체를 상속하는 방법으로 소개한 코드.
var create_object = function(o){
    function F() {}
    F.prototype = o;
    return new F();
};
//  _proto_          _proto_
//F ------> object o -------> Object.prototype

var person = {
    name : "song",
    getName : function(){
        return this.name;
    },
    setName : function(arg){
        this.name = arg;
    }
};

var student = create_object(person);
student.setName("ssong");
console.log(student.getName());

//위와 같이 부모 객체의 메서드를 그대로 상속받아서 사용할 수 있고, 자신의 메서드를 재정의 혹은 추가로 구현할 수 있다.
//이때, 자바스크립트에서는 범용적으로 extend()라는 이름의 함수로 자산이 원하는 객체 혹은 메서드를 추가한다.

function extend(obj, prop){
    if(!prop){prop = obj; obj = this;}
    for(var i in prop) obj[i] = prop[i];
    return obj;
}

var studentAdded = {
    setAge : function(age){
        this.age = age;
    },
    getAge : function(){
        return this.age;
    }
};

extend(student, studentAdded);
student.setAge(25);
console.log(student.getAge());


//한 단계 더 상속을 구현해보도록 하겠다.
//student를 상속받은 HiSchoolStudent를 구현하겠다.

var hiSchoolStudent = create_object(student);

var hiSchoolStudentAdded = {
    grade : 3,
    setGrade : function(grade){
        this.grade = grade;
    },
    getGrade : function(){
        return this.grade;
    }
};

extend(hiSchoolStudent, hiSchoolStudentAdded);
console.log(hiSchoolStudent.getName());
console.log(hiSchoolStudent.getAge());
console.log(hiSchoolStudent.getGrade());


2) 클래스역할을 하는 함수로 상속하기.


아래 예제는 일반적인 상속 예제이다. 


핵심이 되는 부분은 Student.prototype에 부모가 될 Person의 인스턴스를 넣어주고, 생성자를 다시 지정해주는 것이다.

이렇게 함으로써, Student의 객체는 Person 객체의 함수와 메서드를 사용할 수 있게 프로토타입 링크가 형성된다.


주의할 점은 자동으로 부모 객체의 생성자가 호출되지는 않으므로 자식 생성자에서 부모 생성자를 호출해주어야 한다.

Person.call(this, name); 부분이 부모 생성자를 호출해 주는 부분이다. 

부모 생성자에 생성될 객체를 this로 넘긴다. 


//클래스역할을 하는 함수로 상속하기 var Person = function(name){ this.name = name; }; Person.prototype.setName = function(name){ this.name = name; }; Person.prototype.getName = function(){ return this.name; }; var Student = function(name, _calss){ //부모클래스 생성자 호출 Person.call(this, name); this.class = _calss; }; Student.prototype = new Person(); Student.prototype.constructor = Student; Student.prototype.getClass = function(){ return this.class; }; Student.prototype.setClass = function(_calss){ this.class = _calss; }; var song = new Student("song", 3); console.log(song.getName()); console.log(song.getClass()); console.log(song instanceof Person); // true console.log(song instanceof Student); // true


3.캡슐화


자바스크립트의 강력한 특성 중 하나인 클로저를 활용하여 캡슐화를 할 수 있다.


1) 기본 예제

아래 예제는 public 메서드가 클로저 역할을하고, private 멤버인 name에 접근한다.

자바스크립트에서 할 수 있는 기본적인 정보은닉 방법이다.

*모듈 패턴


주의할 점은 private 멤버가 객체나 배열인 경우 사용자가 get~~()함수를 통해 받은 값을 손쉽게 수정할 수 있다. 때문에 깊은 복사를 통해서 새로은 객체나 배열을 리턴해주어야 한다.


var Person = function(arg){
    var name = arg ? arg: "song";
    return {
        getName: function(){
            return name;
        },
        setName: function(arg){
            name = arg;
        }
    };
}

var song = Person(); // or var song = new Person();
console.log(song.getName());


위 예제는 객체를 리턴하는데, 이 객체는 Person 함수 객체의 프로토타입에는 접근할 수 없다.

때문에 Person을 부모로 하는 프로토타입을 이용한 상속을 구현하기에는 용이하지 않다. 때문에 이를 보완하기 위해 함수를 반환한다.


2) 캡슐화를 적용한 상속가능 함수.


var Person = function(arg){
    var name = arg ? arg: "song";

    var Person = function(){}
    Person.prototype = {
        getName: function(){
            return name;
        },
        setName: function(arg){
            name = arg;
        }
    };
    
    return Person;
}();

var song = new Person();
console.log(song.getName());


위도 -> y point


위도를 특정 크기의 이미지에 사상하는 소스입니다.

function convertLatitudeToY(lat, imageHeight) {
  let y = (maxLat - lat) / (maxLat - minLat) * imageHeight;
  return y;
}


경도 -> x point


경도를 특정 크기의 이미지에 사상하는 소스입니다.

function convertLongitudeToX(lon, imageWidth) {
  let x = (lon - minLon) / (maxLon - minLon) * imageWidth;
  return x;
}



인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



클로저


이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 함수를 클로저라고 합니다.

클로저로 참조되는 외부 변수를 자유 변수(Free variable)라고 합니다. closure라는 이름은 함수가 자유 변수에 대해 닫혀 있다는 의미입니다.


//closure 예제
function outerFunc(){
    var x = 1;

    return function(){
        x = x * 2;
        console.log(x);
    };
};

var inner = outerFunc();
// outerFunc 실행 컨텍스트 종료

inner();


위와 같은 코드가 클로저를 구현하는 전형적인 패턴입니다. 외부 함수가 호출되고 이 외부 함수에서 반환된 함수가 클로저입니다.


클로저에서 실행 컨텍스트가 종료된 외부 함수의 변수를 접근할 수 있는 것은,

스코프 체인에서 참조하고 있는 것이 외부 함수의 변수 객체이기 때문입니다. outerFunc의 실행 컨텍스트는 사라젔지만 변수 객체는 여전히 남아있고, inner의 스코프체인으로 참조되고 있습니다.



클로저의 활용


클로저는 스코프체인에서 뒤쪽에 있는 객체에 자주 접근하므로, 성능 저하의 원인이 되기도 합니다. 그러나, 그렇다고 클로저를 쓰지 않는 것은 자바스크립트의 강력한 기능 중 하나를 무시하는 것이므로 잘 사용하는 것이 중요합니다.


1) 특정 함수에 사용자가 정의한 객체의 메서드 연결하기


정해진 형식의 함수를 콜백해주는 라이브러리가 있을 경우, 그 형식과는 다른 형식의 사용자 정의 함수를 호출할 때 유용하게 사용할 수 있습니다.


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

Person.prototype.introduce = function(){
    this.func ? this.func(this.name): null;
};

Person.prototype.setIntroduce =function(func){
    this.func = func;
}

Person이라는 객체는 introduce라는 함수가 구현되어 있습니다.

introduce는 사용자가 등록한 함수를 호출해줍니다.


var userFunc = function(name){ console.log(name); }; var song = new Person("Song"); song.setIntroduce(userFunc); song.introduce(); //"Song"

사용자가 userFunc라는 함수를 구현하고, Person객체를 만들어서 등록하였습니다.

introduce()를 호출하면 "Song"이 출력됩니다.

간단한 콜백함수의 예입니다.


그런데 사용자가 등록할 수 있는 콜백함수는 name인자 하나를 받도록 형식이 정해저 있습니다.

만약 age를 인자로 추가로 받도록 하려면 어떻게해야 할까요?


클로저를 활용해서 할 수 있습니다.


var createIntroduce = function(obj, methodName, age){ return (function(name){ return obj[methodName](name, age); }); } var newObj = function(obj, age){ obj.setIntroduce(createIntroduce(this, "introduce", age)); return obj; } newObj.prototype.introduce = function(name, age){ console.log(name + ", " + age); }; var song2 = new newObj(song, 29); song2.introduce(); // "Song, 29" 출력


newObj는 Person 객체를 좀 더 자유롭게 사용하기위해 정의한 함수입니다.

위와 같이 클로저의 특성을 이용해서 age도 인자로 받을 수 있도록 할 수 있습니다.


2) 함수의 캡슐화


다음과 같이 전역 변수에 접근을 하는 함수 예제가 있습니다.

이러한 방식의 단점은 전역 변수가 외부에 노출되어 있어서, 다른 함수에서 이 값을 변경할 수도 있고 실수로 같은 이름의 변수를 만들어서 버그가 생길 수도있습니다.

var year = 2018; var month = 5; var day = 12; var sayDate = function(){ console.log(year + "년 " + month + "월 " + day + "일"); }; sayDate();


클로저를 활용해서 전역변수를 추가적인 스코프에 넣고 사용하면 이런 문제를 해결할 수 있습니다. 

sayDate에 익명의 함수를 즉시 실행시켜 반환되는 함수를 할당합니다. 

반환되는 이 함수는 클로저가 되고, 이 클로저는 자유 변수 year, month, day를 참조할 수 있다.


var sayDate = function(){
    var year = 2018;
    var month = 5;
    var day = 12;
    return (function(year, month, day){
        console.log(year + "년 " + month + "월 " + day + "일");
    });
}();

sayDate(); // "2018년 5월 12일"


클로저의 활용시 주의사항


클로저는 강력한 기능이지만 주의해서 사용해야 합니다. 



1) 클로저의 프로퍼티 값이 쓰기 가능하므로 그 값이 여러 번 호출로 항상 변할 수 있다.


2) 하나의 클로저가 여러 함수의 스코프 체인에 들어가 있는 경우도 있다.


3) 루프 안에서 클로저를 활용할 때는 주의하자.




인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



스코프 체인


자바스크립트에서 굉장히 중요한 개념입니다. 

스코프 체인, 즉 유효범위인데 c++과는 다른 유효범위를 가지고 있기 때문에 주의해야합니다.


오직 함수만이 유효 범위의 한 단위가 되고 [[scope]] 프로퍼티로 각 함수 객체 내에서 연결 리스트 형식으로 관리됩니다.

이를 스코프 체인이라 합니다.


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


스코프 체인

1. 현재 실행되는 함수 객체의 [[scope]] 프로퍼티를 복사한다.

2. 새롭게 생성된 변수 객체(활성 객체)를 맨앞에 추가한다.


-> 스코프 체인 = 현재 실행 컨텍스트의 변수 객체 + 상위 컨텍스트의 스코프 체인



var name = 'song';

function printName() {
    var age = 29;

    function printAge(){
        console.log(age);
    };

    console.log(name);
}; 


위와 같은 예제의 스코프 체인을 그려 보면 다음과 같습니다.




이렇게 만들어진 스코프 체인으로 식별자 인식이 이루어집니다. 식별자 인식은 첫 번째 변수 객체부터 시작하여 해당하는 프로퍼티가 있는 지 탐색하고, 발견하지 못하면 다음 객체로 이동하며 탐색을 진행합니다.



인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



실행 컨텍스트


ECMAScript에서는 실행 컨텍스트를 "실행 가능한 코드를 형상화하고 구분하는 추상적인 개념"이라고 기술합니다.

이 컨텍스트 안에 실행에 필요한 여러 가지 정보를 담고 있습니다. 


실행 컨텍스트가 생성되는 경우는 세 가지가 있습니다.

1) 전역 코드

2) eval() 함수로 실행되는 코드

3) 함수 안의 코드를 실행할 경우


대부분 함수로 실행 컨텍스트를 만듭니다.

ECMAScript에서는 또 실행 컨텍스트 생성을 다음처럼 설명합니다.

"현재 실행되는 컨텍스트에서 이 컨텍스트와 관련 없는 실행 코드가 실행되면, 새로운 컨텍스트가 생성되어 스택에 들어가고 제어권이 그 컨텍스트로 이동한다."


전역 실행 컨텍스트는 일반적인 실행 컨텍스트와 다릅니다. arguments 객체가 없으며 전역 객체 하나만을 포함하는 스코프 체인이 있습니다.


약간 차이점이 있지만 C++에서 콜스택과 비슷한 개념이라고 보시면 쉬울 것 같습니다.


실행 컨텍스트가 생성되면 자바스크립트 엔진을 활성 객체를 생성합니다. 이 객체에 앞으로 사용하게 될 매개변수나 사용자가 정의한 변수 및 객체를 저장하고, 새로 만들어진 컨텍스트로 접근 가능하게 되어 있습니다.


활성 객체


실행 컨텍스트가 생성되면 자바스크립트에서는 해당 컨텍스트에서 실행에 필요한 여러가지 정보를 담을 객체를 생성합니다. 그게 바로 활성 객체입니다. 사용자가 접근할 수 있는 영역은 아니고 자바스크립트 엔진이 사용하는 영역입니다.


활성 객체가 지닌 정보

1) argumens 객체

인자로 들어온 arguments 객체를 arguments 프로퍼티로 접근합니다.


2) 스코프 정보// 스코프 체인 scope 프로퍼티

현재 컨텍스트의 유효 범위를 나타내는 스코프 정보를 생성합니다.


3) 변수

변수나 내부함수는 활성 객체가 생성되는 시점에 생성은 되지만 초기화는 해당 표현식이 실행될 때 이루어집니다.


4) this



인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



1.prototype과 _proto_ 프로퍼티


자바스크립트에서는 프로토타입 기반의 객체지향 프로그래밍을 지원합니다.(ES6 부터는 클래스가 도입되었음.)

그 근간이 되는 것이 프로토타입과 프로토타입 체이닝입니다.


자바스크립트의 모든 객체는 자신의 부모인 프로토타입 객체를 가리키는 참조 링크 형태의 숨겨진 프로퍼티가 있습니다. ECMAScript에서는 이러한 링크를 암묵적 프로토타입 링크(implicit prototype link) 라 부르며이러한 링크는 모든 객체의 _proto_ 프로퍼티에 저장됩니다.


주의할 점이 _proto_ 프로퍼티와 prototype 프로퍼티가 다르다는 점입니다. 도대체 왜 이렇게 헷갈리게 네이밍을 했는지 모르겠어요..


자바스크립트에서 모든 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 프로토타입 객체를 자신의 부모 객체로 설정하는 _proto_ 프로퍼티에 링크로 연결합니다.



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

var song = new Person('song');


위와 같은 예제에서 Person() 생성자 함수로 생성된 song객체는 Person() 함수의 프로토타입 객체 즉 prototype 프로퍼티와 연결되어있는 객체를 자신의 _proto_ 프로퍼티에 링크로 연결합니다.


prototype 프로퍼티는 함수의 입장에서 자신과 링크된 프로토타입 객체를 가리키고,

_proto_ 프로퍼티는 객체의 입장에서 자신의 부모 객체인 프로토타입 객체를 내부의 숨겨진 링크로 가지고 있습니다.


Person() 생성자의 prototype 프로퍼티와 song 객체의 _proto_ 프로퍼티는 동일한 객체를 가리키는 겁니다.


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


2.프로토타입 체이닝


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


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

자바스크립트에서 모든 타입의 프로토타입 체이닝의 종점은 Object.prototype입니다.

자바스크립트의 기본 데이터 타입들도 표준 메서드를 가지고 있고, 이 표준 메서드들은 각각 Number.prototype, String.prototype, Array.prototype등과 같이 이들의 프로토타입 객체에 정의되어 있습니다.

그리고 결국 이러한 기본 내장 프로토타입 객체 또한 Object.prototype을 자신의 프로토타입으로 가지고 프로토타입 체이닝으로 연결됩니다. 때문에 모든 객체가 아닌 모든 타입의 프로토타입 종점이 Object.prototype이라고 한 것 입니다.

그리고 객체의 프로퍼티를 읽거나 메서드를 실행할 때에만 프로토타입 체이닝이 일어납니다. 당연한 말이지만 해당 객체에 없는 프로퍼티나 메서드를 접근할 때 프로토타입 체이닝이 일어납니다.




3.디폴트 프로토타입 변경


디폴트 프로토타입 객체는 함수가 생성될 때 같이 생성되며, 함수의 prototype 프로퍼티에 연결됩니다. 이렇게 자바스크립트에서 함수를 생성할 때 해당 함수와 연결되는 디폴트 프로토타입 객체를 다른 일반 객체로 변경할 수 있습니다.

이를 통해서 상속을 구현합니다.


프로토타입을 통한 상속 구현이 가능한 이유는 프로토타입 또한 객체이므로 프로퍼티와 메서드를 가질 수 있습니다. 때문에 사용자가 기존에 클래스를 통해 상속을 하듯이 물려주고 싶은 메서드와 프로퍼티를 프로토타입에 정의함으로써 상속을 구현할 수 있습니다. 상속에 관한 자세한 내용은 따로 포스팅 하도록 하겠습니다.







인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



This 바인딩


자바스크립트에서 함수를 호출할 때 기존 매개변수로 전달되는 인자값에 더해, arguments 객체 및 this 인자가 함수 내부로 암묵적으로 전달됩니다. 여기서 this는 함수가 호출되는 방식(호출 패턴)에 따라 다른 객체를 참조하게(this 바인딩) 됩니다.

지금부터 여러가지 호출 패턴에 따른 this 바인딩에 대하여 나열해 보겠습니다.



1.객체의 메서드 호출할 때 This 바인딩


객체의 메서드를 호출할 때에는 해당 메서드를 호출한 객체로 바인딩 됩니다. 


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


위와 같은예제에서 person.sayName();을 호출하면 결과는 'song'이 로그에 찍히게 됩니다. 즉, 자신을 호출한 객체가 this에 바인딩 되었습니다.




2.함수를 호출할 때 this 바인딩


자바스크립트에서 함수를 호출하면, 해당 함수 내부 코드에서 사용된 this는 전역 객체에 바인딩됩니다.(window)

*브라우저에서 전역객체란 window 객체, node.js에서는 global 객체.


var name = 'sooooong';

var sayName =function(){
  console.log(this.name);
}


위와 같은예제에서 sayName();을 호출하면 결과는 'sooooong'이 로그에 찍히게 됩니다. 즉 window.name이 this.name이 되는겁니다.


이러한 함수 호출에서의 this 바인딩 특성은 내부 함수를 호출했을 때에도 동일합니다. 때문에 내부 함수에서 this를 사용할 때 주의해야합니다.

var age = 40;
var person ={
    name : 'song',
    age : 29,
    sayName : function(){
        console.log(this.name);
    },
    sayAge : function(){
     console.log(this.age);

     sayAge2:fucntion(){
      console.log(this.age + 1);
     }
      sayAge2();
    }
}

위와 같은예제에서 person.sayAge();을 호출하면 결과는

29

30

이 아닌

29

41

이 나오게 됩니다. 이유는 sayAge의 내부함수인 sayAge2에서의 this는 window객체이기 때문입니다. 때문에 window.age인 40이 참조되게 되는 것입니다.


이렇게 되는 이유는 자바스크립트에서는 내부 함수 호출 패턴을 정의해 놓지 않기 때문입니다. 내부 함수도 결국 함수이므로 함수 호출 패턴 규칙에 따라서 window에 this가 binding됩니다.


때문에 이러한 자바스크립트의 한계를 극복하려면 부모함수의 this를 내부 함수가 접근 가능한 다른 변수에 저장하는 방법을 사용해야합니다.



var age = 40;
var person ={
    var that = this;
    name : 'song',
    age : 29,
    sayName : function(){
        console.log(this.name);
    },
    sayAge : function(){
     console.log(that.age);

     sayAge2:fucntion(){
      console.log(this.age + 1);
     }
      sayAge2();
    }
}

위와 같이 this를 that에 저장하면 원래 의도했던 29, 30이라는 결과를 얻을수있습니다.

자바스크립트에서는 이러한 this 바인딩의 한계를 극복하기위해 this 바인딩을 명시적으로 할 수 있는 call과 apply라는 메서드를 제공합니다.



3. 생성자 함수를 호출할 때 this 바인딩


자바스크립트의 생성자 함수는 말 그대로 자바스크립트의 객체를 생성합니다. 하지만 c++과 같이 그 형식이 정해지지는 않았고, 기존 함수에 new 연산자를 붙여서 호출하면 해당 함수는 생성자 함수로 동작합니다. 다만 특정 함수가 생성자 함수로 정의되어 있음을 알리려고 첫 글자를 대문자로 하는 것을 권하고 있습니다.


new 연산자로 자바스크립트 함수를 생성자로 호출하면, 다음과 같은 순서로 동작합니다. 


1) 빈 객체 생성 및 this 바인딩

먼저 빈 객체가 생성되고, 이 객체가 생성자 함수가 새로 생성하는 객체입니다. 이 객체는 this로 바인딩됩니다. 따라서 이후 생성자 함수의 코드 내부에서 사용된 this는 이 빈 객체를 가리킵니다.


엄밀히 말해서 빈 객체는 아니고 자신을 생성한 생성자 함수의 prototoype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정합니다.


2) this를 통한 프로퍼티 설정

이후에는 함수 코드 내부에서 this를 사용해서, 앞에서 생성된 빈 객체에 동적으로 프로퍼티나 메서드를 생성할 수 있습니다.


3) 생성된 객체 리턴

특별하게 정의된 리턴문이 없는 경우 this로 바인딩된 새로 생성한 객체가 리턴됩니다. 만약 명시적으로 다른 객체를 리턴하도록 했다면 다른 객체가 리턴됩니다.


var Person = function(name){
     this.name = name;
};
var song = new Person('song');
console.log(song.name);


이렇게 생성자를 이용해 객체를 생성하는 방식과 리터럴을 이용해 생성하는 방식의 차이는 프로토타입 객체(_proto_)에 있습니다.

위에 예제에서의 song객체의 프로토타입 객체는 Person이고 리터럴로 동일한 song 객체를 만들면 그 song객체의 프로토타입 객체는 Object입니다.


이는 자바스크립트 객체 생성 규칙 때문입니다. 자바스크립트 객체는 자신을 생성한 생성자 함수의 prototype 프로퍼티가 가리키는 객체를 자신의 프로토타입 객체로 설정합니다.


객체 리터럴 방식에서는 객체 생성자 함수가 Object이고, 생성자 함수 방식의 경우는 생성자 함수 자체가 프로토타입 객체입니다.



3. call과 apply 메서드를 이용한 명시적인 this 바인딩


자바스크립트에서는 this를 특정 객체에 명시적으로 바인딩시키는 방법도 제공합니다. 이를 가능하게 하는 것이 함수 객체의 기본 프로퍼티인 apply와 call메서드입니다. 


이 메서드들은 모든 함수의 부모 객체인 Function.prototype 객체의 메서드이므로, 모든 함수는 다음과 같이 apply를 호출할 수 있습니다.


function.apply(thisArg, argArray);


call과 apply는 기능은 같고 단지 넘겨받는 인자의 형식만 다릅니다.


apply() 메서드를 호출하는 주체는 함수고, 특정 this를 바인딩할 뿐 본질적인 기능은 함수 호출입니다. sayName(); 은 

sayName.apply(); 와 같습니다.


apply의 첫 번째 인자인 thisArg는 apply() 메서드를 호출한 함수 내부에서 사용한 this에 바인딩할 객체입니다.

즉, 첫 번째 인자로 넘긴 객체가 this에 명시적으로 바인딩됩니다.


두 번째 인자인 argArray는 함수를 호출할 때 넘길 인자들의 배열입니다.


call()은 apply()와 기능은 같지만, apply에서 두 번째 인자로 배열 형태로 넘기는 것을 각각 하나의 인자로 넘기는 차이가 있습니다. 


Person.apply(song, ['song', 29]);

Person.call(song, 'song', 29);


call()과 apply() 함수의 대표적인 용도는 유사 배열 객체에서 배열 표준 메서드를 사용할 때 입니다.




인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



1.함수 생성


자바스크립트에서 함수를 생성하는 방법은 3가지가 있습니다

1)함수 리터럴

아래 코드에서 add에 해당하는 함수 이름은 생략 가능합니다.


function add(x, y){

return x + y;

}


2)함수 선언문

함수명이 반드시 정의되어 있어야 합니다.


function add(x, y){

return x + y;

}


3)함수 표현식(추천!! 이유는 2번 함수 호이스팅에서 설명하겠습니다.)

아래와 같이 함수에 이름을 만들지 않고 변수에 할당하는 방식을 익명 함수 표현식이라 합니다.

함수 내부에서 재귀적으로 호출할 필요가 있을 때에는 함수에 이름을 붙여주면 됩니다.

여기서 add는 함수 이름이 아닌 익명함수를 참조하는 변수입니다. //함수변수


var add = function (x, y){

return x + y;

};


함수 표현식으로 함수를 생성하는 경우에는 세미콜론을 필수로 사용해야합니다.

이유는 다음과 같은 오류가 발생할 수 있기 때문입니다.


var func = function() {
return 42;
}
(function(){
return 24;
})();


위와 같은 코드가 있을 때 func 함수 표현문 끝에 세미콜론이 없기 때문에 자바스크립트 엔진은 중괄호에서 끝났다고 인식하지 못하고 ()로 감싸진 익명함수 생성 리터럴을 무시하고 ()로 func를 실행하게됩니다.

때문에 그 뒤에 있는 ()는 func의 리턴 값인 42()와 같이 인식하게 되어 오류가 발생합니다.




2.함수 호이스팅


앞에서 함수를 생성하는 여러가지 방법에 대해서 나왔는데 이들 사이에는 동작 방식이 약간 차이가 있습니다.

그중의 하나가 바로 함수 호이스팅(Function Hoisting)입니다. 


add(3, 5);

function add(x, y){
return x + y;
};

add(3, 4);


맨 위에 add(3, 5); 같은 경우에는 아직 함수가 선언되지 않았는대도 불구하고 호출이 가능합니다. 

함수가 위치한 위치에 상관없이 함수 선언문 형태로 정의한 함수의 유효 범위는 코드의 맨 처음부터 시작한다. 

이것을 함수 호이스팅이라고 합니다.


함수 표현식으로 함수를 생성하면 이러한 함수 호이스팅이 일어나지 않는다. 


3.함수도 객체다.


자바스크립트에서는 함수를 일급객체라고 부른다.


1) 프로퍼티를 가질 수 있다.


2) 자바스크립트에서 함수는 값으로 취급된다.

- 리터럴에 의해 생성

- 변수나 배열의 요소, 객체의 프로퍼티 등에 할당 가능

- 함수의 인자로 전달 가능

- 함수의 리턴값으로 리턴 가능

- 동적으로 프로퍼티를 생성 및 할당 가능

*위와 같은 자바스크립트 함수의 특성들을 가지고 있는 객체를 일급객체라고 한다.


3) 함수 객체의 기본 프로퍼티

함수는 객체이므로 함수 객체만의 표준 프로퍼티가 정의되어 있습니다.

대표적으로 arguments, caller, length, name, apply, call, bind이 있습니다.

이들 프로퍼티들은 Function.prototype 객체로 부터 받은 것입니다.


- arguemnts

함수로 전달된 인자값들

- length

인자의 수

- caller

자신을 호출한 함수

- name

함수의 이름

- prototype

prototype 프로퍼티는 _proto_와 다릅니다.

* _proto_ : 자신의 부모 역할을 하는 프로토타입 객체를 가리킨다.


prototype 프로퍼티는 함수가 생성될 때 만들어지며 단지 constructor 프로퍼티 하나만 있는 객체를 가리킵니다.

그리고 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor 프로퍼티는 자신과 연결된 함수를 가리킵니다.


즉, 자바스크립트에서는 함수를 생성할 때, 함수 자신과 연결된 프로토타입 객체를 동시에 생성하며

이 둘은 prototype 과 constructor라는 프로퍼티로 서로를 가리킵니다.




그러니까 예를들어 func 함수는 생성될 때 프로토타입 객체가 동시에 생성되고 이 객체를 prototype 프로퍼티로 참조합니다. 이 프로토타입 객체는 constructor라는 프로퍼티를 가지고 있고, 이 또한 객체이므로 _proto_ 프로퍼티도 가지고 있습니다.




prototype 프로퍼티가 가리키고 있는 func의 프로토타입객체는 constructor 프로퍼티로 func 함수를 가리킵니다.




이해하기 힘든 개념일 수 있습니다. 이후 프로토타입과 프로토타입 체이닝에 대해서 자세히 포스팅할 예정이니 이정도 책 내용 정도로만 이해하시고 넘어가시면 될 것 같습니다.



4.함수의 다양한 형태


1) 콜백함수

함수를 명시적으로 호출하는 것이 아니라 개발자는 함수를 등록하고, 특정 이벤트가 발생하거나 특정 시점에 도달하였을 때 시스템에서 호출하는 함수를 말합니다.


대표적인 콜백 함수의 사용 예가 자바스크립트에서의 이벤트 핸들러 처리입니다. 웹페이지가 로드 되거나, 키보드 입력되는 등 DOM이벤트가 발생할 경우 브라우저는 정의된 DOM 이벤트에 해당하는 이벤트 핸들러를 실행시킵니다. 개발자가 만약 해당하는 이벤트 핸들러에 콜백 함수를 등록했다면 이벤트가 발생할 때마다 브라우저에 의해 실행되게 됩니다.



2) 즉시 실행 함수

함수를 정의함과 동시에 바로 실행하는 함수를 즉시 실행 함수(immediate functions)라 합니다.

함수가 선언되자마자 실행되게 만든 즉시 실행 함수의 경우 같은 함수를 다시 호출할 수 없습니다. 따라서 최초 한 번의 실행만을 필요로 하는 초기화 코드에 사용할 수 있습니다.

예를 들어 아래와 같은 형태로 사용하실 수 있습니다.

(function(name){
//initialize 함수
console.log("Init");
InitFrame();
InitModel();
InitMain();
user.name = name;
})("song");

그리고 jQuery와 같은 자바스크립트 라이브러리나 프레임워크 소스들에서 사용되니 알아두면 좋습니다.

jQuery에서 즉시 실행 함수를 사용하는 이유는 함수 유효 범위 때문입니다.

*자바스크립트에서 변수는 전역 유효 범위를 가진다. 그러나 함수 내부의 변수(var)는 함수 유효 범위를 가진다.



따라서 라이브러리 코드를 즉시 실행 함수 내부에 정의해두면, 라이브러리 내의 변수들에 외부에서 접근할 수 없습니다. 따라서 즉시 실행 함수 내에 라이브러리 코드를 추가하면 전역 네임스페이스를 더럽히지 않고, 다른 라이브러리들이 동시에 로드 되더라도 라이브러리간 변수 이름 충돌을 방지할 수 있습니다.

3) 내부 함수

함수 내부에 정의된 함수를 내부 함수라고 부릅니다. 내부 함수는 클로저를 생성하거나 부모 함수 코드에서 외부에서의 접근을 막고 독립적인 헬퍼 함수를 구현하는 용도로 사용합니다.


내부 함수의 특징은 다음과 같습니다.

- 내부 함수에서는 자신을 둘러싼 부모 함수의 변수에 접근이 가능하다.

C++과 같은 언어를 사용하셨던 분들은 낯설게 느끼실 수 있습니다. 이러한 것이 가능한 이유는 자바스크립트의 스코프 체이닝 때문입니다.  


*클로저, 스코프 체이닝은 따로 포스팅하겠습니다. ^^



- 내부 함수는 일반적으로 자신이 정의된 부모 함수 내부에서만 호출이 가능하다.


5. 함수 리턴


1) 일반 함수나 메서드는 리턴값을 지정하지 않을 경우, undefined 값이 리턴된다.

2) 생성자 함수에서 리턴값을 지정하지 않을 경우 생성된 객체가 리턴된다.

3) 생성자 함수에서 리턴값을 명시적으로 특정 객체로 지정한 경우 지정된 객체가 리턴된다.

그러나 만약 객체가 아닌 value값을 리턴 하려한다면 무시하고 this로 바인딩된 객체가 리턴됩니다.


다음번 포스팅은 함수와 this에 대한 내용입니다.



인사이드 자바스크립트를 공부하며 정리하는 포스팅입니다.



배열(Array)

배열은 자바스크립트 객체의 특별한 형태다. C의 배열과 유사하지만 크기를 지정하지 않아도 되며, 어떠한 타입의 데이터를 저장하더라도 상관없다.




1. 배열 생성

배열의 생성은 두 가지 방법으로 할 수 있습니다.

일반적으로 배열 리터럴 방식으로 생성합니다.


1) 배열 리터럴

[] 대괄호 문법을 이용한 리터럴 방식으로 배열 생성


var str = 'test';
var colorArr = ['orange', 'yellow', blue];

2) 생성자 함수를 이용한 생성

호출할 때 인자가 1개이고, 숫자인 경우: 호출된 인자를 length로 갖는 빈 배열을 생성한다.

그 외의 경우: 호출된 인자를 요소로 갖는 배열 생성.

var colorArr = new Array(3);
colorArr.push('orange');
colorArr.push('yellow');
colorArr.push('blue');
//var colorArr = new Array('orange', 'yellow', 'blue');


2. length 프로퍼티

배열의 원소 개수를 나타낸다. 그러나 배열에 존재하는 실제 원소 개수와 일치하는 것은 아니다. 

배열 내에 가장 큰 인덱스에 1을 더한 값이다.


배열의 가장 큰 인덱스값이 변하면, length 값 또한 자동으로 그에 맞춰 변경된다.

var arr = []; arr[0] = 1; arr[100] = 2; console.log(arr.length); // 101


배열의 length 프로퍼티는 명시적으로 값을 지정할 수도 있습니다.

만약 현재 배열의 length보다 큰 값을 지정한다면 현재 length Index 이후 값들은 undefined로 채워지게 됩니다.


length는 배열 표준 메서드에 영향을 미친다.


배열 표준 메서드인 push 메서드는 length값을 기준으로 수행됩니다. 

만약 length가 5라면 length는 마지막 Index + 1이므로 push 메서드의 수행결과로 새롭게 추가될 원소의 자리는 배열[length]가 되게됩니다.

length 프로퍼티는 이렇게 배열 표준 메서드에 영향을 미칠 수 있는 프로퍼티이므로 중요합니다.


이렇게 중요한 length 프로퍼티가 객체에 프로퍼티로 존재하면 어떻게될까요?

자바스크립트에서는 이렇게 length 프로퍼티를 가진 객체를 유사 배열 객체라고 부릅니다. 


* 배열에 동적으로 프로퍼티가 추가되어도 length는 증가하지 않는다. 배열의 length 프로퍼티는 오직 배열 원소의 가장 큰 인덱스가 변했을 경우만 변경된다.




3. 배열과 객체

자바스크립트에서는 배열 역시 객체입니다. 


객체와 배열의 typeof 값은 둘 다 Object로 동일합니다. 


그치만 분명히 배열과 객체는 다릅니다.

- length 프로퍼티의 존재 여부

- 배열 표준 메서드 존재 여부


이러한 차이점이 생기는 이유는 

객체의 경우 프로토타입으로 Object.prototype 객체를 갖고,

배열의 경우 프로토타입으로 Array.prototype 객체를 갖게되기 때문입니다.


length, 배열 표준 메서드는 Array.prototype 객체가 가지고 있는 프로퍼티입니다.


3. 배열의 프로퍼티 열거


배열을 객체와 같이 for in 문으로 프로퍼티를 열거하면 의도치 않은 동작이 수행될 수 있습니다.


var arr = ['a', 'b' , 'c']; arr['name'] = 'song'; for(var prop in arr) { console.log(prop, arr[prop]); } //0 a //1 b //2 c //name song //....여러가지 배열 프로퍼티 for(var i = 0; i< arr.length; i++){ console.log(i, arr[i]) } //0 "a" //1 "b" //2 "c"

위와 같이 for in문을 사용하면 배열의 원소 뿐아니라 여러가지 프로퍼티들도 같이 출력되게 됩니다. 의도치 않은 동작이 수행될 수 있겠죠?

때문에 배열의 원소를 탐색할 때에는 for문을 사용하는 것이 좋습니다.


4. 배열의 요소 삭제


1) delete

delete 연산자는 length 값은 그대로 유지한채 삭제합니다. 즉, 배열의 원소값을 undefined로 바꿔줍니다.


2) splice

만약 배열의 요소를 완전히 삭제하고 싶다면 splice()메서드를 이용해서 배열의 요소를 삭제할 수 있습니다.


splice(start, deleteCount, item...)

- start: 배열에서 시작 인덱스

- deleteCount: 삭제할 요소의 수

- item: 삭제할 위치에 추가할 요소