Last active
November 4, 2019 03:24
-
-
Save joe-oli/ab3164b8b68bb17f1d368c1583333010 to your computer and use it in GitHub Desktop.
namespacing approaches
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var MyNamespace = { | |
foo: function() { | |
}, | |
bar: function() { | |
} | |
}; | |
... | |
//USAGE. | |
MyNamespace.foo(); | |
--- | |
C. | |
This does not create a closure for your code - it makes it tedious to call your other functions because they always have to look like: yourNamespace.bar(); | |
============ | |
#2. | |
var myNamespace = {} //alternatively var myNamespace = myNamespace || {}; may be safer if it already exists; | |
myNamespace._construct = function() | |
{ | |
var staticVariable = "This is available to all functions created here" | |
function MyClass() | |
{ | |
// Depending on the class, we may build all the classes here | |
this.publicMethod = function() | |
{ | |
//Do stuff | |
} | |
} | |
// Alternatively, we may use a prototype. | |
MyClass.prototype.altPublicMethod = function() | |
{ | |
//Do stuff | |
} | |
function privateStuff() | |
{ | |
} | |
function publicStuff() | |
{ | |
// Code that may call other public and private functions | |
} | |
// List of things to place publically | |
this.publicStuff = publicStuff | |
this.MyClass = MyClass | |
} | |
myNamespace._construct() | |
// The following may or may not be in another file | |
myNamespace.subName = {} | |
myNamespace.subName._construct = function() | |
{ | |
// Build namespace | |
} | |
myNamespace.subName._construct() | |
External code can then be: | |
var myClass = new myNamespace.MyClass(); | |
var myOtherClass = new myNamepace.subName.SomeOtherClass(); | |
myNamespace.subName.publicOtherStuff(someParameter); | |
============= | |
#3. | |
a little bit less restrictive than the object literal form, is this: | |
var ns = new function() { | |
var internalFunction = function() { | |
}; | |
this.publicFunction = function() { | |
}; | |
}; | |
Notes: | |
i) like the module pattern https://yuiblog.com/blog/2007/06/12/module-pattern/ | |
ii) allows you to expose all your functions as public, while avoiding the rigid structure of an object literal. | |
iii) this approach allows for private functions, variables, and pseudo-constants (i.e. var API_KEY = 12345;) | |
========== | |
#4. | |
Q. import specific function names or import namespace, what is preferred? | |
Option 1 is to import certain functions: | |
import { cond, T, always, curry, compose } from 'ramda'; | |
Option 2 is to import the whole module like: | |
import * as R from "ramda"; | |
old ES5: const R = require('ramda'); | |
--- | |
A. by Bergi, https://github.com/bergus | |
TL;DR: It does not matter. | |
import * as … from 'ramda'; | |
import { … } from 'ramda'; | |
will both by default always bring in the complete Ramda module with all its dependencies. All code inside the module would be run, and which syntax was used to reference the exported bindings doesn't matter. Whether you use named or namespaced imports comes down to preference entirely. | |
What can reduce the file size to download and the used memory is static analysis. After having evaluated the module, the engine can garbage-collect those bindings that are referenced from nowhere. Module namespace objects might make this slightly harder, as anyone with access to the object can access all exports. But still those objects are specified in a way (as immutable) to allow static analysis on their usage and if the only thing you're doing with them is property access with constant names, engines are expected to utilise this fact. | |
Any size optimisation involves guessing which parts of the module need to be evaluated and which not, and happens in your module bundler (like Rollup https://rollupjs.org/guide/en#tree-shaking or WebPack https://webpack.js.org/guides/tree-shaking/). This is known as Tree Shaking, dropping parts of the code and entire dependencies when not needed (used by anything that got imported). It should be able to detect which imports you are using regardless of the import style, although it might have to bail out when are doing unusual things with the namespace object (like looping it or using dynamic property access). | |
To learn about the exact guesses your bundler can make, contact its documentation. | |
============ | |
#5 | |
I use the approach found on the Enterprise jQuery site: | |
https://appendto.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/ | |
Here is their example showing how to declare private & public properties and functions. Everything is done as a self-executing anonymous function. | |
(function( skillet, $, undefined ) { | |
//Private Property | |
var isHot = true; | |
//Public Property | |
skillet.ingredient = "Bacon Strips"; | |
//Public Method | |
skillet.fry = function() { | |
var oliveOil; | |
addItem( "\t\n Butter \n\t" ); | |
addItem( oliveOil ); | |
console.log( "Frying " + skillet.ingredient ); | |
}; | |
//Private Method | |
function addItem( item ) { | |
if ( item !== undefined ) { | |
console.log( "Adding " + $.trim(item) ); | |
} | |
} | |
}( window.skillet = window.skillet || {}, jQuery )); | |
So if you want to access one of the public members you would just go skillet.fry() or skillet.ingredients. | |
What's really cool is that you can now extend the namespace using the exact same syntax. | |
//Adding new Functionality to the skillet | |
(function( skillet, $, undefined ) { | |
//Private Property | |
var amountOfGrease = "1 Cup"; | |
//Public Method | |
skillet.toString = function() { | |
console.log( skillet.quantity + " " + | |
skillet.ingredient + " & " + | |
amountOfGrease + " of Grease" ); | |
console.log( isHot ? "Hot" : "Cold" ); | |
}; | |
}( window.skillet = window.skillet || {}, jQuery )); | |
The third undefined argument | |
The third, undefined argument is the source of the variable of value undefined. I'm not sure if it's still relevant today, but while working with older browsers / JavaScript standards (ecmascript 5, javascript < 1.8.5 ~ firefox 4), the global-scope variable undefined is writable, so anyone could rewrite its value. The third argument (when not passed a value) creates a variable named undefined which is scoped to the namespace/function. Because no value was passed when you created the name space, it defaults to the value undefined. | |
The benefit of window.skillet = window.skillet || {} is that it permits multiple scripts to safely add to the same namespace when they don't know in advance in what order they will execute. This can be helpful either if you want to be able to reorder your script inclusions arbitrarily without breaking your code, or if you want to load scripts asynchronously with the async attribute and so have no guarantee about execution order. | |
https://stackoverflow.com/questions/6439579/what-does-var-foo-foo-assign-a-variable-or-an-empty-object-to-that-va | |
#5b. | |
TL;DR; | |
if(!MY_NAMESPACE) {var MY_NAMESPACE={};} | |
the intent of || {} is as follows: | |
This particular pattern when seen at the top of files is used to create a namespace, i.e. a named object under which functions and variables can be created without unduly polluting the global object. | |
The reason why it's used is so that if you have two (or more) files: | |
var MY_NAMESPACE = MY_NAMESPACE || {}; | |
MY_NAMESPACE.func1 = { | |
} | |
and | |
var MY_NAMESPACE = MY_NAMESPACE || {}; | |
#5c. | |
Another common use for || is to set a default value for an undefined function parameter also: | |
function display(a) { | |
a = a || 'default'; // here we set the default value of a to be 'default' | |
console.log(a); | |
} | |
// we call display without providing a parameter | |
display(); // this will log 'default' | |
display('test'); // this will log 'test' to the console | |
The equivalent in other programming usually is: | |
function display(a = 'default') { | |
// ... | |
} | |
MY_NAMESPACE.func2 = { | |
} | |
both of which share the same namespace it then doesn't matter in which order the two files are loaded, you still get func1 and func2 correctly defined within the MY_NAMESPACE object correctly. | |
The first file loaded will create the initial MY_NAMESPACE object, and any subsequently loaded file will augment the object. | |
Usefully, this also allows asynchronous loading of scripts that share the same namespace which can improve page loading times. If the <script> tags have the defer attribute set you can't know in which order they'll be interpreted, so as described above this fixes that problem too. | |
==================== | |
#6. From https://github.com/brettryan | |
I normally build it in a closure: | |
var MYNS = MYNS || {}; | |
MYNS.subns = (function() { | |
function privateMethod() { | |
// Do private stuff, or build internal. | |
return "Message"; | |
} | |
return { | |
someProperty: 'prop value', | |
publicMethod: function() { | |
return privateMethod() + " stuff"; | |
} | |
}; | |
})(); | |
My style over the years has had a subtle change since writing this, and I now find myself writing the closure like this: | |
var MYNS = MYNS || {}; | |
MYNS.subns = (function() { | |
var internalState = "Message"; | |
var privateMethod = function() { | |
// Do private stuff, or build internal. | |
return internalState; | |
}; | |
var publicMethod = function() { | |
return privateMethod() + " stuff"; | |
}; | |
return { | |
someProperty: 'prop value', | |
publicMethod: publicMethod | |
}; | |
})(); | |
In this way I find the public API and implementation easier to understand. Think of the return statement as being a public interface to the implementation. | |
======================== | |
#7. From Stoyan Stefanov in his `JavaScript Patterns` book | |
http://shop.oreilly.com/product/9780596806767.do | |
(it also shows how he does comments that allows for auto-generated API documentation, and how to add a method to a custom object's prototype): | |
/** | |
* My JavaScript application | |
* | |
* @module myapp | |
*/ | |
/** @namespace Namespace for MYAPP classes and functions. */ | |
var MYAPP = MYAPP || {}; | |
/** | |
* A maths utility | |
* @namespace MYAPP | |
* @class math_stuff | |
*/ | |
MYAPP.math_stuff = { | |
/** | |
* Sums two numbers | |
* | |
* @method sum | |
* @param {Number} a First number | |
* @param {Number} b Second number | |
* @return {Number} Sum of the inputs | |
*/ | |
sum: function (a, b) { | |
return a + b; | |
}, | |
/** | |
* Multiplies two numbers | |
* | |
* @method multi | |
* @param {Number} a First number | |
* @param {Number} b Second number | |
* @return {Number} The inputs multiplied | |
*/ | |
multi: function (a, b) { | |
return a * b; | |
} | |
}; | |
/** | |
* Constructs Person objects | |
* @class Person | |
* @constructor | |
* @namespace MYAPP | |
* @param {String} First name | |
* @param {String} Last name | |
*/ | |
MYAPP.Person = function (first, last) { | |
/** | |
* First name of the Person | |
* @property first_name | |
* @type String | |
*/ | |
this.first_name = first; | |
/** | |
* Last name of the Person | |
* @property last_name | |
* @type String | |
*/ | |
this.last_name = last; | |
}; | |
/** | |
* Return Person's full name | |
* | |
* @method getName | |
* @return {String} First name + last name | |
*/ | |
MYAPP.Person.prototype.getName = function () { | |
return this.first_name + ' ' + this.last_name; | |
}; | |
================= | |
#8. using IIFE, pre-ES6; now you can just use Object literals... https://ponyfoo.com/articles/es6-object-literal-features-in-depth | |
var namespace = (function() { | |
// expose to public; put this at the top, so you can se at a glance wha the API exposes. | |
return { | |
a: internalA, | |
c: internalC | |
} | |
// all private | |
/** | |
* Full JSDoc | |
*/ | |
function internalA() { | |
// ... | |
} | |
/** | |
* Full JSDoc | |
*/ | |
function internalB() { | |
// ... | |
} | |
/** | |
* Full JSDoc | |
*/ | |
function internalC() { | |
// ... | |
} | |
/** | |
* Full JSDoc | |
*/ | |
function internalD() { | |
// ... | |
} | |
})(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment