0

Given that I have declared a function in the for of foo = function F() {} how can I copy but not clone that function. For example with the code below each time one calls the function log() with an argument that argument is added to the array log.list that list can be shown by calling the function log.alert().

How can one declare a new variable log2 that will copy the functionality of the log function but remain independent of it?

i.e an independent list will be maintained for log and log2 and by calling log2.alert() the log2.list will be shown and by calling log.alert() the log.list will be shown?

var log = log || function F (x) {
    F.list = F.list || [];
    if (x != undefined) F.list.push(x);
    if (!F.fired) {
        F.alert = function () {
            alert (F.list.join("\n"));
        }
        F.reset = function () {
            F.list = [];
        }
    F.fired = true;
    }
}

// Demonstrate the function
log("log #1");
log("log #2");
log("log #3");
log("log #4");
log.alert();
Trevor
  • 525
  • 2
  • 6
  • 19
  • If you want mutable state, why not make an object? – Davin Tryon Jul 05 '15 at 15:55
  • Functions are held by reference, which means they're never copied on assignment. I don't know what difference you see in a copy and a clone but there's no real way to fully copy a function *(Including its closure state)* unless maybe you do some `eval` ugliness. –  Jul 05 '15 at 16:00
  • Davin I and looking to have the functionality copied also, not just the list. I guess I could shove in the list Object in the function in the form log("sdgh", myObject) but I was wondering how to avoid that. – Trevor Jul 05 '15 at 16:18
  • squint, regarding cloning a functions here's some answers http://stackoverflow.com/questions/1833588/javascript-clone-a-function if the function is cloned using one of the methods there then the list will not remain independent. If the function could be copied the list would remain independent. I was thinking of using `var log2 = Function (log.toString())` but the problem with that is that the function **F*** is wrapped in _anonymous function_. I'll wait and see if anyone has a solutions – Trevor Jul 05 '15 at 16:27

1 Answers1

2

This ones a bit a tricky. You've half described factories, which are functions that return new objects (including functions), and half described a regular constructor, which creates instances.

The crux of this question is whether or not directly invoking your log and log2 variables is of real importance. JavaScript doesn't offer a way to meta-call non-function objects, and it even looks like ES6 Proxies skipped on implementing this functionality (You can only intercept invocations of functions). The only real way to invoke log is if it is a function.

You could define a factory function that returns a function with the appropriate methods attached directly to it, and encapsulate any variables you need. The overhead involved with this pattern is that you have to redefine a function and two methods every time you create a new 'log'.

function logFactory () {
  var stack = [];
  
  function f () {
    stack.push.apply(stack, arguments);
  }
  
  f.list = stack;
  
  f.alert = function () {
    alert(stack);
  };
  
  f.reset = function () {
    while (stack.length > 0) {
      stack.pop();
    }
  };
  
  return f;
}

var log = logFactory(),
    log2 = logFactory();

log('one', 'three');
log2('two', 'four');

log.alert();
log.reset();
log2.alert();

alert('Lists are different? ' + log.list !== log2.list);

However, if you don't need to invoke the log object directly, you can create a normal constructor with prototype methods, and drop the overhead.

function Log () {
  this.list = [];
}

Log.prototype.add = function () {
  this.list.push.apply(this.list, arguments);
};

Log.prototype.alert = function () {
  alert(this.list);
};

Log.prototype.reset = function () {
  while (this.list.length > 0) {
    this.list.pop();
  }
};

var log = new Log(),
    log2 = new Log();

log.add('one', 'three');
log2.add('two', 'four');

log.alert();
log.reset();
log2.alert();

alert('Lists are different? ' + log.list !== log2.list);

It should also be noted that in your example, the function F is redefining its own methods every time it is invoked to push something to the list. That's very undesirable.

Oka
  • 23,367
  • 6
  • 42
  • 53
  • Hi @Oka, Thank you very much for your comprehensive answer which I think answers the question very well. I made a small edit to my snippet which avoids the redefinition of the methods upon invoking the function. It is a shame that people down voted my question. I don't see what's so bad about it. Any up votes would be appreciated. – Trevor Jul 06 '15 at 12:23
  • Your named `F` function is just implementing a very strange pattern, and I would not recommend using it. `log` becomes this self constructor / pseudo-instance that gets defined, but doesn't have any properties until you invoke it for the first time. You're giving `F` and the subsequent invocations of `log` _too_ many responsibilities, that could be better delegated with a more traditional pattern. – Oka Jul 06 '15 at 13:26
  • The snippet here is part of a much larger snippet. The main idea is to have a log function that has to be actively initiated. This might be done either by a method `log.initiated(debug)` where `debug` is set a the beginning of the script or can be triggered by input in a UI. When I finish the script hopefully in the next couple of days I shall post a link to it. I would really appreciate any comments on how to improve it after I post it. Thanks – Trevor Jul 06 '15 at 16:03