본문 바로가기

FrontEnd/JavaScript

JS Function.method

JS Function.method

Function.prototype.apply()

주어진 this 값과 배열 (또는 유사 배열 객체) 로 제공되는 arguments 로 함수를 호출합니다.

참고:
이 함수의 구문은 거의 call() 구문과 유사합니다.
근본적인 차이점은 call() 은 함수에 전달될 인수 리스트를 받는데 비해,
apply() 는 인수들의 단일 배열을 받는다는 점입니다.

  • 매개변수
    • thisArg
      • 옵션. func 를 호출하는데 제공될 this 의 값.
        this 는 메소드에 의해 실제로 보여지는 값이 아닐 수 있음을 유의합니다.
        메소드가 non-strict mode 코드의 함수일 경우,
        null 과 undefined 가 전역 객체로 대체되며, 기본 값은 제한됩니다.
    • argsArray 옵션
      func 이 호출되어야 하는 인수를 지정하는 유사 배열 객체,
      함수에 제공된 인수가 없을 경우 null 또는 undefined.
      ECMAScript 5 의 시작으로 이러한 인수들은 배열 대신
      제네릭 유사 배열 객체로 사용될 수 있습니다.
      브라우저 호환성 정보를 확인해 보세요.
  • 반환값
    • 지정한 this 값과 인수들로 호출한 함수의 결과.

이미 존재하는 함수를 호출할 때 다른 this 객체를 할당할 수 있습니다.
this 는 현재 객체, 호출하는 객체를 참조합니다.
apply 를 사용해, 새로운 객체마다 메소드를 재작성할 필요없이
한 번만 작성해 다른 객체에 상속시킬 수 있습니다.

apply 는 지원되는 인수의 타입만 제외하면 call() 과 매우 유사합니다.
인수(파라미터)의 리스트 대신 인수들의 배열을 사용할 수 있습니다.
또한 apply 를 사용해, 배열 리터럴이나 (예, func.apply(this, ['eat', 'bananas']),
Array 객체 (예, func.apply(this, new Array('eat', 'bananas'))) 를 사용할 수 있습니다.

argsArray 파라미터를 위한 arguments 를 사용할 수도 있습니다.
arguments 는 함수의 지역 변수입니다.
이는 호출된 객체의 지정되지 않은 모든 인수에 대해 사용할 수 있습니다.
따라서, apply 메소드를 사용할 때 호출된 객체의 인수를 알 필요가 없습니다.
arguments 를 사용해 모든 인수들을 호출된 객체로 전달할 수 있습니다.
그럼 호출된 객체는 그 인수들을 처리할 수 있게 됩니다.

ECMAScript 5번 째 판의 시작으로, 모든 유사 배열 객체 타입을 사용할 수 있으며,
실제로 이는 프로퍼티 length 와 범위 (0..length-1) 내의
정수 프로퍼티를 갖는 다는 것을 의미합니다.
예를 들면, 이제 NodeList 또는 { 'length': 2, '0': 'eat', '1': 'bananas' } 와 같은
커스텀 객체를 사용할 수 있습니다.

구문

// fun.apply(thisArg, [argsArray])
Math.max.apply(null, [5, 6, 2, 3, 7]) // 7
Math.min.apply(null, [5, 6, 2, 3, 7]) // 2

배열에 배열을 붙이기 위해 apply 사용하기

var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]

// concat 은 우리가 원하는 동작을 하지만 
// 실제로는 기존 배열에 추가되지 않고 새 배열을 만들어 반환 합니다.
["a", "b", "c"].concat([0, 1, 2]) // ["a", "b", "c", 0, 1, 2]

// push 는 개별 입력
array.pusy(0, 1, 2) // ["a", "b", 0, 1, 2]
array.push([0, 1, 2]) // ["a", "b", 0, 1, 2, [0, 1, 2]]

apply 와 내장함수 사용

apply 를 보다 효과적으로 이용하면
일부 내장 함수는 어떤 작업에 대해서는
배열과 루프없이 쉽게 처리됩니다.
다음 예제에서는
배열에서 최대값과 최소값을 구하기 위해
Math.max/Math.min 함수를 이용하고 있습니다.


