几年前,我们这样写前端代码:

<div id="el" style="......" onclick="......">测试</div>

慢慢的,我们发现这样做的很多弊端,单就样式一块,改一个样式会涉及到多处调整,所以慢慢的dom标签中的css全部去了一个独立的css文件

再后来,交互变得异常复杂,onclick也不好使了,所以js也分离开了,经典的html+css+javascript结构分离逐步清晰,三种代码各司其职

HTML+CSS+Javascript体现着结构、表现、交互分离的思想,分离到极致后,css相关便完全由独立团队(UED)负责,会给出不包含javascript的“原型”demo

事有利弊,分离只是第一步,最终他们还是得合到一起,所以过度的拆分反而会有问题,最近工作中遇到了两个令人头疼的问题:

① 框架UI组件的CSS在UED处,一旦在线的UI出了样式问题,UED需要改动DOM结构和CSS的话,无论是框架还是UED先发布必定会导致生产样式问题(发布系统分离)

② H5站点会等依赖的CSS全部加载结束才能渲染页面。框架的css文件尺寸必定过100K,3G情况不稳定时要等很长时间,2G情况下5S秒以上更是家常便饭

PS:问题一是一个典型的发布依赖问题,本来与今天的内容不太相关,但是在讨论问题一的时候引出了问题二,解决问题二的时候又顺便解决了问题一,所以这里一并提出来,讲述了前端html、css、javascript的分分合合

做过全站前端优化的同学都会明白,优化做到最后,法宝往往都是减少请求,减低尺寸,所以缓存、轻量级框架在前端比较流行,但CSS却不容易被拆分,css业务分离还带来了重用性与发布依赖的问题,分离是问题产生的主要原因。而“分离”也是这里的优化手段:

① 分离:将全站的css“分离”到各个UI中

② 合并:将分离的html、css、javascript重新“合并”

css非常容易引起变量“污染”,UI中的css应该最大程度的保证不影响业务css,并且不被影响,这一前提若是完全依赖与.css文件很难处理。

传说中web应用的未来:Web Components也提将HTML、CSS、JS封装到一起。其中比较令人惊讶的是不论js还是css会处于一沙箱中不会对外污染,学习web components的过程中意识到将css放到各自UI中的方案是可行的,也是上面问题的一种解决方案:

Web Components:组件相关html、css、js全部处于一个模块!

所以,似乎我应该将框架css分为两部分:
① 核心通用css(10k左右)
② 各部分UI样式

框架加载时候只需要加载10k的通用部分,或者常用UI;剩下的UI对应样式以及js文件便按需加载,并且UI的样式还不会互相影响,于是一个“奇怪”的做法出现了,以num组件为例

原来num组件包括两个文件:

① ui.num.js
② ui.num.html

文件一为核心控制器,文件二为html实体,对应样式在全局css中,现在新增文件三:

① ui.num.js
② ui.num.html
③ ui.num.css

这个时候将全局css中对应的UI样式给抽出来了,放到了具体UI中,以实际代码为例我们数字组件变成了这个样子:

