引子

博客断了一段时间,不是不写,一是没时间,二是觉得自己沉淀不够,经过一段时间的学习和实战,今天来总结下一个老生常谈的东西: 代码复用。

为何复用

JS门槛低,故很多人以为写几个特效就会JS,其实真正混前端的人都知道,JS写的好和写的不好差距是很大的,前段时间看到一个前端为了实现一个浮层的显示与隐藏花了100多行代码,去遍历各个子元素,挨个去设置display。还有一个很常见的场景,为了给一个UL下面的n个li绑定事件,遂遍历所有的li去bind,这些例子在日常的开发中,比比皆是,为什么有这种情况,一和业务有关,而他们压根不知道有更好的方式。造成这张现象的深层次原因无非有两种,一是他们无意去优化自己的代码,完成即好,还有一种便是没有一个好的指引,想优化却不知从何开始,时间一久,随着代码量的增加,大神也救不了你。

前段时间负责Pad端的局部代码重构,说的好听叫重构,其实就是代码组件化,由于本鸟资历尚浅,加上对一些东西的认知度也不够,做的过程中看了很多的书,但是还是觉得把握不了精髓,但是期间学到的一些比较好,可以对大家代码结构化提供一些帮助的手段。故本鸟在这里献丑一二,希望起到一个抛砖引玉的作用。期间不仅是谈代码复用,在我眼中代码的优化也是取决于代码复用是否有意义的关键指标,所以文中也会说到如何进行代码优化的几个常用手段。

罗嗦了这么多,下面切入正题,本段的标题叫 “为何复用”,相信大家看了我举得2个例子,也能猜到一二,我的理解就是优化自己的代码,说到底还是为了更加方便以后的开发。比如你写的一段为了实现验证的代码,如果不考虑代码复用,你每次都写一遍,造成代码冗余不说,光是看到那一摸一样的代码,你自己不难受吗,至少我是受不了。还有在很多场景下,大家都知道对DOM元素的操作很浪费性能,但是还是一遍一遍的去轮询,为了取某个index去遍历整个数组,为了方便图省事,定义n个全局变量,自己都不清楚prototype就给原生对象绑定n个他自己都不知道的方法。。等等。这些常识性的错误,在代码复用之前都应该极力去避免。我的观点是,好的代码才能去复用,不是随便写几个恶心的代码就跑去给别人用,这是对你自己代码的侮辱,更深的是对JS的不尊重。吐槽了这么多,舒服多了。。

简而言之,代码复用是对高强度代码的整理,减少重复开发的时间。

常用代码复用的手段

本鸟下面总结了下自己知道的几种方式,没有好坏之分,这要结合业务场景和你喜欢的方式,一句话 “适合你的才是最好的”

普通继承

在强类型语言中,实现代码复用很难绕过继承,而JS的强大之处在于模拟,故你们能做的我也能做,而在JS中,代码复用使用继承也是可行的。

首先,先上一段最基本的继承的实现代码。

  1. ```javascript
  2. function Animal(name){
  3.  
  4. }
  5. Animal.prototype.run = function(){
  6. console.log(this.name + " is running!!!");
  7. }
  8. function Cat(name){
  9. this.name = name;
  10. }
  11. Cat.prototype = new Animal();
  12. var tom = new Cat("tom");
  13. tom.run(); // tom is running
  14. ```

上面的继承,说的直白点就是把Cat的原型重定义了,链接了原型链,以至于Cat和Animal链接起来了,故在Animal上定义的方法和属性,在Cat上也能通过委托访问到。这是一种最简单的继承,但是也有他的缺陷,一个子构造函数并不是总是希望继承全部的父构造函数的所有属性和方法。这样就违背了我们代码复用的准则,尽可能的减少代码的冗余。

借用式继承

为了解决普通继承的缺陷,我们可以在子构造函数中去借用父构造函数去构造本地的this,而无需去继承那些父构造函数原型上的方法。上代码:

  1. ```javascript
  2. function Animal(name){
  3. this.name = name;
  4. }
  5. Animal.prototype.run = function(){
  6. console.log(this.name + " is running!!!");
  7. }
  8. function Cat(name){
  9. Animal.call(this, name);
  10. }
  11. var tom = new Cat("tom");
  12. console.log(tom.name); // tom
  13. tom.run(); //undefined is not a function
  14. ```

通过上面的代码,我们发现Cat只是简单的使用call调用了父构造方法,从而达到无需继承Animal的prototype上的run方法。甚至我们可以使用借用式继承去实现多重继承,上代码:

  1. ```javascript
  2. function Person(){
  3. this.legs = 2;
  4. this.hands = 2;
  5. }
  6. function Man(){
  7. this.sex = "Male";
  8. }
  9. function Guy(){
  10. Person.call(this);
  11. Man.call(this);
  12. }
  13. var wr = new Guy();
  14. console.dir(wr);
  15. ```

