JavaScript: клонувати функцію


115

Який найшвидший спосіб клонувати функцію в JavaScript (зі своїми властивостями чи без них)?

Два варіанти, що спадають на думку, є eval(func.toString())і function() { return func.apply(..) }. Але я переживаю за ефективність eval і обгортку погіршить стек і, ймовірно, погіршить ефективність, якщо застосовано багато або застосовано до вже обгорнутого.

new Function(args, body) виглядає приємно, але як саме я можу надійно розділити існуючу функцію на арг і тіло без аналізатора JS в JS?

Заздалегідь спасибі.

Оновлення: те, що я маю на увазі, це вміє робити

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

Чи можете ви навести приклад, який показує, що ви маєте на увазі.
JoshBerke

Звичайно, додав. (15 годин потрібні)
Андрій

Я не впевнений, але міг скопіювати = new your_function (); робота?
Савагеман

1
Я не думаю, що це створить екземпляр, використовуючи функцію конструктора
Андрій Щекін

Відповіді:


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));

Гаразд, так застосовувати - це єдиний спосіб? Я б трохи покращив цю проблему, щоб вона не загорталася двічі, коли дзвонила двічі, але в іншому випадку, добре.
Андрій Щекін

застосовувати використовується, щоб легко передати аргументи. також це буде працювати в тих випадках, коли ви хочете клонувати конструктор.
Джаред

6
так, я писав про застосувати в оригінальній публікації. Проблема полягає в тому, що така функція загортання знищує її назву і сповільниться після багатьох клонів.
Андрій Щекін

Здається, є один із способів принаймні вплинути на властивість .name, як це: функція fa () {} var fb = function () {fa.apply (це, аргументи); }; Object.defineProperties (fb, {name: {value: 'fb'}});
Кіллрой

109

Ось оновлена ​​відповідь

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

Однак .bindце сучасна (> = iE9) функція JavaScript (із вирішенням сумісності з MDN )

Примітки

  1. Це НЕ клон об'єкта функції додаткові приєднані властивості , в тому числі і в прототипі власності. Кредит на @jchook

  2. Нова функція цієї змінної застрягла з аргументом, наведеним на bind (), навіть у викликах нової функції apply (). Кредит у @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
Крістофер

1
@ChristopherSwasey: Насправді це може бути і перевершенням при розширенні функціональних можливостей. Але на жаль, це буде заплутано, якщо його не зрозуміли добре (додано у відповідь)
PicoCreator

Велика проблема з цією відповіддю полягає в тому, що раз прив’язуючи, ви не можете зв’язати вдруге. Подальші дзвінки для подання заявки також ігнорують переданий об'єкт "цей". Приклад: var f = function() { console.log('hello ' + this.name) }коли прив’язаний до {name: 'Bob'}друку "привіт Боб". f.apply({name: 'Sam'})також надрукує "привіт Боб", ігноруючи "цей" об'єкт.
Кевін Муні

1
Ще один крайній випадок, який слід зазначити: Принаймні, у V8 (і, можливо, інших двигунах) це змінює поведінку Function.prototype.toString (). Виклик .toString () на зв'язаній функції дасть вам рядок типу function () { [native code] }замість повного вмісту функції.
GladstoneKeep

19

Ось трохи краща версія відповіді Джареда. Ця функція не буде закінчуватися глибоко вкладеними функціями, тим більше ви клонуєте. Він завжди називає оригінал.

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, має ту ж проблему, що і відповідь Джареда - вона буде тримати вкладення, викликаючи повільніші та повільніші функції кожного разу, коли вона буде використана.


у 2019 році +, мабуть, краще використовувати Symbol () замість __properties.
Олександр Міллс

10

Будучи цікавим, але все ще не в змозі знайти відповідь на тему ефективності вищезазначеного питання, я написав цю суть для nodejs, щоб перевірити як продуктивність, так і надійність усіх представлених (і набраних) рішень.

Я порівняв стінні часи створення функції клону та виконання клону. Результати разом із помилками твердження включені до коментаря суті.

Плюс мої два центи (на основі пропозиції автора):

клон0 цент (швидше, але потворніше):

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 і нова функція були не рівнозначними. eval opers на локальному рівні, але функція не робить. Це може призвести до проблем, пов'язаних із доступом до інших змінних всередині коду функції. Див. Perfectionkills.com/global-eval-what-are-the-options для вичерпного пояснення.
П’єр

Правильно, використовуючи або eval, або нову функцію, ви не можете клонувати функцію разом із її початковою областю.
royaltm

Власне кажучи: щойно ви додасте Object.assign(newfun.prototype, this.prototype);до заяви про повернення (чисту версію), ваш метод - найкраща відповідь.
Vivick

9

Було досить захоплююче змусити цей метод працювати, тому він робить клон функції за допомогою виклику функції.

Деякі обмеження щодо закриттів, описані в довіднику функцій MDN

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-ти інших місцях).
Ендрю Фолкнер

2
це рішення має своє місце (коли ви клонуєте функцію користувача і не байдуже, що використовується eval)
Lloyd

2
Це також втрачає область функцій. Нова функція може стосуватися змін зовнішньої області, які більше не існують у новій області.
trusktr


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

Зауважте, що це неповно. Це скопіює властивості з originalFunction, але насправді не виконає їх під час запуску clonedFunction, що несподівано.
Девід Калхун

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
Якщо є причина змінити саму функцію поля (автономний кеш, "статичні" властивості), тоді виникають ситуації, коли я хочу клонувати функцію та змінювати її, не впливаючи на початкову.
Андрій Щекін

Я маю на увазі властивості самої функції.
Андрій Щекін

1
функції можуть мати властивості, як і будь-який об’єкт, ось чому
Radu Simionescu

1

Якщо ви хочете створити клон за допомогою конструктора функцій, щось подібне повинно працювати:

_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

Я по-справжньому допоміг відповідь Джареда:

    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) тепер він підтримує клонування конструкторів (може дзвонити з новими); у такому випадку бере лише 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.assign). Це означає, що зміна глибоких властивостей у клонованій функції вплине на об’єкт, на який посилається в початковій функції!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.