这里涉及到的文件有:

 /**
* UI组件基类,提供一个UI类基本功能,并可注册各个事件点:
① onPreCreate 在dom创建时触发,只触发一次
② onCreate 在dom创建后触发,只触发一次 * @namespace UIView
*/
define([], function () { /**
* @description 闭包保存所有UI共用的信息,这里是z-index
* @method getBiggerzIndex
* @param {Number} level
* @returns {Number}
*/
var getBiggerzIndex = (function () {
var index = 3000;
return function (level) {
return level + (++index);
};
})(); return _.inherit({ /**
* @description 设置实例默认属性
* @method propertys
*/
propertys: function () {
//模板状态
this.wrapper = $('body');
this.id = _.uniqueId('ui-view-'); this.template = '';
this.datamodel = {};
this.events = {}; //自定义事件
//此处需要注意mask 绑定事件前后问题,考虑scroll.radio插件类型的mask应用,考虑组件通信
this.eventArr = {}; //初始状态为实例化
this.status = 'init'; this.animateShowAction = null;
this.animateHideAction = null; // this.availableFn = function () { } }, /**
* @description 绑定事件点回调,这里应该提供一个方法,表明是insert 或者 push,这样有一定手段可以控制各个同一事件集合的执行顺序
* @param {String} type
* @param {Function} fn
* @param {Boolean} insert
* @method on
*/
on: function (type, fn, insert) {
if (!this.eventArr[type]) this.eventArr[type] = []; //头部插入
if (insert) {
this.eventArr[type].splice(0, 0, fn);
} else {
this.eventArr[type].push(fn);
}
}, /**
* @description 移除某一事件回调点集合中的一项
* @param {String} type
* @param {Function} fn
* @method off
*/
off: function (type, fn) {
if (!this.eventArr[type]) return;
if (fn) {
this.eventArr[type] = _.without(this.eventArr[type], fn);
} else {
this.eventArr[type] = [];
}
}, /**
* @description 触发某一事件点集合回调,按顺序触发
* @method trigger
* @param {String} type
* @returns {Array}
*/
//PS:这里做的好点还可以参考js事件机制,冒泡捕获处于阶段
trigger: function (type) {
var _slice = Array.prototype.slice;
var args = _slice.call(arguments, 1);
var events = this.eventArr;
var results = [], i, l; if (events[type]) {
for (i = 0, l = events[type].length; i < l; i++) {
results[results.length] = events[type][i].apply(this, args);
}
}
return results;
}, /**
* @description 创建dom根元素,并组装形成UI Dom树
* @override 这里可以重写该接口,比如有些场景不希望自己创建div为包裹层
* @method createRoot
* @param {String} html
*/
createRoot: function (html) {
this.$el = $('<div class="view" style="display: none; " id="' + this.id + '"></div>');
this.$el.html(html);
}, _isAddEvent: function (key) {
if (key == 'onCreate' || key == 'onPreShow' || key == 'onShow' || key == 'onRefresh' || key == 'onHide')
return true;
return false;
}, /**
* @description 设置参数,重写默认属性
* @override
* @method setOption
* @param {Object} options
*/
setOption: function (options) {
//这里可以写成switch,开始没有想到有这么多分支
for (var k in options) {
if (k == 'datamodel' || k == 'events') {
_.extend(this[k], options[k]);
continue;
} else if (this._isAddEvent(k)) {
this.on(k, options[k])
continue;
}
this[k] = options[k];
}
// _.extend(this, options);
}, /**
* @description 构造函数
* @method initialize
* @param {Object} opts
*/
initialize: function (opts) {
this.propertys();
this.setOption(opts);
this.resetPropery();
//添加系统级别事件
this.addEvent();
//开始创建dom
this.create();
this.addSysEvents(); this.initElement(); }, //内部重置event,加入全局控制类事件
addSysEvents: function () {
if (typeof this.availableFn != 'function') return;
this.removeSysEvents();
this.$el.on('click.system' + this.id, $.proxy(function (e) {
if (!this.availableFn()) {
e.preventDefault();
e.stopImmediatePropagation && e.stopImmediatePropagation();
}
}, this));
}, removeSysEvents: function () {
this.$el.off('.system' + this.id);
}, $: function (selector) {
return this.$el.find(selector);
}, //提供属性重置功能,对属性做检查
resetPropery: function () {
}, //各事件注册点,用于被继承
addEvent: function () {
}, create: function () {
this.trigger('onPreCreate');
this.createRoot(this.render()); this.status = 'create';
this.trigger('onCreate');
}, //实例化需要用到到dom元素
initElement: function () { }, render: function (callback) {
data = this.getViewModel() || {};
var html = this.template;
if (!this.template) return '';
if (data) {
html = _.template(this.template)(data);
}
typeof callback == 'function' && callback.call(this);
return html;
}, //刷新根据传入参数判断是否走onCreate事件
//这里原来的dom会被移除,事件会全部丢失 需要修复*****************************
refresh: function (needEvent) {
this.resetPropery();
if (needEvent) {
this.create();
} else {
this.$el.html(this.render());
}
this.initElement();
if (this.status == 'show') this.show();
this.trigger('onRefresh');
}, show: function () {
if (!this.wrapper[0] || !this.$el[0]) return;
//如果包含就不要乱搞了
if (!$.contains(this.wrapper[0], this.$el[0])) {
this.wrapper.append(this.$el);
} this.trigger('onPreShow'); if (typeof this.animateShowAction == 'function')
this.animateShowAction.call(this, this.$el);
else
this.$el.show(); this.status = 'show';
this.bindEvents();
this.trigger('onShow');
}, hide: function () {
if (!this.$el || this.status !== 'show') return; this.trigger('onPreHide'); if (typeof this.animateHideAction == 'function')
this.animateHideAction.call(this, this.$el);
else
this.$el.hide(); this.status = 'hide';
this.unBindEvents();
this.removeSysEvents();
this.trigger('onHide');
}, destroy: function () {
this.status = 'destroy';
this.unBindEvents();
this.removeSysEvents();
this.$el.remove();
this.trigger('onDestroy');
delete this;
}, getViewModel: function () {
return this.datamodel;
}, setzIndexTop: function (el, level) {
if (!el) el = this.$el;
if (!level || level > 10) level = 0;
level = level * 1000;
el.css('z-index', getBiggerzIndex(level)); }, /**
* 解析events,根据events的设置在dom上设置事件
*/
bindEvents: function () {
var events = this.events; if (!(events || (events = _.result(this, 'events')))) return this;
this.unBindEvents(); // 解析event参数的正则
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
var key, method, match, eventName, selector; // 做简单的字符串数据解析
for (key in events) {
method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) continue; match = key.match(delegateEventSplitter);
eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateUIEvents' + this.id; if (selector === '') {
this.$el.on(eventName, method);
} else {
this.$el.on(eventName, selector, method);
}
} return this;
}, /**
* 冻结dom上所有元素的所有事件
*
* @return {object} 执行作用域
*/
unBindEvents: function () {
this.$el.off('.delegateUIEvents' + this.id);
return this;
} }); });

