💻/BOOK

Inside JavaScript - 내용정리(2)

김씨리 2021. 1. 28. 02:06

4. 함수와 프로토타입 체이닝

 

함수 리턴 :

  • 규칙 1) 일반 함수나 메소드는 리턴값을 지정하지 않을 경우, undefined 가 리턴된다
  • 규칙 2) 생성자 함수에서 리턴값을 지정하지 않을 경우 생성된 객체가 리턴된다
    • 별도의 리턴값 지정 안했을 경우, this로 바인딩된 새 객체가 리턴. 따라서 생성자 함수에서는 일반적으로 리턴값 지정 안함.
    • 명시적으로 리턴값을 넘기면 해당 객체나 배열이 리턴되지만, 객체가 아닌 boolean, number, string의 경우는 무시되고 this로 바인딩된 객체가 리턴.

 

프로토타입 체이닝 :

  • 자바스크립트의 객체 생성 규칙
    • 모든 객체는 자신을 생성한 생성자 함수의 prototype property가 가리키는 프로토타입 객체를 자신의 부모 객체로 설정하는 [[Prototype]] 링크로 연결한다.
function Person(name){
  this.name = name;
}

var foo = new Person('foo');

 

  • Person() 생성자 함수는 prototype property로 자신과 링크된 프로토타입 객체(Person.prototype)를 가리킴.
  • 이 프로토타입 객체는 constructor property로 Person() 생성자 함수를 가리킴.
  • new로 foo 객체를 생성하면, foo 객체는 [[Prototype]] 링크로 프로토타입 객체(Person.prototype)를 가리킴.
  • 객체를 생성하는 건 생성자 함수의 역할이지만, 생성된 객체의 실제 부모 역할을 하는 건 생성자의 prototype property가 가리키는 프로토타입 객체.

 

  • 객체 리터럴 방식으로 생성된 객체의 프로토타입 체이닝
    • 프로토타입 체이닝을 통해, 객체는 자기 자신의 property뿐만 아니라 부모 역할을 하는 프로토타입 객체의 property까지 자기 것처럼 접근 가능.
var myObject = {
  name: 'foo',
  sayName: function(){
    console.log('My name is '+this.name);
  }
};

myObject.sayName(); //My name is foo
console.log(myObject.hasOwnProperty('name')); //true
console.log(myObject.hasOwnProperty('nickName')); //false
myObject.sayNickName(); //error

 

 

  • hasOwnProperty()는 표준 API 함수로, Object.prototype 객체에 있어서 호출 시 에러가 안났음. (myObject는 객체 리터럴로 생성되었기 때문에 Object() 내장 생성자 함수로 생성)

 

  • 생성자 함수로 생성된 객체의 프로토타입 체이닝
    • 객체 리터럴 방식은 객체의 prototype property가 바로 Object.prototype 객체로 이어지지만, 생성자 함수로 생성되면 한 단계를 더 거쳐서 Object.prototype 객체에 도달하게 된다.
function Person(name, age, hobby){
  this.name = name;
  this.age = age;
  this.hobby = hobby;
}

var foo = new Person('foo', 30, 'tennis');

console.log(foo.hasOwnProperty('name')); //true

 

  • foo 객체는 부모인 Person.prototype 객체에 hasOwnProperty() 메소드가 없다. 하지만 Person.protytpe이 Object.prototype을 프로토타입 객체로 가지므로 Object.prototype 객체에 hasOwnProperty() 메소드가 있어서 true가 출력됨.

 

  • Object.prototype 객체가 프로토타입 체이닝의 종점. Number.prototype, String.prototype, Array.prototype 등도 자신의 프로토타입으로 Object.prototype 을 가짐.
  • Object.prototype, String.prototype 등 표준 빌트인 프로토타입 객체에도 사용자 정의 메소드 추가 허용.
  • 프로토타입 객체 역시 자바스크립트 객체이므로 동적으로 property 추가/삭제 가능.
function Person(name){
  this.name = name;
}

var foo = new Person('foo');

Person.prototype.sayHello = function(){
  console.log('Hello');
}

