자바스크립트 특징을 보여주는 코드

2017. 1. 18. 18:31Dev/javascript

반응형



아래의 D3 코드를 막힘없이 전부 이해한다면 자바스크립트 고수가 아닐까 싶다.


D3 소스 중 일부


var rj3 = {};

rj3.svg = {};

rj3.svg.line = function() {
  var getX = function(point) {
    return point[0]; 
  },
  getY = function(point) {
    return point[1]; 
  },
  interpolate = function(points) {
    return points.join("L"); 
  };

  function line(data) {
    var segments = [],
      points = [],
      i = -1,
      n = data.length,
      d;

    function segment() {
      segments.push("M",interpolate(points)); 
    }

    while (++i < n) {
      d = data[i]; 
      points.push([+getX.call(this,d,i), +getY.call(this,d,i)]); 
    }

    if (points.length) {
      segment();
    }

    return segments.length ? segments.join("") : null;
  }

  line.x = function(funcToGetX) {
    if (!arguments.length) return getX;
    getX = funcToGetX;
    return line;
  };

  line.y = function(funcToGetY) {
    if (!arguments.length) return getY;
    getY = funcToGetY;
    return line;
  };

  return line;
};


코드 실행


(function() {
  var arrayData = [
        [10,130],
        [100,60],
        [190,160],
        [280,10]
      ],
      lineGenerator = rj3.svg.line(),
      path = lineGenerator(arrayData);
  console.log(path);  //"M10,130L100,60L190,160L280,10"
}());

분석


결과적으로 path는 D3에서 그래프를 그리는데 사용할 수 있도록 가공받은 데이터를 받았다.

lineGenerator = rj3.svg.line() 에서 lineGenerator에는 뭐가 들어 있을까?

마지막 return line; 코드로 부터 함수 line()이 들어 있는 것을 알 수 있다.