我们发现Guy构造函数分别借用了Person和Man两个构造函数,从而实现了多继承,这和Java不支持多继承(普通类)就有了区别了,JS确实是无所不能啊。

借用式的缺点

借用式的优点在于可以获得父类自身属性的真实副本(包括定义在this的方法),而且不会存在覆盖父类属性的风险。缺点也很明显,无法继承父类的原型属性。

但是我们的JS多么强大,当然有解决方案,我们可以手动的给子类指定原型赋为父类,上代码:

  1. ```javascript
  2. function Animal(name){
  3. this.name = name;
  4. }
  5. Animal.prototype.run = function(){
  6. console.log(this.name + " running!");
  7. }
  8. function Cat(name){
  9. Animal.call(this,name);
  10. }
  11. Cat.prototype = new Animal();
  12.  
  13. var tom = new Cat("tom");
  14. console.log(tom.name); //tom
  15. console.log(tom.run()); //tom running!
  16. ```

以上代码,我们通过重新设置了Cat的prototype去获得了父类的原型方法,但是这样做也有缺陷,父类的构造函数被调用了2次,因此导致了其效率低下,而且如果在父类和子类中存在同名属性,子类会重复继承2次

共享原型

这种方式是通过把父类和子类的prototype指向同一个原型,所以父类和所有的子类都可以访问到父类的所有原型上的方法,这样做的前提是把父类所有可继承的方法都写在原型中,代码如下所示:

  1. ```javascript
  2. function Animal(name){
  3. this.name = name;
  4. }
  5. Animal.prototype = {
  6. constructor : Animal,
  7. sayName : function(){
  8. console.log("my name is " + this.name);
  9. }
  10. }
  11. function Cat(name){
  12. this.name = name;
  13. }
  14. Cat.prototype = Animal.prototype;
  15. var tom = new Cat("tom");
  16. tom.sayName(); //my name is tom
  17. console.dir(Animal.prototype); //Animal
  18. console.dir(Cat.prototype); //Animal
  19. ```

这样的做法确实帮Cat继承了Animal上的方法,但是也是有缺点的,如果父类的某个子类去修改了原型那么将会修改所有的这条原型链上的所有的类,而且我们发现这样做只是共享了原型,我们无法去继承父类通过this设置的属性和方法,所以我们可以使用下面的方式

临时构造函数

简而言子,这种方式就是在父类的原型和子类的原型间设置一个缓冲区,断开了父类于子类之间的直接链接关系,从而解决了共享原型方式带来的问题,同时有可以继续享受原型链带来的好处,代码如下:

  1. ```javascript
  2. var inherit = (function(){
  3. var buffer = function(){};
  4. return function(child, parent){
  5. buffer.prototype = parent.prototype;
  6. child.prototype = new buffer();
  7. child.prototype.constructor = child;
  8. }
  9. }());
  10. ```

上面代码中,我们设置了一个buffer,去充当child和parent之间的缓冲区,这样的化我们每次仅需创建一次临时构造函数,这里有个小技巧,为了避免重复创建buffer我们吧buffer放在闭包中存储。

小结

在上面我们总结的代码优化方式的几种方式,在下一篇,本鸟还会梳理下一些其他的方式和一些有趣的小技巧,总算书没白读。。如在文中发现错误,欢迎指正。

