JavaScript : 함수 복제


115

JavaScript에서 (속성 유무에 관계없이) 함수를 복제하는 가장 빠른 방법은 무엇입니까?

떠오르는 두 가지 옵션은 eval(func.toString())function() { return func.apply(..) }입니다. 하지만 eval의 성능이 걱정되고, 랩핑은 스택을 악화시키고 많이 적용하거나 이미 랩핑에 적용하면 성능이 저하 될 수 있습니다.

new Function(args, body) 멋져 보이지만 JS에서 JS 파서없이 어떻게 기존 함수를 args와 body로 정확하게 분할 할 수 있습니까?

미리 감사드립니다.

업데이트 : 내가 의미하는 것은 할 수 있다는 것입니다

var funcB = funcA.clone(); // where clone() is my extension
funcB.newField = {...};    // without affecting funcA

당신이 의미하는 바를 보여주는 예를 들어 줄 수 있습니까?
JoshBerke

물론입니다. (15charsrequired)
Andrey Shchekin 2009

잘 모르겠지만 copy = new your_function (); 작업?
Savageman 2009

1
나는 그렇게 생각하지 않는다. 그것은 생성자로 함수를 사용하여 인스턴스를 생성 할 것이다
Andrey Shchekin 2009

답변:


54

이 시도:

var x = function() {
    return 1;
};

var t = function(a,b,c) {
    return a+b+c;
};


Function.prototype.clone = function() {
    var that = this;
    var temp = function temporary() { return that.apply(this, arguments); };
    for(var key in this) {
        if (this.hasOwnProperty(key)) {
            temp[key] = this[key];
        }
    }
    return temp;
};

alert(x === x.clone());
alert(x() === x.clone()());

alert(t === t.clone());
alert(t(1,1,1) === t.clone()(1,1,1));
alert(t.clone()(1,1,1));

좋아요, 신청이 유일한 방법인가요? 두 번 호출 될 때 두 번 래핑되지 않도록 약간 개선 할 수 있지만 그렇지 않으면 괜찮습니다.
Andrey Shchekin 2009

apply는 인수를 쉽게 전달하는 데 사용됩니다. 또한 생성자를 복제하려는 인스턴스에서도 작동합니다.
Jared

6
예, 원래 게시물에 지원에 대해 썼습니다. 문제는 이와 같은 래핑 기능이 그 이름을 파괴하고 많은 복제 후에 느려진다는 것입니다.
Andrey Shchekin 2009

최소한 다음과 같이 .name 속성에 영향을주는 한 가지 방법이있는 것 같습니다. function fa () {} var fb = function () {fa.apply (this, arguments); }; Object.defineProperties (fb, {이름 : {값 : 'fb'}});
Killroy

109

다음은 업데이트 된 답변입니다.

var newFunc = oldFunc.bind({}); //clones the function with '{}' acting as it's new 'this' parameter

그러나 .bindJavaScript의 최신 (> = iE9) 기능 ( MDN호환성 해결 방법 포함 )

노트

  1. 그것은 복제하지 않는 함수 객체 추가 연결 속성을 , 포함 프로토 타입 속성입니다. @jchook에 대한 크레딧

  2. 변수 의 새로운 함수 는 새로운 함수 apply () 호출에서도 bind ()에 주어진 인수와 함께 붙어 있습니다. @Kevin에 대한 크레딧

function oldFunc() {
  console.log(this.msg);
}
var newFunc = oldFunc.bind({ msg: "You shall not pass!" }); // this object is binded
newFunc.apply({ msg: "hello world" }); //logs "You shall not pass!" instead
  1. 바인딩 된 함수 객체 인 instanceof는 newFunc / oldFunc를 동일하게 취급합니다. @Christopher에 대한 크레딧
(new newFunc()) instanceof oldFunc; //gives true
(new oldFunc()) instanceof newFunc; //gives true as well
newFunc == oldFunc; //gives false however

2
newFunc에 대한 자신의 프로토 타입이되지 않습니다 new newFunc반면, 인스턴스를 oldFunc의지.
jchook

1
실제 단점 : instanceof를 newFunc 및 oldFunc을 구별 할 수 없습니다
크리스토퍼 Swasey

1
@ChristopherSwasey : 기능을 확장 할 때도 실제로는 장점이 될 수 있습니다. 그러나 아아, 잘 이해되지 않으면 혼란 스러울 것입니다 (답변에 추가됨)
PicoCreator dec.