var max = Math.max.apply(null, [5, 6, 2, 3, 7]); 
// 이는 Math.max(numbers[0], ...) 또는 Math.max(5, 6, ...) 와 거의 같음

var min = Math.min.apply(null, [5, 6, 2, 3, 7]);

// vs. simple loop based algorithm
max = -Infinity, min = +Infinity;

for (var i = 0; i < numbers.length; i++) {
  if (numbers[i] > max) {
    max = numbers[i];
  }
  if (numbers[i] < min) {
    min = numbers[i];
  }
}

하지만 이러한 방식으로 apply를 사용하는 경우 주의해야 합니다.
JavaScript 엔진의 인수 길이 제한을 초과하는
위험성에 대해 이해할 필요가 있습니다.
함수에 너무 많은(대략 몇 만개 이상)인수를 줄 때의
상황은 엔진마다 다른데(예를 들어 JavaScriptCore의 경우 인수의 개수 제한은 65536),
상한이 특별히 정해져 있지 않기 때문입니다.
어떤 엔진은 예외를 던집니다.
더 심한 경우는
실제 함수에 인수를 전달했음에도 불구하고
참조할 수 있는 인수의 수를 제한하고 있는 경우도 있습니다
(이러한 엔진에 대해 더 자세히 설명하면,
그 엔진이 arguments의 상한을 4개로 했다고 하면[실제 상한은 물론 더 위일 것입니다],
위 예제 코드의 전체 배열이 아니라 5, 6, 2, 3 만
apply 에 전달되어 온 것처럼 작동합니다).

// 만약 사용하는 배열 변수의 수가  
// 수만을 초과하는 경우에는 
// 복합적인 전략을 강구해야할 것입니다:
// 한 번에 전달할 배열을 분할하여 사용하기:
function minOfArray(arr) {
  var min = Infinity;
  var QUANTUM = 32768;

  for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
    var submin = Math.min.apply(null,
                                arr.slice(i, Math.min(i + QUANTUM, len)));
    min = Math.min(submin, min);
  }

  return min;
}

var min = minOfArray([5, 6, 2, 3, 7]);

생성자 체이닝을 위한 apply 사용

Java 와 유사하게,
객체를 위한 constructors 체이닝을 위해 apply 를 사용할 수 있습니다.
다음 예제에서 인수 리스트 대신 생성자로 유사 배열 객체를 사용할 수 있게
해주는 construct 라는 전역 Function 메소드를 생성할 것입니다.

Function.prototype.construct = function(aArgs) {
  var oNew = Object.create(this.prototype);
  this.apply(oNew, aArgs);
  return oNew;
};

// Object.__proto__ 사용
Function.prototype.construct = function (aArgs) {
  var oNew = {};
  oNew.__proto__ = this.prototype;
  this.apply(oNew, aArgs);
  return oNew;
};

// 클로져 사용:
Function.prototype.construct = function(aArgs) {
  var fConstructor = this, fNewConstr = function() { 
    fConstructor.apply(this, aArgs); 
  };
  fNewConstr.prototype = fConstructor.prototype;
  return new fNewConstr();
};

// Function 생성자 사용
Function.prototype.construct = function (aArgs) {
  var fNewConstr = new Function("");
  fNewConstr.prototype = this.prototype;
  var oNew = new fNewConstr();
  this.apply(oNew, aArgs);
  return oNew;
};

// Ex
function MyConstructor() {
  for (var nProp = 0; nProp < arguments.length; nProp++) {
    this['property' + nProp] = arguments[nProp];
  }
}

var myArray = [4, 'Hello world!', false];
var myInstance = MyConstructor.construct(myArray);

console.log(myInstance.property1);                // logs 'Hello world!'
console.log(myInstance instanceof MyConstructor); // logs 'true'
console.log(myInstance.constructor);              // logs 'MyConstructor'

알림:
위에서 사용된 Object.create() 메소드는 상대적으로 새로운 것입니다.
대안으로, 다른 접근법 중 하나를 고려하세요.

