2019独角兽企业重金招聘Python工程师标准>>>

为什么做这个东西

我是做后端的,看到前端代码组织很难受。

  • 各种乱放文件
  • 同样的代码复制一个页面一份
  • 一个文件囊括所有东西,更有甚者,一个html 3、4千行解决战斗。
  • 有一些把js放一个目录,css一个目录,html放一个目录。在写一个功能时html,js,css都会随时用到, 一下打开这个目录,一下打开那个路径。虽然java也分dao、services什么的,可能强类型语言IDE比较好跟踪,而且一般一层写完再写另一层。 文件数量根据功能不同时多时少,东西一多就乱。

项目业务一多很难维护。更别谈其他项目复用。当然前端开发人员的水平参差不齐。另外很多项目都有后台管理类似的功能

  • 搜索栏
  • 一张表格
  • 增删改查,其他操作等

功能差不多自然想到复用和封装。

就算是用了vue,react,dva什么的,感觉该乱还是乱,这些框架什么的,最多只是解决代码级别组件的组织归类,谈到根据业务模块来分,该乱的还是乱。

背景

公司项目比较low,一般都是后台管理系统,有很多的table,上面有查询条件,每个表都是增删改查各种单记录操作。所以相似的功能很多。
最快速的方案是写完一个模块,复制粘贴修改修改,就成了一个新的模块。这样做最大的弊端是就后期维护成本非常高,基本很难管理,要统一改个东西就各个模块的代码都处理一遍。然后项目人员一多编码风格都无法管理,后面就无zf状态。
虽然有时候会把一些公用的对像或方法放在公用的js文件中,但这远远不够,还有有大量重复的代码。
$extends只能解决对象或者方法级别的东西,碰到需要几个步骤的东西就嗝屁了。

解决什么问题

  • (重点)代码文件组织,按业务模块分目录
  • (重点)按功能切分代码到不同文件中。
  • (重点)解决复用个性化的问题,不单单是使用公用属性 或 $extends,而是使用对象继承与重写,理论上可以重写父类所有的方法。
  • 按需要加载,比如用户只打开查看界面,只加载相关文件,用户操作 新增、修改才加载编辑相关页面和代码。
  • 解决加载顺序依赖的问题,可能有人会奇怪,都8102年了还有这种事。没错公司的项目比较low,有些还存在这个问题。

用到的技术或知识点

  • js原型链,面向对象,继承与重写
  • requieJs,主要用来加载文件,用其他加载插件也同理。顺便处理闭包
  • 生命周期问题,如单例与重新new对象。使用requieJs加载文件时的闭包,执行一次性代码,定义context对象。
  • $.Deferred,处理异步的封装,顺便干掉回调地狱

文件组织结构

  • 前端代码module目录按业务划分各模块目录,各个模块目录下,放着该模块的各种文件包括js、html、css等
  • module目录下有一个base目录,里面放着各种组件的基类(如baseTable、baseEdit、baseDetail)。还有相关的面向对象相关工具类(如classUtil)。

组件划分

  • 一般组件有:
  1. table:分页数据列表、操作栏(查询条件和功能按钮,如查询、新增、其他按钮)
  2. edit:新增和修改,差别比较大可以分两个
  3. detail:查看详情
  4. 其他组件根据需要,角色分配置权限,用户重置密码,审核驳回

思路

  • 每个组件有一个init的入口,
  • 每个组件一般原型方法有以下几个,想要理解也可以参考vue的生命周期(我是后来才看vue,觉得有些挺像的)
  1. 渲染(或初始化)
  2. 赋值,如查看详情、修改等,表格列表可能没有
  3. 执行,比如打开详情模态窗口,要把初始化完成的窗口show出来
  4. 第二次调用的时候,可能就要调用reset方法,比如vue不能重复new
  • 把有可能被子类重写的方法分出单独的原型方法出来,子类只要覆写这个方法,其他代码会调用基类的方法
  • 如果碰到需要在父类方法前面或后面增加代码,可以覆写该方法写上子类逻辑,并使用this.__proto__.__proto__.functionName访问父类方法。具体几层.__proto__,自己调试一下看看。