이 답변의 큰 문제는 일단 바인딩하면 두 번째 바인딩 할 수 없다는 것입니다. 이후의 apply 호출도 전달 된 'this'객체를 무시합니다. 예 : var f = function() { console.log('hello ' + this.name) }바인딩 된 경우 {name: 'Bob'}'hello Bob' 을 인쇄합니다. f.apply({name: 'Sam'})또한 'this'개체를 무시하고 'hello Bob'을 인쇄합니다.
Kevin Mooney

1
주목해야 할 다른 한 가지 중요한 경우 : 적어도 V8 (및 가능한 다른 엔진)에서는 Function.prototype.toString ()의 동작을 변경합니다. 바인딩 된 함수에서 .toString ()을 호출 function () { [native code] }하면 전체 함수의 내용 대신 다음 과 같은 문자열이 제공됩니다 .
GladstoneKeep

19

Jared의 답변에 대한 약간 더 나은 버전이 있습니다. 이것은 더 많이 복제할수록 깊이 중첩 된 함수로 끝나지 않습니다. 항상 원본이라고 부릅니다.

Function.prototype.clone = function() {
    var cloneObj = this;
    if(this.__isClone) {
      cloneObj = this.__clonedFrom;
    }

    var temp = function() { return cloneObj.apply(this, arguments); };
    for(var key in this) {
        temp[key] = this[key];
    }

    temp.__isClone = true;
    temp.__clonedFrom = cloneObj;

    return temp;
};

또한 pico.creator가 제공 한 업데이트 된 답변에 대한 응답으로 bind()Javascript 1.8.5에 추가 된 함수가 Jared의 답변과 동일한 문제를 가지고 있다는 점에 주목할 가치가 있습니다. 중첩을 계속하여 사용할 때마다 기능이 느려지고 느려집니다.


2019+에서는 __properties 대신 Symbol ()을 사용하는 것이 좋습니다.
Alexander Mills

10

호기심이 많지만 여전히 위 질문의 성능 주제에 대한 답을 찾을 수 없었기 때문에, 제시된 (및 점수가 매겨진) 모든 솔루션의 성능과 신뢰성을 테스트하기 위해 nodejs에 대한 이 요점 을 작성했습니다 .

클론 기능 생성과 클론 실행의 벽 시간을 비교했습니다. 주장 오류와 함께 결과는 요점의 주석에 포함됩니다.

플러스 내 2 센트 (저자의 제안에 따라) :

clone0 cent (빠르지 만 더 못 생김) :

Function.prototype.clone = function() {
  var newfun;
  eval('newfun=' + this.toString());
  for (var key in this)
    newfun[key] = this[key];
  return newfun;
};

clone4 cent (느리지 만 그들과 조상에게만 알려진 목적으로 eval ()을 싫어하는 사람들을 위해) :

Function.prototype.clone = function() {
  var newfun = new Function('return ' + this.toString())();
  for (var key in this)
    newfun[key] = this[key];
  return newfun;
};

성능에 관해서는 eval / new Function이 래퍼 솔루션보다 느리면 (실제로 함수 본문 크기에 따라 다름) 불필요한 퍼지없이 베어 함수 클론 (속성이있는 진정한 얕은 클론을 의미하지만 공유되지 않은 상태)을 제공합니다. 숨겨진 속성, 래퍼 함수 및 스택 문제가 있습니다.

또한 고려해야 할 중요한 요소가 항상 하나 있습니다. 코드가 적을수록 실수 할 가능성이 적습니다.

eval / new 함수 사용의 단점은 복제본과 원래 함수가 다른 범위에서 작동한다는 것입니다. 범위 변수를 사용하는 함수에서는 잘 작동하지 않습니다. 바인딩 유사 래핑을 사용하는 솔루션은 범위에 독립적입니다.


eval과 new Function은 동일하지 않습니다. eval은 로컬 범위에서 작동하지만 Function은 그렇지 않습니다. 이로 인해 함수 코드 내부에서 다른 변수에 액세스하는 데 문제가 발생할 수 있습니다. 자세한 설명 은 perfectionkills.com/global-eval-what-are-the-options 를 참조하십시오 .
Pierre

eval 또는 new Function을 사용하면 원래 범위와 함께 함수를 복제 할 수 없습니다.
royaltm

사실 : Object.assign(newfun.prototype, this.prototype);return 문 (깨끗한 버전) 앞에 추가하면 방법이 가장 좋은 답변입니다.
Vivick

9

이 메서드를 작동시키는 것은 매우 흥미 로웠으므로 Function 호출을 사용하여 함수의 복제본을 만듭니다.

MDN Function Reference에 설명 된 클로저에 대한 몇 가지 제한 사항

