Module pattern- How to split the code for one module into different js files?
Asked Answered
S

4

12

For the module pattern, I'm doing something like:

(function(namespace) {
    // tons of code
    // blabla
})(window.myGlobalNamespace);

How do I split the code? I can think of a few ways, like use a hierachy of namespaces, or expand the object outside by window.myGlobalNamespace.additionalFunc = function () {//blabla}. What are the other ways? What are the pros and cons? Which one is considered better practice?

Both of the two answers suggest RequireJS. Can you please explain how RequireJS can solve these problems:

first.js:

(function(context) {
    var parentPrivate = 'parentPrivate';
})(window.myGlobalNamespace);

second.js:

(function(context) {
    this.childFunction = console.log('trying to access parent private field: ' + parentPriavte);
}(window.myGlobalNamespace.subNamspace);

main.js:

window.myGlobalNamespace.subNamspace.childFunction(); // doesn't work

And people can do

window.myGlobalNamespace.subNamspace.childFunction = function() {alert("a new function");}

to change my code's behaviour!

Here, there are two problems:

  1. We can't have a field that's accessible by child but not to outside public (i.e. protected). Is there any way to achieve that?

  2. If not, meaning if we wanteparentPrivate to be accessible, we need to make it public. Then the user will be able to modify it!

What's more, all the public functions can be altered and replaced. I don't want that to happen.

I don't see how RequireJS solves these problems. Can someone shed some light?

Stavros answered 1/12, 2013 at 11:25 Comment(0)
B
38

There are only 2 ways to get JavaScript into HTML:

  1. Inline - <script> some JavaScript </script>
  2. Link - <script src='main.js'></script>

I know this is obvious but we need that common ground for what comes next. ;)

JavaScript does not have the ability to "import" other JavaScript files into it's self. All the "importing" is done in the HTML. You can do this several ways:

  • Link each one individually into the HMTL
  • Dynamically link them in through some JavaScript

    var script = document.createElement("script");
    script.src = "all.js";
    document.documentElement.firstChild.appendChild(script);
    
  • Library like RequireJS. RequireJS uses Asynchronous Module Definition (AMD) API. It is the JavaScript mechanism for defining modules such that the module and its dependencies can be asynchronously loaded.

It is import to consider reasons for separating JavaScript into separate files.

  • Maintainability - it becomes easier to work on one piece at a time
  • Readability - if everything is in one big file it is very hard to see what is what
  • Division of Labor - it is easier to have multiple developers working on multiple files instead of one big one
  • Reuse - all your functions can be broken up into highly cohesive modules

Separate JavaScript files DO NOT make things Private, Closures make things Private.

Now, consider at the end of the day when everything is ready for production the best thing you could do is Optimize your JavaScript by combining it all into one file so that the user only has one file to download.


When dealing with Private variables in JavaScript, you will at some point want to access them.

  • Public function - can be altered.
  • Privileged function - a Public function that can access the Private variable.
  • However if the function is in an Instance then it can only be altered in each Object.

Let me illustrate with some code.

module-test.html and main.js (merged first.js, second.js, and main.js for easier testing)

var MODULE = (function () {
	//Private variables
	var privateParent,
	    app;
	
	privateParent = 'parentPrivate';
	
	return app = {
		//Privileged method
		getPrivateParent: function() {
			return privateParent;
		}
	};
}());

MODULE.sub = (function (parentApp) {
	//Private variables
	var childMessage,
	    Constr;
	
	childMessage = ' - trying to access parent private field: ' + parentApp.getPrivateParent();  //prints parentPrivate

	Constr = function () {
		this.childF = this.childFunction();
	};
	
	//Constructor
	Constr.prototype = {
		constructor: MODULE.sub,
		version: "1.0",
		childFunction: function () {
			$("#testing-div").append(childMessage + "</br>");
		}
	};
	return Constr;
	
}(MODULE));
	
//We could just as easily print to the console, but the 'append' allows us to display the results on the page.

$("#testing-div").append("This first part shows what <b>does not work</b>; everything is 'undefined'. " + "</br>");
$("#testing-div").append("You are unable to access the var or func directly. " + "</br>");
$("#testing-div").append("MODULE.privateParent = " + MODULE.privateParent + "</br>");
$("#testing-div").append("MODULE.app = " + MODULE.app + "</br>");
$("#testing-div").append("MODULE.sub.childMessage = " + MODULE.sub.childMessage + "</br>");
$("#testing-div").append("MODULE.sub.Constr = " + MODULE.sub.Constr + "</br>");
$("#testing-div").append("MODULE.sub.childFunction = " + MODULE.sub.childFunction + "</br>");
$("#testing-div").append("END lesson. You must access childFunction() through the <b>new</b> operator." + "</br>");
$("#testing-div").append("----------------------------------------------------" + "</br>");
	
$("#testing-div").append("Let's see if making an instance of the Object works" + "</br>");
var test = new MODULE.sub();
test.childFunction(); //run the method
$("#testing-div").append("Looks like it did!!!!" + "</br>");
$("#testing-div").append("----------------------------------------------------" + "</br>");
	
$("#testing-div").append("Now let's try to change the childFunction() ?" + "</br>");
test.childFunction = function() {$("#testing-div").append(" - This is a new function." + "</br>");}
test.childFunction(); // altered version
$("#testing-div").append("Looks like it was changed. :(" + "</br>");
$("#testing-div").append("----------------------------------------------------" + "</br>");
$("#testing-div").append("Does it stay changed?" + "</br>");
var test2 = new MODULE.sub();
test2.childFunction(); // doesn't work
$("#testing-div").append("NO, it was only Overriden in the 'test' Object.  It did not effect all the other new objects. :)" + "</br>");
$("#testing-div").append("----------------------------------------------------" + "</br>");
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Module Test</title>
<!-- 	<script data-main="scripts/main" src="scripts/require.js"></script> -->
</head>
<body>
    <h1>This is a test for separate Modules and Private variables.</h1>
    <div id="testing-div">
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="main.js"></script>
</body>
</html>
---

If you want to use RequireJS to accomplish the above, you can. RequireJS uses the Module Pattern which is what you and I are already using. If you want to separate out the files then there are two ways to do this.

  1. Normal - Just set up your JS files to use RequireJS and drop in the above Modules with only a slight modification.
  2. Leveraged - Use the Module nature of RequireJS as the modules to set up the closures. This looks like it may be harder to figure out but it may be more efficient in the long run.

NOTE: I haven't had a chance to compare these two options yet but wanted to include them for completeness.


You may find the following references helpful:

Batt answered 3/12, 2013 at 18:52 Comment(7)
Let's say I have module A and module B, module A wants to call myFunction from module B. But, if I make myFunction public, doesn't it mean that the client can also alter the function (by doing window.B.myFunction = ...)? How should I solve that problem? Assuming myFunction shouldn't be modified and it's criticalStavros
Are we talking about 2 separate JS files or everything in 1 file? If separate are you going to use RequireJS? Once this is clear we can try to answer the other question.Batt
2 separate files and assuming we are using RequireJS. I have edited the question.Stavros
OK. Now let me ask for clarity, I want to understand "We can't have". 1) "We are not able to figure out how to have a field that's accessible by child but not to outside public." OR 2) "We do not want (trying to prevent) to have a field that's accessible by child but not to outside public." Are you saying option 1 or 2? Or something else?Batt
@CharlesW. I think I understand what you are asking and updated my answer.Batt
I meant 1. It was indeed ambiguous.Stavros
The instance pattern is very useful. Thanks!Stavros
H
3

