【问题标题】:Javascript code organization data driven applicationJavascript代码组织数据驱动应用
【发布时间】:2017-10-16 12:26:14
【问题描述】:

我目前正在开发一个中/大型 数据驱动 Asp.net MVC 应用程序的前端,我对正确的代码组织/设计模式有一些疑问跟随。 Web 应用程序由多个页面组成,其中包含许多使用 Razor 模板定义的 Kendo UI MVC 小部件。

对于那些不熟悉剑道的人,剃刀语法被翻译成 Javascript 如下 sn-p:

我在我的 Script 文件夹中定义了两个主要文件夹,我的 js 文件结构如下:

  • shared //包含共享的js文件 -file1.js -file2.js

  • pages //每页一个文件

    • page1.js
    • page2.js
    • ...
    • Ticket.js // 第 4 页 :)

每个 js 文件都是一个单独的模块,使用以下模式定义:
注意:在 init 函数内部,每个回调函数都注册到窗口事件,偶尔还会注册一个 $(document).ready(function(){}) 块。

;(function () {
    "use strict";

    function Ticket(settings) {
        this.currentPageUrls = settings.currentPageUrls;
        this.currentPageMessages = settings.currentPageMessages;
        this.currentPageEnums = settings.currentPageEnums;
        this.currentPageParameters = settings.currentPageParameters;         


        this.gridManager = new window.gridManager(); //usage of shared modules

        this.init();
    }

    Ticket.prototype.init = function () {           

            $("form").on("submit", function () {
                $(".window-content-sandbox").addClass("k-loading");
            });

            ...
    }    

    Ticket.prototype.onRequestStart = function (e) {

        ...
    }

    //private functions definition
    function private(a, b, c){

    }

    window.Ticket = Ticket;
}());   

一旦我需要在模块中定义我的 Javascript 函数,我就会在页面中包含相关的 Javascript 文件。 我的对象的一个​​ istance 存储在一个变量中,最重要的是,一个函数绑定到小部件事件(请参阅:onRequestStart)。

HTML/JAVASCRIPT

@(Html.Kendo().DropDownList()
      .Name("Users")
      .DataValueField("Id")
      .DataTextField("Username")
      .DataSource(d => d.Read(r => r.Action("UsersAsJson", "User"))
                        .Events(e => e.RequestStart("onRequestStart"))))



var settings = {};

var ticket = new window.Ticket(settings);

function onRequestStart(e){
    ticket.onRequestStart(e);
}

我觉得我的设计模式可能对像我这样的其他前端开发者不友好,主要是因为我选择不在 Jquery 插件中实现 Javascript 模块。

第一,我做错了什么吗?
第二,我的设计模式是否适合 Javascript 测试框架?
第三,Jquery插件必备的场景有哪些?

更新

通过上述 Razor 语法添加了 Javascript 输出。

【问题讨论】:

  • 就我个人而言,我发现在 Javascript(尤其是网格)中对 Kendo 控件的“控制”要多得多。我们去年开始使用它们,在使用 Razor 实现一个多月后,仅仅因为这个原因,我们决定将所有内容都迁移到 Javascript/jQuery。不过,这只是我的看法,似乎在网上找不到任何讨论差异/利弊等的材料。
  • 我的意见也是搭配 kendo ui java-script 框架。因为您对要实现的所有内容都具有完全的闪存控制权。代码会干净得多。支持非常好,因为我们已经在两个大型应用程序中使用了它,并且在过去 3 年中一直在使用它。尚未遇到任何限制。
  • 您好,我是剑道 ui 和剃刀模板的新手。但是在实施大规模应用程序方面有相当的经验。就标记和 javascript 而言,这如何转换为 .DataValueField("Id") .DataTextField("Username") .DataSource(d => d.Read(r => r.Action("UsersAsJson", "User") ) .Events(e => e.RequestStart("onRequestStart")))
  • 你好,我会在一个小时后发布...我还没有工作

标签: javascript jquery asp.net-mvc design-patterns kendo-ui


【解决方案1】:

文件夹结构

就功能(共享)和模块(模块化方法)而言,开发或应用程序代码应该代表您在 HTML 中可以遇到的内容。对您的解决方案进行简单的 ctrl+f 应该会产生所有可能的更改。根据多年来的经验,我个人更喜欢将其分为:

  • 应用程序 (应用程序代码)
    • (可重复使用)
    • 模块(单例)
  • lib (包管理器/grunt/gulp/...)
    • jquery (正确的库名称/未压缩的 dist 文件或根文件)
    • 剑道

文件名

