用类与原型写一个组件(二)——学习笔记
上一篇里,我们为smartList组件写了一个简单的setData方法,运用此原型方法创建了member list和group list两个列表,今天我们继续完善插件功能。
上一篇里,最终效果是这样:
生成了2个一模一样的列表,都有编辑和删除的功能。所以现在我们要改进一下插件,使用户可以定制出不一样的列表(选择性的拥有编辑和删除功能)。
我们想在创建对象的时候就能定制。像下面这样:
1 // 定义一个SmartList类 2 function SmartList(selector, isDeletable) { 3 this.list = []; 4 this.isDeletable = isDeletable; 5 this.element = $(selector) 6 .addClass('panel-group xx-smartlist'); 7 console.log('----创建了一个SmartList对象----'); 8 }
1 $(function () { 2 // 初始化一个SmartList的实例对象 3 var memberList = new xx.SmartList('#memberList',false); 4 var groupList = new xx.SmartList('#groupList',true); 5 // 调用原型方法更新数据 6 memberList.setData(members); 7 groupList.setData(groups); 8 9 });
由于我们是个字符串,所以可以判断一下,不能删除时就返回一个空字符串。
1 this.isDeletable ? '<i data-act="del" class="glyphicon glyphicon-remove pull-right"></i>' : '',
到这里就处理完毕了,看一下效果:
同理可定制编辑,再加一个参数就行。完整代码如下:
smartList.js
1 /** 2 * SmartList 3 * 4 * 数据源:id为必须项 5 */ 6 7 + function (Flexx) { 8 'use strict'; 9 10 // 定义一个SmartList类 11 function SmartList(selector, isDeletable,isEditable) { 12 this.list = []; 13 this.isDeletable = isDeletable; 14 this.isEditable = isEditable; 15 this.element = $(selector) 16 .addClass('panel-group xx-smartlist'); 17 console.log('----创建了一个SmartList对象----'); 18 } 19 20 // 更新列表 21 SmartList.prototype.setData = function (list) { 22 var self = this; 23 // 更新list数据 24 this.list = list; 25 // 清空dom容器 26 this.element.html(''); 27 // 渲染元素 28 $.each(this.list, function (i, data) { 29 self._genItem(data).appendTo(self.element); 30 31 }); 32 33 console.log('-> setData done', this.element); 34 }; 35 36 37 // 生成一个条目 38 SmartList.prototype._genItem = function (data) { 39 var heading = 'heading_' + data.id; 40 var collapse = 'collapse_' + data.id; 41 var html = [ 42 '<div class="panel panel-default" data-id="' + data.id + '">', 43 '<div class="panel-heading" role="tab" id="' + heading + '">', 44 '<h4 class="panel-title">', 45 '<a role="button" data-toggle="collapse" data-parent="#memberList" href="#' + 46 collapse + '" aria-expanded="true" aria-controls="' + collapse + '">', 47 data.title, 48 '</a>', 49 this.isDeletable ? '<i data-act="del" class="glyphicon glyphicon-remove pull-right"></i>' : '', 50 this.isEditable?'<i data-act="edit" class="glyphicon glyphicon-pencil pull-right"></i>':'', 51 '</h4>', 52 '</div>', 53 '<div id="' + collapse + '" class="panel-collapse collapse" role="tabpanel" aria-labelledby="' + heading + '">', 54 '<div class="panel-body">', 55 data.content, 56 '</div>', 57 '</div>', 58 '</div>' 59 ].join(''); 60 61 return $(html); 62 }; 63 64 Flexx.SmartList = SmartList; 65 66 }(window.xx = window.xx || {});
在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',false,true); 32 var groupList = new xx.SmartList('#groupList',true,false); 33 // 调用原型方法更新数据 34 memberList.setData(members); 35 groupList.setData(groups); 36 37 }); 38 39 }();
效果图如下:
但是大家有没有注意到,为了这2个功能的定制,使得创建对象的参数变多了,而且参数还要一一对应,如果前面某个参数不传,还得写个null。
如果参数再多一点呢?谁用谁知道!所以我们把重要的参数selector留着,其他定制的参数全放在一个options中。
1 // 定义一个SmartList类 2 function SmartList(selector, options) { 3 this.list = []; 4 this.options = options; 5 this.element = $(selector) 6 .addClass('panel-group xx-smartlist'); 7 console.log('----创建了一个SmartList对象----'); 8 }
1 this.options.isDeletable ? '<i data-act="del" class="glyphicon glyphicon-remove pull-right"></i>' : '', 2 this.options.isEditable?'<i data-act="edit" class="glyphicon glyphicon-pencil pull-right"></i>':'',
改动以上2处代码后这样调用:
1 $(function () { 2 // 初始化一个SmartList的实例对象 3 var memberList = new xx.SmartList('#memberList',{ 4 isDeletable:false, 5 isEditable:true 6 }); 7 var groupList = new xx.SmartList('#groupList',{ 8 isDeletable:true, 9 isEditable:false 10 }); 11 // 调用原型方法更新数据 12 memberList.setData(members); 13 groupList.setData(groups); 14 15 });
可以达到同样的效果。但是这样有一个问题,不知道大家发现没有:如果传入参数,就能定制,但不传参数就报错了。
那么,不传第二个参数的时候 options 是什么?当然是undefined啦!所以,为了解决这个问题,我们可以给这2个参数设一个默认值,在使用的时候,如果没有参数传进来,就使用默认值。
怎么设置这个默认值呢?方法有很多,我们从易到难一一道来(代码截图注意看行号,就知道改动了哪里)。
一:判断options里面有没有对应属性:
二:判断对应的属性是否被赋值:
三:判断对应的属性值是否是布尔值(判断更精确):
但以上方法代码还是太多了,下面用三目来判断:
上面的两个感叹号,用来将某个值转化为bool,因为isDeletable和isEditable有可能未定义,一个!是取反,只要有值就是false,再来一个,只要有值就是true了。
到这里,得到最佳解决方案了吗?
NO,我们虽然给options设置了默认值,但是,如果属性再继续增加呢?所以我们要把属性追加到options,而不是赋值给options。故最终解决办法如下:
如果没有传options,也不会未定义,如果传入了,则覆盖默认属性。这还有一个好处就是让属性名显示出来,让用户知道有哪些属性可以设置。
现在可以控制相关功能的按钮是否在页面显示了,下一步就是写一个remove事件并绑定。
1 SmartList.prototype.remove = function(id){ 2 var self = this; 3 4 $.each(this.list, function (i, val) { 5 if (val.id === id) { 6 self.list.splice(i, 1); 7 self.element.find('[data-id="' + id + '"]').remove(); 8 return false; 9 } 10 }); 11 }
1 // 生成一个条目 2 SmartList.prototype._genItem = function (data) { 3 var self = this; 4 var heading = 'heading_' + data.id; 5 var collapse = 'collapse_' + data.id; 6 var html = [ 7 '<div class="panel panel-default" data-id="' + data.id + '">', 8 '<div class="panel-heading" role="tab" id="' + heading + '">', 9 '<h4 class="panel-title">', 10 '<a role="button" data-toggle="collapse" data-parent="#memberList" href="#' + 11 collapse + '" aria-expanded="true" aria-controls="' + collapse + '">', 12 data.title, 13 '</a>', 14 this.options.isDeletable ? '<i data-act="del" class="glyphicon glyphicon-remove pull-right"></i>' : '', 15 this.options.isEditable?'<i data-act="edit" class="glyphicon glyphicon-pencil pull-right"></i>':'', 16 '</h4>', 17 '</div>', 18 '<div id="' + collapse + '" class="panel-collapse collapse" role="tabpanel" aria-labelledby="' + heading + '">', 19 '<div class="panel-body">', 20 data.content, 21 '</div>', 22 '</div>', 23 '</div>' 24 ].join(''); 25 var item = $(html); 26 27 if(this.options.isDeletable){ 28 item.find('[data-act="del"]').click(function(){ 29 self.remove(data.id); 30 }); 31 } 32 33 return item; 34 };
现在,我们可以让用户定制列表是否拥有编辑或删除功能了。完整代码如下。
index.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>
smartList.js:
1 /** 2 * SmartList 3 * 4 * 数据源:id为必须项 5 */ 6 7 + function (Flexx) { 8 'use strict'; 9 10 // 定义一个SmartList类 11 function SmartList(selector, options) { 12 this.list = []; 13 //覆盖默认属性 14 this.options = $.extend({ 15 //默认属性 16 isDeletable: true,// 设置是否有行删除按钮 17 isEditable: true// 设置是否有行编辑按钮 18 }, options); 19 20 this.element = $(selector) 21 .addClass('panel-group xx-smartlist'); 22 console.log('----创建了一个SmartList对象----'); 23 } 24 25 // 更新列表 26 SmartList.prototype.setData = function (list) { 27 var self = this; 28 // 更新list数据 29 this.list = list; 30 // 清空dom容器 31 this.element.html(''); 32 // 渲染元素 33 $.each(this.list, function (i, data) { 34 self._genItem(data).appendTo(self.element); 35 36 }); 37 38 console.log('-> setData done', this.element); 39 }; 40 41 SmartList.prototype.remove = function(id){ 42 var self = this; 43 44 $.each(this.list, function (i, val) { 45 if (val.id === id) { 46 self.list.splice(i, 1); 47 self.element.find('[data-id="' + id + '"]').remove(); 48 return false; 49 } 50 }); 51 } 52 53 // 生成一个条目 54 SmartList.prototype._genItem = function (data) { 55 var self = this; 56 var heading = 'heading_' + data.id; 57 var collapse = 'collapse_' + data.id; 58 var html = [ 59 '<div class="panel panel-default" data-id="' + data.id + '">', 60 '<div class="panel-heading" role="tab" id="' + heading + '">', 61 '<h4 class="panel-title">', 62 '<a role="button" data-toggle="collapse" data-parent="#memberList" href="#' + 63 collapse + '" aria-expanded="true" aria-controls="' + collapse + '">', 64 data.title, 65 '</a>', 66 this.options.isDeletable ? '<i data-act="del" class="glyphicon glyphicon-remove pull-right"></i>' : '', 67 this.options.isEditable?'<i data-act="edit" class="glyphicon glyphicon-pencil pull-right"></i>':'', 68 '</h4>', 69 '</div>', 70 '<div id="' + collapse + '" class="panel-collapse collapse" role="tabpanel" aria-labelledby="' + heading + '">', 71 '<div class="panel-body">', 72 data.content, 73 '</div>', 74 '</div>', 75 '</div>' 76 ].join(''); 77 var item = $(html); 78 79 if(this.options.isDeletable){ 80 item.find('[data-act="del"]').click(function(){ 81 self.remove(data.id); 82 }); 83 } 84 85 return item; 86 }; 87 88 Flexx.SmartList = SmartList; 89 90 }(window.xx = window.xx || {});
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 isDeletable:false, 33 isEditable:true 34 }); 35 var groupList = new xx.SmartList('#groupList'); 36 // 调用原型方法更新数据 37 memberList.setData(members); 38 groupList.setData(groups); 39 40 }); 41 42 }(); 43 aaa
现在做个总结:
1、注意性能优化。
最后一步绑定事件为什么还需要判断?为false的时候根本不会显示在页面呀!但是,不显示,还去选择干什么?记住:性能优化就是从代码的点点滴滴累积出来的。
2、两个js潜规则。
一般只要是bool值我们都用is开头(比如smartList.js中的isDeletable和isEditable);
数组我们习惯用复数(比如main.js中的members),但如果你叫xxxList 就不用了。
潜规则能让你快速读懂别人的代码,反之亦然,这些都是一些良好习惯,从字面上让人理解作者意图。
最后,感谢大神CX的讲解。