알림:
네이티브가 아닌 Function.construct 메소드는
Date 와 같은 일부 네이티브 생성자와는 동작하지 않을 것입니다.
그런 경우,
Function.prototype.bind 메소드를 사용해야 합니다.
예를 들어,
다음과 같은 배열이 있다고 할 때,
Date 생성자: [2012, 11, 4] 와 함께 사용되려면
다음과 같이 작성해야 합니다:
new (Function.prototype.bind.apply(Date, [null].concat([2012, 11, 4])))().
이는 가장 좋은 방법이 아니며,
어떤 상용 환경에서도 사용되지 않을 수 있습니다.

Function.prototype.bind()

새로운 함수를 생성합니다.
bind() 가 호출된 함수의 this 키워드를 주어진 값으로 설정하고,
새로운 함수의 인수(argument) 앞에 지정한 인수 시퀀스가 사용됩니다.

  • 매개변수
    • thisArg
      • 바인딩 함수가 대상 함수(target function)의 this에 전달하는 값입니다.
        바인딩 함수를 new 연산자로 생성한 경우 무시됩니다.
        bind를 사용하여 setTimeout 내에 콜백 함수를 만들 때,
        thisArg로 전달된 원시 값은 객체로 변환됩니다.
        bind할 인수(argument)가 제공되지 않으면
        실행 스코프 내의 this는 새로운 함수의 thisArg로 처리됩니다.
    • arg1, arg2, ...
      • 대상 함수의 인수 앞에 사용될 인수.
  • 반환 값
    • 지정한 this 값 및 초기 인수를 사용하여 변경한 원본 함수의 복제본.
      bind() 함수는 새로운 바인딩한 함수를 만듭니다.
      바인딩한 함수는 원본 함수 객체를 감싸는 함수로,
      ECMAScript 2015에서 말하는 특이 함수 객체exotic function object입니다.
      바인딩한 함수를 호출하면 일반적으로 래핑된 함수가 호출 됩니다.

바인딩한 함수는 다음과 같은 내부 속성을 가지고 있습니다.

  • [[BoundTargetFunction]] - 바인딩으로 감싼(wrapped) 원본 함수 객체.
  • [[BoundThis]] - 감싸진 함수를 호출했을 때 항상 전달되는 값.
  • [[BoundArguments]] - 감싸진 함수가 호출될 때 첫 번째 인수로 사용되는 값들의 목록.
  • [[Call]] - 이 객체와 관련된 코드 실행.
    함수 호출 식을 통해 호출됨.
    내부 메소드의 인수는 this 값 및 호출 식으로 함수에 전달되는 인수를 포함하는 목록입니다.

바인딩된 함수가 호출될 때 [[BoundTargetFunction]]의 내부 메소드 [[Call]]을 호출합니다.
[[Call]] 은 Call(boundThis, args)와 같은 인자를 가집니다.
이 때, boundThis는 [[BoundThis]]이고,
args는 함수가 호출될 때 전달되어 따라오는 [[BoundArguments]] 입니다.

바인딩된 함수는 new 연산자를 사용하여 생성될 수도 있습니다:
그렇게 하면 대상 함수가 마치 대신 생성된 것처럼 행동합니다.
제공된 this 값은 무시됩니다,
앞에 붙인(prepend) 인수는 에뮬레이트된 함수에 제공되지만.

구문

// func.bind(thisArg[, arg1[, arg2[, ...]]])

바인딩된 함수 생성

bind()의 가장 간단한 사용법은 호출 방법과 관계없이
특정 this 값으로 호출되는 함수를 만드는 겁니다.
초보 JavaScript 프로그래머로서 흔한 실수는 객체로부터
메소드를 추출한 뒤 그 함수를 호출할때,
원본 객체가 그 함수의 this로 사용될 것이라 기대하는 겁니다
(예시 : 콜백 기반 코드에서 해당 메소드 사용).
그러나 특별한 조치가 없으면, 대부분의 경우 원본 객체는 손실됩니다.
원본 객체가 바인딩 되는 함수를 생성하면,
이러한 문제를 깔끔하게 해결할 수 있습니다.

this.x = 9;
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX(); // 9 반환 - 함수가 전역 스코프에서 호출됐음

// module과 바인딩된 'this'가 있는 새로운 함수 생성
// 신입 프로그래머는 전역 변수 x와
// module의 속성 x를 혼동할 수 있음
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81

부분 적용 함수

