js组件

比如我们要实现这样一个组件,就是一个输入框里面字数的计数。

为了更清楚的演示,下面全部使用jQuery作为基础语言库。

最简陋的写法

<!DOCTYPE html>
<html>
  <head>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  </head>
  <body>
    <input type="text" id="J_input">
    <script>
      $(function(){
        var input = $('#J_input');
        //用来获取字数
        function getNum(){
          return input.val().length;
        }
        function render() {
          var num = getNum();

          //没有字数的容器就新建一个
          if ($('#J_input_count').length == 0) {
            input.after('<span id="J_input_count"></span>');
          };

          $('#J_input_count').html(num+'个字');
        }
          //监听事件
        input.on('keyup',function(){
          render();
        });

        //初始化,第一次渲染
        render();
      })
    </script>
  </body>
</html>

以上是一串面向过程的写法。各种变量混乱,没有很好的隔离作用域,当页面变的复杂的时候,会很难去维护。
于是我们会想到命名空间。就是把所以东西放在一个对象里。我们要调用的时候通过这个对象调用。用来实现全局变量只有一个。其他的变量都是这个对象内部的。

var textCount = {
  input:null,
  init:function(config){
    this.input = $(config.id); //  textCount.init({id:'#J_input'})的调用方式使得this指向textCount。
    this.bind();
    //这边范围对应的对象,可以实现链式调用
    return this;
  },
  bind:function(){
    var self = this;
    this.input.on('keyup',function(){
 //这里的this作为inputCount对象内bind方法的内部函数,在这个内部函数里this是
 //指向全局,故将之前的this先存下来,便于在函数内部使用。
 //就像setTimeout函数的参数执行this也指向全局一样
      self.render();
    });
  },
  getNum:function(){
    return this.input.val().length;
  },
  //渲染元素
  render:function(){
    var num = this.getNum();

    if ($('#J_input_count').length == 0) {
      this.input.after('<span id="J_input_count"></span>');
    };

    $('#J_input_count').html(num+'个字');
  }
}

$(function() {
  //在domready后调用
  textCount.init({id:'#J_input'}).render();  //链式调用,先初始化胡渲染。
})

上面的代码getNum,bind虽说没有污染全局变量。但是其他代码可以很随意的改动这些。本应该内部私有的就不要被外面访问到。当代码量特别特别多的时候,很容易出现变量重复,或被修改的问题。我们接着改动
利用匿名函数自执行返回值的形式形成的闭包

var TextCount = (function(){
  //私有方法,外面将访问不到
  var _bind = function(that){
    that.input.on('keyup',function(){
      that.render();
    });
  }

  var _getNum = function(that){
    return that.input.val().length;
  }

  var TextCountFun = function(config){

  }

  TextCountFun.prototype.init = function(config) {
    this.input = $(config.id);
    _bind(this);

    return this;
  };

  TextCountFun.prototype.render = function() {
    var num = _getNum(this);

    if ($('#J_input_count').length == 0) {
      this.input.after('<span id="J_input_count"></span>');
    };

    $('#J_input_count').html(num+'个字');
  };
  //返回构造函数
  return TextCountFun;

})();

$(function() {
  new TextCount().init({id:'#J_input'}).render();
})

这种写法,把所有的东西都包在了一个自动执行的闭包里面,所以不会受到外面的影响,并且只对外公开了TextCountFun构造函数,生成的对象只能访问到init,render方法。但是为什么要返回一个构造函数。直接返回render函数不可以吗。render函数里进行init初始化操作。这样的做法把init融合在render的做法就不推荐。函数的功能单一性被破坏。造成代码耦合。

面向对象

但是呢,当一个页面特别复杂,当我们需要的组件越来越多,当我们需要做一套组件。仅仅用这个就不行了。首先的问题就是,这种写法太灵活了,写单个组件还可以。如果我们需要做一套风格相近的组件,而且是多个人同时在写。那真的是噩梦。

在编程的圈子里,面向对象一直是被认为最佳的编写代码方式。比如java,就是因为把面向对象发挥到了极致,所以多个人写出来的代码都很接近,维护也很方便。js中没有类Class。实现继承是靠原型链。我们可以用原型链封装出一个类似的Class。真正的oo语言,实现继承是靠复制。传统的类被实例化时,它的行为会被复制到实例中。类被继承时,行为也会被复制到子类中。js通过混入mixin来模仿类。并且实现多继承。
mixin