初涉JavaScript模式 (13) : 代码复用 【上】的更多相关文章

  1. javascript 模式(1)——代码复用

    程序的开发离不开代码的复用,通过代码复用可以减少开发和维护成本,在谈及代码复用的时候,会首先想到继承性,但继承并不是解决代码复用的唯一方式,还有其他的复用模式比如对象组合.本节将会讲解多种继承模式以实 ...

  2. 初涉JavaScript模式系列 阶段总结及规划

    总结 不知不觉写初涉JavaScript模式系列已经半个月了,没想到把一个个小点进行放大,竟然可以发现这么多东西. 期间生怕对JS的理解不到位而误导各位,读了很多书(个人感觉JS是最难的oo语言),也 ...

  3. 【读书笔记】读《JavaScript模式》 - 函数复用模式之现代继承模式

    现代继承模式可表述为:其他任何不需要以类的方式考虑得模式. 现代继承方式#1 —— 原型继承之无类继承模式 function object(o) { function F() {}; F.protot ...

  4. 【读书笔记】读《JavaScript模式》 - 函数复用模式之类式继承模式

    实现类式继承的目标是通过构造函数Child()获取来自于另外一个构造函数Parent()的属性,从而创建对象. 1.类式继承模式#1 —— 默认方式(原型指向父函数实例) function Paren ...

  5. 初涉JavaScript模式 (12) : 沙箱模式

    引子 上一篇说了模块模式,而对于其中的命名空间模式其实也是有着一些问题,比如每添加一个模块或则深入叠加都会导致长命名,并且对于多个库的不同版本同时运行,一不小心就会污染全局标识,而这两天也发现了JSe ...

  6. 初涉JavaScript模式 (11) : 模块模式

    引子 这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中去,在北川的提醒下,我才发觉这是非常不严谨的,于是我把这些内容拎出来,这就是这篇的由来. 什么是模块模式 在 ...

  7. 初涉JavaScript模式 (10) : 函数 【进阶用法】

    写在前面 不知不觉写到第10篇了.这篇写起来很忐忑,终于和高级搭上边了(呵呵),这篇我们 主要 说一下 JS 方法的部分高级用法(我知道的),笔者水平有限,难免有错.废话不多少,进入正文. 初始化 我 ...

  8. 初涉JavaScript模式 (9) : 函数 【常用方式】

    回调模式 上一篇,对JavaScript函数进行了大体的介绍,这一篇对一些在工作中经常遇到的情况进行扩展. 在工作中,我们经常遇到很多需求,比如现在有一个需求: 一栋10层的大楼,当我们在坐电梯时,电 ...

  9. 初涉JavaScript模式 (7) : 原型模式 【三】

    组合使用构造函数模式和原型模式 上篇,我们提到了原型模式的缺点,就是每个实例不能拥有自己的属性,因为纯原型模式所有的属性都是公开给每个实例的,故我们可以组合使用构造函数模式和原型模式.构造函数用来定义 ...

随机推荐

  1. HDU Collect More Jewels 1044

    BFS + 状态压缩 险过 这个并不是最好的算法 但是写起来比较简单 , 可以AC,但是耗时比较多 下面是代码 就不多说了 #include <cstdio> #include <c ...

  2. 【最短路】FOJ 2243 Daxia like uber

    题目链接: http://acm.fzu.edu.cn/problem.php?pid=2243 题目大意: 给一张N个点M条边的有向图,从s出发,把在x1的人送到y1,在x2的人送到y2用的最短距离 ...

  3. 《Mathematical Olympiad——数论》——整除

    数论这个东西吧,虽说也是高中IMOer玩的数学游戏,颇具美学性的证明比较多.就目前所知,它在算法里是一些加密技术的基础,不多言,开始具体题目的分析. 问题一:已知数列{an},且a0 = 2 , a1 ...

  4. 对Spring的一些理解

    最近在复习一些关于框架的概念性问题,主要是为了最近的面试,怕被问到这些概念性的问题.不过在真正做开发的时候还是要好好理解这些框架的基本原理,以及它们的工作流程.这样,我们才能更好的使用这些框架.下面就 ...

  5. STRUCTS 2 LABLE

    {LJ?Dragon}[标题]structs2标签的作用 {LJ?Dragon}[Diary]2017年,愉快的开始:离别不一定总伤感,虽然只是安慰着自己......... 问与答 问题 在Strut ...

  6. 如何看懂Code128条形码

    条形码就是我们看到的商品上有的那些竖条条. 要不是项目上用到这个或许我一辈子也不会对那个感兴趣. 条形码其实是分成很多类的,虽然他们看起来都差不多…… 常见的条形码的码制被称为39码.128码.417 ...

  7. Oracle内存结构(之三)

    [深入解析--eygle] 学习笔记 1.2.3 PGA的管理: sys@felix SQL>show parameter area_size NAME TYPE VALUE --------- ...

  8. Swift: 继承

    为了在属性值改变的时候获得通知,类可以为继承的属性添加属性观察者.属性观察者可以添加到任何属性上,不管这个属性原来是存储属性还是计算属性. Swift中的类没有一个统一的基类. 为了讲明白继承,我们先 ...

  9. discuz! X3 门户文章添加字段

    1. 首先需要去数据表里[llgp_portal_article_title]手动添加需要添加的字段. (注意: 数据表前缀依据自己的设置而定) 2. 修改模版template\default\por ...

  10. 2015 UESTC Training for Search Algorithm & String - M - Palindromic String【Manacher回文串】

    O(n)的复杂度求回文串:Manacher算法 定义一个回文值,字符串S是K重回文串,当且仅当S是回文串,且其长度为⌊N/2⌋的前缀和长度为⌊N/2⌋的后缀是K−1重回文串 现在给一个2*10^6长度 ...