jModulizer如何模块化和解耦你的JavaScript代码

分享于 

25分钟阅读

Web开发

  繁體

介绍

jModulizer是为现有项目而开发的框架,它帮助开发可以维护的JavaScript代码。 在本文中,我将使用JavaScript共享一些 Pattern 实现,以及如何使用这些模式来克服一些问题。 另外,你可以学习"如何开发你自己的JavaScript库"。

本文不是针对JavaScript初学者的。 这是针对专业人士的,他们想提高他们的一些JavaScript模式的知识。 了解JavaScript基础知识对于理解本文非常重要。

我开发这个框架是为了处理当今应用的问题。 开发人员面临的一些常见问题是 below ;

  • 由于代码行数的大量增加而难以维护的。

JavaScript目前被用作主流的web开发语言。 可以有 1500个- 3000代码行,即使在小型项目中。

  • structured结构

JavaScript在开发人员中仍然不是一种非常流行的语言。 因此,不同的开发人员使用不同的编码风格,可能导致代码维护问题。

  • 的全局变量

全局变量不适用于网络应用程序。 当全局变量和函数数量增加时,命名冲突的可能性增加,这可能会导致覆盖已经声明的函数或者变量。 一个依赖全局变量的函数与环境紧密耦合。 如果环境发生变化,那么函数很可能会意外地中断变量。 维护最简单的代码是代码,其中的所有变量都在本地定义。

  • 第三方库( 如 jQuery。Kendo等) 高度依赖。

当前上下文中使用了第三方库的数量。 因此,确定将任何其他库或者插件合并到应用程序中的好方法是有意义的。 如果这些库函数都被使用,那么就无法轻松完成替换。

  • 性能问题

性能是开发大型应用程序时的一个非常重要的方面。 缺少JavaScript性能将会破坏应用程序可用性。

  • 差的依赖管理使得单元测试困难

如果 MODULE 中存在依赖项数量,则不能单独测试 MODULE,而不需要加入它的他依赖项。

  • 可用性问题

可用性是任何编程语言中的一个重要特性,在很多JavaScript代码中都没有很好的便利。

  • 错误处理问题

这又是一个重要的领域,因为用户在错误处理错误时会遇到奇怪的行为。

  • 并行装载问题

通常,脚本标记会将所有其他内容下载。 如果有很多脚本要加载,那么其他内容将被阻止,直到所有脚本都被下载。

基于以下设计模式的jModulizer框架已经开发。

  • 沙箱 Pattern
  • 模块 Pattern
  • 发布/订阅 Pattern

除了上面提到的模块和沙箱模式之外,其他模式也是众所周知的设计模式。 在进入沙盒 Pattern 之前,要注意一些设计模式的注意事项。

命名空间 Pattern

命名空间 Pattern 减少了程序在程序中的使用,同时也有助于避免命名冲突或者过多的NAME 前缀。 JavaScript没有构建到语言语法中的名称空间,但是这是一个非常容易实现的特性。 可以为应用程序或者库创建一个全局对象,而不是将全局范围污染为多个函数。

JavaScript变量和函数可以声明如下:

varurl = 'www.myurl.com';function connect() {}function process() {}function disconnect() {}

在这个代码中,有一个全局变量和三个全局函数。 这里代码有问题;如果代码出现在两个或者多个位置,则存在命名冲突。 通过使用名称空间 Pattern,我们可以克服这个问题。

下面是使用 namespace ; 来重构 上面 代码的方法

var jModulizer = jModulizer || {}
jModulizer.conManager = jModulizer.conManager || {};
jModulizer.conManager.url = 'www.myurl.com';
jModulizer.conManager.connect = function () { };
jModulizer.conManager.process = function () { };
jModulizer.conManger.disconnect = function () { };

或者可以用对象文字声明:

 jModulizer.conManager = {
 url: 'www.myurl.com',
 connect: function () { },
 process: function () { },
 disconnect: function () { }
}

在 上面 代码中只有一个全局变量。 下面的代码用于访问 connect() 函数;

jModulizer.conManager.connect();

模块 Pattern

模块( 定义):