It sounds like what you're after is Require JS. I use this in almost all of my builds now. Alternatively you can have a look at the revealing module pattern as a fallback but for what you're after it sounds like Require is much more suitable.

http://requirejs.org

A good read: http://net.tutsplus.com/tutorials/javascript-ajax/principles-of-maintainable-javascript/

Hellenic answered 1/12, 2013 at 11:32 Comment(0)
R
3

Protected variables in javascript can be achieved by passing in the protected variables as a dependency. The subclass must be created within the parent as only there does it have access to the protected variables. Example jsFiddle

App = window.App || {};
App.ParentClass = (function(){

   var protectedState = {
      protectedVar: 'wow such protection'
   }

   return{
      SubClassInstance: App.SubClass(protectedState), //make the subclass accessible from outside
   }
})(); //closure gives us privacy

SubClass.js

App = window.App || {};
App.SubClass = function(protectedState){

    return {
        logProtectedVar : function(){
            //can access protectedState of parent
            console.log(protectedState.protectedVar);
        }
    }
}// I require protected state to work

main.js

// protected variable is accessible from parent and subclass and nowhere else

App.ParentClass.SubClassInstance.logProtectedVar();
// 'wow such protection' 

NOTE: as Charles W. mentioned, this pattern only works when protectedState is an object. If it were a string/int it would be passed by value and changes made in the subclass would not be visible from the parents copy.

