DaveFar
DaveFar

Reputation: 7447

Add a method within a function expression (or function declaration)

I would like to add a method to a function expression as simply as possible. In my case, I use a function expression, but a function declaration would also be ok. Since functions are objects, I guess it is possible. But how?


Details:

I want to add the method as simply and as directly as possible, so I neither want to use Constructor functions nor inheritance.

The following code adds a method (setListener) to a function (secondsEvent) without Constructors and inheritance, but not within the anonymous function expression, which I would like to achieve somehow (see comments):

function listener1 (date) {
    console.log("listener1: " + date);
}

function listener2 (date) {
    console.log("listener2: " + date);
}

var secondsEvent = function () {
    //The following causes "listener is not defined" in firefox console                                                      
    //listener: listener1,                                                                                                   

    //The following causes "function statement requires a name" in firefox console,                                          
    //and if I give the function a name, I get "secondsEvent.setListener is not a function".                                  
    //Thus I add the property after this function expression.                                                                   
    //setListener: function (newListener) {                                                                                  
    //    listener = newListener;                                                                                            
    //},                                                                                                                     
    secondsEvent.listener(new Date());
    var timeout = setTimeout(secondsEvent, 1000);
}

// Thus the following 5 lines are needed.                                                                                    
secondsEvent.listener = listener1;
secondsEvent.setListener = function (newListener) {
    secondsEvent.listener = newListener;
    console.log("listener now " + secondsEvent.listener.name);
}

secondsEvent();
secondsEvent.setListener(listener2);

The code yields the following output:

listener1: Fri Dec 28 2018 21:55:05 GMT+0100 (Central European Standard Time) 
listener now listener2 
listener2: Fri Dec 28 2018 21:55:06 GMT+0100 (Central European Standard Time) 
listener2: Fri Dec 28 2018 21:55:07 GMT+0100 (Central European Standard Time) 
.....

Updates:

Originally, I used Douglas Crockford's term "function literal" instead of function expression, but updated the title and question now.

In response to Teemu's suggestion, I tried the following code:

function listener1 (date) {
    console.log("listener1: "+date + "<br>");
}

function listener2 (date) {
    console.log("listener2: "+date + "<br>");
}

var secondsEvent = function () {
    secondsEvent.listener = listener1;
    secondsEvent.setListener = function (newListener) {
        secondsEvent.listener = newListener;
        console.log("listener now " + secondsEvent.listener.name);
    }
    clearTimeout(secondsEvent.timeout);
    secondsEvent.listener(new Date());
    secondsEvent.timeout = setTimeout(secondsEvent, 1000);
};

secondsEvent();
secondsEvent.setListener(listener2);

However, its output is the following:

listener1: Sat Dec 29 2018 10:47:48 GMT+0100 (Central European Standard Time)<br> test.js:2:5
listener now listener2 test.js:27:2
listener1: Sat Dec 29 2018 10:47:49 GMT+0100 (Central European Standard Time)<br> test.js:2:5
listener1: Sat Dec 29 2018 10:47:50 GMT+0100 (Central European Standard Time)<br>

To avoid setting the properties secondsEvent.listener and secondsEvent.setListener each time secondsEvent is called, I use the following:

secondsEvent.listener = secondsEvent.listener || listener1;
secondsEvent.setListener = secondsEvent.setListener || function (newListener) {
    secondsEvent.listener = newListener;
    console.log("listener now " + secondsEvent.listener.name);
}