foo.sayHello(); //Hello

 

 

  • 프로토타입 객체는 메소드를 가질 수 있음. 프로토타입 메소드 내부에서 this를 사용한다면 그 메소드를 호출한 객체에 바인딩.
function Person(name){
  this.name = name;
}

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

var foo = new Person('foo');
console.log(foo.getName()); //foo

Person.prototype.name = 'person';
console.log(Person.prototype.getName()); //person

 

  • foo.getName() 할 때는 this가 foo객체를 바인딩, Person.prototype.getName() 할 때는 this가 Person.prototype객체를 바인딩

 

  • 디폴트 프로토타입 객체는 다른 일반 객체로 변경 가능.
    • 생성자 함수의 프로토타입 객체가 변경되면, 변경된 시점 이후에 생성된 객체들은 변경된 프로토타입 객체로 [[Prototype]] 링크를 연결해야 함.
    • 프로토타입 객체 변경 전에 생성된 객체들이면 기존대로 유지.
function Person(name){
  this.name = name;
}

console.log(Person.prototype.constructor); //Person(name)
var foo = new Person('foo');
console.log(foo.country); //undefined

Person.prototype = {
  country: 'korea',
};
console.log(Person.prototype.constructor); //Object()

var bar = new Person('bar');

console.log(foo.country); //undefined
console.log(bar.country); //korea
console.log(foo.constructor); //Person(name)
console.log(bar.constructor); //Object()

 

 

  • 객체의 property 읽기나 메소드를 실행할 때만 프로토타입 체이닝이 동작.

 

 

 

 

5. 실행 컨텍스트와 클로저

 

개념 :

  • 실행 컨텍스트 : 콜 스택(함수 호출 시 그 정보가 쌓여있는 스택)에 들어가는 실행 정보 하나와 비슷
  • 실행 가능한 자바스크립트 코드 블록이 실행되는 환경.
  • 실행 컨텍스트 생성 과정
//예시 코드
function execute(param1, param2){
  var a = 1, b = 2;
  function func(){
    return a+b;
  }
  return param1+param2+func();
}

execute(3, 4);

 

  1. 활성 객체 생성 = 실행 컨텍스트 생성된 후 자바스크립트 엔진이 생성하는 해당 컨텍스트에서 실행에 필요한 여러 가지 정보를 담을 객체.
    매개변수나 사용자 정의 변수/객체 저장, 새로 만들어진 컨텍스트로 접근 가능(사용자 접근X)
  2. arguments 객체 생성. 앞서 만들어진 활성 객체는 arguments property로 이 arguments 객체를 참조.
  3. 스코프 정보 생성 = 현재 컨텍스트의 유효 범위를 나타냄. 스코프 정보는 현재 실행 중인 실행 컨텍스트 안에서 연결 리스트와 유사한 형식으로 만들어짐.
    현재 컨텍스트의 특정 변수에 접근 또는 상위 실행 컨텍스트의 변수 접근 시, 리스트 활용.

    이 리스트가 스코프 체인([[scope]])이고, 현재 생성된 활성 객체는 스코프 체인 제일 앞에 추가됨. execute() 함수의 인자나 지역 변수 등에 접근 가능.
  4. 변수 생성. 앞서 생성된 활성 객체가 변수 객체로 사용됨.(활성 객체=변수 객체) 함수 인자는 각각의 property가 만들어지고 그 값이 할당됨. execute() 함수 안에 정의된 변수 a, b, func가 생성.(메모리에 생성만 하고 초기화는 아직!)
  5. this 바인딩. 위 코드에서는 this가 참조하는 객체가 없으므로 전역 객체 Object를 참조.
  6. 코드 실행. 변수의 초기화 및 연산, 또 다른 함수 실행 등이 이루어짐.
    전역 실행 컨텍스트의 경우, arguments 객체가 없고 전역 객체 하나만을 포함하는 스코프 체인이 있음. 전역 실행 컨텍스트에서는 변수 객체=전역 객체.

    전역적으로 선언된 함수와 변수가 전역 객체의 property가 됨.

 

  • 스코프 체인
    • 자바스크립트에서는 for(){...}, if{...} 와 같은 블록 구문은 유효 범위(스코프)가 없음. 함수만이 유효 범위의 한 단위.
    • 이 유효 범위를 나타내는 스코프가 [[scope]] property로 각 함수 객체 내에서 연결 리스트 형식으로 관리되는 것.
    • 각각의 함수는 [[scope]] property로 자신이 생성된 실행 컨텍스트의 스코프 체인을 참조. 함수가 실행되면서 실행 컨텍스트가 만들어지고, 실행된 함수의 [[scope]] property 기반으로 새로운 스코프 체인을 만듦.
    • 전역 실행 컨텍스트의 스코프 체인
      • 전역 실행 컨텍스트 하나만 실행되고 있어 참조할 상위 컨텍스트가 없으므로, 이 변수 객체의 스코프 체인은 자기 자신만을 가진다. 변수 객체의 [[scope]]는 변수 객체 자신을 가리킴.
    • 함수 호출한 경우 생성되는 실행 컨텍스트의 스코프 체인
      • 함수 객체 생성 시, [[scope]]는 현재 실행되는 컨텍스트의 변수 객체에 있는 [[scope]]를 그대로 가진다. 즉, 함수 func() 객체가 생성되면 func()의 [[scope]]는 [func 변수 객체 - 전역 객체]가 된다.
      • 각 함수 객체는 [[scope]] property로 현재 컨텍스트의 스코프 체인을 참조.