表示某项功能并能够在眨眼间重用它,这将缩短您的开发时间。我相信你知道选择正确的名称是有价值的。我的文件名总是以namespace 开头,通常简称为可重复使用的“搜索”词:

  • 应用/原型
    • ns.calendar.js (多个配置)
    • ns.maps.js (组合或单次使用)
    • ns.places.js (表单或地图插件)
    • ns.validation.js (多种形式和通用处理)
  • 应用/单身人士
    • ns.cookiebox.js (单一配置)
    • ns.socialmedia.js (单一配置)
    • ns.dom.js (为 dom 更正、全局调整大小事件、小部件等提供场所...)

再补充一点,您所说的共享功能是一种全球性的功能。一个很好的例子是使用下划线库。或者您自己创建一组功能(设备检测、节流、一般的帮助程序)以在整个项目中重用 => ns.fn.js 由于您只在整个命名空间中添加它们一次,因此它也是作为单例构建的,可以添加到模块文件夹或直接添加到应用根目录中。

最后添加一个加载器文件来启动您的控制点 => 应用根目录中的 ns.load.js。该文件包含用于绑定原型和模块的单个 DOM 就绪事件。

因此,您可能需要重新考虑划分页面的想法。相信我,我去过那里。在某些时候,您会注意到功能变得太大,以至于无法单独配置所有页面并因此重复。

文件结构

老实说,我最喜欢@TxRegex 的提示 1 的回答,它添加了一个小附加功能来绑定命名空间并在加载时将其从一个文件传递到另一个文件。

核心原理:IIFE绑定到window对象

window.NameSpace = (function($, ns){
    'strict'
    function private(){}
    var x;
    ns.SearchTerm = {};
    return ns;
}(window.jQuery, window.NameSpace || {}));

更多示例代码我想指出我的github account

捆绑

尝试实现从 lib 到应用程序的单个捆绑和缩小文件,在 async 上的 head 中加载以用于生产版本。在defer 上使用分离和未缩小的脚本文件用于开发和调试目的。如果这样做,则必须避免在整个项目中使用具有全局依赖关系的内联脚本。

  • js/lib/**/*.js 的路径(通常分开以保持顺序)
  • js/app/ns.load.js 的路径
  • js/app/ns.fn.js 的路径
  • js/app/**/*.js 的路径(自动更新包)

输出 => ns.bundle.js => ns.bundle.min.js

通过这种方式,您可以避免 JavaScript 中的渲染阻塞问题,并加快加载过程,进而提升 SEO。还使您能够即时组合移动布局和桌面布局的功能,而不会出现内存问题或生涩的行为。缩小得非常好,并且在从加载器文件调用实例时产生的开销很小。由于单个包将被缓存在您的整个页面中,这完全取决于您可以从包中删除多少依赖项或库。非常适合可以共享代码并将其插入不同项目的中型和大型项目。

更多信息请访问another post

结论

首先,我是不是做错了事?

  • 一点也不,您的模块化方法似乎没问题...
  • 缺少一个全局命名空间,如果没有至少一个就很难避免。您为每个模块创建一个,但最好将它们全部分组到一个命名空间下,这样您就可以将库代码与窗口对象中的应用程序代码区分开来。
  • Kendo 似乎创建了内联脚本?你不能反击放置服务器端吗?

其次,我的设计模式是否适合 Javascript 测试框架?

  • 除了 Kendo 实例,您可以添加一个层用于测试目的。请记住,如果 jQuery 是您的内联依赖项,则必须渲染它正在加载的块。否则 => jQuery is undefined
  • 如果您无法控制内联脚本,请从捆绑包中排除 Kendo 依赖项。移至 </body> 捆绑解决方案。

三、Jquery插件必备的场景有哪些?

  • 模块化方法
  • 多个实例的可配置方法(提示:从逻辑中移动所有字符串,查看 Kendo 如何使用对象字面量)
  • 包管理器将“垃圾”与“黄金”分开
  • grunt/gulp/... 设置从 js 中分离 scss 和 css
  • 尝试实现数据属性绑定,所以一旦全部编写完毕,您就可以通过 HTML 配置新实例。

一次编写,在必要时轻松适应并进行大量配置!

【讨论】:

  • 您好,感谢您的回答。我肯定会花一些精力重新思考整个结构,我将探索全局命名空间的概念,以在项目中引入以缩小所有 js。我得到了您在上面写的所有内容,期望这一点:“对您的解决方案进行简单的 ctrl+f 应该会产生所有可能的更改。”你介意用简单的话解释一下吗? :)
  • 找回嘶嘶声选择器的简单想法:在我的例子中 cache.container 在 js 文件或视图 (html) 文件中。例如:在 html 中搜索data-class="calendar"。或者在js文件中搜索[data-class="calendar"]。一旦您在 HTML 中遇到数据属性,您就会知道它附加了一个模块。因此,当您在一段时间后重新开始时,以减轻小组或自己的发展。简单查看一下配置即可了解它的作用。
