[classes and modules]


An important reason for organizing code into classes is to make the code more "modular", which can realize code reuse in many different scenarios. But classes are not the only way to modularize code. Generally speaking, a module is a separate JavaScript file. A module file can contain a class definition, a set of related classes, a utility library, or some code to be executed. As long as you write code in the form of modules, any JavaScript code segment can be treated as a module. There is no language structure defined in JavaScript to support modules (but imports and exports are indeed reserved keywords in JavaScript, so future versions of JavaScript may support them), which also means that writing modular code in JavaScript mostly follows a certain coding convention.

Many JavaScript libraries and client-side programming frameworks contain modular systems. For example, the Dojo toolkit and Google's Closure library define the provide() and require() functions to declare and load modules. Moreover, the CommonJS server-side JavaScript Standard Specification (refer to http://commonjs.org )A module specification is created, which also uses the require() function. This modular system is usually used to handle module loading and dependency management. If you use these frameworks, you must define modules according to the module authoring conventions provided by the framework.

The goal of modularization is to support large-scale program development, deal with the assembly of code in decentralized sources, and make the code run correctly. Even if it contains the module code that the author does not expect, the code can be executed correctly. To do this, unresponsive modules must avoid modifying the global execution context, so subsequent modules should execute in the original (or near original) context in which they expect to run. This actually means that modules should define as few global IDs as possible. Ideally, no module should define more than one (Global ID). Next, we give a simple method to do this.

Objects used as namespaces

One way to avoid polluting global variables during module creation is to use an object as a namespace. Instead of defining global functions and variables, it stores functions and values as namespace object properties (which can be referenced by global variables).

The top-level namespace is often used to identify the author or organization creating the module and avoid namespace naming conflicts. For example, Google's Closure library defines the Set class in its namespace Google. Structs. Each developer reverses the components of the Internet domain name, so that the namespace prefix created is globally unique and generally will not be adopted by other module authors.

The way to import modules with long namespaces is very important. However, programmers tend to import the whole module into the global namespace rather than importing (a single class in the namespace).

var sets = com.davidflanagan.collections.sets;

By convention, the file name of the module should match the namespace. The sets module should be saved in the file sets.js. If the module uses the namespace collections. Sets, the file should be saved in the directory collections / (this directory should also contain another file maps.js). And the module using the namespace com.davidflanagan.collections.sets should be in the file com/davidflanagan/collections/sets.js.

Functions as private namespaces

The module exports some public API s to other programmers, including functions, classes, properties and methods. However, the implementation of the module often requires some additional auxiliary functions and methods, which do not need to be visible outside the module.

This can be achieved by defining modules inside a function. Variables and functions defined in a function belong to local members of the function and are invisible outside the function. In fact, this function scope can be used as the private namespace of the module (sometimes called "module function"). The following example shows how to use "module function" to implement the Set class:

Example: in module function Set class
// Declare the global variable Set and assign a value to it using the return value of a function
// A pair of parentheses immediately following the end of the function indicates that the function is executed immediately after it is defined
// Its return value will be assigned to Set instead of this function
// Note that it is a function expression, not a statement, so the function "invocation" does not create a global variable
var Set = (function invocation() {
  function Set() { // This constructor is a local variable
	this.values = {};	// The properties of this object are used to hold the collection
	this.n = 0;	// Number of values in the set 	,
	this.add.apply (this, arguments); // Add all parameters to the collection

// Define instance methods for Set.prototype
// The detailed code is omitted here
Set.prototype.contains = function(value) {
	// Notice that we called v2s() instead of set._v2s() with a cumbersome prefix
	return this.values.has0wnProperty(v2s(value));
Set.prototype.size = function() { return this.n; };
Set.prototype.add = function() {/*...*/};
Set.prototype.remove = function() (/*...*/};
	Set.prototype.foreach = function(f, context) {/*...*/};

// Here are some auxiliary functions and variables used in the above method
// They do not belong to the common API of the module, but they are hidden within the scope of this function
// Therefore, we do not have to define them as properties of Set or prefix them with underscores
function v2s(val) {/*...*/}
function objectId(o) {/*...*/}
var nextId = 1;
// The common API for this module is the Set() constructor
// We need to export this function from the private namespace
// So that it can be used externally, in which case we export it by returning this constructor
// It becomes the value of the expression referred to in the first line of code
return Set;
} ()); // Execute immediately after the function is defined

Note that the anonymous function of immediate execution is used here, which is a common usage in JavaScript. If you want the code to run in a private namespace, just prefix the code with "(function() {" and suffix "} ())" . the opening parenthesis ensures that this is a function expression rather than a function definition statement, so you can add a function name to the prefix to make the code clearer. In the above example, the name "invocation" is used to emphasize that the function should be executed immediately after definition. The name "namespace" It can also be used to emphasize that this function is used as a namespace.

Once the module code is encapsulated into a function, some methods are needed to export its public API so that they can be called outside the module function. In the above example, the module function returns a constructor, which is then assigned to a global variable. Returning the value clearly indicates that the API has been exported outside the function scope. If the module API contains multiple units, then It can return namespace objects. For the sets module, the code can be written as follows:

// Create a global variable to store the modules related to the collection
var collections;
if (!collections) collections = {};

// Define sets module
collections.sets = (function namespace() {
	// A variety of "collection" classes are defined here, using local variables and functions
	//... a lot of code is omitted here

	// Export the API by returning a namespace object
	return {
		// Exported attribute name: local variable name
		AbstractSet: AbstractSet,
		NotSet: NotSet,
		AbstractEnumerableSet: AbstractEnumerableSet,
		SingletonSet: SingletonSet, 
		AbstractWritableSet: AbstractWritableSet, 
		ArraySet: ArraySet

Another similar technique is to use module functions as constructors, call them through new, and export them by assigning them to this:

var collections;
if (!collections) collections = {};
collections.sets = (new function namespace() {
	// ... a lot of code is omitted here

	// Export API to this object
	this.AbstractSet = AbstractSet;
	this.NotSet = NotSet;		// ...... 	
	// Note that there is no return value

As an alternative, if a global namespace object has been defined, this module function can directly set the properties of that object without returning anything:

var collections;
if (!collections) collections = {};
collections.sets = {};
(function namespace() {
	// ... a lot of code is omitted here

	// Export the shared API to the namespace object created above
	collections.sets.AbstractSet = AbstractSet;
	collections.sets.NotSet = NotSet; // ......

	// The export operation has been performed. There is no need to write a return statement here

Some frameworks implement module loading, including other methods to export module APIs. For example, use the provides() function to register its API and provide the exports object to store the module API. Because JavaScript does not have the ability of module management at present, you should choose the appropriate method of module creation and export API according to the framework and tool package used.

Tags: Javascript Front-end

Posted on Sun, 05 Dec 2021 01:52:08 -0500 by toddg