var value = "value1";

function printFunc(){
  var value = "value2";

  function printValue(){
    return value;
  }

  console.log(printValue());
}

printFunc(); //value2

 

  • 각 함수 객체가 처음 생성될 당시 실행 컨텍스트가 무엇인지도 생각해야 한다.
var value = "value1";

function printValue(){
  return value;
}

function printFunc(func){
  var value = "value2";
  console.log(func());
}

printFunc(printValue()); //value1

 

 

클로저 :

//예제 코드
function outerFunc(){
  var x = 10;
  var innerFunc = function(){
    console.log(x);
  }
  return innerFunc;
}

var inner = outerFunc();
inner(); //10

 

  • outerFunc 실행 컨텍스트가 사라진 이후에 innerFunc 실행 컨텍스트가 생성됨. outerFunc 실행 컨텍스트가 사라진 이후에도 outerFunc 변수 객체는 여전히 남아있고, innerFunc의 스코프 체인으로 참조.(클로저의 개념)
  • outerFunc이 innerFunc을 리턴하더라도 outerFunc의 변수 객체는 innerFunc의 스코프 체인에 남아있어야지만 지역변수 접근이 가능.
  • 이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 함수 = 클로저
  • 따라서 예제 코드에서는 outerFunc에서 선언된 x를 참조하는 innerFunc 가 클로저. 클로저로 참조되는 외부 변수인 x는 자유 변수.
  • 성능, 자원 면에서 손해 볼 수 있어서 잘 활용해야 함!
  • 클로저 활용
    • 특정 함수에 사용자가 정의한 객체의 메소드 연결하기
function HelloFunc(){
  this.greeting = "hello";
}

HelloFunc.prototype.call = function(func){
  func ? func(this.greeting) : this.func(this.greeting); //this.func(this.greeting)을 통해 동작함
}

var userFunc = function(greeting){
  console.log(greeting);
}

var objHello = new HelloFunc();
objHello.func = userFunc;
objHello.call(); //hello

 

  • 여기서 HelloFunc()은 자신의 지역 변수인 greeting만을 인자로 사용자 정의 함수에 넘긴다.
//위 코드와 비슷
function saySomething(obj, methodName, name){
  return (function(greeting) {
    return obj[methodName](greeting, name);
  });
}

function newObj(obj, name){
  obj.func = saySomething(this, "who", name);
  return obj;
}

newObj.prototype.who = function(greeting, name){
  console.log(greeting + " " + (name || "everyone") );
}

