JS namespace schema parsing

Posted by nightowl on Thu, 23 May 2019 21:21:21 +0200

brief introduction

See such a question on the SF:

For example, for compelling reasons, several global functions need to be written. But I don't want to do that.

window.a = function(){}
window.b = function(){}
window.c = function(){}

What's a good way to write this question?

Answer:

If you use jQuery, you can write as follows

$.extend(window, {
    a: function() {},
    b: function() {},
    c: function() {}
});

If you don't use jQuery, you can directly implement similar extensions:

(() => {
    var defining = {
        a: function() { },
        b: function() { },
        c: function() { }
    };

    Object.keys(defining).forEach(key => {
        window[key] = defining[key];
    });
})();

In JavaScript, namespaces can help us prevent conflicts with other objects or variables in the global namespace. Namespaces also help organize code, making it more maintainable and readable. This article aims to explore several common namespace patterns in JavaScript and provide us with a way of thinking.

In general, the implementation of namespaces is rooted in windows. When applying for a.b.c namespace to windows, first check whether a member exists in windows, if not, create a new empty associative array named a under windows, and if a already exists, continue to check whether b exists in windows.a, and so on. The following are the implementation methods in Atlas and YUI respectively.

The implementation method of Atlas namespace:

Function.registerNamespace =function(namespacePath){
    //with window Root
    var rootObject =window;
    //Break up namespace paths into arrays
    var namespaceParts =namespacePath.split('.');
    for (var i =0;i <namespaceParts.length;i++) {
        var currentPart =namespaceParts[i];
        //If it does not exist in the current namespace, create a new one Object Object, equivalent to an associative array.
        if(!rootObject[currentPart])      {
           rootObject[currentPart]=new Object();
        }
        rootObject =rootObject[currentPart];
    }
}

Another way of implementation:

namespace = function(){
    var argus = arguments;
    for(var i = 0; i < argus.length; i++){
        var objs = argus[i].split(".");
        var obj = window;
        for(var j = 0; j < objs.length; j++){
            obj[objs[j]] = obj[objs[j]] || {};
            obj = obj[objs[j]];
        }
    }
    return obj;
};

namespace("tools.base");

We often use literal quantities of js objects to implement js namespaces:

var school = {
    addClass:function(classnum){
        console.log(classnum);
    },
    addStudent:function(stuId){
        console.log(stuId);
    }
}
school.addClass("2");

Any variables and functions declared in the global scope are attributes of window s objects. When names conflict, some uncontrollable problems will arise. Global variables can cause the following problems:

  • name conflict
  • Code vulnerability
  • Difficult to test

Rational use of namespaces in programming development can avoid conflicts caused by the same variable or object names. Namespaces also help organize code, making it more maintainable and readable. Although native namespace support is not provided in JavaScript, we can use other methods (objects and closures) to achieve similar results. Here are some common namespace patterns:

1. Single global variable

A popular namespace pattern in JavaScript is to select a global variable as the primary reference object. Since every possible global variable becomes the property of the only global variable, there is no need to create more than one global variable, thus avoiding conflicts with other declarations.

The single global variable pattern has been used in many JavaScript class libraries, such as:

  • YUI defines a unique YUI global object
  • JQuery defines $and jQuery, $used by other class libraries
  • Dojo defines a Dojo global variable
  • Closure class library defines a goog global object
  • Underscore class library defines a _global object

Examples are as follows:

var myApplication = (function() {
    var count = 1;
     function funcur() {
        console.log(count);
     };
     return {
        funcur:funcur
     }
 })();
myApplication.funcur();

Although the single global variable pattern is suitable for some situations, the biggest challenge is to ensure that a single global variable is used exclusively on the page and that no naming conflicts occur.

2. Namespace prefix

The idea of namespace prefix pattern is very clear, that is, to select a unique namespace, and then declare variables, methods and objects after it. Examples are as follows:

var = myApplication_propertyA = {};
var = myApplication_propertyA = {};

function myApplication_myMethod() {
    // ***
}

To some extent, it does reduce the probability of naming conflicts, but it does not reduce the number of global variables. When the application scale is enlarged, many global variables will be generated. In a global namespace, this pattern relies heavily on the prefix that no one else uses, and sometimes it is difficult to judge whether someone has used a particular prefix. Special attention must be paid to using this pattern.

3. Object literal representation

The object literal pattern can be thought of as an object containing a set of key-value pairs, each pair of keys and values separated by colons, and keys can also be a new namespace for code. Examples are as follows:

var myApplication = {
    // It's easy to define functions for object literals
    getInfo:function() {
        // ***
    },
    
    // Object namespaces can be further supported
    models:{},
    views:{
        pages:{}
    },
    collections:{}
};

Just like adding attributes to objects, we can add attributes directly to namespaces. The object literal method does not pollute the global namespace and logically helps organize code and parameters. Moreover, this method is very readable and maintainable. Of course, we should test the existence of variables with the same name in order to avoid conflicts. Here are some commonly used detection methods:

