用类与原型写一个组件(一)——学习笔记
前两篇博文里学习了类与原型的相关知识,现在就理论结合实际,看看如何用类和原型来写一个插件。
首先写好html页面如下,主要是引入jquery和bs,页面中放了一个id为memberList的div,用来放我们待会使用类和原型生成的item。
1 <html> 2 <head> 3 <meta charset="UTF-8"> 4 <title>Flexx</title> 5 <link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet"> 6 <link rel="stylesheet" href="./css/main.css"> 7 <script src="http://apps.bdimg.com/libs/jquery/2.0.0/jquery.min.js"></script> 8 <script src="http://apps.bdimg.com/libs/bootstrap/3.3.0/js/bootstrap.min.js"></script> 9 <script src="./js/flexx.js"></script> 10 <script src="./js/smartList.js"></script> 11 <script src="./js/main.js"></script> 12 </head> 13 <body> 14 <div class="container"> 15 <h1>Flexx Library</h1> 16 <hr> 17 <div class="row"> 18 <div class="col-xs-6"> 19 <div id="memberList"></div> 20 </div> 21 </div> 22 </div> 23 </body> 24 </html>
下面我们就创建一个原型,并为其初始化一个实例。
下面我们复习一下上一篇中说过的set方法,用来更新列表。
console可以看到,数据更新了,但还只是在console中看到,要怎么反应到页面中呢?
为此,我们需要写一个原型方法用于生成节点(我们还会用到bs3的Collapse 插件),将生成的节点添加到DOM树上,之所以要写成原型的方法,是因为这个方法重用性高。
1 // 生成一个条目 2 SmartList.prototype._genItem = function (data) { 3 var heading = 'heading_' + data.id; 4 var collapse = 'collapse_' + data.id; 5 var html = [ 6 '<div class="panel panel-default" data-id="' + data.id + '">', 7 '<div class="panel-heading" role="tab" id="' + heading + '">', 8 '<h4 class="panel-title">', 9 '<a role="button" data-toggle="collapse" data-parent="#memberList" href="#' + 10 collapse + '" aria-expanded="true" aria-controls="' + collapse + '">', 11 data.title, 12 '</a>', 13 '<i data-act="del" class="glyphicon glyphicon-remove pull-right"></i>', 14 '<i data-act="edit" class="glyphicon glyphicon-pencil pull-right"></i>', 15 '</h4>', 16 '</div>', 17 '<div id="' + collapse + '" class="panel-collapse collapse" role="tabpanel" aria-labelledby="' + heading + '">', 18 '<div class="panel-body">', 19 data.content, 20 '</div>', 21 '</div>', 22 '</div>' 23 ].join(''); 24 25 return $(html); 26 };
大家可能注意到了,这个方法名称是以下划线开头,为什么和其它原型方法不同呢?是因为这个方法是打算封装在插件内部,不打算对用户可见的。详细缘由后文再说。
那么,现在就可以使用原型的_genItem方法来生成节点,从而向页面添加节点了。
但是页面报错了,是为什么呢?我们注意到渲染元素的时候用了this,是不是这个this导致的呢?不用猜,看不出来的话,可以打印看看this到底是什么。
现在可以清楚的看到2个this的不同,而我们要用_genItem方法的对象应该是SmartList对象,怎么把each里面的this变一下呢?当然,变是变不了的,但是我们可以想办法在each里面用SmartList对象。方法如下,将SmartList对象用变量存起来,在each里面要用的时候就用这个变量。
1 // 更新列表 2 SmartList.prototype.setData = function (list) { 3 var self = this; 4 // 更新list数据 5 this.list = list; 6 // 渲染元素 7 $.each(this.list, function (i, data) { 8 self._genItem(data).appendTo(self.element); 9 }); 10 };
现在我们setData方法也有了,循环传进来的list数据,一个一个生成节点后添加到DOM里(SmartList的element属性用来表示要添加到那个DOM节点里)。那么现在代码和效果如下:
效果图看起来是不是和bs的Collapse 插件示范的有点不一样呢?可以去参考一下bs的官方文档,原来是我们的id="memberList"的div少了一个class,但是我们又想让别人使用插件的时候页面尽可能简单,所以不把这个class写进Html里,于是,可以在原型里面加上。
1 // 定义一个SmartList类 2 function SmartList(selector) { 3 this.list = []; 4 this.element = $(selector).addClass('panel-group xx-smartlist'); 5 6 console.log('----创建了一个SmartList对象----'); 7 }
同时,还可以加上我们插件的自定义样式xx-smartlist。
现在,已经可以通过原型来生成一个member list 了,那么同样,可以利用该原型生成很多很多类似的List,比如现在我们就生成一个group list。
现在html里放一个id="groupList"的div,再在main.js里面初始化一个groupList的实例,使它可以调用原型里的方法,接下来调用一下原型里setData方法,就可以批量生产list了。
html代码如下:
1 <html> 2 <head> 3 <meta charset="UTF-8"> 4 <title>Flexx</title> 5 <link href="http://apps.bdimg.com/libs/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet"> 6 <link rel="stylesheet" href="./css/main.css"> 7 <script src="http://apps.bdimg.com/libs/jquery/2.0.0/jquery.min.js"></script> 8 <script src="http://apps.bdimg.com/libs/bootstrap/3.3.0/js/bootstrap.min.js"></script> 9 <script src="./js/flexx.js"></script> 10 <script src="./js/smartList.js"></script> 11 <script src="./js/main.js"></script> 12 </head> 13 <body> 14 <div class="container"> 15 <h1>Flexx Library</h1> 16 <hr> 17 <div class="row"> 18 <div class="col-xs-6"> 19 <div id="memberList"></div> 20 </div> 21 <div class="col-xs-6"> 22 <div id="groupList"></div> 23 </div> 24 </div> 25 </div> 26 </body> 27 </html>
main.js代码如下:
1 + function () { 2 'use strict'; 3 4 // 创建一个用来测试的模拟数据 5 var members = [{ 6 id: 0, 7 title: 'Brandon', 8 content: 'hello' 9 }, { 10 id: 1, 11 title: 'Kim', 12 content: 'hi' 13 }, { 14 id: 2, 15 title: 'Bunny', 16 content: 'hi' 17 }, { 18 id: 3, 19 title: 'Lovelyun', 20 content: 'hi' 21 }]; 22 23 var groups = [{ 24 id: 1, 25 title: 'Web Dev', 26 content: 'hello again' 27 }]; 28 29 $(function () { 30 // 初始化一个SmartList的实例对象 31 var memberList = new xx.SmartList('#memberList'); 32 var groupList = new xx.SmartList('#groupList'); 33 memberList.setData(members); 34 groupList.setData(groups); 35 }); 36 37 }();
smartList.js不需要做任何改动。是不是方便快捷,实现同样的效果,避免了大段的重复代码?现在插件虽然还只有获取列表的功能,但是看了这一个功能的实现,是不是对其它功能的实现有所启发了呢?
现在做个总结:
1、注意this指向。
就近原则,this指向它作用域的方法(函数)所属的对象,一个对象的方法上所有的函数中的this,都指向这个对象,即这个方法的主人。一个全局函数的this指向window,因为所有全局函数都是window上的一个方法。
2、注意性能优化。
选择器从dom中把一个元素选出来,是有成本的,有个查找的过程。所以要把使用次数较多的节点用变量存起来。
3、注意js执行顺序。
js中涉及DOM操作的,要等到document ready后再执行,否则在执行的时候DOM还没有加载。
4、一个js潜规则。
一般已下划线开头的方法,表示仅在类的方法内部使用的方法,不对外暴露接口。即带_开头的方法,不允许也不建议在对象上使用,仅是类内部使用,私有方法。(因为逻辑要重用,所以必须把一段段逻辑拆分成一个个方法,通过名称很清晰的能了解方法干什么用的,我又需要提供什么方法出去,哪些不提供,仅内部调用。你若不区分,会造成别人使用上的困惑,所以养成习惯)
5、注意提前退出for循环的方法。
原生js用 break退出for循环,jq里面用return false来退出循环(这个要记着哟,光return是没用的,要return false才行)。
最后,感谢大神CX的讲解。