Referential answered 9/12, 2013 at 8:25 Comment(7)
Short answer but exactly what I'm looking for. The variable passed this way has to be an object though, so that it is the reference that's passed. Thanks!Stavros
@Eru I tried your example and I only get Uncaught TypeError: Cannot call method 'logProtectedVar' of null. Am I missing something? Does it work for you?Batt
Ah I made a mistake. SubClassInstance: null, should be SubClassInstance: App.SubClass(protectedState),Referential
@Eru I tried the update and I get Uncaught TypeError: Object #<Object> has no method 'SubClass'. Also, I won't get notified that your replied if you don't @ me.Batt
ahh sorry Josh! I forgot to initialize App App = window.App || {}; I tested my code in a jsFiddle now :) jsfiddle.net/cwf9C/3Referential
@Eru If you add App.ParentClass.SubClassInstance.logProtectedVar = function() {console.log("This is a new function.");}; App.ParentClass.SubClassInstance.logProtectedVar(); // altered version after you call logProtectedVar() you will see that anyone can still change the value that is supposed to be Private. This is what the OP was trying to solve. "How to keep a Private var from being changed."Batt
Josh, the logProtectedVar function is supposed to be public, it's the protectedState that is private/protectedReferential
A
1

Modularization (splitting the code) is not the same as data protection (hiding data).

RequireJS solves the modularization issue, not the data-protection issue. Or to put it differently... Whatever issues exist with trying to protect data and whatever solutions exist to protect data, these issues and solutions are the same with or without RequireJS.

RequireJS implements all the mechanics to specify dependencies between modules, to load these dependencies only as needed, to avoid reloading things that have already been loaded, avoid loading things that are not required at all, quickly change the location of modules, have redundancy, etc.

After deployment if one finds RequireJS somehow too heavy, there's the almond library that can be used instead.

We can't have a field that's accessible by child but not to outside public (i.e. protected). Is there any way to achieve that?

If you want modularization (i.e. you want the child to be coded separately from the parent), I do not believe this is possible in JavaScript. It would be possible to have child and parent operate in the same closure but then this would not be modular. This is true with or without RequireJS.

If not, meaning if we wanteparentPrivate to be accessible, we need to make it public. Then the user will be able to modify it!

If you want to prevent assigning to parentPrivate, you can use Object.freeze() on the namespace that contains parentPrivate.

However, I don't know how well it is supported by various browsers. And if what is in parentPrivate is itself an object rather than a primitive value, it also needs to be frozen if you don't want it to be modified by clients of your code. And once an object is frozen, it is frozen for everyone so the module that owns the object does not get special treatment to modify it. And freezing does not hide anything.

Or you could use setters and getters like in this RequireJS example:

define(function () {
    'use strict';

    var writable = "initial value";

    var namespace = {
        get unwritable() { return writable; },
        doSomething: function () { writable = "changed value"; }

    };

    return namespace;

});

If the module is imported as parent, then parent.unwritable cannot be written to but the module itself can still change the value returned by writing to writable. Note that if the return value returned by the getter is an object rather than a primitive value, this object can be modified by the caller.

Again, this is true whether or not you use RequireJS. The solutions are the same, the problems are same, etc.

Alika answered 4/12, 2013 at 14:3 Comment(1)
You are right by pointing out data protection and modularization are not the same. However, they are related because if we don't do modularization (i.e. put all code in one file), we can achieve data protection by using module pattern and making variables private. Now I want to achieve modularization and split code into multiple files. I don't know how to achieve the same data protection under modularization.Stavros

© 2022 - 2024 — McMap. All rights reserved.