知乎 live 原地址:编写优雅的前端业务代码css
当咱们在写业务代码的时候,咱们到底在写什么?html
实际上是对交互的一些处理。全部的交互都是基于用户或者浏览器的一些行为来触发的,好比渲染页面,在页面onload方法触发以后,咱们的js代码才会执行,好比说懒加载,根据用户滚动或者可视区域的变化来触发,好比按钮的点击以后页面的局部刷新,好比input框输入、表单提交、上传文件等等,以上所说的这些行为组成起来的就是咱们前端要写的业务逻辑。前端
咱们所写的业务都是基于事件来对产品功能和场景进行描述。vue
页面的执行顺序是怎样的?react
页面在初始化的时候,先加载css,而后加载html的节点,最后将js放在页尾按顺序执行,而后css加载,模板输出以后css生效,js再加载,js再对页面的模板进行交互处理,好比编译模板,最后再把交互功能好比事件进行绑定。不管写什么业务,都会是这个生命周期。设计模式
一个页面的输出是有它的生命周期的,同时,一个js程序,好比vue或者react也是有本身的生命周期的,因此每个类,每个业务逻辑都是有本身的生命周期的。浏览器
下面是react的组件的生命周期:最开始的时候先拿到默认的参数属性,而后初始化状态,在render以前有一个事件的广播,在render以后有一个事件的广播,这时候这个组件就render到页面上了。组件在运行的时候有两个方式,一个是状态的改变,它会触发update,再去触发state的改变,而后再对应地从新render,在render以前会先触发事件广播,render以后也会触发一个事件广播。另外一个方式是卸载,卸载以前会触发一个广播。安全
vue的生命周期和react的实际上是很像的,它只不过比react多了一步对template和el进行一个判断的扫描,对应的也是render渲染,在渲染以后会有一个属性的update,有一个销毁的过程。前端框架
总结一下,vue和react的生命周期其实就是完成它这个前端框架的业务逻辑。闭包
下面这段代码是将一个面向过程的js程序改成面向对象方式以后的js程序。
(function (global, $, _, doc) { "use strict"; // 定义一个构造器,是这个文件的入口 var app = function (options) { options = options || {}; this.a = options.a; // ... this.eventMap = { 'click .title': 'titleClick', 'dbclick .input': 'inputDbclick', }; // 初始化全部节点属性 this.initEles(); this.init(); }; // 定义构造函数静态属性,挂载全部选择器的属性 app.Eles = { pap: $('.paper'), centryY: $('.centry-y') }; // 工具方法 var utils = { has: function(arr, name) { return arr.indexOf(name) > -1; }, // ... }; app.prototype = { constructor: app, initEles: function () { var eles = app.Eles; for (var name in eles) { if (eles.hasOwnProperty(name)) { this[name] = $(eles[name]); } } }, init: function () { this.bindEvent(this.eventMap); }, initDrag: function () { // ... }, uninitDrag: function () { // ... }, bindEvent: function (maps) { this.initDrag(); this.initOrdinaryEvents(maps); }, unbindEvent: function (maps) { this.uninitDrag(); this.unInitOrdinaryEvents(maps); }, initOrdinaryEvents: function (maps) { this._scanEventsMap(maps, true); }, unInitOrdinaryEvents: function (maps) { this._scanEventsMap(maps, false); }, _scanEventsMap: function (maps, isOn) { var delegateEventSplitter = /^(\S+)\s*(.*)$/, bind = isOn ? this._delegate : this._undelegate; for (var keys in maps) { if (maps.hasOwnProperty(keys)) { var matchs = keys.match(delegateEventSplitter); bind(matchs[1], matchs[2], this[maps[keys]].bind(this)); } } }, _delegate: function (name, selector, func) { // 事件委派,將事件綁定在$(documet)上 doc.on(name, selector, func); }, _undelegate: function (name, selector, func) { doc.off(name, selector, func); }, destroy: function() { this.unbindEvent(); } }; // 将构造函数挂在window上,那么在外部也能访问闭包内的属性,至关于对外暴露了一个接口 global.app = app; $(function () { // 实例化构造函数,至关于这个构造函数就开始执行了 new app(); }); })(this, this.jQuery, this._, this.jQuery(document));
上述代码作的事情:
eventMap
,定义bindEvent
、initOrdinaryEvents
、_scanEventsMap
、_delegate
方法。遍历eventMap
,经过事件委派,將事件綁定在$(documet)
上。以前的使用的是onclick
或者on
方法来进行事件的绑定,维护的时候要到各个地方去找,如今只须要关注eventMap
就很方便,可以清楚知道事件、选择器和事件处理函数名。$('.paper')
,而是用this.pap
(initEles方法实现的)便可,好处是在压缩的时候,字符串是不能被压缩的,而this.pap
会被压缩,压缩率会更高。if (e.target.id === 'titleDrag' || e.target.id == 'subtitleDrag') {} // 改成 if (['titleDrag', 'subtitleDrag'].indexOf(e.target.id) > -1) {}
$('.center-box').removeClass('hidden').css({ width: ui.helper.width(), left: parseInt(centerY.css('left')) - Math.floor(ui.helper.width() / 2) }); // 改成 var width = ui.helper.width(); var left = parseInt(centerY.css('left'), 10); this.centerBox.removeClass('hidden').css({ width: width, left: left - Math.floor(width / 2) });
posObj[event.target.id] = { id: event.target.id, outerHTML: this.delStyle(event.target.outerHTML), style: '#' + event.target.id + '{position: absolute;left:' + (ui.offset.left - 260) / mmToPx + 'mm;top:' + (ui.offset.top - 40) / mmToPx + 'mm;}' }; // 改成 posObj[id] = { id: id, outerHTML: this.delStyle(target.outerHTML), style: '#' + id + '{' + this._getPositionLT(top, left, mmToPx) + '}' };
$(target).next().removeClass('hidden'); $(target).next().find('.line-left').css({ top: 40, left: ui.offset.left, height: pap.css('height'), width: parseInt(pap.css('width')) - ui.offset.left + 260 + 'px' }); $(target).next().find('.line-top').css({ left: 260, top: ui.offset.top, width: pap.css('width'), height: parseInt(pap.css('height')) - ui.offset.top + 40 + 'px' }); // 改成 this._drawNextLine(nextEle, left, top);
this.centerBox.addClass('hidden'); // 改成 utils.hide(centerBox);
var ul = this.widgetUl; var ht; switch (e.target.id) { case 'thin': ht = '<li><div class="hr thin"></div></li>'; ul.append(ht); break; case 'middle': ht = '<li><div class="hr middle"></div></li>'; ul.append(ht); break; case 'thick': ht = '<li><div class="hr thick"></div></li>'; ul.append(ht); break; } // 改成 this.widgetUl.append('<li><div class="hr ' + e.target.id + '"></div></li>');
var txt = this.txt; var cus = this.cus; var mmToPx = this.mmToPx; var pap = this.pap; var target = $(e.target); txt.text(target.text()); switch (e.target.id) { case 'a4': pap.css({ width: 210 * mmToPx + 'px', height: 297 * mmToPx + 'px' }); cus.addClass('hidden'); break; case 'b5': pap.css({ width: 176 * mmToPx + 'px', height: 250 * mmToPx + 'px' }); cus.addClass('hidden'); break; case '16k': pap.css({ width: 184 * mmToPx + 'px', height: 260 * mmToPx + 'px' }); cus.addClass('hidden'); break; case 'cus': cus.removeClass('hidden'); break; } // 改成 var txt = this.txt; var cus = this.cus; var id = e.target.id; var target = $(e.target); var setpapCss = { 'a4': [210, 297], 'b5': [176, 250], '16k': [184, 260] }; txt.text(target.text()); if (setpapCss[id]) { var wh = setpapCss[id]; this.setPapWH(wh[0], wh[1]); utils.hide(cus); } if (id === 'cur') { utils.show(cus); }
常见的 js 业务场景分析以及解决思路:
把全部的选择器的属性挂载构造函数静态属性上,统一进行管理,并经过initEles
方法将其挂载在this上。
使用eventMap
进行统一管理。
app在实例化的时候会往页面里加这个组件,调用this.destory
时会解绑全部事件(还能够补充把html删掉)。
参考artTemplate.js。
解耦你的业务 js 代码,如何在业务中使用设计模式?
模块化就是把文件拆分红小文件。
常见的一些业务优化方法总结:
参照上述优化代码中的switch...case
例子。
建议用没有样式意义的自定义属性data-*
来做为选择器,而不是用具备样式意义的class和id,由于若是有一天css类名变了,那么js也得改变。
jQuery的css方法,或者对jQuery的css方法的进一步封装。
增长你的项目可维护性和代码可读性:
先写伪代码,全部的方法不考虑它的实现,把它拆成粒度比较细的颗粒,用方法名来描述业务逻辑,把方法名都写好了以后,互相调用,最后再添加最细粒度的方法的实现。用这种方法来写的话,不写注释也能够,由于方法名就把业务逻辑解释了。