Self-reference in a RequireJS module

2019-04-14 11:17发布

问题:

First of all I'm not sure is this the correct way to use this library. I would like to define multiple functions/static methods in a module and use them inside the module's scope. For example:

define({
        foo: function() {
            return "Hello";
        },

        bar: function() {
            alert( this.foo() );
        }
});

Main.js

require( ['foobar'], function( foobar) {

        foobar.bar(); // works
        $('body').click( foobar.bar ); // crash - Object #<HTMLBodyElement> has no method 'foo' 

});

This code won't work if bar() is triggered by an event because obviously this will mean something different in that scope. Is there any global variable which refers to the defined object and will allow me to can access methods and attributes from inside the define() code?

回答1:

EDIT: For information about this see below.

You can have an inner scope by using a function. You may want to declare your module like this:

define((function () { var self = {   // this line is changed
        foo: function() {
            return "Hello";
        },

        bar: function() {
            alert( self.foo() );
        }
}; return self; }()));    // and this

This looks awful, I acknowledge. Point is, it stores a reference to the object you return, and uses this to call foo. I'm not sure if you want it to be like this... But it works.


NOTE: This part is about the handling of this in JS. See the comment why this is not that relevant anymore for this question.

The problem is actually in main.js, not in your module. The problem is how JS handles this. This is not the same as in most other languages! this in JS is the context in which the function is called, not in which it is defined. You can apply functions to other objects, even without them having this function.

In your case you want to bind bar to your foobar object, so the this in bar is the correct object. You use the function bind for that. It creates a new function with the context set to the object you specified. Example:

function doubleMe () { return this * 2; }

You can call this function by doing this:

doubleMe.apply(5) // returns 10

You can also do this:

var giveMeTen = doubleMe.bind(5);

Now giveMeTen is a function, that returns 10 if executed. I encourage you to read more on this topic, the handling of this is strange at first, but beautiful if understood ;-)


Regarding your code, I would write:

require( ['foobar'], function( foobar) {

        foobar.bar();
        $('body').click( foobar.bar.bind(foobar));
});

Note that jQuery sets this to the element that has been clicked in the click() handler.



回答2:

Not sure what you are trying to accomplish here, perhaps with more context as what you are trying to do, we could give you more accurate counselling.

Nevertheless, here's how I typically make modules with requireJS (classes, mostly)

if ( typeof define === "function" && define.amd ) {
    define( "module", function () {
        var MyModule = function(){
            this.foo = function(){
                return "Hello!";
            };
            this.bar = function(){
                return this.foo();
            };
         }

         return MyModule;
     });
}

And now to use:

require("module", function(MyMod){
    var a = new MyMod();
    console.log(a.bar());
} 

Hope this helps. (You can probably guess how to make singletons from this using a closed scope)



回答3:

This can be done by passing a function to define (this function is called a definition function). Inside the definition function:

  1. Define the functions you'd like to use inside the module.
  2. Make the definition function return an object with the functionality you'd like the module to expose. Here, in the object you are returning, you can call the functions defined in the previous step.

    define(function() {
        function foo() {
            return "Hello";
        };
    
        return {
            bar: function() {
                alert(foo());
            }
        }
    });