ui.abstract.view

 define(['UIView', getAppUITemplatePath('ui.num'), getAppUICssPath('ui.num')], function (UIView, template, style) {
return _.inherit(UIView, {
propertys: function ($super) {
$super(); this.datamodel = {
min: 1,
max: 9,
curNum: 1,
unit: '',
needText: false
}; this.template = template; this.events = {
'click .js_num_minus': 'minusAction',
'click .js_num_plus': 'addAction',
'focus .js_cur_num': 'txtFocus',
'blur .js_cur_num': 'txtBlur'
}; this.needRootWrapper = false; }, initElement: function () {
this.curNum = this.$('.js_cur_num');
}, txtFocus: function () {
this.curNum.html('');
}, txtBlur: function () {
this.setVal(this.curNum.html());
}, addAction: function () {
this.setVal(this.datamodel.curNum + 1);
}, minusAction: function () {
this.setVal(this.datamodel.curNum - 1);
}, //用于重写
changed: function (num) {
console.log('num changed ' + num);
}, getVal: function () {
return this.datamodel.curNum;
}, setVal: function (v) {
var isChange = true;
var tmp = this.datamodel.curNum;
if (v === '') v = tmp;
if (v == parseInt(v)) {
//设置值不等的时候才触发reset
v = parseInt(v);
this.datamodel.curNum = v;
if (v < this.datamodel.min) {
this.datamodel.curNum = this.datamodel.min;
}
if (v > this.datamodel.max) {
this.datamodel.curNum = this.datamodel.max;
}
this.curNum.val(this.datamodel.curNum);
isChange = (this.datamodel.curNum != tmp);
} this.resetNum(isChange); }, //重置当前值,由于数值不满足条件
resetNum: function (isChange) {
this.refresh();
if (isChange) this.changed.call(this, this.datamodel.curNum);
}, initialize: function ($super, opts) {
$super(opts);
}, //这里需要做数据验证
resetPropery: function () {
if (this.datamodel.curNum > this.datamodel.max) {
this.datamodel.curNum = this.datamodel.max;
} else if (this.datamodel.curNum < this.datamodel.min) {
this.datamodel.curNum = this.datamodel.min;
}
}, addEvent: function ($super) {
$super();
} }); });

ui.num.js

 <div class="cm-num-adjust">
<span class="cm-adjust-minus js_num_minus <% if(min == curNum) { %> disabled <% } %> "></span><span class="cm-adjust-view js_cur_num " <%if(needText == true){ %>contenteditable="true"<%} %>><%=curNum %><%=unit %></span>
<span class="cm-adjust-plus js_num_plus <% if(max == curNum) { %> disabled <% } %>"></span>
</div>

