@lxjwlt
2016-07-07T20:09:03.000000Z
字数 2627
阅读 2306
博文
在项目开发中我多次遇到由于命名冲突导致页面样式混乱,甚至跨模块获取同名元素导致功能失效的情况。对于这种情况,我之前的一篇文章中介绍过如何使用BEM命名法来解决命名冲突的问题。
当项目中引入了BEM命名后,不仅CSS代码,JS代码也应该做相应的调整,以下是我个人实践的总结,如果你也在项目中使用BEM命名,希望本文对你有所帮助。
当我们提起BEM命名,我们知道一个元素的名称由三部分组成:
BEM = block + element + modifier = 模块名 + 元素名 + 修改器
假设我们有张联系页面,使用BEM命名的模板如下:
<div class="contact-page">
<div class="contact-page_header"></div>
</div>
联系页面的模块标识为contact-page,所以该页面的所有子元素的名称都以contact-page为前缀来命名。
考虑到JS中需要通过模块标识来获取页面元素,而且HTML中手工编写模块标识影响可读性,不利于维护,所以我们将模块标识交由JS来统一管理,而HTML中的模块标识则使用JS模板引擎进行动态插入。
这里我们使用的是Ext.XTemplate引擎:
var $ = require('jquery'),
R_Xtpl = require('xtpl');
var C = function (wrap, options) {
this.modName = 'contact-page';
this.$elem = $((new R_Xtpl(require('./index.html'))).apply({
modName: this.modName
}));
$wrap.html(this.$elem);
};
在模板中,我们用modName变量代替模块标识:
<div class="{modName}">
<div class="{modName}_header"></div>
</div>
这样,阅读HTML时我们只需要关注页面元素名(BEM的E)即可,可读性更好,而且我们能够在JS获取元素。
模块标识存储在this.modName
中,所以按照BEM命名,我们可以编写以下函数来获取元素的选择器:
C.prototype.selector = function (selector) {
return this.modName + '_' + selector;
};
联系页面的header元素的选择器,我们可以这么获取:
'.' + this.selector('header'); // .contact-page_header
目前的selector函数不好的地方在于,每次获取元素选择器都要进行字符串的结合,如果点号(.)或井号(#)写在元素名中就好了,比如this.selector('.header')
,我们对selector函数进行改进:
C.prototype.selector = function (selector) {
var self = this,
reg = /^(\.|#)?/;
return selector.replace(reg, function (match, $1) {
$1 = $1 || '';
return $1 + self.modName + '_';
});
};
这样我们就可以根据不同的场景分别获取元素的“名称”或“选择器”:
// .contact-page_header元素有类名contact-page_header
$(this.selector('.header')).hasClass(this.selector('header')); // true
我们已经有了selector方法来获取元素选择器,那么接下来我们可以轻易的实现获取模块元素的接口:
C.prototype.elem = function (selector) {
return selector ?
this.$elem.find(this.selector(selector)) : this.$elem;
};
以下用elem方法获取联系页面的头部元素:
this.elem('.header');
引入了VueJS之后,我们彻底抛弃了模板引擎库Ext.Xtemplate,我们使用Vue来作为视图层。
var $ = require('jquery'),
R_Vue = require('Vue');
var C = function (wrap, options) {
this.modName = 'contact-page';
this.initVm(wrap);
};
C.prototype.initVm = function (wrap) {
$elem = $(require('./index.tpl'));
this.vm = new R_Vue ({
el: $elem[0],
data: {
modName: this.modName
}
});
$(wrap).html($elem);
};
module.exports = C;
引入vue之后我们不再使用类名来获取元素了,因为Vue提供了元素钩子,用来给元素建立索引,我们通过Vue实例的$els
访问这些元素。
所以我们的模板可以改为:
<div class="{{modName}}">
<div class="{{modName}}_header" v-el:header></div>
<!-- ... -->
</div>
(注:类名中保留modName的写法是为了CSS中编写样式)
上面我们使用v-el语法为header元素建立了索引。
获取元素的方法改写为:
C.prototype.elem = function (selector) {
selector = selector.replace(/-([a-zA-Z])/g, function (match, $1) {
return $1.toUpperCase();
});
return this.vm.$els[selector];
};
获取header元素:
this.elem('header');
不使用CSS选择器(类名或ID)获取元素的好处在于:
* CSS获取元素和JS获取元素的方式彻底分隔开,互不影响(记得有这么个规定:使用js-前缀的类名获取元素,就是为了达到这个目的)
* 元素已经缓存在Vue实例中,我们不需要手动获取元素,也不需要手动进行缓存,由Vue进行统一的模板管理、元素缓存以及元素的注销
VueJS大法好啊!