(function(){
 //initializing是为了解决我们之前说的继承导致原型有多余参数的问题。
 //当我们直接将父类的实例赋值给子类原型时。是会调用一次父类的构造函数的。所以这边会把真正的构造流程放到init函数里面,通过initializing来表示当前是不是处于构造原型阶段,
 //为true的话就不会调用init。
 //fnTest用来匹配代码里面有没有使用super关键字。对于一些浏览器`function(){xyz;}`会生成个字符串,并且会把里面的代码弄出来,有的浏览器就不会。
 //`/xyz/.test(function(){xyz;})`为true代表浏览器支持看到函数的内部代码,所以用`/\b_super\b/`来匹配。如果不行,就不管三七二十一。
 //所有的函数都算有super关键字,于是就是个必定匹配的正则。
 var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

 // The base Class implementation (does nothing)
 // 超级父类
 this.Class = function(){};

 // Create a new Class that inherits from this class
 // 生成一个类,这个类会具有extend方法用于继续继承下去
 Class.extend = function(prop) {// 这里的extend很像mixin
   //保留当前类,一般是父类的原型
   //this指向父类。初次时指向Class超级父类
   var _super = this.prototype;
  
   // Instantiate a base class (but only create the instance,
   // don't run the init constructor)
   //开关 用来使原型赋值时不调用真正的构成流程。就是执行var p = Class.extend()时候不执行init。而是执行 new p()和Person.extend()时候执行init。
   initializing = true;
   var prototype = new this();
   initializing = false;
  
   // Copy the properties over onto the new prototype
   for (var name in prop) {
     // Check if we're overwriting an existing function
     //这边其实就是很简单的将prop的属性混入到子类的原型上。如果是函数我们就要做一些特殊处理
     prototype[name] = typeof prop[name] == "function" &&
       typeof _super[name] == "function" && fnTest.test(prop[name]) ?
       (function(name, fn){
         //通过闭包,返回一个新的操作函数.在外面包一层,这样我们可以做些额外的处理
         return function() {
           var tmp = this._super;// 这个this是实例化对象的。var p = new Person(true);就是p的
          
           // Add a new ._super() method that is the same method
           // but on the super-class
           // 调用一个函数时,会给this注入一个_super方法用来调用父类的同名方法
           this._super = _super[name];
          
           // The method only need to be bound temporarily, so we
           // remove it when we're done executing
           //因为上面的赋值,是的这边的fn里面可以通过_super调用到父类同名方法
           var ret = fn.apply(this, arguments);  
           //离开时 保存现场环境,恢复值。
           this._super = tmp;
          
           return ret;
         };
       })(name, prop[name]) :
       prop[name];
   }
  
   // 这边是返回的类,其实就是我们返回的子类
   function Class1() {
     // All construction is actually done in the init method
     if ( !initializing && this.init )
       this.init.apply(this, arguments);
   }
  
   // 赋值原型链,完成继承
   Class1.prototype = prototype;
  
   // 改变constructor引用
   Class1.prototype.constructor = Class1;

   // 为子类也添加extend方法
   Class1.extend = arguments.callee;
  
   return Class1;
 };
})();

使用方法

var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  },
  dance: function(){
    return this.dancing;
  }
});
 
var Ninja = Person.extend({
  init: function(){
    this._super( false );
  },
  dance: function(){
    // Call the inherited version of dance()
    return this._super();
  },
  swingSword: function(){
    return true;
  }
});
 
var p = new Person(true);
p.dance(); // => true
 
var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true
 
// Should all be true
p instanceof Person && p instanceof Class &&
n instanceof Ninja && n instanceof Person && n instanceof Class

javascript oo实现
有了这个类的扩展,我们可以这么编写代码了:

var TextCount = Class.extend({
  init:function(config){
    this.input = $(config.id);
    this._bind();
    this.render();
  },
  render:function() {
    var num = this._getNum();

    if ($('#J_input_count').length == 0) {
      this.input.after('<span id="J_input_count"></span>');
    };

    $('#J_input_count').html(num+'个字');

  },
  _getNum:function(){
    return this.input.val().length;
  },
  _bind:function(){
    var self = this;
    self.input.on('keyup',function(){
      self.render();
    });
  }
})

$(function() {
  new TextCount({
    id:"#J_input"
  });
})

抽象出base

可以看到,我们的组件有些方法,是大部分组件都会有的。

  • 比如init用来初始化属性。
  • 比如render用来处理渲染的逻辑。
  • 比如bind用来处理事件的绑定。
    当然这也是一种约定俗成的规范了。如果大家全部按照这种风格来写代码,开发大规模组件库就变得更加规范,相互之间配合也更容易。
    这个时候面向对象的好处就来了,我们抽象出一个Base类。其他组件编写时都继承它。
var Base = Class.extend({
  init:function(config){
    //自动保存配置项
    this.__config = config
    this.bind()
    this.render()
  },
  //可以使用get来获取配置项
  get:function(key){
    return this.__config[key]
  },
  //可以使用set来设置配置项
  set:function(key,value){
    this.__config[key] = value
  },
  bind:function(){
  },
  render:function() {

  },
  //定义销毁的方法,一些收尾工作都应该在这里
  destroy:function(){

  }
})

base类主要把组件的一般性内容都提取了出来,这样我们编写组件时可以直接继承base类,覆盖里面的bind和render方法。

于是我们可以这么写代码:

var TextCount = Base.extend({
  _getNum:function(){
    return this.get('input').val().length;
  },
  bind:function(){
    var self = this;
    self.get('input').on('keyup',function(){
      self.render();
    });
  },
  render:function() {
    var num = this._getNum();

    if ($('#J_input_count').length == 0) {
      this.get('input').after('<span id="J_input_count"></span>');
    };

    $('#J_input_count').html(num+'个字');

  }
})

$(function() {
  new TextCount({
  //这边直接传input的节点了,因为属性的赋值都是自动的。
    input:$("#J_input")
  });
})

javascript组件化

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 本文章是我最近在公司的一场内部分享的内容。我有个习惯就是每次分享都会先将要分享的内容写成文章。所以这个文集也是用来...
    Awey阅读 9,406评论 4 67
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,505评论 18 399
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,073评论 25 707
  • 生活总是不能尽遂人意,青柠今天回了老家,现在刚开车回来,房间很冷,今年的冬天来的比往年早了一些,坐在电脑前看课件脚...
    牡丹风情阅读 217评论 0 1
  • hello,我是倩倩。一个卖灵气水晶的girl 我喜欢水晶剔透的外表和神秘的寓意,所以我成为了水晶师 如果你也对水...
    人来人往lsq阅读 120评论 0 0