@lxjwlt
2016-07-07T12:09:03.000000Z
字数 2627
阅读 2561
博文
在项目开发中我多次遇到由于命名冲突导致页面样式混乱,甚至跨模块获取同名元素导致功能失效的情况。对于这种情况,我之前的一篇文章中介绍过如何使用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大法好啊!