var obj1 = new newObj(objHello, "zzoon");

 

  • newObj()는 위에 코드에서 HelloFunc()의 객체를 더 자유롭게 활용하려고 쓴 함수. 첫 인자인 obj는 HelloFunc()의 객체가 되는 것과 같다.
  • var obj1 = new newObj(objHello, "zzoon"); 으로 newObj()안의 코드가 실행된다. obj1은 인자로 넘겼던 objHello 객체에서 func property에 참조된 함수만 바뀐 객체.
  • obj1.call(); 을 실행하면 newObj.prototype.who가 호출되어 hello zzoon 출력됨.
  • 여기서 클로저는 saySomething()에서 반환되는 function(greeting){...} 이 되고, 이 클로저는 자유 변수 obj, methodName, name을 참조.

 

함수의 캡슐화 :

var getCompletedStr = (function(){
  var buffAr = [ 'I am ', '', '. I live in ', '', '. I\'m ', '', ' years old.',
  ];
  return (function(name, city, age){ //클로저. 자유변수 buffAr를 스코프 체인에서 참조.
    buffAr[1] = name;
    buffAr[3] = city;
    buffAr[5] = age;
    return buffAr.join('');
  });
})();

var str = getCompletedStr('zzoon', 'seoul', 16);
console.log(str);

 

  • 익명 함수를 반환받은 getCompletedStr이 실행될 때 buffAr를 참조한다.
  • setTimeout()에 지정되는 함수의 사용자 정의
    •  setTimeout()은 웹 브라우저에서 제공하는 함수로, 첫 번째 인자로 넘겨지는 함수 실행의 스케쥴링을 할 수 있음. 두 번째 인자는 ms 단위의 시간 간격.
    • 변수 func에 callLater의 반환값 함수(클로저)를 받아서 setTimeout()의 첫 번째 인자로 넣어준다.
function callLater(obj, a, b){
  return (function(){
    obj["sum"] = a+b; 
    console.log(obj["sum"]);
  });
}

var sumObj = { 
  sum: 0
} 

var func = callLater(sumObj, 1, 2);
setTimeout(func, 500);

 

  • 클로저 활용 시 주의사항
    • 클로저의 property값이 쓰기 가능하므로 그 값이 여러 번 호출로 항상 변할 수 있음에 유의
    • 하나의 클로저가 여러 함수 객체의 스코프 체인에 들어가 있을 수도 있음
function func(){ 
  var x = 1; 
  return {
    func1: function(){ console.log(++x); }, 
    func2: function(){ console.log(-x); }
  }; 
};
  
var exam = func();
exam.func1();
exam.func2();

 

  • 루프 안에서 클로저를 활용할 때 주의
function countSeconds(howMany){
  for(var i = 1; i <= howMany; i++){ 
    setTimeout(function(){ 
      console.log(i);
    },  i*1000);
  }
};
countSeconds(3);

 

  • 결과는 4가 연속 3번 1초 간격으로 출력된다.
  • setTimeout의 인자로 들어가는 함수는 자유변수 i를 참조. 하지만 이 함수가 실행되는 시점은 countSeconds() 종료된 이후이고, i는 이미 4.