【解决方案2】:

组织和模式看起来不错,但我有一些提示:

提示 1:

也许您可以返回对象,而不是在模块中设置特定的全局变量。所以不要这样做:

;(function () {
"use strict";

    function Ticket(settings) {
        console.log("ticket created", settings);
    }
    ...
    window.Ticket = Ticket;
}()); 

你会这样做:

;window.Ticket = (function () {
"use strict";

    function Ticket(settings) {
        console.log("ticket created", settings);
    }
    ...
    return Ticket;
}()); 

这样做的原因是能够获取您的模块代码并在需要时给它一个不同的全局变量名称。如果存在名称冲突,您可以将其重命名为 MyTicket 或其他名称,而无需实际更改模块的内部代码。

提示 2:

忘记提示 1,全局变量很臭。与其为每个对象类型创建单独的全局变量,不如创建一个对象管理器并使用单个全局变量来管理所有对象:

window.myCompany = (function () {
    function ObjectManager(modules) {
        this.modules = modules || {};
    }

    ObjectManager.prototype.getInstance = function(type, settings) {
        if (!type || !this.modules.hasOwnProperty(type)) {
            throw "Unrecognized object type:";
        }
        return new this.modules[type](settings);
    };

    ObjectManager.prototype.addObjectType = function(type, object) {
        if (!type) {
            throw "Type is required";
        }
        if(!object) {
            throw "Object is required";
        }
        this.modules[type] = object;
    };

    return new ObjectManager();
}());

现在,您的每个模块都可以使用附有您的公司名称的单个全局对象进行管理。

;(function () {
"use strict";

    function Ticket(settings) {
        console.log("ticket created", settings);
    }
    ...
    window.myCompany.addObjectType("Ticket", Ticket);
}()); 

现在您可以轻松地为每个对象类型获取一个实例,如下所示:

var settings = {test: true};
var ticket = window.myCompany.getInstance("Ticket", settings);

而且你只需要担心一个全局变量。

【讨论】:

  • 感谢您的提示,我可能会关注第二个。我仍然对我的实现有点怀疑,主要是因为我选择不使用 jQuery 插件......我应该忘记它们并继续遵循当前的设计模式吗?我将来会遇到一些关于我的实施的缺点吗? (例如是否需要 Javascript 测试框架?)
  • jQuery 插件只是为了方便。他们帮助你做类似 var ticket = $('selector').Ticket(options); 来创建你的票
【解决方案3】:

假设每个组件都有一个文件夹,您可以尝试将文件分成不同的组件。

例如:第 1 页是关于矩形的,因此您在该文件夹内创建了一个文件夹调用 rectangle,您创建了 3 个文件 rectangle.component.html、rectangle.component.css、rectangle.component.js(可选的 rectangle.spec.js 用于测试)。

app
└───rectangle
        rectangle.component.css
        rectangle.component.html
        rectangle.component.js

所以如果矩形发生任何不好的事情,你就知道问题出在哪里

隔离变量并在正确位置执行的一种好方法是使用路由器,基本上它在 url 处检查并执行您分配给该页面的代码部分

如果您需要更多帮助,希望对我有所帮助。

【讨论】:

  • 首先,我做的一切都是错的吗?您没有做错任何事情,只是不适用于大型应用程序“您可以尝试将文件分隔在不同的组件中,假设每个组件都有一个文件夹。例如:第 1 页是关于矩形的,因此您在创建的文件夹内创建了一个文件夹调用矩形 3文件 rectangle.component.html、rectangle.component.css、rectangle.component.js(可选的 rectangle.spec.js 用于测试)。"
  • 第二,我的设计模式适合 Javascript 测试框架吗?所以如果矩形发生任何不好的事情,你知道问题出在哪里(可选的 rectangle.spec.js 用于测试)。
  • 第三,哪些是 Jquery 插件的必备场景?隔离变量并在正确位置执行的好方法是使用路由器,基本上它在 url 处检查并执行您分配给该页面的代码部分
  • 希望如果您需要更多帮助,请告诉我,很抱歉没有参考 john papa style guide for angular github.com/johnpapa/angular-styleguide 的答案,使用路由器您将能够隔离变量
猜你喜欢
  • 1970-01-01
  • 2020-07-09
  • 2011-03-21
  • 2023-03-07
  • 1970-01-01
  • 1970-01-01
  • 2013-09-24
  • 2018-10-12
  • 2015-05-12
相关资源
最近更新 更多