可以互换的一个较大系统的可以互换的单一部分。"

因为它提供了结构并帮助组织代码,所以 MODULE Pattern 被广泛使用。 它的他语言不同,JavaScript没有特殊的语法,但是 MODULE Pattern 提供了创建of的工具,可以根据 black的( 更改过) 要求。 MODULE Pattern 是多个JavaScript设计模式的组合,如命名空间。即时函数。private 和特权成员。

让我们创建 conManager MODULE 对象;

var jModulizer = jModulizer || {};
jModulizer.conManager = {};

现在,模块的conManager 逻辑可以作为 below 来开发:

jModulizer.conManager = (function() {
 var self = this;
 self.url = 'www.myurl.com';
 self.connect = function () { };
 self.process = function () { };
 self.disconnect = function () { };
 return {
 url: self.url,
 self.connect,
 process: self.process,
 disconnect: self.disconnect
 };
} ());

这里 jModulizer.conManager 已经公开URL属性,并将函数连接。进程和断开功能连接到 public。

注意最后一行上的" ());"? 这里 conManager 是即时函数。

立即功能

立即函数 Pattern 是一个语法,它使你可以在定义函数时立即执行函数。

可以按如下方式标识立即功能:

(function () {
 /* Implementation goes here */}());

还可以将参数传递给即时函数,可以按照如下方式将窗口和文档对象传递给函数:

(function (window, document) {
 /* Implementation goes here */} (window, document);

这个 Pattern 本质上只是一个 function expression (either named or anonymous),它在创建之后执行。

Pattern 由以下部分组成:

  • 使用函数表达式定义函数。 ( 函数声明将不起作用。)
  • 在末尾添加一组括号,这会使函数立即执行。
  • 在括号( 仅当你不将函数分配给变量时,才需要) 中包装整个函数。

下面的替代语法也是常见的( 请注意右括号的位置):

(function () {
 /* Implementation goes here */})();

这个 Pattern 很有用,因为它为你的初始化代码提供了一个范围沙箱。

沙箱 Pattern

沙箱 Pattern 解决了命名空间 Pattern的问题:

  • 依赖单个全局变量成为应用程序的全局变量。

在命名空间 Pattern 中,同一个应用程序或者库的两个版本不能在同一个页面上运行,因为它们都需要相同的全局符号 NAME,例如 jModulizer。

  • 在运行时键入和解析的长划线名称,例如。

jModulizer.conManager.url

就像 NAME 所暗示的那样,沙箱 Pattern 为模块提供了环境,而不影响其他模块及其个人沙箱。 在沙箱 Pattern 中,单个全局是一个名为 AppSandbox()的构造函数。 使用这里构造函数创建对象,并传递回调函数,该函数将成为代码的独立沙箱环境。

new AppSandbox(function (box) {
 /* module logic goes here */});

对象框在命名空间示例中将类似于 conManager。 它将拥有你的代码工作所需的全部库功能。

AppSandbox() 构造函数可以接受额外的配置参数( 或者参数),指定这里对象实例所需模块的名称。 我们希望代码是模块化的,因此 AppSandbox() 提供的大部分功能都将包含在模块中。

new AppSandbox(['creditor', 'debtor'], function (box) {
});

现在使用" box"对象,可以在 AppSandbox 中访问债权人和贷方模块。 这 也 称为 依赖 关系 注入. 在创建 AppSandbox 对象时,检查检查的Sandbox 构造函数,并通过" b<code> ox"对象注入这些依赖项。

jModulizer框架概述

在沙箱和 MODULE 模式的帮助下开发了JModulizer框架。 给出了框架架构概览图 below:

图 01: jModulizer体系结构概述图
  • 区域- 中介 Pattern 节
  • 区域B - MODULE Pattern

根据 上面 架构,核心处于控制沙箱的位置。 沙箱可以控制模块,模块只知道沙箱,不知道任何其他外部对象,除了它自己的对象。

jModulizer核心实现

这里是所有核心对象存在的地方。 这里内核负责 registering/starting/stopping/loading 模块和错误处理。

jModulizer 是这个框架的全局变量,它与jQuery中的" jQuery"类似。 jQuery有两个全局变量,换句话说," jQuery"和" $"符号。 我们使用单个全局变量避免了与 jModulizer的命名冲突。 jModulizer全局设置配置也处理 inside 核心。 从代码中分离配置数据是一个好的编程实践,因为好的设计应用程序在主源代码。

jModulizer 是一个单独的对象,在加载 jModulizer.core.js 时创建。

(function (window, undefined) {
 var jModulizer = window.jModulizer = window.jModulizer || {};
 jModulizer.core = (function () {/*jModulizer core functionality goes here*/var self = this;
self.config = {
 DEBUG: false };
self.setup = function (config) {
 self.config = $.extend(self.config, config);
 };
 self.isDebug = function () {
 return self.config.DEBUG
 };
self.register = function (moduleId, Creator) {
 jModulizer.moduleData[moduleId] = {
 creator: Creator, instance: null, id: moduleId
 };
 };
self.start = function () {var args = Array.prototype.slice.call(arguments), moduleId = args[0], 
 configuration = args[1]? args[1] : null, module = jModulizer.moduleData[moduleId];
module.instance = module.creator(jModulizer.sandbox(self));if (!self.isDebug())
 self.errorHandler(module.instance);
module.instance.init(configuration); 
 };
 self.stop = function (moduleId) {
 var data = jModulizer.moduleData[moduleId];
 if (data.instance) {
 data.instance.dispose();
 data.instance = null;
 }
};
 self.jsLib = jQuery;
self.errorHandler = function (object) {
 var name, method;
 for (name in object) {
 method = object[name];
 if (typeof method == "function") {
 object[name] = function (name, method) {
 returnfunction () {
 try {
 return method.apply(this, arguments);
 } catch (ex) {
 self.displayMsg({ method: name, message: ex.message });
 }
 };
 } (name, method);
 }
 }
};return {
 register: self.register,
 start: self.start,
 stop:self.stop,
 jsLib: self.jsLib 
} 
 })();
window.jModulizer = jModulizer;
jModulizer.moduleData = {};
})(window);

这里,全局错误处理程序是预先配置的。 我们可以使用jModulizer全局设置函数配置这个配置,并且只能在生产模式中启用错误处理程序。 否则。

在这里,让窗口作为局部变量比全局变量快得多。 没有名为undefined的参数的值。 当没有为参数传递值时,它将被设置为未定义的值,因这里 inside的函数 block 将具有未定义的值。 因为在最近版本的JavaScript中未定义全局标识符,所以在旧的浏览器中,它不代表未定义的值。 ( 注意没有定义值的参数将获得未定义的实际值,而不是未定义的全局属性的当前值)。

jModulizer中介

jModulizer中介负责模块间的通信。 任何 MODULE 可以发布带有相关数据的消息,其他模块可以在订阅该消息时响应。

(function (window, undefined) {
 var jModulizer = window.jModulizer = window.jModulizer || {};
 jModulizer.com = function () {
 var handlers = {};
 return {
 subscribe: function (msg) {
 var type = msg.type;
 handlers[type] = handlers[type] || [];
 if (!this.subscribed(msg))
 handlers[type].push({ context: msg.context, callback: msg.callback });
 },
 subscribed: function (msg) {
 var subscribers = handlers[msg.type], i;
 for (i = 0; i <subscribers.length; i++) {
 if (subscribers[i].context.id === msg.context.id)
 returntrue;
 }
 returnfalse;
 },
 publish: function (msg) {
 if (!msg.target) {
 msg.target = this;
 }
 var type = msg.type;
 if (handlers[type] instanceof Array) {
 var msgList = handlers[type];
 for (var i = 0, len = msgList.length; i <len; i++) {
 msgList[i].callback.call(msgList[i].context, msg.data);
 }
 }
 },
 remove: function (msg) {
 var type = msg.type, callback = msg.callback, handlersArray = handlers[type];
 if (handlersArray instanceof Array) {
 for (var i = 0, len = handlersArray.length; i <len; i++) {
 if (handlersArray[i].callback == callback) {
 break;
 }
 }
 handlers[type].splice(i, 1);
 }
 },
 };
 } ();
})(window);

在这里,可以按如下方式单独定义消息:

var jModulizer = window.jModulizer = window.jModulizer || {};
jModulizer.messages = {
 SYS_ERROR: "SYS_ERROR",
 SAVED: "SAVED" 
}
jModulizer沙箱实现

这包括可以在任何 MODULE 中使用的功能。 如果仔细观察核心实现 上面。启动函数和 MODULE 启动,就会看到核心对象被传递给沙箱构造函数。 沙箱可以访问核心属性和函数,由于沙箱对象被传递到,模块可以访问沙箱属性和函数的public,从而实现对数据的访问。

(function (window, undefined) {var jModulizer = window.jModulizer = window.jModulizer || {};
jModulizer.sandbox = function (core) {var self = this;
self.publish = function (msg) {
 if (msg instanceof Array) {
 for (var i = msg.length - 1; i> = 0; i--) {
 jModulizer.com.publish(msg[i]);
 }
 }
 else {
 jModulizer.com.publish(msg);
 }
 };
 self.subscribe = function (msg) {
 if (msg instanceof Array) {
 for (var i = msg.length - 1; i> = 0; i--) {
 jModulizer.com.subscribe(msg[i]);
 }
 }
 else {
 jModulizer.com.subscribe(msg);
 }
 };
self.$ = function (selector) {returnnew self.internalCls(selector);
};
self.internalCls = function (selector) {this.elements = core.jsLib(selector);
};
self.internalCls.prototype = {
jModulizerTabs: function (options) {returnthis.elements.tabs(options); //de-coupled jqueryui tabs},
jModulizerGrid: function (options) {returnthis.elements.kendoGrid(options); //de-coupled kendo grid}
};
self.$.jModulizerAjax = function (options) {
core.jsLib.ajax(options); //de-coupled jquery ajax}
 self.extend = function extend(defaults, options) {return core.jsLib.extend(defaults, options);
};return {
subscribe: self.subscribe,
publish: self.publish,
$: self.$,
extend: self.extend
};
};
})(window);

这里"jModulizer.com" 是 jModulizer 中介。 我们可以按照如下方式发布/订阅消息。

发布( 广播)

sandbox.publish({ type: jModulizer.messages.SAVED, data: { creditorId: 100 } });

订阅( 侦听)

sandbox.subscribe({ type: jModulizer.messages.SAVED, callback: handler, context: this});function handler(data){ /* Message handling logic goes here */ }

这里" SAVED"是 jModulizer.messages 中的消息 NAME。

沙箱还可以用于对核心库( 如 jQuery。下划线。等等 ) 和优势,MODULE 不知道应用程序中使用的库。 在这些情况下,如果需求在后续阶段更改核心库(。比如,下划线下划线),那么可以不改变模块和小修改( 编写小型适配器)。

这里耦合的工作原理

沙箱本身有自己的符号"$"。 这与 jQuery $. 不同 沙箱中的对象 $ 为模块提供了核心JavaScript库对象。 在 上面 实例中,jQuery全局对象是由 $ 在沙箱中提供的。 所以 MODULE 不知道使用哪个基库。 MODULE 只知道沙箱中的$。

jModulizer MODULE 注册

jModulizer内核具有 MODULE 注册功能。 它接受两个参数,首先是 MODULE NAME,然后是 MODULE 对象。

每个 MODULE 必须有两个 public 方法,init()destroy() 因为jModulizer核心需要在开始和停止时使用它们。 这两个函数作为模块的构造函数和析构函数。

jModulizer.core.register("jModulizer.module.loanCalculator", function (sandbox) {
 var self = this;
 self.config = {
 loanAmount: 0,
 period: 0,
 interestRate: 0 };
 self.init = function (options) {
 self.config = sandbox.extend(self.config, options);
 sandbox.$('#loanAmount').val(self.config.loanAmount);
 sandbox.$('#loanPeriod').val(self.config.period);
 sandbox.$('#loanInterestRate').val(self.config.interestRate);
 sandbox.$('#calculate').click(self.calculateLoanInstallment);
 };
 self.calculateLoanInstallment = function () {
 self.config.loanAmount = sandbox.$('#loanAmount').val();
 self.config.period = sandbox.$('#loanPeriod').val();
 self.config.interestRate = sandbox.$('#loanInterestRate').val();
 var monthlyInterestInstallment = 
 new Number((self.config.loanAmount * self.config.interestRate)/
 (12 * self.config.period)),
 monthlyCapitalInstallment = new Number(self.config.loanAmount/
 (12 * self.config.period)),
 monthlyTotalInstallment = new Number
 (monthlyInterestInstallment + monthlyCapitalInstallment);
 sandbox.$("#monthlyInterestInstallment").val(monthlyInterestInstallment);
 sandbox.$("#monthlyCapitalInstallment").val(monthlyCapitalInstallment);
 sandbox.$("#monthlyTotalInstallment").val(monthlyTotalInstallment);
 };
 self.dispose = function () {
 };
 return {
 init: self.init,
 destroy: self.dispose
 };
});

你可以看到,这个 MODULE 有沙箱对象访问。 通过以下方式注册后,我们可以启动 MODULE:

jModulizer.start("jModulizer.module.loanCalculator");

或者:

jModulizer.start("jModulizer.module.loanCalculator", { /* Startup arguments */ });

你可以在内核中看到 start() 函数的行为。 第一个参数是 MODULE NAME,第二个参数是 MODULE 中的可选启动参数。 例如DOM元素id或者类名可以作为启动参数传递。

启动 MODULE 时,将自动调用它的init() 函数。 若要停止 MODULE,请调用 stop 函数:

jModulizer.stop("jModulizer.module.loanCalculator");

这将自动调用模块函数的destroy()。 MODULE 中的destroy() 函数可以用于刷新对象。取消绑定事件等。

如何用QUnit单元测试框架测试" loanCalculator"MODULE。 可以在没有任何其他 MODULE 依赖关系的情况下单独进行测试。

test("Test loan calculation output", 2, function () {
jModulizer.core.start("jModulizer.module.loanCalculator", {
 loanAmount: 100000,
 period: 2,
 interestRate: 0.10});var calculateButton = $("#calculate");
calculateButton.trigger('click');var monthlyInterestInstallment = $("#monthlyInterestInstallment").val(),
 monthlyCapitalInstallment = $("#monthlyCapitalInstallment").val(),
 monthlyTotalInstallment = $("#monthlyTotalInstallment").val();
equal(monthlyInterestInstallment, 416.67, "monthly interest installment correctly calculated");
equal(monthlyCapitalInstallment, 4166.67, "monthly capital installment correctly calculated");
equal(monthlyTotalInstallment, 4583.34, "monthly total installment correctly calculated");
});

我们从开发 jModulizer 中学到了什么?

  • 现有web应用程序的遗留JavaScript代码可以很容易地适应这个。
  • 几乎所有的web应用程序都包含非独立类,并且每个类都与其他类耦合。 通过使用这个开发的框架,这些类可以很容易地成为独立的模块。
  • web应用程序的每个 MODULE 都可以单独独立地进行测试。
  • 通过这个框架创建的模块可以并行加载,而脚本标记( <script> </script> ) 会阻止HTML内容的下载。 你可以使用requirejs或者headJS库并行加载这些模块。
  • 这个框架具有内置的中介来与互相互通的MODULE 进行通信。 如果你使用requirejs或者其他库来模块化 JavaScript,你必须构建自己的通信机制。
  • 开发人员提供了遵循编程指南的指导,最终的代码将是整洁整洁的代码。
  • 有自动错误处理机制,开发人员不处理错误,框架会自动处理错误。 ( 这是预先配置的功能,因此,仅在生产版本中启用它)。

此外,你还可以使用一些设计模式( 如代理。flyweight等) 来改进性能。

上面 文章只包含了框架功能及其架构的概述。

我认为,仍然需要开发和改进某些领域和最佳实践。 然而,这个框架实际上已经被使用和测试,从而确认了相同的运行成功。


JAVA  Javascript  cod  dec  Modular  decouple  
相关文章