function countSeconds(howMany){
  for(var i = 1; i <= howMany; i++){
  (function (currentI){ 
    setTimeout(function(){ 
      console.log(currentI);
    }, currentI*1000);
  }(i));
};
countSeconds(3);

 

  • 루프 i값의 복사본인 currentI를 setTimeout에서 사용하면, 1, 2, 3을 1초 간격으로 출력한다.

 

 

 

6. 객체지향 프로그래밍

프로토타입 기반 언어는 객체의 자료구조, 메소드 등을 동적으로 바꿀 수 있다.
정확성, 안전성, 예측성은 클래스 기반 언어보다 못하지만 자유도가 높다는 장점이 있다.

 

클래스, 생성자, 메소드 :

  • 모두 함수로 구현 가능.
  • 클래스 및 생성자의 역할을 하는 함수가 있고, new 키워드로 인스턴스 생성하여 사용.
  • 함수를 prototype property에 정의하면 객체를 새로 생성할 때마다 각 객체가 따로 함수 객체를 생성할 필요가 없다. 더 효율적.
Function.prototype.method = function(name, func){ 
  this.prototype[name] = func; 
}

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

Person.method("setName", function(value){ 
  this.name = value;
});

Person.method("getName", function(){
  return this.name;
});

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

 

 

상속 :

  • 객체의 프로토타입 체인을 이용해 상속을 구현한다. 객체 리터럴 중심.
  • 프로토타입을 이용한 상속
function create_object(o){ 
  function F(){}
  F.prototype = o;
  return new F(); 
}

 

  • create_object() 함수는 인자로 들어온 객체를 부모로 하는 자식 객체 생성해서 반환.
  • 자식 객체는 자신의 메소드를 재정의, 추가 확장 가능. => extend() 함수로 자신이 원하는 객체나 함수를 추가시킬 수 있음.
jQuery.extend = jQuery.fn.extend = function(obj.prop){//jQuery.fn은 jQuery.prototype
  if(!prop){ //extend 함수의 인자가 하나만 들어오면 현재 객체 this에 인자로 들어오는 객체 property 복사, 두 개가 들어오면 첫 번째 객체에 두 번째 객체 property 복사. 
    prop = obj;
    obj = this;
  }
  
  for(var i in prop) //루프 돌면서 prop의 property를 obj로 복사
    obj[i] = prop[i]; //얕은 복사.(객체 자체를 복사하지 않고 참조만) 
    
  return obj;
};

 

  • jQuery 함수 객체와 그 인스턴스 모두 extend 함수가 있겠다. jQuery.extend()로 호출할 수도 있고, var elem = new jQuery(..); elem.extend();로도 호출 가능.
  • 얕은 복사가 일어나면 의도하지 않은 결과가 나올 수 있음. 깊은 복사를 하려면 빈 객체를 만들어 extend 함수를 재귀적으로 다시 호출.
//extend() 함수 추가해서 활용
var person = { 
  name: "zzoon",
  getName: function(){
    return this.name;
  },
  setName: function(arg){
    this.name = arg;
  }
}; 

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

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

var student = create_object(person);
var added = {
  setAge: function(age){
    this.age = age;
  },
  getAge: function(){ 
    return this.age;
  }
};

extend(student, added); //얕은 복사를 사용하는 extend() 사용하여 student 객체를 확장시켰음.

student.setAge(25);
console.log(student.getAge()); 

//Person 객체의 property: name, setName, getName
//student 객체의 property: age, setAge, getAge

 

  • 클래스 기반의 상속
    • 객체 리터럴로 생성된 객체 상속이 아니라, 클래스의 역할을 하는 함수로 상속을 구현
    • 여기서 부모 클래스의 인스턴스인 you와 자식 클래스의 인스턴스인 me가 독립적이어야 메소드 추가, 참조 시 문제가 없음..!
function Person(arg){ 
  this.name = arg;
}

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

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

function Student(arg){
  Person.apply(this, arguments); //부모 클래스 Person의 생성자를 호출해야 인스턴스 초기화 제대로 이루어짐 
}

var you = new Person("iamhjoo");
Student.prototype = you; 

var me = new Student("zzoon");
me.setName("zzoon"); 

console.log(me.getName());

 

 

캡슐화 :

  • 관련된 여러 가지 정보를 하나의 틀 안에 담는 것.
  • public, private 등의 키워드가 없지만 정보 은닉은 가능
    • public 메소드가 클로저 역할을 하면서 private 멤버에 접근 가능. 다만 접근하는 private 멤버가 객체나 배열이면 얕은 복사로 참조만 반환하므로 사용자가 쉽게 변경할 수 있음.
    • var로 선언된 멤버들은 외부에서 접근이 불가능.
var Person = function(arg){
  var name = arg ? arg : "zzoon";
  
  var Func = function(){} 
  Func.prototype = { //Fuc이 클로저가 되고 name property는 자유 변수. 사용자는 name에 대한 접근이 불가해진다.
    getName : function(){ 
      return name;
    },
    setName : function(arg){ 
      name = arg;
    }
  };

  return Func; //Person 함수 객체의 프로토타입에도 접근하게 하기 위해 객체 대신 함수를 반환
}();

var me new Person();
console.log(me.getName());

 

 

객체지향 프로그래밍 응용 예제 :

  • 클래스의 기능을 가진 subClass 함수.
    다음을 활용하여 구현: 1) 함수의 프로토타입 체인, 2) extend 함수, 3) 인스턴스를 생성할 때 생성자 호출

1. subClass 함수 구조

  • subClass는 상속받을 클래스에 넣을 변수 및 메소드가 담긴 객체를 인자로 받아 부모 함수(subClass() 호출 시 this 객체)를 상속받는 자식 클래스를 만든다.
  • SuperClass를 상속받는 subClass를 만들 때, SuperClass.subClass()로 호출하도록 구현.
  • 최상위 클래스인 SuperClass는 자바스크립트의 Function을 상속받게 함.
var SuperClass = subClass(obj);
var SubClass = SuperClass.subClass(obj);

function subClass(obj){
  /*다음 단계들*/ 
}

 

2. 자식 클래스 생성 및 상속

function subClass(obj){ 
  /*...*/ 
  
  var parent = this; 
  var F = function(){}; 
  
  var child = function(){
  }; 
  
  F.prototype = parent.prototype; 
  child.prototype = new F();
  child.prototype.constructor = child;
  child.parent = parent.prototype;
  child.parent_constructor = parent; 
  
  /*...*/
  
  return child; 
}

 

3. 자식 클래스 확장

  • 사용자가 인자로 넣은 객체를 자식 클래스에 넣어 확장
for(var i in obj){ 
  if (obj.hasOwnProperty(i)){ //.hasOwnProperty()는 프로토타입 체인 타고 올라가지 않고 해당 객체 내에서만 찾음 
    child.prototype[i] = obj[i];
  }
}

 

4. 생성자 호출

  • 클래스의 인스턴스가 생성될 때, 클래스 내에 정의된 생성자가 호출돼야 함. 부모 클래스의 생성자도 호출되어야 함.
var child = function(){ 
  var parent = child.parent_constructor; 
  
  //if(parent.hasOwnProperty("_init")){
  // parent._init.apply(this, arguments);
  //}
  
  if(_parent && _parent !== Function){ //현재 클래스의 부모 생성자가 있으면 그 함수를 호출. 부모가 Function일 경우에는 최상위 클래스이므로 실행 안함.
    _parent.apply(this, arguments); //부모 함수의 재귀적 노출
  }
  
  if(child.prototype.hasOwnProperty("_init")){
    child.prototype._init.apply(this, arguments);
  }
};

 

5. subClass 보완

  • parent를 단순히 this.prototype으로 지정하면 안된다. 처음에 최상위 클래슬를 Function을 상속받는 것으로 했는데, 처리 코드가 없다.
//parent = this; 이 코드를 아래처럼 수정! 
var parent = this === window ? Function : this;

 

  • subClass 안에서 생성하는 자식 클래스 역할을 하는 함수는 subClass 함수가 있어야 하므로 child.subClass = arguments.callee; 추가.
    arguments.callee는 현재 호출된 함수이고, 이게 subClass이므로 child.subClass는 subClass 함수를 참조.

 

6. subClass 활용

var person_obj = { 
  _init : function(){ 
    console.log("person init"); 
  },
  getName : function(){ 
    return this._name;
  },
  setName : function(name){ 
    this._name = name; 
  }
};

var student_obj = {
  _init : function(){
    console.log("student init");
  },
  getName : function(){ 
    return "Student Name: "+this._name; 
  }
};

var Person = subClass(person_obj); //Person 클래스 정의
var person = new Person(); //person init 출력 
person.setName("zzoon");
console.log(person.getName()); //zzoon 출력 

var Student = Person.subClass(student_obj); //Student 클래스 정의
var student = new Student(); //person init, student init 출력 
student.setName("iamhjoo"); 
console.log(student.getName()); //Student Name: iamhjoo
console.log(Person.toString()); //Person이 Function을 상속받는지 확인

 