bind()의 다음으로 간단한 사용법은 미리 지정된 초기 인수가 있는 함수를 만드는 겁니다.
지정될 초기 인수가 있다면 제공된 this 값을 따르고,
바인딩 된 함수에 전달되어 바인딩 된 함수가 호출될 때마다 대상 함수의 인수 앞에 삽입됩니다.

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// 선행될 인수를 설정하여 함수를 생성합니다. 
var leadingThirtysevenList = list.bind(null, 37);

var list2 = leadingThirtysevenList();  // [37]

var list3 = leadingThirtysevenList(1, 2, 3);  // [37, 1, 2, 3]


function addArguments(arg1, arg2) {
    return arg1 + arg2
}

var result1 = addArguments(1, 2); // 3

// 첫 번째 인수를 지정하여 함수를 생성합니다.
var addThirtySeven = addArguments.bind(null, 37); 

var result2 = addThirtySeven(5); // 37 + 5 = 42 

// 두 번째 인수는 무시됩니다.
var result3 = addThirtySeven(5, 10); // 37 + 5 = 42

setTimeout과 함께 사용

window.setTimeout() 내에서 this 키워드는
기본으로 window (또는 global) 객체로 설정됩니다.
클래스 인스턴스를 참조하는 this를 필요로 하는 클래스 메소드로 작업하는 경우,
인스턴스를 유지하기 위해 명시해서 this를 콜백 함수에 바인딩할 수 있습니다.

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 1초 지체 후 bloom 선언
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();
// 1초 뒤, 'declare' 메소드 유발

생성자로 쓰이는 바인딩된 함수

경고:
이 부분은 JavaScript 능력을 보이고 bind() 메소드의 일부 극단 상황(edge case)을 기록합니다.
아마도 상용 환경에서 전혀 사용되지 않을 겁니다.

바로 가기 생성

bind()는 특정 this 값을 필요로 하는 함수의 바로 가기(shortcut)를 만들고 싶은 경우에도 도움이 됩니다.

가령, 배열 같은 객체를 실제 배열로 변환하는 데 사용하고 싶은 Array.prototype.slice를 취하세요.
이와 같은 바로 가기를 만들 수 있습니다:

var slice = Array.prototype.slice;

// ...

slice.apply(arguments);

bind()로, 이는 단순화될 수 있습니다.
다음 조각 코드에서,
slice는 this 값을 Array.prototype의 slice() 함수로 설정 한 채,
Function.prototype의 apply() 함수에 바인딩된 함수입니다.
이는 추가 apply() 호출은 삭제될 수 있음을 뜻합니다:

// 이전 예에서 "slice"와 같음
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.apply.bind(unboundSlice);

// ...

slice(arguments);

Function.prototype.call()

주어진 this 값 및 각각 전달된 인수와 함께 함수를 호출합니다.

주의:
이 함수 구문은 apply()와 거의 동일하지만,
중요한 차이는 call()은 인수 목록을 받지만,
반면에 apply()는 인수 배열 하나를 받는다는 점입니다.

  • 매개변수
    • thisArg
      • fun 호출에 제공되는 this의 값.
        this는 메소드에 의해 보이는 실제값이 아닐 수 있음을 주의하세요:
        메소드가 비엄격 모드 코드 내 함수인 경우,
        null 및 undefined는 전역 객체로 대체되고
        원시값은 객체로 변환됩니다.
    • arg1, arg2, ...
      • 객체를 위한 인수.
        서로 다른 this 객체가 기존 함수를 호출할 때 할당될 수 있습니다.
        this는 현재 객체(호출하는 객체)를 참조합니다.
        메소드를 한번 작성하면 call을 이용해,
        새 객체를 위한 메소드를 재작성할 필요 없이 다른 객체에 상속할 수 있습니다.

구문

// fun.call(thisArg[, arg1[, arg2[, ...]]])

객체의 생성자 연결에 call 사용

Java와 비슷하게, 객체의 생성자 연결(chain)에 call을 사용할 수 있습니다.
다음 예에서,
Product 객체의 생성자는 name 및 price 두 매개변수로 정의됩니다.
다른 두 함수 Food 및 Toy는 this 및 name과 price를 전달하는 Product를 호출합니다.
Product는 name 및 price 속성을 초기화하고,
특수한 두 함수(Food 및 Toy)는 category를 정의합니다.

