Namespaces

In most programming languages we know the concept of namespaces (or packages).Namespaces allow us to group code and help us to avoid name-collisions.

In c# for example you have this declaration

namespace MyNameSpace
{
    public class MyClass
    {
    }
}

If you want to use MyClass, you need to do explicitly say in which namespace it lives:

MyNameSpace.MyClass obj;

Unfortunately, the concept of namespaces does not exist in JavaScript. To add insult to injury, everything you create in JavaScript is by default global. Now obviously, this is a recipe for disaster, especially since in any modern application you’ll probably end upll using third-party components. How do you avoid these collisions then?

Let’s first look at an anti-pattern, which is declaring all your functions and variables globally:

function calculateVat(prod) {
    return prod.price * 1.21;
}
 
var product = function (price) {
    this.price = price;
    this.getPrice = function(){
                       return this.price;
        };
    };
 
function doCalculations() {
    var p = new product(100);
    alert(calculateVat(p.getPrice()));
}

This is a simple snippet, but there are already 3 global items: CalculateVat, product and doCalculations. This could get us in trouble if we start using third-party libraries. What would happen if they use the same names? Well, if they are included after our script, they will overwrite our variables and function. Bad! If they’re included before our script, we’re overwriting their variables, which is also bad, since we’ll probably break the library.

How to create a namespace in JavaScript

To solve this problem you can create a single global object for your app and make all functions and variables properties of that global object:

var MYAPPLICATION = {
    calculateVat: function (base) {
        return base * 1.21;
    },
    product: function (price) {
        this.price = price;
        this.getPrice = function(){
                          return this.price;
                       };
    },
    doCalculations: function () {
        var p = new MYAPPLICATION.product(100);
        alert(this.calculateVat(p.getPrice()));
    }
}

Now we only have one global variable (MYAPPLICATION). Although this is not really a namespace, it can be used as one, since you have to go through the MYAPPLICATION object in order to get to your application code:

var p = new MYAPPLICATION.product(150);
alert(p.getPrice());

Nested namespaces

In most languages you can declare a namespace inside a namespace. This allows for even better modularization. We can just apply the pattern again and define an object inside the outer object:

var MYAPPLICATION = {
    MODEL: {
        product: function (price) {
                     this.price = price;
                    this.getPrice = function(){
                         return this.price;
                     };
                 }
    },
    LOGIC: {
        calculateVat: function (base) {
            return base * 1.21;
        },
        doCalculations: function () {
            var p = new MYAPPLICATION.MODEL.product(100);
            alert(this.calculateVat(p.getPrice()));
        }
    }
}

This pattern is fairly simple and is a good way to avoid naming collisions with other libraries (or your own code for that matter).

Safely creating namespaces

Since we still have one global object, there’s still a possibility that we are overwriting another global object with the same name. Therefore, we need to build in some safety:

// not safe, if there's another object with this name we will overwrite it
var MYAPPLICATION = {};
 
// We need to do a check before we create the namespace
if (typeof MYAPPLICATION === "undefined") {
    var MYAPPLICATION = {};
}
 
// or a shorter version
var MAYAPPLICATION = MYAPPLICATION || {};

As you can see in this example, if we want to safely create namespaces it can be quite cumbersome and require a bit of boilerplate code. Wouldn’t it be nice if we could avoid all of this?

Creating a multi-purpose Namespace function

What we’d like to do is simply call a function that creates a namespace safely and then lets us define function and variables in that namespace. Here’s where JavaScript’s dynamic nature really shines. Let’s start with an example of what we want to achieve:

var MAYAPPLICATION = MYAPPLICATION || {};
 
var ns = MYAPPLICATION.createNS("MYAPPLICATION.MODEL.PRODUCTS");
 
ns.product = function(price){
   this.price = price;
   this.getPrice = function(){
    return this.price;
   }
};

We still need to check our main namespace (you have to start somewhere), but it will allow us to create a hierarchy of namespaces with a single line and have it all figured out.

So how can we do that? Let’s take a look at the implementation of createNS:

MYAPPLICATION.createNS = function (namespace) {
    var nsparts = namespace.split(".");
    var parent = MYAPPLICATION;
 
    // we want to be able to include or exclude the root namespace so we strip
    // it if it's in the namespace
    if (nsparts[0] === "MYAPPLICATION") {
        nsparts = nsparts.slice(1);
    }
 
    // loop through the parts and create a nested namespace if necessary
    for (var i = 0; i < nsparts.length; i++) {
        var partname = nsparts[i];
        // check if the current parent already has the namespace declared
        // if it isn't, then create it
        if (typeof parent[partname] === "undefined") {
            parent[partname] = {};
        }
        // get a reference to the deepest element in the hierarchy so far
        parent = parent[partname];
    }
    // the parent is now constructed with empty namespaces and can be used.
    // we return the outermost namespace
    return parent;
};

This function splits the string you pass it and creates a nested namespace for each part. This means that this code:

MYAPPLICATION.createNS("MYAPPLICATION.MODEL.PRODUCTS");

is essentially the same as declaring the following (but with the advantage of checking whether we’re not overriding any existing namespace):

var MYAPPLICATION = {
    MODEL: {
        PRODUCTS: {
        }
    }
}

Aliasing namespaces

When your namespace hierarchy becomes deeper and deeper you will notice that it becomes cumbersome since you always have to type the complete reference to a function or a variable. This can easily be solved by aliasing a namespace (similar to the using statement in C# and Imports in JAVA and VB). Let’s take a look at an example:

MYAPPLICATION.createNS("MYAPPLICATION.MODEL.PRODUCTS");
MYAPPLICATION.createNS("MYAPPLICATION.LOGIC.BUSINESS");
 
MYAPPLICATION.MODEL.PRODUCTS.product = function(price){                                          
    this.price = price;   
    this.isVatApplicable = true;
    this.getPrice = function(){                                              
        return this.price;                                           
    }                                      
};
 
MYAPPLICATION.MODEL.PRODUCTS.currentvatrate = 1.21;
 
MYAPPLICATION.LOGIC.BUSINESS.getSelectedProductTotal = function () {
    var p = new MYAPPLICATION.MODEL.PRODUCTS.product(100);
    if(p.isVatApplicable){
        return p.getPrice() * MYAPPLICATION.MODEL.PRODUCTS.currentvatrate;
    }
    else {
        return p.getPrice();
    }
}

This simple snippet declares two namespaces and then adds an object to the first and a function to the second. The function uses the object in the other namespace. As you can see, this simple method which just uses one function and a couple of variables is very cumbersome to write because of all the references.

A shortcut would be easier. We can achieve this by simply aliasing the namespace in a local variable:

MYAPPLICATION.LOGIC.BUSINESS.getSelectedProductTotal = function () {
    var model = MYAPPLICATION.MODEL.PRODUCTS;
    var p = new model.product(150);
    if (p.isVatApplicable) {
        return p.getPrice() * model.currentvatrate;
    }
    else {
        return p.getPrice();
    }
}

Apart from saving you typing work and decreasing your file size, this pattern has one more advantage that I think is even more important. Since you are declaring your namespace at the top of each function, you are explicitly clarifying in your code on what module it depends. That, in my opinion, is invaluable and will help you understand written code a lot better.

Disadvantages

Although this pattern is easy to use and perfectly valid for many tasks and scenarios there is one big disadvantage:

The one global instance is vulnerable. This means that anyone could access its internals. For example, the product has a property price and a method getPrice(). However, nothing prevents us from directly accessing the price property. We don’t want this, we want to expose only the method so we can control the access to our private variable. To solve these problems we need to take a look at how can create modules that encapsulate certain data and behavior.

Modules

Private members

Because JavaScript has no syntax to denote scope, we need to use closures to implement private members. An extended example of this can be seen in my post about private variables in JavaScript A simple example of this is shown in the following snippet:

MYAPPLICATION.MODEL.PRODUCTS.product = function(price){
    var price = price;   
     
    return {
        isVatApplicable: true,
        getPrice: function(){                                              
            return price;                                           
        }
    };
};

In this snippet, the variable price is private, but it is accessible to the method because it’s inside the closure. The public members are isVatApplicable and getPrice.

Another pattern would be the revealing module. It’s essentially the same as the previous pattern, but now we declare everything privately and then decide what we return (and thus expose as public members):

MYAPPLICATION.MODEL.PRODUCTS.product = function(price){
    var price = price;   
    var isVatApplicable = true;
    var getPrice: function(){                                              
            return price;                                           
        };
 
    return {
        isVatApplicable: isVatApplicable,
        getPrice: getPrice
    };
};

To me this last pattern is the clearest, because you are declaring everything up front and then explicitly make some members public. It shows the intent.

Private members: caveats

There’s one caveat with the pattern described above. Since in JavaScript all variables are passed by reference you could potentially expose members that you didn’t want to be public. In the example above that doesn’t happen because price is a value. However, let’s consider the following example:

MYAPPLICATION.MODEL.PRODUCTS.product = function(width, height){
    var dimensions = {
        width: width,
        height: height
    };
    var getDimensions = function(){                                              
            return dimensions;                                           
        };
    return {
        getDimensions: getDimensions
    };
};

We are following the same pattern here, so one might think that the dimensions-variable is private. The following code shows a caveat though:

var model = MYAPPLICATION.MODEL.PRODUCTS;
var p = new model.product(50,100);
var dims = p.getDimensions();
dims.width = 1000;
dims.height = 1000;
// alerts 1000 1000 => unexpected
alert(p.getDimensions().width + “ “ + p.getDimensions().height); 

This code will actually alert “1000 1000”. Because the dimensions variable is returned by reference the first time we call p.getDimensions, changing its values will affect the values of the private variable.

What solutions are there for this problem? There are a few things you can do to mitigate this problem:

  • Do not return objects, but only actual values. In our example this would constitute of creating two methods: getWidth and getHeight.
  • Create a copy of the object. You could do this in the getDimensions method.

Tying it together

The following example ties all the previous techniques together:

// create the root namespace and making sure we're not overwriting it
var MYAPPLICATION = MYAPPLICATION || {};
 
// create a general purpose namespace method
// this will allow us to create namespace a bit easier
MYAPPLICATION.createNS = function (namespace) {
    var nsparts = namespace.split(".");
    var parent = MYAPPLICATION;
 
    // we want to be able to include or exclude the root namespace
    // So we strip it if it's in the namespace
    if (nsparts[0] === "MYAPPLICATION") {
        nsparts = nsparts.slice(1);
    }
 
    // loop through the parts and create
    // a nested namespace if necessary
    for (var i = 0; i < nsparts.length; i++) {
        var partname = nsparts[i];
        // check if the current parent already has
        // the namespace declared, if not create it
        if (typeof parent[partname] === "undefined") {
            parent[partname] = {};
        }
        // get a reference to the deepest element
        // in the hierarchy so far
        parent = parent[partname];
    }
    // the parent is now completely constructed
    // with empty namespaces and can be used.
    return parent;
};
 
// Create the namespace for products
MYAPPLICATION.createNS("MYAPPLICATION.MODEL.PRODUCTS");
 
MYAPPLICATION.MODEL.PRODUCTS.product = function(width, height){
    // private variables
    var dimensions = {
        width: width,
        height: height
    };
    // private methods
    // creating getWidth and getHeight
    // to prevent access by reference to dimensions
    var getWidth = function(){
        return dimensions.width;
    };
    var getHeight = function(){
        return dimensions.height;
    };
    // public API
    return {
        getWidth: getWidth,
        getHeight: getHeight
    };
};
 
// Create the namespace for the logic
MYAPPLICATION.createNS("MYAPPLICATION.LOGIC.BUSINESS");
 
 
MYAPPLICATION.LOGIC.BUSINESS.createAndAlertProduct = function () {
    var model = MYAPPLICATION.MODEL.PRODUCTS;
    var p = new model.product(50,100);
    alert(p.getWidth() + " " + p.getHeight());
};

As you can see, this patterns allows for modular and well-structured JavaScript. Using these techniques can make your code easier to read and to modify. There are other techniques available too, and you can build and vary this pattern. In my next post I will show you can create a sandbox. This will allow your code to operate in an isolated environment. It also allows for a type of dependency Injection.

For the readers who made it all the way here, thanks for your patience, the post turned out a bit longer than I anticipated. I hope you enjoyed it.

转自:http://www.kenneth-truyers.net/2013/04/27/javascript-namespaces-and-modules/

[转]JavaScript Namespaces and Modules的更多相关文章

  1. TypeScript & JavaScript

    http://www.typescriptlang.org/docs/tutorial.html handbook: Basic Types Variable Declarations Interfa ...

  2. 6周学习计划,攻克JavaScript难关(React/Redux/ES6 etc.)

    作者:余博伦链接:https://zhuanlan.zhihu.com/p/23412169来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 和大家一样,最近我也看了Jo ...

  3. 实现一个JavaScript模块化加载器

    对任何程序,都存在一个规模的问题,起初我们使用函数来组织不同的模块,但是随着应用规模的不断变大,简单的重构函数并不能顺利的解决问题.尤其对JavaScript程序而言,模块化有助于解决我们在前端开发中 ...

  4. Javascript modules--js 模块化

    https://medium.freecodecamp.org/javascript-modules-a-beginner-s-guide-783f7d7a5fcc 这个网站也是非常好:https:/ ...

  5. JavaScript modularity with RequireJS (from spaghetti code to ravioli code)

    http://netmvc.blogspot.com/2012/11/javascript-modularity-with-requirejs.html Today I would like to d ...

  6. Import Statements 导入语句

    Syntax of an Import Statement 导入语句的语法 An import statement allows clients to tell the engine which mo ...

  7. go-ethereum源码分析 PartIII 共识流程

    A: js指令转化为新transaction 在web3上操作流程 1. import to web3 2. connect to peers 3. read local key store 4. d ...

  8. 深入理解typescript的Functions

    Functions Introduction # Functions are the fundamental building block of any application in JavaScri ...

  9. SuiteScript > Script Queue Monitor (Beta)

    Share Note: Installing and Accessing the Script Queue Monitor Script Queue Monitor (Beta) is availab ...

随机推荐

  1. requireJS的使用_API-1

    requireJS的使用_API(1) 之前有介绍过requireJS(模块化开发),可以看看 ,但是不详细,所以今天参考官网来详细介绍一下: 1.加载js文件: RequireJS的目标是鼓励代码的 ...

  2. Java虚拟机类型卸载和类型更新解析(转)

    转自:http://www.blogjava.net/zhuxing/archive/2008/07/24/217285.html [摘要]          前面系统讨论过java 类型加载(loa ...

  3. SPOJ 375 树链剖分

    SPOJ太慢了,SPOJ太慢了, 题意:给定n(n<=10000)个节点的树,每条边有边权,有两种操作:1.修改某条变的边权:2.查询u,v之间路径上的最大边权. 分析:树链剖分入门题,看这里: ...

  4. Unity3D 如何图形问题修正旋转模型已导入?

     如何纠正旋转模型被导入? 一些立体艺术资源包导出其模式,以便 Z 轴向上.Unity 大多数标准的脚本中假定的三维世界 Y 轴代表了.在 Unity 比改动脚本使其契合easy得多. Z 轴朝上 ...

  5. jsScript中的一些操作方法

    1.采用dom方式对script标签进行操作 var h = document.getElementsByTagName('HEAD').item(0); var s = document.creat ...

  6. 如何定义自己的ViewGroup

    在发展中,有时会遇到一些要求.布局和控制系统不仅提供使用,以满足我们的发展,所以这一次就行,通常是你自己的自定义布局(ViewGroup)并控制(View)该.我在这里,我们将用一个简单的例子,当他们 ...

  7. 我学的是设计模式的视频教程——装饰图案,装饰图案VS代理模式

    课程视频 装饰模式 装饰模式VS代理模式1 装饰模式VS代理模式2 课程笔记 课程笔记 课程代码 课程代码 新课程火热报名中 课程介绍 版权声明:本文博主原创文章,博客,未经同意不得转载.

  8. oracle_job 清空冗余数据 ,每一分钟执行一次

    参照这个例子:http://cherryqq.iteye.com/blog/855022 思路: data表中有4条数据 ,relation有3条数据,通过data_id 对应,需要定时删除 data ...

  9. 前端学习笔记(zepto或jquery)——对li标签的相关操作(一)

    对li标签的相关操作——点击li标签进行样式切换的两种方式 Demo演示: 1 2 3 4 // 详解: 第一种方式(以ul为基础): $("ul").bind("cli ...

  10. Appium在手机浏览器使用滑屏Not yet implemented解决办法。

    在手机浏览器使用swipe.scroll等手机特有行为时,因为默认context是WEBVIEW,所有一定要切换回NATIVE_APP才可以使用. python: driver.switch_to.c ...