7. subClass 함수에 클로저 적용

  • 2에서 프로토타입 체이닝을 위해 만든 임시 함수 객체 F는 subClass 함수가 호출될 때마다 생성된다. 클로저로 한번만 생성되게 수정
var subClass = function(){
  var F = function(){}; 
  var subClass = function(obj){ 
  ......
  } 
  return subClass; 
}();

 

  • 즉시 실행 함수로 새로운 컨텍스트를 만들어 F() 함수 객체 생성. 이를 참조하는 안쪽의 subClass() 함수를 반환받는다.
  • 이렇게 하면 F() 함수 객체는 클로저에 엮여 가비지 컬렉션 대상이 되지 않고, subClass() 함수를 호출할 때마다 사용됨.

 

 

7. 함수형 프로그래밍

 

개념 :

  • 함수형 프로그래밍 = 함수의 조합으로 작업을 수행. 작업이 이루어지는 동안 데이터와 상태는 불변. 함수가 연산의 대상.
  • 순수 함수 : 외부에 아무런 영향을 미치지 않는 함수.
  • 고계 함수 : 함수를 또 하나의 값으로 간주하여 함수의 인자 혹은 반환값으로 사용할 수 있는 함수.
  • 높은 수준의 모듈화 가능.

 

 

자바스크립트에서의 함수형 프로그래밍 :

  • 일급 객체로서의 함수, 클로저 지원하기 때문에.
  • (예시)배열의 각 원소 총합 구하기
function reduce(func, arr, memo){ 
  var len = arr.length,
  i = 0, 
  accum = memo;
  
  for(; i<len; i++){
    accum = func(accum, arr[i]); 
  }
  return accum; 
}

var arr = [1,2,3,4];

var sum = function(x, y){ 
  return x+y;
};
var multiply = function(x, y){ 
  return x*y;
};

console.log(reduce(sum, arr, 0));
console.log(reduce(multiply, arr, 1));

 

  • (예시)팩토리얼
var fact = function(){ 
  var cache = {'0': 1};
  var func = function(n){ 
    var result = 0; 
    if(typeof(cache[n]) === 'number'){ 
      result = cache[n];
    } 
    else{ 
      result = cache[n] = n*func(n-1);
    }
    
    return result;
  }
  return func; 
}();

console.log(fact(10));
console.log(fact(20));

 

  • fact는 cache에 접근할 수 있는 클로저를 반환받는다. cache에는 팩토리얼을 연산한 값 저장.
  • 캐시 저장을 통해 중복된 연산을 피하여 성능이 좋다.

 

 

자바스크립트에서의 함수형 프로그래밍을 활용한 주요 함수 :

  • 커링 : 특정 함수에서 정의된 인자의 일부를 넣어 고정시키고, 나머지를 인자로 받는 새로운 함수를 만드는 것.
function calculate(a, b, c){
  return a*b+c; 
}

function curry(func){
  var args = Array.prototype.slice.call(arguments, 1); //curry() 함수로 넘어온 인자를 담아둠
  
  return function(){ //새로운 함수 호출로 넘어온 인자와 하쳐서 함수 적용
    return func.apply(null, args.concat(Array.prototype.slice.call(arguments))); 
  }
}

var new_func1 = curry(calculate, 1); //1*b+c를 반환
console.log(new_func1(2, 3)); //5 (1*2+3) 
var new_func2 = curry(calculate, 1, 3); //1*3+c를 반환 
console.log(new_func2(3)); //6 (1*3+3) 

//인자의 위치를 고정하고 싶다면

function curry2(func){ 
  var args = Array.prototype.slice.call(arguments, 1); 
  
  return function(){
    var arg_idx = 0;
    for(var i = 0; i < args.length && arg_idx < arguments.length; i++)
      if(args[i] === undefined) 
        args[i] = arguments[arg_idx++];
        
    return func.apply(null, args);
  }
}