function Product(name, price) {
  this.name = name;
  this.price = price;

  if (price < 0) {
    throw RangeError('Cannot create product ' +
                      this.name + ' with a negative price');
  }
}

function Food(name, price) {
  Product.call(this, name, price);
  this.category = 'food';
}

function Toy(name, price) {
  Product.call(this, name, price);
  this.category = 'toy';
}

var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);

익명 함수 호출에 call 사용

순수하게 생성된 이 예에서,
익명 함수를 만들고 배열 내 모든 객체에서 이를 호출하기 위해 call을 사용합니다.
여기서 익명 함수의 주목적은
배열 내 객체의 정확한 인덱스를 출력할 수 있는 모든 객체에 print 함수를 추가하는 겁니다.
this 값으로 객체 전달이 반드시 필요하지는 않았지만 설명 목적으로 작성 했습니다.

var animals = [
  { species: 'Lion', name: 'King' },
  { species: 'Whale', name: 'Fail' }
];

for (var i = 0; i < animals.length; i++) {
  (function(i) {
    this.print = function() {
      console.log('#' + i + ' ' + this.species
                  + ': ' + this.name);
    }
    this.print();
  }).call(animals[i], i);
}

함수 호출 및 'this'를 위한 문맥 지정에 call 사용

아래 예제에서,
greet을 호출하면 this 값은 객체 obj에 바인딩됩니다.

function greet() {
  var reply = [ this.animal, 'typically sleep between', 
                this.sleepDuration
              ].join(' ')
  console.log(reply);
}

var obj = {
  animal: 'cats', sleepDuration: '12 and 16 hours'
};

greet.call(obj);  // cats typically sleep between 12 and 16 hours

첫번째 인수 지정 없이 함수 호출에 call 사용

아래 예제에서,
display 함수에 첫번째 인수를 전달하지 않고 호출합니다.
첫번째 인자를 전달하지 않으면,
this의 값은 전역 객체에 바인딩됩니다.

var sData = 'Wisen';
function display(){
  console.log('sData value is %s ', this.sData);
}

display.call();  // sData value is Wisen

Function.prototype.toString()

함수의 소스 코드를 나타내는 문자열을 리턴합니다.
브라우저 지원여부를 확인하세요.

  • 반환 값
    • 반환 값 함수의 소스 코드를 나타내는 문자열입니다.
      Function 개체는 개체로부터 상속된 toString 메서드를 재정의하며,
      Object.prototype.toString을 상속하지 않는다.
      사용자 정의 함수 객체의 경우,
      toString 방법은 함수를 정의하는 데 사용된
      소스 텍스트 세그먼트를 포함하는 문자열을 반환한다.

JavaScript는 함수를 문자열과 결합한 경우와 같이
함수를 텍스트 값으로 나타낼 때 toString 방법을 자동으로 호출한다.

구문

// function.toString()

이 값 객체가 Function 객체가 아닌 경우
toString() 메소드는 TypeError 예외 ("호환되지 않는 객체에서 호출 된 Function.prototype.toString")를 발생시킵니다.

Function.prototype.toString.call('foo'); // TypeError

내장 함수 객체 또는 function.prototype.bind로 만든 함수에서
toString() 메소드를 호출하면 toString ()은 다음과 같은 기본 함수 문자열을 반환합니다.

"function () {\n    [native code]\n}"

Ex

function f(){}
f.toString()  // "function f(){}"

class A { a(){} }
A.toString() // "class A { a(){} }"

function* g(){}
g.toString() // "function* g(){}"

({ a(){} }.a).toString() // "a(){}"
var a2 = ({ a(){} }.a)
a2.toString() // "a(){}"

var a3 = ({ *a(){} }.a)
a3.toString() // "*a(){}"

var a4 = ({ [0](){} }[0])
a4.toString() // "[0](){}"

'FrontEnd > JavaScript' 카테고리의 다른 글

Canvas Tutorials  (0) 2019.12.17
VanillaJS Form serialize()  (0) 2019.12.16
JS Function.method  (0) 2019.12.11
JS Array.Method  (0) 2019.12.10
JS Array.prototype.reduce()  (0) 2019.12.09
ECMA6 타입배열  (0) 2019.06.07