lineGenerator = function line(){....


line이 여러군데에서 사용되어 조금 헷갈릴 수 있지만 천천히 살펴보자.

rj3.svg.line 변수에 담긴 유산을 보자. 익명 함수가 실행되면서 남긴 유산들이다.

lin() 함수와 line() 함수의 멤버 x(line.x), y(line.y)

이 3가지가 있다. 물론 직접적으로 사용할 수 있는 것들에 한정된 것이다. 

getX, getY, interpolate 이들은 어디로 갔을까?

rj3.svg.line이 즉실 실행되면서 되돌려 준것은 line이라는 것을 명심하자.

그렇다고 getX, getY, interpolate 이들이 영영 사라진 것은 아니다.

line()과 line.x, line.y를 통해서 접근(사용) 가능하도록 만들어놨다. 

getX, getY, interpolate 이들은 private 멤버가 된것이다.

실제로 다른 언어로 개발했던 개발자들은 line이 return되면서 getX, getY, interpolate 들은 스택에서 뛰쳐나가 영영 사라질 것이라 생각하지만 자바스크립트는 다르다. closure가 존재하기 때문이다. line() 함수와 형제였던(같은 레벨 혹은 같은 부모를 둔 멤버라고 이해하면 되겠다) 멤버들도 line과 함께 남아서 의리를 지킨다.

이것이 closure다. 의리.

결론적으로 lineGenerator 변수를 통해서 직접적으로 사용할 수 있는 것들은 line(), line.x, line.y 3가지이며 이들을 통해서 간접적으로 접근할 수 있는 애들은 getX, getY, interpolate 3가지가 있는 것이다.


이쯤에서 궁금해진다. line.x와 line.y는 어디에 쓰는 물건일꼬?

만약 배열이 아닌 객체로 값이 넘어간다면?

(function() {
  var objectData = [
        { x: 10, y: 130 },
        { x: 100, y: 60 },
        { x: 190, y: 160 },
        { x: 280, y: 10 }
      ],
      lineGenerator = rj3.svg.line()
        .x(function(d) { return d.x; }) //여기서 
        .y(function(d) { return d.y; }), //쓰였다.
      path = lineGenerator(objectData); // 아까와 달리 배열이 아닌 객체를 넘겨줌
  console.log(path); // "M10,130L100,60L190,160L280,10"  결과는 아까와 똑같다.
}());


위의 코드를 천천히 이해해보면 

rj3.svg.line().x(function(d) { return d.x; }).y(function(d) { return d.y; })

이 코드로 인해서 getX와 getY x,y가 전달하는 인자, 그러니까 익명 함수로 교체된 것을 알 수 있다.

여기서 x,y의 return값은 line이므로 체인링을 가능케 하는 것도 확인할 수 있다.

line.x line.y는 getX getY 내용을 변경하는 역할을 맡고 있는 것이다.

결과적으로 배열을 기본적으로 사용할 수 있도록 설계되어있지만 다른 형태도 사용하도록 커스텀 기능도 제공하고 있는 아주 멋진 설계이다.


여기서 궁금한 코드가 하나 더 있다. line() 함수의 while문에 존재하는 코드.

points.push([+getX.call(this,d,i), +getY.call(this,d,i)]);

+는 자료형을 Number로 바꿔주고( +"10" -> 10)... 나머지 call() 함수와 this에 대해서 알아보자.


call() / this


this는 함수를 호출하는 '점(.) 앞의 객체' 라고 할 수 있다.

아래의 예제를 보고 이해했으면 좋겠다..

위 d3코드를 사용한다는 것을 알고보자.

rj3.svg.samples = {};

rj3.svg.samples.functionBasedLine = function functionBasedLine() {
  var firstXCoord = 10,
    xDistanceBetweenPoints = 50,
    lineGenerator,
    svgHeight = 200;

  lineGenerator = rj3.svg.line()
    .x(function(d,i) { return firstXCoord + i * xDistanceBetweenPoints; }) // getX 익명함수 교체
    .y(function(d) { return svgHeight - this.getValue(d); }); // getY 익명함수 교체

  return lineGenerator; // line() 반환받음
};

(function() {
  var yearlyPriceGrapher = {
    lineGenerator: rj3.svg.samples.functionBasedLine(),

    getValue: function getValue(year) {
      return 10 * Math.pow(1.8, year-2010);
    }
  },
  years = [2010, 2011, 2012, 2013, 2014, 2015],
  path = yearlyPriceGrapher.lineGenerator(years);

  console.log(path); // "M10,190L60,182L110,167.6L160,141.68L210,95.02399999999997L260,11.043199999999956"
}());


분석


(function(){... 안에 코드 중 lineGenerator: rj3.svg.samples.functionBasedLine(), 로 인해서 getX, getY 내용이 교체되며 return lineGenerator; 코드로 인해 yearlyPriceGrapher.lineGenerator 값은 line() 함수를 갖는다.

결론적으로 yearlyPriceGrapher는 line()과 getValue()를 갖는다.

그리고 yearlyPriceGrapher.lineGenerator(years); 코드가 실행되면 교체된 getY = function(d) { return svgHeight - this.getValue(d); } 가 실행될텐데 여기서 궁금한것은 this가 어딜 바라보고 있는지다. 여기서 this는 yearlyPriceGrapher바라보고 있다.

yearlyPriceGrapher.lineGenerator(years); 코드를 저자의 말에 따라 보자면 lineGenerator 점 앞에 yearlyPriceGrapher를 바라보는 것이다.

자세히 설명하자면 getY에 교체된 함수에 있는 this는 points.push([+getX.call(this,d,i), +getY.call(this,d,i)]); 코드에 의해서 this 컨텍스트가 설정된다. 현재 실행되고 있는 lineGenerator() 함수의 상위 컨텍스트다. 바로 yearlyPriceGrapher 객체. yearlyPriceGrapher객체에는 getValue가 존재하고 있으니 문제 없이 깔끔하게 실행된다. 여기서 call은 바로 this 컨텍스트를 변경하는 역할을 하는 것이다


결론


위의 d3 코드 일부분을 처음 봤을때는 call() 함수와 line.x, line.y 가 무슨 의미가 있는지 이해하기 힘들었다.

하지만 이것은 코드의 확장성을 나타내는 아주 멋진 설계라는 것을 깨달았다.

이 코드는 짧지만 그 안에는 함수의 객체성, 중첩 함수, 함수 오버로딩, 덕 타이핑, 클로저, this의 강력함을 보여준다.


  • 자바스크립트에서 함수는 객체이다.
  • 중첩 함수는 rj3.svg.line에 있는 line()함수와 같이 함수안에 함수가 있는 형태를 말한다.
  • 함수 오버로딩은 인자가 없거나 하나이상 정의된 함수에 인자의 갯수를 가변적으로 적용해서 함수를 사용할 수 있는 기법을 말한다.
  • 덕 타이핑은 형태는 다르지만 똑같은 결과를 나타내는 코딩 기법이다.
  • 클로저는 반환되는 객체 범위에 있는 스코프를 모두 유지하는 상태를 말한다.
  • 자바스크립트에서 this는 작성된 위치가 아닌 실행될 때 평가된다.




반응형

'Dev > javascript' 카테고리의 다른 글

Array.prototype.slice.call(arguments) 에 대하여  (2) 2017.06.25
패턴  (0) 2017.06.04
자바스크립트 객체  (0) 2017.01.19
자바스크립트 도구 다루기  (0) 2017.01.19
javascript call()  (0) 2017.01.18