var new_func = curry2(calculate, 1, undefined, 4); //1*b+4를 반환 
console.log(new_func(3)); //7 (1*3+4)

 

  • bind
    • 커링 기법을 활용한 함수.
    • 사용자가 고정시키고자 하는 인자를 bind() 함수 호출 시 인자로 넘겨주고 반환받은 함수를 호출하면서 나머지 인자를 넣을 수 있음.
    • 함수를 호출할 때 this에 바인딩시킬 객체를 사용자가 넣어줄 수 있다.
var print_all = function(arg){ 
  for(var i in this) 
    console.log(i + " : "+ this[i]); 
    
  for(var i in arguments) 
    console.log(i + " : "+ arguments[i]);
}

var myobj = { name: "zzoon" };

var myfunc = print_all.bind(myobj); //myobj 객체를 this에 바인딩시켜 print_all() 실행하는 새로운 함수.
myfunc(); //"name: zzoon" 출력 

var myfunc1 = print_all.bind(myobj, "iamhjoo", "others");
myfunc1("insidejs");

//name: zzoon
//0 : iamhjoo 
//1 : others 
//2 : insidejs

 

  • 래퍼 : 특정 함수를 자신의 함수로 덮어쓰는 것
    • 객체지향 프로그래밍에서 다형성의 특성 살리는 오버라이드와 유사
function wrap(object, method, wrapper){
  var fn = object[method]; 
  return object[method] = function(){ 
    return wrapper.apply(this, [ fn ].concat( 
      Array.prototype.slice.call(arguments)
    ));
  };
}

Function.prototype.original = function(value){ //인자로 넘어온 값을 value에 할당하고 출력.
  this.value = value; 
  console.log("value : "+this.value); 
}

var mywrap = wrap(Function.prototype, "original", function(orig_func, value){ //original 함수를 세번째 인자인 익명 함수로 덮어쓰기 위해 wrap 함수 호출. 익명 함수의 첫 번째 인자로 원래 함수의 참조를 받음.
  this.value = 20; 
  orig_func(value);
  console.log("wrapper value : "this.value);
});

var obj = new mywrap("zzoon"); 

//value : zzoon 
//wrapper value : 20

 

  • wrap 함수를 보면, 세 번째 인자로 받은 wrapper()를 apply()로 호출. 첫 인자가 기존 함수의 참조인 [fn]이므로 사용자는 이 함수에 접근할 수있다.
  • 원래 함수 original()이 호출될 때의 this와 반환되는 익명 함수가 호출될 때의 this가 다름.
    => wrap() 안의 apply 호출 시 [fn] 대신 [fn.bind(this)]를 쓰면 원래 함수에 반환되는 익명 함수의 this가 바인딩 된다.
  • 반복 함수
    • each
    • map
    • reduce 

 

 

8. jQuery 소스 코드 분석

 

구조 :

  • jQuery = 웹 개발 시 필요한 DOM 파싱, 이벤트 처리나 Ajax 같은 기능을 쉽게 작성할 수 있게 도와주는 라이브러리
  • HTML 문서에서 원하는 DOM 객체를 선택한 후, 해당 객체의 속성을 변경하거나 효과, 이벤트 등을 처리
  • jQuery 함수 객체
    • 함수 정의로 jQuery 객체를 생성하는 기능.
    • 변수 $를 jQuery() 함수로 매핑
    • jQuery.prototype 객체 변경
    • 객체 확장 - extend() 메소드
    • jQuery.prototype 객체에 포함된 프로토타입 메소드 호출 가능하고, 또한 jQuery 함수 객체 자신이 메소드를 포함하고 있음
  • jQuery의 id 셀렉터 동작 분석
    • $("#myDiv")로 함수를 호출(jQuery("#myDiv") 함수 호출과 같다)
    • jQuery.find(a,c)로 셀렉터 기능 처리
    • this.get() 메소드 호출 시 인자로 사용되는 것이 jQuery.find()의 결괏값
  • jQuery 이벤트 핸들러 분석
    •  .click()
    • 이벤트 핸들러 처리는 onclick property 같은 DOM 이벤트 핸들러를 직접 사용하지 않고, DOM 객체 내에 자체 이벤트 관련 property(event 객체)를 생성하고 각 이벤트 타입별로 여러 이벤트 핸들러 동시 등록.