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



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) 루프 안에서 클로저를 활용할 때는 주의하자.