function cloneFunc( func ) {
  var reFn = /^function\s*([^\s(]*)\s*\(([^)]*)\)[^{]*\{([^]*)\}$/gi
    , s = func.toString().replace(/^\s|\s$/g, '')
    , m = reFn.exec(s);
  if (!m || !m.length) return; 
  var conf = {
      name : m[1] || '',
      args : m[2].replace(/\s+/g,'').split(','),
      body : m[3] || ''
  }
  var clone = Function.prototype.constructor.apply(this, [].concat(conf.args, conf.body));
  return clone;
}

즐겨.


5

짧고 간단합니다.

Function.prototype.clone = function() {
  return new Function('return ' + this.toString())();
};

1
또한 여러 가지 이유로 피하는 것이 가장 좋은 내부 eval의 변형을 사용합니다 (여기서는 다루지 않고 다른 1000 개에서 다룹니다).
Andrew Faulkner 2015

2
이 솔루션이 그 자리를 차지합니다 (사용자 함수를 복제하고 eval이 사용되는 것은 상관하지 않음)
Lloyd

2
이것은 또한 기능 범위를 잃습니다. 새 함수는 새 범위에 더 이상 존재하지 않는 외부 범위 변수를 참조 할 수 있습니다.
trusktr

4
const oldFunction = params => {
  // do something
};

const clonedFunction = (...args) => oldFunction(...args);

3
const clonedFunction = Object.assign(() => {}, originalFunction);

이것은 불완전합니다. 이것은에서 속성을 복사 originalFunction하지만를 실행할 때 실제로 실행하지는 않습니다 clonedFunction. 이는 예상치 못한 일입니다.
David Calhoun

2

이 대답은 원하는 사용법에 대한 대답으로 함수 복제를 보지만 실제로 함수를 복제 할 필요가 없는 사람들 을위한 것입니다. 왜냐하면 그들이 진정으로 원하는 것은 단순히 동일한 함수에 다른 속성을 연결할 수있는 것이기 때문입니다. 그 함수를 한 번 선언하십시오.

함수 생성 함수를 생성하여이를 수행합니다.

function createFunction(param1, param2) {
   function doSomething() {
      console.log('in the function!');
   }
   // Assign properties to `doSomething` if desired, perhaps based
   // on the arguments passed into `param1` and `param2`. Or,
   // even return a different function from among a group of them.
   return doSomething;
};

let a = createFunction();
a.something = 1;
let b = createFunction();
b.something = 2; // does not overwrite a.something
console.log(a.something);
a();
b();

이것은 위에서 설명한 것과 정확히 같지는 않지만 복제하려는 기능을 사용하려는 방법에 따라 다릅니다. 또한 실제로 호출 당 한 번씩 함수의 여러 복사본을 만들기 때문에 더 많은 메모리를 사용합니다. 그러나이 기술은 복잡한 clone기능 없이도 일부 사람들의 사용 사례를 해결할 수 있습니다.


1

궁금한 점이 있습니다. 프로토 타입이 있고 함수 호출 범위를 원하는대로 설정할 수 있는데 왜 함수를 복제하고 싶습니까?

 var funcA = {};
 funcA.data = 'something';
 funcA.changeData = function(d){ this.data = d; }

 var funcB = {};
 funcB.data = 'else';

 funcA.changeData.call(funcB.data);

 alert(funcA.data + ' ' + funcB.data);

1
함수 자체의 필드 (자체 포함 캐시, '정적'속성)를 변경해야하는 이유가있는 경우 원래 함수에 영향을주지 않고 함수를 복제하고 수정하려는 경우가 있습니다.
Andrey Shchekin 2009

기능 자체의 속성을 의미합니다.
Andrey Shchekin 2009

1
기능 속성을 가질 수있는 객체처럼, 그 이유
라두 Simionescu

1

Function 생성자를 사용하여 복제본을 만들려면 다음과 같이 작동해야합니다.

_cloneFunction = function(_function){
    var _arguments, _body, _result;
    var _regexFunction = /^function[\s]+[\w]*\(([\w\s,_\$]*)?\)\{(.*)\}$/;
    var _regexArguments = /((?!=^|,)([\w\$_]))+/g;
    var _matches = _function.toString().match(_regexFunction)
    if(_matches){
        if(_matches[1]){
            _result = _matches[1].match(_regexArguments);
        }else{
            _result = [];
        }
        _result.push(_matches[2]);
    }else{
        _result = [];
    }
    var _clone = Function.apply(Function, _result);
    // if you want to add attached properties
    for(var _key in _function){
        _clone[_key] = _function[_key];
    }
    return _clone;
}

간단한 테스트 :

(function(){
    var _clone, _functions, _key, _subKey;
    _functions = [
        function(){ return 'anonymous function'; }
        ,function Foo(){ return 'named function'; }
        ,function Bar(){ var a = function(){ return 'function with internal function declaration'; }; return a; }
        ,function Biz(a,boo,c){ return 'function with parameters'; }
    ];
    _functions[0].a = 'a';
    _functions[0].b = 'b';
    _functions[1].b = 'b';
    for(_key in _functions){
        _clone = window._cloneFunction(_functions[_key]);
        console.log(_clone.toString(), _clone);
        console.log('keys:');
        for(_subKey in _clone){
            console.log('\t', _subKey, ': ', _clone[_subKey]);
        }
    }
})()

이러한 클론은 폐쇄 변수에 대한 이름과 범위를 잃게됩니다.


1

나는 내 방식으로 Jared의 답변을 개선했습니다.

    Function.prototype.clone = function() {
        var that = this;
        function newThat() {
            return (new that(
                arguments[0],
                arguments[1],
                arguments[2],
                arguments[3],
                arguments[4],
                arguments[5],
                arguments[6],
                arguments[7],
                arguments[8],
                arguments[9]
            ));
        }
        function __clone__() {
            if (this instanceof __clone__) {
                return newThat.apply(null, arguments);
            }
            return that.apply(this, arguments);
        }
        for(var key in this ) {
            if (this.hasOwnProperty(key)) {
                __clone__[key] = this[key];
            }
        }
        return __clone__;
    };

1) 이제 생성자의 복제를 지원합니다 (new로 호출 가능). 이 경우 원래 생성자에서 모든 인수를 전달할 수 없기 때문에 10 개의 인수 만 사용합니다 (변경할 수 있음).