var myApplication = myApplication || {};

if(!myApplication) {
    myApplication  = {};
}

window.myApplication || (window.myApplication || {});

// In the light of jQuery
var myApplication = $.fn.myApplication = function() {};

var myApplication = myApplication === undefined ? {} :myApplication;

Object literals provide us with elegant key/value grammar. We can organize code very conveniently, encapsulate different logic or functions, and have strong readability, maintainability and extensibility.

4. Nested namespaces

Nested namespace pattern can be said to be an upgraded version of the object literal pattern. It is also an effective conflict avoidance pattern, because even if a namespace exists, it is unlikely to have the same nested sub-objects. Examples are as follows:

var myApplication = myApplication || {};
 
 // Define nested subobjects
 myApplication.routers = myApplication.routers || {};
 myApplication.routers.test = myApplication.routers.test || {};

Of course, we can also choose to declare new nested namespaces or attributes as index attributes, such as:

myApplication['routers'] = myApplication['routers'] || {};

Using nested namespace pattern can make the code readable and organized, and relatively safe, and not easy to produce conflicts. The disadvantage is that if our namespace is too nested, it will increase the query workload of browsers, and we can reduce the query time by caching the sub-objects that are accessed many times.

5. Functional expressions called immediately

The immediate call function (IIFE) is actually an anonymous function, which is called immediately after being defined. In JavaScript, since variables and functions are explicitly defined in a context that can only be accessed internally, function calls provide a convenient way to implement private variables and methods. IIFE is a common method for encapsulating application logic to protect it from global namespaces, and it can also play a special role in namespaces. Examples are as follows:

;(function (namespace, undefined) {

    // Private attributes
    var foo = "foo";
        bar = "bar";

    // Public methods and attributes
    namespace.foobar = "foobar";
    namespace.sayHello = function () {
        say("Hello World!");
    };

    // Private method
    function say(str) {
        console.log("You said:" + str);
    };
})(window.namespace = window.namespace || {});
console.log(namespace.foobar);
//foobar

Scalability is the key to any scalable namespace schema, which can be easily achieved with IIFE. We can use IIFE again to add more functions to the namespace.

6. Namespace injection

Namespace injection is another variant of IIFE, which "injects" methods and attributes into a specific namespace from within the function wrapper, using this as a namespace proxy. The advantage of this pattern is that functional behavior can be applied to multiple objects or namespaces. Examples are as follows:

var myApplication = myApplication || {};
myApplication.utils = {};

;(function () {
    var value = 5;
    
    this.getValue = function () {
        return value;
    }

    // Define a new subnamespace
    this.tools = {};
}).apply(myApplication.utils);

(function () {
    this.diagnose = function () {
        return "diagnose";
    }
}).apply(myApplication.utils.tools);

// The same way is common. IIFE Up-scaling, passing context as a parameter and modifying it, not just using it this

There is also a way to use API to achieve the natural separation of context and parameters. This pattern feels more like the creator of a module, but as a module, it also provides an encapsulation solution. Examples are as follows:

var ns = ns || {},
    ns1 = ns1 || {};

// Module, namespace Creator
var creator = function (val) {
    var val = val || 0;
    
    this.next = function () {
        return val ++ ;
    };

    this.reset = function () {
        val = 0;
    }
}

creator.call(ns);
// ns.next, ns.reset It already exists.

creator.call(ns1, 5000);
// ns1 Contains the same method, but the value is rewritten to 5000

console.log(ns.next()); //0
console.log(ns1.next());//5000

Namespace injection is used to specify a similar set of basic functions for multiple modules or namespaces, but it's best to use it when declaring private variables or methods, otherwise nested namespaces are enough.

7. Automatically nested namespaces

Nested namespace patterns provide an organizational hierarchy for code units, but each time a hierarchy is created, we also need to ensure that it has a corresponding parent hierarchy. When the number of levels is large, it will bring us a lot of trouble. We can not create the desired level quickly and conveniently. So how to solve this problem? Stoyan Stefanov proposes to create a method that receives string parameters as a nest, parses it, and automatically fills the basic namespace with the required objects. Here is an implementation of this pattern:

function extend(ns, nsStr) {
    var parts = nsStr.split("."),
        parent = ns,
        pl;

    pl = parts.length;

    for (var i = 0; i < pl; i++) {
        // Property is created if it does not exist
        if (typeof parent[parts[i]] === "undefined") {
            parent[prats[i]] = {};
        }
        parent = parent[parts[i]];
    }
    return parent;
}

// usage
var myApplication = myApplication || {};
var mod = extend(myApplication, "module.module2");

In the past, we had to explicitly declare various nested objects for their namespaces, and now we do so in a more concise and elegant way.

Reference address: http://www.cnblogs.com/syfwhu/p/4885628.html

Topics: Javascript JQuery Windows Programming