ui.num.html

 .cm-num-adjust { height: 33px; color: #099fde; background-color: #fff; display: inline-block; border-radius: 4px; }
.cm-num-adjust .cm-adjust-minus, .cm-num-adjust .cm-adjust-plus, .cm-num-adjust .cm-adjust-view { width: 33px; height: 33px; line-height: 31px; text-align: center; float: left; -webkit-box-sizing: border-box; box-sizing: border-box; }
.cm-num-adjust .cm-adjust-minus, .cm-num-adjust .cm-adjust-plus { cursor: pointer; border: 1px solid #099fde; }
.cm-num-adjust .cm-adjust-minus.disabled, .cm-num-adjust .cm-adjust-plus.disabled { cursor: default !important; background-color: #fff !important; border-color: #999 !important; }
.cm-num-adjust .cm-adjust-minus.disabled::before, .cm-num-adjust .cm-adjust-minus.disabled::after, .cm-num-adjust .cm-adjust-plus.disabled::before, .cm-num-adjust .cm-adjust-plus.disabled::after { background-color: #999 !important; }
.cm-num-adjust .cm-adjust-minus:active, .cm-num-adjust .cm-adjust-minus:hover, .cm-num-adjust .cm-adjust-plus:active, .cm-num-adjust .cm-adjust-plus:hover { background-color: #099fde; }
.cm-num-adjust .cm-adjust-minus:active::before, .cm-num-adjust .cm-adjust-minus:active::after, .cm-num-adjust .cm-adjust-minus:hover::before, .cm-num-adjust .cm-adjust-minus:hover::after, .cm-num-adjust .cm-adjust-plus:active::before, .cm-num-adjust .cm-adjust-plus:active::after, .cm-num-adjust .cm-adjust-plus:hover::before, .cm-num-adjust .cm-adjust-plus:hover::after { background-color: #fff; }
.cm-num-adjust .cm-adjust-minus { border-right: none; border-radius: 4px 0 0 4px; position: relative; }
.cm-num-adjust .cm-adjust-minus::before { content: ""; height: 2px; width: 16px; background-color: #099fde; position: absolute; top: 50%; left: 50%; -webkit-transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0); }
.cm-num-adjust .cm-adjust-minus + .cm-adjust-plus { border-left: 1px solid #099fde; }
.cm-num-adjust .cm-adjust-plus { border-left: none; border-radius: 0 4px 4px 0; position: relative; }
.cm-num-adjust .cm-adjust-plus::before, .cm-num-adjust .cm-adjust-plus::after { content: ""; width: 16px; height: 2px; background-color: #099fde; position: absolute; top: 50%; left: 50%; -webkit-transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0); }
.cm-num-adjust .cm-adjust-plus::after { width: 2px; height: 16px; }
.cm-num-adjust .cm-adjust-view { border: 1px solid #099fde; overflow: hidden; }

ui.num.css

断点一看,对应文本拿出来了:

因为这个特性是全组件共有的,我们将之做到统一的基类ui.abstract.view中即可:

 /**
* @File ui.abstract.view.js
* @Description: UI组件基类
* @author l_wang@ctrip.com
* @date 2014-10-09
* @version V1.0
*/ /**
* UI组件基类,提供一个UI类基本功能,并可注册各个事件点:
① onPreCreate 在dom创建时触发,只触发一次
② onCreate 在dom创建后触发,只触发一次 * @namespace UIView
*/
define([], function () { /**
* @description 闭包保存所有UI共用的信息,这里是z-index
* @method getBiggerzIndex
* @param {Number} level
* @returns {Number}
*/
var getBiggerzIndex = (function () {
var index = 3000;
return function (level) {
return level + (++index);
};
})(); return _.inherit({ /**
* @description 设置实例默认属性
* @method propertys
*/
propertys: function () {
//模板状态
this.wrapper = $('body');
this.id = _.uniqueId('ui-view-'); this.template = ''; //与模板对应的css文件,默认不存在,需要各个组件复写
this.uiStyle = null;
//保存样式格式化结束的字符串
this.formateStyle = null; this.datamodel = {};
this.events = {}; //自定义事件
//此处需要注意mask 绑定事件前后问题,考虑scroll.radio插件类型的mask应用,考虑组件通信
this.eventArr = {}; //初始状态为实例化
this.status = 'init'; this.animateShowAction = null;
this.animateHideAction = null; // this.availableFn = function () { } }, /**
* @description 绑定事件点回调,这里应该提供一个方法,表明是insert 或者 push,这样有一定手段可以控制各个同一事件集合的执行顺序
* @param {String} type
* @param {Function} fn
* @param {Boolean} insert
* @method on
*/
on: function (type, fn, insert) {
if (!this.eventArr[type]) this.eventArr[type] = []; //头部插入
if (insert) {
this.eventArr[type].splice(0, 0, fn);
} else {
this.eventArr[type].push(fn);
}
}, /**
* @description 移除某一事件回调点集合中的一项
* @param {String} type
* @param {Function} fn
* @method off
*/
off: function (type, fn) {
if (!this.eventArr[type]) return;
if (fn) {
this.eventArr[type] = _.without(this.eventArr[type], fn);
} else {
this.eventArr[type] = [];
}
}, /**
* @description 触发某一事件点集合回调,按顺序触发
* @method trigger
* @param {String} type
* @returns {Array}
*/
//PS:这里做的好点还可以参考js事件机制,冒泡捕获处于阶段
trigger: function (type) {
var _slice = Array.prototype.slice;
var args = _slice.call(arguments, 1);
var events = this.eventArr;
var results = [], i, l; if (events[type]) {
for (i = 0, l = events[type].length; i < l; i++) {
results[results.length] = events[type][i].apply(this, args);
}
}
return results;
}, /**
* @description 创建dom根元素,并组装形成UI Dom树
* @override 这里可以重写该接口,比如有些场景不希望自己创建div为包裹层
* @method createRoot
* @param {String} html
*/
createRoot: function (html) { var style = this.createInlineStyle();
if (style) {
this.formateStyle = '<style id="' + this.id + '_style">' + style + '</style>';
html = this.formateStyle + html;
} this.$el = $('<div class="view" style="display: none; " id="' + this.id + '"></div>');
this.$el.html(html);
}, //创建内嵌style相关
createInlineStyle: function () {
//如果不存在便不予理睬
if (!_.isString(this.uiStyle)) return null;
var style = '', uid = this.id; //创建定制化的style字符串,会模拟一个沙箱,该组件样式不会对外影响,实现原理便是加上#id 前缀
style = this.uiStyle.replace(/(\s*)([^\{\}]+)\{/g, function (a, b, c) {
return b + c.replace(/([^,]+)/g, '#' + uid + ' $1') + '{';
}); return style; }, _isAddEvent: function (key) {
if (key == 'onCreate' || key == 'onPreShow' || key == 'onShow' || key == 'onRefresh' || key == 'onHide')
return true;
return false;
}, /**
* @description 设置参数,重写默认属性
* @override
* @method setOption
* @param {Object} options
*/
setOption: function (options) {
//这里可以写成switch,开始没有想到有这么多分支
for (var k in options) {
if (k == 'datamodel' || k == 'events') {
_.extend(this[k], options[k]);
continue;
} else if (this._isAddEvent(k)) {
this.on(k, options[k])
continue;
}
this[k] = options[k];
}
// _.extend(this, options);
}, /**
* @description 构造函数
* @method initialize
* @param {Object} opts
*/
initialize: function (opts) {
this.propertys();
this.setOption(opts);
this.resetPropery();
//添加系统级别事件
this.addEvent();
//开始创建dom
this.create();
this.addSysEvents(); this.initElement(); }, //内部重置event,加入全局控制类事件
addSysEvents: function () {
if (typeof this.availableFn != 'function') return;
this.removeSysEvents();
this.$el.on('click.system' + this.id, $.proxy(function (e) {
if (!this.availableFn()) {
e.preventDefault();
e.stopImmediatePropagation && e.stopImmediatePropagation();
}
}, this));
}, removeSysEvents: function () {
this.$el.off('.system' + this.id);
}, $: function (selector) {
return this.$el.find(selector);
}, //提供属性重置功能,对属性做检查
resetPropery: function () {
}, //各事件注册点,用于被继承
addEvent: function () {
}, create: function () {
this.trigger('onPreCreate');
this.createRoot(this.render()); this.status = 'create';
this.trigger('onCreate');
}, //实例化需要用到到dom元素
initElement: function () { }, render: function (callback) {
data = this.getViewModel() || {};
var html = this.template;
if (!this.template) return '';
if (data) {
html = _.template(this.template)(data);
}
typeof callback == 'function' && callback.call(this);
return html;
}, //刷新根据传入参数判断是否走onCreate事件
//这里原来的dom会被移除,事件会全部丢失 需要修复*****************************
refresh: function (needEvent) {
var html = '';
this.resetPropery();
if (needEvent) {
this.create();
} else {
html = this.render();
this.$el.html(this.formateStyle ? this.formateStyle + html : html);
}
this.initElement();
if (this.status == 'show') this.show();
this.trigger('onRefresh');
}, show: function () {
if (!this.wrapper[0] || !this.$el[0]) return;
//如果包含就不要乱搞了
if (!$.contains(this.wrapper[0], this.$el[0])) {
this.wrapper.append(this.$el);
} this.trigger('onPreShow'); if (typeof this.animateShowAction == 'function')
this.animateShowAction.call(this, this.$el);
else
this.$el.show(); this.status = 'show';
this.bindEvents();
this.trigger('onShow');
}, hide: function () {
if (!this.$el || this.status !== 'show') return; this.trigger('onPreHide'); if (typeof this.animateHideAction == 'function')
this.animateHideAction.call(this, this.$el);
else
this.$el.hide(); this.status = 'hide';
this.unBindEvents();
this.removeSysEvents();
this.trigger('onHide');
}, destroy: function () {
this.status = 'destroy';
this.unBindEvents();
this.removeSysEvents();
this.$el.remove();
this.trigger('onDestroy');
delete this;
}, getViewModel: function () {
return this.datamodel;
}, setzIndexTop: function (el, level) {
if (!el) el = this.$el;
if (!level || level > 10) level = 0;
level = level * 1000;
el.css('z-index', getBiggerzIndex(level)); }, /**
* 解析events,根据events的设置在dom上设置事件
*/
bindEvents: function () {
var events = this.events; if (!(events || (events = _.result(this, 'events')))) return this;
this.unBindEvents(); // 解析event参数的正则
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
var key, method, match, eventName, selector; // 做简单的字符串数据解析
for (key in events) {
method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
if (!method) continue; match = key.match(delegateEventSplitter);
eventName = match[1], selector = match[2];
method = _.bind(method, this);
eventName += '.delegateUIEvents' + this.id; if (selector === '') {
this.$el.on(eventName, method);
} else {
this.$el.on(eventName, selector, method);
}
} return this;
}, /**
* 冻结dom上所有元素的所有事件
*
* @return {object} 执行作用域
*/
unBindEvents: function () {
this.$el.off('.delegateUIEvents' + this.id);
return this;
} }); });

波及到的代码片段是:

 createRoot: function (html) {

   var style = this.createInlineStyle();
if (style) {
this.formateStyle = '<style id="' + this.id + '_style">' + style + '</style>';
html = this.formateStyle + html;
} this.$el = $('<div class="view" style="display: none; " id="' + this.id + '"></div>');
this.$el.html(html);
}, //创建内嵌style相关
createInlineStyle: function () {
//如果不存在便不予理睬
if (!_.isString(this.uiStyle)) return null;
var style = '', uid = this.id; //创建定制化的style字符串,会模拟一个沙箱,该组件样式不会对外影响,实现原理便是加上#id 前缀
style = this.uiStyle.replace(/(\s*)([^\{\}]+)\{/g, function (a, b, c) {
return b + c.replace(/([^,]+)/g, '#' + uid + ' $1') + '{';
}); return style; },

refresh: function (needEvent) {
var html = '';
this.resetPropery();
if (needEvent) {
this.create();
} else {
html = this.render();
this.$el.html(this.formateStyle ? this.formateStyle + html : html);
}
this.initElement();
if (this.status == 'show') this.show();
this.trigger('onRefresh');
},

这个时候对应ui.num.js只需要一点点变化即可:

 define(['UIView', getAppUITemplatePath('ui.num'), getAppUICssPath('ui.num')], function (UIView, template, style) {
return _.inherit(UIView, {
propertys: function ($super) {
$super(); this.datamodel = {
min: 1,
max: 9,
curNum: 1,
unit: '',
needText: false
}; this.template = template;
this.uiStyle = style; this.events = {
'click .js_num_minus': 'minusAction',
'click .js_num_plus': 'addAction',
'focus .js_cur_num': 'txtFocus',
'blur .js_cur_num': 'txtBlur'
}; this.needRootWrapper = false; }, initElement: function () {
this.curNum = this.$('.js_cur_num');
}, txtFocus: function () {
this.curNum.html('');
}, txtBlur: function () {
this.setVal(this.curNum.html());
}, addAction: function () {
this.setVal(this.datamodel.curNum + 1);
}, minusAction: function () {
this.setVal(this.datamodel.curNum - 1);
}, //用于重写
changed: function (num) {
console.log('num changed ' + num);
}, getVal: function () {
return this.datamodel.curNum;
}, setVal: function (v) {
var isChange = true;
var tmp = this.datamodel.curNum;
if (v === '') v = tmp;
if (v == parseInt(v)) {
//设置值不等的时候才触发reset
v = parseInt(v);
this.datamodel.curNum = v;
if (v < this.datamodel.min) {
this.datamodel.curNum = this.datamodel.min;
}
if (v > this.datamodel.max) {
this.datamodel.curNum = this.datamodel.max;
}
this.curNum.val(this.datamodel.curNum);
isChange = (this.datamodel.curNum != tmp);
} this.resetNum(isChange); }, //重置当前值,由于数值不满足条件
resetNum: function (isChange) {
this.refresh();
if (isChange) this.changed.call(this, this.datamodel.curNum);
}, initialize: function ($super, opts) {
$super(opts);
}, //这里需要做数据验证
resetPropery: function () {
if (this.datamodel.curNum > this.datamodel.max) {
this.datamodel.curNum = this.datamodel.max;
} else if (this.datamodel.curNum < this.datamodel.min) {
this.datamodel.curNum = this.datamodel.min;
}
}, addEvent: function ($super) {
$super();
} }); });
 define(['UIView', getAppUITemplatePath('ui.num'), getAppUICssPath('ui.num')], function (UIView, template, style) {
return _.inherit(UIView, {
propertys: function ($super) {
$super();
//...... this.template = template;
this.uiStyle = style; //......
} //......
});
});

这个时候形成的dom结构变成了这个样子:

如图所示,对应的css被格式化为带id的选择器了,不会对外污染,这个样子解决了几个问题:

① html、css、js统一归UI管理,不存在发布不同步的问题

② css也可以按需加载

③ 一定程度解决组件css污染问题

④ 组件destroy时候样式节点会被移除

但是也引起了一些新的问题:

① ui占用节点增多,不destroy组件的情况下,是否会引起手机性能问题,对于webapp尤其重要

② 其中的css依然是UED分拆过来的,是否会引起更新不同步问题

③ html是不能跨域的,css是否会有同样问题,未做实际验证

④ css通用模块需要得到处理,防治重复代码

......

抛开以上问题不管,实现了相关功能的js钩子保持一致的情况下,甚至可以以一个开关/版本号管理当前究竟显示哪个样式的组件,比如我们将html与css还原到以前:

到底使用V1版本或者标准版本,完全控制到requireJS的管理,这里简单依赖于这两个方法的实现:

window.getAppUITemplatePath = function (path) {
return 'text!' + app + 'ui/' + path + '.html';
} window.getAppUICssPath = function (path) {
return 'text!' + app + 'ui/' + path + '.css';
}

我们可以简单的在这里定制开关,我们也可以在一个页面里面让两个组件同时出现,并且他们是同一个控制器,ver不同显示的版本就不一样:

 //在此设置版本号,或者由url取出或者由服务器取出...
var ver = 'v1';
window.getAppUITemplatePath = function (path) {
return 'text!' + app + 'ui/' + path + (ver ? '_' + ver : '') + '.html';
}
window.getAppUICssPath = function (path) {
return 'text!' + app + 'ui/' + path + (ver ? '_' + ver : '') + '.css';
}

当然,也可以走更加合理的模块管理路线,我们这里不做论述,这里做一番总结,便结束今天的学习。

该问题的引出最初是由于发布配合问题,结果上升了一下便成了性能优化问题,最后发现居然是解耦的问题,HTML、CSS、Javascript应该分离,但是业务应该在一块,过度分离反而会引起开发效率问题,上面处理的方式,依旧是主动由UED将需要的CSS拿了回来,因为三者密不可分。

demo地址:http://yexiaochai.github.io/cssui/demo/debug.html#num

代码地址:https://github.com/yexiaochai/cssui/tree/gh-pages

文中有误或者有不妥的地方请您提出

【前端优化之拆分CSS】前端三剑客的分分合合的更多相关文章

  1. 前端优化,包括css,jss,img,cookie

    前端优化,来自某懒观看麦子学院视频的笔记. 尽可能减少HTTP的请求数 使用CDN 添加Expirs头,或者Cache-control Gzip组件压缩文件内容 将CSS放在页面上方 将脚本放到页面下 ...

  2. YUI前端优化之javascript,css篇

    三.JavaScript和CSS篇 JavaScript和CSS也是我们页面中经常用到的内容,对它们的优化也提高网站性能的重要方面:CSS:把样式表置于顶部避免使用CSS表达式(Expression) ...

  3. [转][前端优化]使用Combres合并对js、css文件的请求

    本文转自:http://www.cnblogs.com/parry/archive/2011/01/28/Reduce_Http_Request_Using_Combres_For_Js_Css.ht ...

  4. 转:浅谈CSS在前端优化中一些值得注意的关键点

    前端优化工作中要考虑的元素多种多样,而合理地使用CSS脚本可以在很大程度上优化页面的加载性能,以下我们就来浅谈CSS在前端优化中一些值得注意的关键点: 当谈到Web的“高性能”时,很多人想到的是页面加 ...

  5. [转] Web前端优化之 CSS篇

    原文链接: http://lunax.info/archives/3097.html Web 前端优化最佳实践第四部分面向 CSS.目前共计有 6 条实践规则.另请参见 Mozilla 开发者中心的文 ...

  6. 关于大型网站技术演进的思考(二十一)--网站静态化处理—web前端优化—下【终篇】(13)

    本篇继续web前端优化的讨论,开始我先讲个我所知道的一个故事,有家大型的企业顺应时代发展的潮流开始投身于互联网行业了,它们为此专门设立了一个事业部,不过该企业把这个事业部里的人事成本,系统运维成本特别 ...

  7. 关于大型网站技术演进的思考(十九)--网站静态化处理—web前端优化—上(11)

    网站静态化处理这个系列马上就要结束了,今天我要讲讲本系列最后一个重要的主题web前端优化.在开始谈论本主题之前,我想问大家一个问题,网站静态化处理技术到底是应该归属于web服务端的技术范畴还是应该归属 ...

  8. 网站静态化处理—web前端优化—下【终篇】(13)

    网站静态化处理—web前端优化—下[终篇](13) 本篇继续web前端优化的讨论,开始我先讲个我所知道的一个故事,有家大型的企业顺应时代发展的潮流开始投身于互联网行业了,它们为此专门设立了一个事业部, ...

  9. 网站静态化处理—web前端优化—上

    网站静态化处理—web前端优化—上(11) 网站静态化处理这个系列马上就要结束了,今天我要讲讲本系列最后一个重要的主题web前端优化.在开始谈论本主题之前,我想问大家一个问题,网站静态化处理技术到底是 ...

随机推荐

  1. 一个简单的网站web项目的详解

    有不对的术语,或者不好理解的部分,欢迎大家批评指正,谢谢大家! 近期做的网站web项目,实现登录功能,查询功能.首先把这个项目分为几个模块来处理,当前用户模块,历史用户模块,历史记录模块,数据库模块, ...

  2. 浅谈单片机中C语言与汇编语言的转换

    做了一单片机设计,要用C语言与汇编语言同时实现,现将这次设计的感受和收获,还有遇到的问题写下,欢迎感兴趣的朋友交流想法,提出建议. 单片机设计:基于51单片机的99码表设计 软件环境:Proteus8 ...

  3. java根据html生成摘要

    转自:http://java.freesion.com/article/48772295755/ 开发一个系统,需要用到这个,根据html生成你指定多少位的摘要 package com.chendao ...

  4. ubuntu-14.04-server配置Jexus --安装步骤记录

    作者:郝喜路   个人主页:http://www.cnicode.com      博客地址:http://haoxilu.cnblogs.com 说明:我是Linux菜鸟,自己尝试配置Jexus服务 ...

  5. ASP.NET 5 Beta 8 发布

    ASP.NET 5 的路线图(详见 ASP.NET 5 Schedule and Roadmap : https://github.com/aspnet/home/wiki/roadmap ):Bet ...

  6. ABP源码分析十四:Entity的设计

    IEntity<TPrimaryKey>: 封装了PrimaryKey:Id,这是一个泛型类型 IEntity: 封装了PrimaryKey:Id,这是一个int类型 Entity< ...

  7. 日期格式 CST

    从es 取出来一个date 字段, 结果竟然是 2016-10-10T10:48:58.000Z 这样的字符串, 这个是什么格式啊??? CST ? 只能自己转换了! 通过"yyyy-MM- ...

  8. 关于php语言的使用!

    ------php语言与JavaScript的使用 方法是相似 <script type="text/javascript"> </script>--js与 ...

  9. Cleaver快速制作网页PPT

    原文首发链接:http://www.jeffjade.com/2015/10/15/2015-10-16-cleaver-make-ppt/ 写在开始之前 互联网时代,以浏览器作为入口,已经有越来越多 ...

  10. 在网上摘录一段对于IOC的解析,比较直观,大家观摩观摩

    其实IoC非常简单,基本思想就是面向接口的编程,只是老外给起了个名字名充分利用之. 简单的说,传统模式下,如果你要用钱,你需要去银行取,IoC模式下,银联在你家安了一个取款机,你直接找取款机要钱就可以 ...