2) 모든 것이 올바르게 닫혀 있습니다.


대신 arguments[0], arguments[1] /*[...]*/단순히 사용하지 ...arguments않습니까? 1) 논쟁의 양 (여기서는 10 개로 제한됨)에 대한 종속성이 없습니다. 2) 더 짧음
Vivick

스프레드 연산자를 사용하면 이것은 분명히 기능에 대한 OG 복제 방법이 될 것입니다.
Vivick

0
function cloneFunction(Func, ...args) {
  function newThat(...args2) {
    return new Func(...args2);
  }
  function clone() {
    if (this instanceof clone) {
      return newThat(...args);
    }
    return Func.apply(this, args);
  }
  for (const key in Func) {
    if (Func.hasOwnProperty(key)) {
      clone[key] = Func[key];
    }
  }
  Object.defineProperty(clone, 'name', { value: Func.name, configurable: true })
  return clone
};

function myFunction() {
  console.log('Called Function')
}

myFunction.value = 'something';

const newFunction = cloneFunction(myFunction);

newFunction.another = 'somethingelse';

console.log('Equal? ', newFunction === myFunction);
console.log('Names: ', myFunction.name, newFunction.name);
console.log(myFunction);
console.log(newFunction);
console.log('InstanceOf? ', newFunction instanceof myFunction);

myFunction();
newFunction();

나는 이것을 사용하는 것을 결코 권하지 않을 것이지만, 최상인 것처럼 보이는 몇 가지 관행을 취하고 약간 수정함으로써 더 정확한 클론을 찾는 것이 흥미롭고 작은 도전이라고 생각했습니다. 로그의 결과는 다음과 같습니다.

Equal?  false
Names:  myFunction myFunction
{ [Function: myFunction] value: 'something' }
{ [Function: myFunction] value: 'something', another: 'somethingelse' }
InstanceOf?  false
Called Function
Called Function

0
const clone = (fn, context = this) => {
  // Creates a new function, optionally preserving desired context.
  const newFn = fn.bind(context);

  // Shallow copies over function properties, if any.
  return Object.assign(newFn, fn);
}

// Usage:

// Setup the function to copy from.
const log = (...args) => console.log(...args);
log.testProperty = 1;

// Clone and make sure the function and properties are intact.
const log2 = clone(log);
log2('foo');
// -> 'foo'
log2.testProperty;
// -> 1

// Make sure tweaks to the clone function's properties don't affect the original function properties.
log2.testProperty = 2;
log2.testProperty;
// -> 2
log.testProperty;
// -> 1

이 복제 기능 :

  1. 컨텍스트를 유지합니다.
  2. 래퍼이며 원래 기능을 실행합니다.
  3. 함수 속성을 복사합니다.

참고 이 버전은 얕은 복사를 수행하는. 함수에 속성으로 객체가있는 경우 원래 객체에 대한 참조가 유지됩니다 (Object spread 또는 Object.assign과 동일한 동작). 즉, 복제 된 함수에서 깊은 속성을 변경하면 원래 함수에서 참조 된 개체에 영향을줍니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.