示例

以表格为例。注意代码只是截取一些关键部分的代码,只是为了说明逻辑和细节,有些可能运行不通。

  • baseTable.js
define(['class-util', 'usage', 'vue', 'ELEMENT'], function (util, usage, Vue, ELEMENT) {Vue.use(ELEMENT);function BaseTable(bean) {var self = this;//功能对象this.inited = false;this.defers = {};//子类常量,一般由子类覆盖this.elm = '#t_datatable';//表格的选择器this.$elm = null;//表格的jq对象,一般需要加载页面后再赋值this.$table = null;//表格的jq对象,一般需要加载页面后再赋值this.url = {//相关的操作的接口search: '',//分页查询delete: '',};this.form;//提交的数据//操作不同的数据记录,每次都会变的属性放在opt里。this.opt = {//类变量$table: null,$dialog: null,$form: null,vm: null,//主要的vue对象//bean变量id: null,bean: null,action: null//操作:add,modify,detail};//属性this.page = {detail: null,edit: null};this.js = {edit: null,detail: null,upload: 'base-upload',audit: 'base-audit',collect: 'base-collect',downloads: 'base-downloads',};//相关操作显示的窗口标题this.titles = {'delete': '您确定删除该项记录吗?','report': '您确定上报该项记录吗?','detail': '详细信息','modify': '修改','add': '新增','audit': '审核',};//提示信息this.msg = {report: '上报成功!',delete: '删除成功!',};//表格操作列按钮的代码,因为常用就放在基类里,个性化的情况子类可以覆盖this.btn = {search: '#b_search',add: '#b_add',collect: '#b_collect',detail: function (id) {return '<a data-id="' + id + '" data-action="detail" class="blue action" title="查看" href="#">\<i class="icon-zoom-in  bigger-130" data-row="" data-path="" data-index=""></i>\</a>';},delete: function (id) {return '<a data-id="' + id + '" data-action="delete" class="red action" title="删除" href="#">\<i class="icon-trash  bigger-130" data-row="" data-path="" data-index=""></i>\</a>';},modify: function (id) {return '<a data-id="' + id + '" data-action="modify" class="green action" title="修改" href="#">\<i class="icon-pencil  bigger-130" data-row="" data-path="" data-index=""></i>\</a>';},};//Bootstrap Table(或者其他)的统一配置,个性化情况子类覆盖需要修改的属性即可this.tableOpt = {toolbarAlign: 'false',searchAlign: 'right',buttonsAlign: 'right',sidePagination: 'server',//指定服务器端分页url: this.baseUrl + this.url.search,method: 'POST',contentType: "application/x-www-form-urlencoded; charset=UTF-8",pagination: true,//是否分页pageNumber: 1, //初始化加载第一页,默认第一页pageSize: 10,//单页记录数// pageList:[5,10,20,30],//分页步进值queryParams: function (params) {var params2 = self.queryParams.call(self, params);var defParam = {'access_token': $.cookie('token'),'limit': params.limit, // 每页要显示的数据条数'start': params.offset, // 每页显示数据的开始行号columns: params.sort,isDesc: params.order === 'desc' ? true : false};return $.extend(defParam, params2);},responseHandler: function (res) {var respData = {total: 0,rows: []};if (res && res.content) {var content = res.content;var total = content.recordsTotal;var rows = content.data;if (total && $.isNumeric(total)) {respData.total = total;}if (rows && $.isArray(rows)) {respData.rows = rows;}}return respData;},clickToSelect: true,//是否启用点击选中行striped: true, //是否显示行间隔色sortable: true,sortOrder: 'desc',columns: [{title: '序号', field: 'p_id'},],//columns一般都是子类覆盖的};//汇总表默认选项};/**********************原型方法*************************///原型方法主要有几个//1. init、//2. 渲染操作栏,如下拉选择框、日历选择框,用vue等就更不用说了、//3. 渲染操作栏按钮,绑定事件什么的//4. 初始化表格//初始化,主要的执行入口,传入参数的入口BaseTable.prototype.init = function (elm, bean, option) {this.$elm = $(elm || this.elm);if (bean) {this.bean = bean;}if (option) {this.opt = option;}this.defers.initToolbar = this.initToolbar();this.defers.initToolbar = this.initToolbarBtn();this.defers.initToolbar = this.initTable(this.$elm, this.tableOpt);this.$table = this.initTable(this.$elm, this.tableOpt);};/*** 初始化操作栏* 一般子类覆盖*/BaseTable.prototype.initToolbar = function () {};/*** 初始化操作栏按钮* 子类基本不用重写*/BaseTable.prototype.initToolbarBtn = function () {var self = this;$.when(this.defers).done(function () {//按钮if (self.btn.search) $(self.btn.search).on("click", null, self, self.search);if (self.btn.add) $(self.btn.add).on("click", null, self, self.edit);if (self.btn.template) $(self.btn.template).on("click", null, self, self.downloadTemplate);if (self.btn.upload) $(self.btn.upload).on("click", null, self, self.upload);if (self.btn.collect) $(self.btn.collect).on("click", null, self, self.collect);if (self.btn.downloads) $(self.btn.downloads).on("click", null, self, self.downloads);});};/*** 初始化表格,* 子类基本不用重写*/BaseTable.prototype.initTable = function (elm, tableOpt) {var self = this;var $elm = $(elm);//初始化datatabletableOpt.$el = $elm;//这样就可以在bootstrap.table实例中访问当前的jq table 对象,方便调用bootstrap.table的方法var $myTable = $elm.bootstrapTable(tableOpt);//绑定操作列按钮事件$elm.off('click').on('click', 'tbody .action', $myTable, function (e) {var $btn = $(this);var action = $btn.data('action');var id = $btn.data('id');var bean = $elm.bootstrapTable('getRowByUniqueId', id);var actionFun = self.actions[action] || self.confirmAction;if ($.isFunction(actionFun)) {var defer = actionFun(e, id, bean, self, action);//操作完成后执行if (self.callbacks) {var actionCallback = self.callbacks[action]if (actionCallback) {$.when(defer).done(function (data) {actionCallback.apply(self, arguments);})}}}});return $myTable;};//其他只个重要方法/*** 查询条件方法* 一般子类覆盖* @param d:默认datatable参数* @returns {*}:提交的data集合*/BaseTable.prototype.queryParams = function (params) {return {};};/*** 删除操作,实现省略,一般都一样,子类不用重写,只要定义好 url.delete给他调就好*/BaseTable.prototype.delete = function (e, id, bean, self) {};/*** 显示详情操作*/BaseTable.prototype.showDetail = function (e, id, bean, self, action) {var title = self.titles[action];var dialogDefer = usage.tableDialog(title, self.page.detail, action);$.when(dialogDefer).then(function (dialog) {var modulePath = self.js[action];require([modulePath], function (Detail) {self.Detail = Detail;self.detail = new self.Detail(bean, dialog, self);//新建实例self.detail.init();})});};/*** 新增/修改操作(设置form为disable后显示详情)*/BaseTable.prototype.edit = function (e, id, bean, self, action) {if (!self) {self = e.data;}action = action || 'add';var modulePath = self.js.edit;require([modulePath], function (Edit) {self.Edit = Edit;self.edit = util.getInstance(Edit.context, Edit)self.edit.init({$table: self,action: action,bean: bean,id: id,});self.edit.run();});};return BaseTable;
});
  • userTable.js
define(['base-table', 'class-util', 'usage', 'vue', 'ELEMENT', 'component'], function (BaseTable, util, usage, Vue, ELEMENT) {Vue.config.devtools = true;Vue.use(ELEMENT);var _context = {};function UserTable(bean) {BaseTable.call(this, bean);//继承父类属性var self = this;this.elm = '#table';//overridethis.url.search = 'api/user/search';this.url.delete = 'api/user/delete';this.js.edit = 'assets/module/user/user-edit';this.tableOpt.url = this.url.search;this.tableOpt.sortName = 'id',this.tableOpt.sortOrder = 'desc';this.tableOpt.columns = [{title: '序号', field: 'id'},{title: '名称', field: 'username'},{title: '所属单位', field: 'depart_name'}{title: '角色', field: 'roleNames',},{title: '状态', field: 'user_enable',formatter: function (value, row, index) {return userStatusMap[value];}},{title: '备注', field: 'user_remark',},{title: '操作',field: this.idField,formatter: function () {return self.renderActionBtn.apply(self, arguments);}},];};util.beget2(UserTable, BaseTable);//继承父类方法//原型方法UserTable.prototype.initToolbar = function () {if (!this.opt.vm) {var vm = this.opt.vm = new Vue({el: '#toolbar',data: {},mounted: function () {this.defers.toolbar.resolve(this);},});}return this.defers.toolbar.promise();};UserTable.prototype.queryParams = function (params) {//中间的内容自己处理return params;};return UserTable;
});

其中class-util两个方法

  • class-util.js
define([], function () {/*** 生孩子函数 beget:龙beget龙,凤beget凤。* 用于继承中剥离原型中的父类属性* @param obj* @returns {F}*/function beget(obj) {var F = function () {};F.prototype = obj;return new F();}function beget2(Sub, Sup) {var F = function () {};F.prototype = Sup.prototype;var proto = new F();proto.constructor = Sub;//继承代码for (var key in Sub.prototype) {//如果在子类声明了prototype方法之后才调用此继承方法,复制子类方法以覆盖父类方法proto[key] = Sub.prototype[key];}Sub.prototype = proto;//继承代码return Sub;}/*** 获取单例对象* @param context 存放对象的上下文,用于检测是否已实例化,返回已实例化对象;存放其他对象如模态框的jq对象* @param clazz 需要new的类* @returns {*}*/function getInstance(context, clazz) {if (!context.inst) {context.inst = new clazz();//context.inst.setDialog(context.$dialog);}return context.inst;};return {beget: beget,beget2: beget2,getInstance: getInstance,};
});

其他注意

  • beget方法中的原型链 模拟继承,得好好理解。
  • beget2主要是解决beget会把子类的原型方法会丢失,如果beget在子类的原型方法赋值之后,但使用beget2好像会导致 访问父类方法就要多一层__proto__。

转载于:https://my.oschina.net/u/2438634/blog/3052622

js模块化与面向对象编程思考与实践相关推荐

  1. JS中的面向对象编程

    JS中的面向对象编程 小课堂 目录 1.背景介绍 2.知识剖析 3.常见问题 4.解决方案 5.编码实战 6.扩展思考 7.参考文献 8.更多讨论 1.背景介绍 什么是对象? ECMA-262把对象定 ...

  2. [js] 举例说明面向对象编程有什么缺点?

    [js] 举例说明面向对象编程有什么缺点? 有实例化开销,内存消耗比较大,性能消耗比较大 个人简介 我是歌谣,欢迎和大家一起交流前后端知识.放弃很容易, 但坚持一定很酷.欢迎大家一起讨论 主目录 与歌 ...

  3. VSCode自定义代码片段9——JS中的面向对象编程

    JavaScript的面向对象编程 {// JS'OOP// 9 如何自定义用户代码片段:VSCode =>左下角设置 =>用户代码片段 =>新建全局代码片段文件... =>自 ...

  4. Javascript面向对象编程思考与总结

    Javascript面向对象编程 什么是对象?万物皆对象 -对象:特指的某个事物,具有属性和方法(一组无序的属性的集合) 特征:-----> 属性 行为:----->方法 对象是单个事物的 ...

  5. 这可能是Python面向对象编程的最佳实践

    作者 | 崔庆才 来源 | 进击的Coder(ID:FightingCoder) Python 是支持面向对象的,很多情况下使用面向对象编程会使得代码更加容易扩展,并且可维护性更高,但是如果你写的多了 ...

  6. 这可能是 Python 面向对象编程的最佳实践

    Python 是支持面向对象的,很多情况下使用面向对象编程会使得代码更加容易扩展,并且可维护性更高,但是如果你写的多了或者某一对象非常复杂了,其中的一些写法会相当相当繁琐,而且我们会经常碰到对象和 J ...

  7. 图形化编程实现模块化和面向对象编程

    图形化编程支持更多功能 新增功能 支持模块化编程 支持面向对象编程 支持多线程编程 代码实现原理 其他特色实现 总结 新增功能 最近折腾了一个月,给图形化编程增加了很多功能,让图形化编程可以使用更多的 ...

  8. JS面向过程+面向对象编程区别,原型的应用

    1.1面向过程编程 思路:面向过程就是分析出解决问题所需要的步骤,再用函数把这些步骤一步一步实现,使用的时候一个一个的依次调用即可. 1.2面向对象过程 思路:把事务分解成一个一个对象,然后由对象之间 ...

  9. JS 面向对象编程、原型链、原型继承(个人学习总结)

    一.面向对象 1. 面向对象 是所有语言 都有的一种编程思想,组织代码的一种形式 基于对象的语言:JS语言 面向对象的语言:c++ java c# 2. 面向对象 3大特征 封装:将重用代码封装到函数 ...

最新文章

  1. URL/URI/URN的认识
  2. 谈谈C++继承中的重载,覆盖和隐藏
  3. You have not accepted the license agreements of the following SDK components
  4. 菜鸟之路-浅谈设计模式之单例设计模式
  5. ref是什么意思_终于有人说出A股不敢说的话:为什么股价不断下跌,大单却持续流入,你知道是什么缘由吗?...
  6. java class类_关于创建java中的class类的对象的方法
  7. shell下的常用语句
  8. 批量word转pdf——VBS脚本,在office宏中运行即可
  9. Unity object强转int、string
  10. PDF太大怎么办?缩小PDF的4种常用方法
  11. 人体的神经系统图 分布,人体脑神经系统分布图
  12. python sklearn metrics,在Python中sklearn.metrics.mean_squared_error越大越好(否定)?
  13. 什么意思中文_中文十级考题,请解释这四句话是什么意思!
  14. Work with Hans
  15. B站韩顺平老师Linux笔记(截止到85集)
  16. iOS 手机淘宝 自动创建一个人的群聊 实现源码 hook 代码源码
  17. Andrej Karpathy读博建议和写论文的方法
  18. Migrando电子商务可以实现Iluria para o Shopify(Python的标准)
  19. 订单管理系统 订货系统平台建设三阶段分享
  20. 微信公众号文章互阅平台

热门文章

  1. js img转换base64
  2. Spring AMQP + Rabbit 配置多数据源消息队列
  3. MySQL 半同步复制+MMM架构
  4. Delphi2CS破解 Delphi 转换C#
  5. 一些有关。NET界面处理与多线程的文章
  6. Visual Stdio下安装WTL的向导Widzard
  7. 大数据之-Hadoop3.x_MapReduce_MapJoin案例调试_debug---大数据之hadoop3.x工作笔记0135
  8. Mycat安全_监控平台简介---MyCat分布式数据库集群架构工作笔记0035
  9. STM32工作笔记0030---编写跑马灯实验--使用库函数
  10. 经典线程同步 事件Event