Is there a nicer solution for setting the properties only once? Maybe some js initializer idiom? (I know that pulling this out of the function would be another solution, but that leads to my first solution which I want to avoid since it would be much nicer to include the property definitions within the function expression (or function declaration, whichever you use), before they are used.

Upvotes: 1

Views: 354

Answers (3)

Teemu
Teemu

Reputation: 23396

I'm answering only to the very first part of the question and ignoring the "use case". There's an old, and a bit worn phrase: "Everything in JS is object." Yes, everything, but primitives, would be more correct.

But functions, they really are objects, callable objects. That means these function objects can have properties, either in form of values, or methods. The original question was: "[How] to add a method to a function [object]".

The answer is quite simple, get a reference to the function, and add a property (a method is a property as well) to the referred function, just like you'd do when you're adding a property to a regular object, and that's it. Or is it? There are several situations where a property is wanted to be assigned to a function, and that function could've been allocated to the memory in different ways. Now the question can be boiled down to "How to get a reference to a function at the point it is needed?"

There are three different main ways to define a function:

  1. declared function (a declared function always has a name) [fn1]
  2. a named function expression (can be assigned to a variable or used elsewhere) [fn2]
  3. an anonymous function expression (can be assigned to a variable or used elsewhere) [fn3]

#1 is the easiest case, you can refer a declared function anywhere in the current scope by using its name, including the function body itself. Thus you can create a method for this function by doing fn1.method = function () {...} inside the fn1s body, or outside of the function using the same notation.

#2 you can use the the given name only in the function body. If you need to assign a method outside of this function, use the name of the variable, or in the case the function was passed as an argument to another function, refer fn2 with the name of the argument.

#3 doesn't provide any reference to the function itself inside of the body of the function. However, if this function is assigned to a variable, the variable name can be used to refer the function itself inside the function's body, provided the function is not executed as an IIFE. Outside of the function body, the name of the variable can be used to refer the function as soon as the variable has been initilized.

What comes to this value in the function methods, you can't trust it referring the "method owning" function. If you need to refer this in the methods, it's better to use real objects instead of functions.

As a side note, using properties of function objects is not a regular use case in JS. Usually real objects are used instead. A general example of taking the advanage of the properties of a function object is jQuery's $, which actually is a function, but has also some methods and properties.

Upvotes: 0

trincot
trincot

Reputation: 350242

You speak of a "function literal", but there is no such thing in JavaScript. We do speak of a function expression though.

One of the things you could consider is using Object.assign which will allow an object (provided as a literal) to be merged into a (function) object:

function listener1 (date) {
    console.log("listener1: "+date);
}

function listener2 (date) {
    console.log("listener2: "+date);
}

var secondsEvent = Object.assign(function () {
    clearTimeout(secondsEvent.timeout);
    secondsEvent.listener(new Date());
    secondsEvent.timeout = setTimeout(secondsEvent, 1000);
}, {
    listener: listener1,
    setListener(newListener) {
        this.listener = newListener;
        console.log("listener now " + this.listener.name);
    }
});

secondsEvent();
secondsEvent.setListener(listener2);

The Traditional Alternative

What follows is not what you asked, but I provide it anyway as a comparison.

The more traditional way to have behaviour and data combined is by starting with an object. Here we do not extend a function with extra properties; the function becomes a method of the object that has the properties:

function listener1 (date) {
    console.log("listener1: "+date);
}

function listener2 (date) {
    console.log("listener2: "+date);
}

var secondsEvent = {
    listener: listener1,
    start() {
        clearTimeout(this.timeout);
        this.listener(new Date());
        this.timeout = setTimeout(() => this.start(), 1000);
    },
    setListener(newListener) {
        this.listener = newListener;
        console.log("listener now " + this.listener.name);
    }
};

secondsEvent.start(); // secondsEvent is not a function. We call a method
secondsEvent.setListener(listener2);

Altough here you need to call the function with .start(), it has as advantage that it looks and works like most other objects: the users of this API will not be surprised.

Upvotes: 2

Happy Machine
Happy Machine

Reputation: 1163

The syntax you are using looks like object or more suitably class syntax. Its not very clear from your question whether you are trying to avoid classes. Though you mention wanting to avoid constructors, so i'll assume you mean you dont want to use classes.

You can have methods in functions

function methodFunction () {
   return {
       myMethod (argument) {
           if (argument) console.log(argument)
           return new Date()
       }
   }
}

methodFunction().myMethod('heres something i want to pass my function method')
// will console.log heres something i want to pass my function method
console.log(methodFunction().myMethod())
// will console.log the date as it is returned from the function method

Here the first time we call the function we pass in the string 'heres something i want to pass my function method' which we log back out from within the function method where it comes in as the argument 'argument'

the second time we dont pass a string so nothing is console logged as a side effect of the function and instead the returned new Date() is console logged as the return value from the function

Upvotes: 0

Related Questions