js组件开发

首先附上jquery组件开发的网站:http://www.poluoluo.com/jzxy/201406/277886.html 

jquery组件开发保证chainability,通过返回this.each();

jQuery.fn.test2= function(){ 
this.css("background","#ff0");//这里面的this为jquery对象,而不是dom对象
return this.each(function(){ //遍历匹配的元素,此处的this表示为jquery对象,而不是dom对象
alert("this"+this+this.innerHTML); //提示当前对象的dom节点名称,这里的this关键字都指向一个不同的DOM元素(每次都是一个不同的匹配元素)。
});
};

this.css(),this.each()里面的this为jquery对象,但是alert里面this为dom对象.

为什么要return this.each()对象,所以这样就可以继续链式操作了。

js组件写法

参考链接:http://blog.csdn.net/bingqingsuimeng/article/details/44451481

组件写的不多,使劲回忆以前用到的组件,完全用js开发的组件,主要有两部分,tag名称定义和组件api,具体的实现过程不记得了。

先给出一个计算输入字符数组件的原始写法:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>test</title>
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  <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>
</head>
<body>
<input type="text" id="J_input"/>
</body>
</html>

网上查了js写组件的过程,在结合自己的实践工,主要使用了三种写组件的方式:

1.作用域隔离

使用对象字面量定义一个对象,调用对象的函数方法;

var textCount = {
  input:null,
  init:function(config){
    this.input = $(config.id);
    this.bind();
    //这边范围对应的对象,可以实现链式调用
    return this;
  },
  bind:function(){
    var self = this;
    this.input.on('keyup',function(){
      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方法应该是私有的方法,但是其他代码可以访问和更改他们,当代码量特别特别多的时候,很容易出现变量重复,或被修改的问题。
为了私有化方法,出现的闭包的写法。
2.闭包写法
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方法。这种写法已经满足绝大多数的需求了。事实上大部分的jQuery插件都是这种写法。

3.面向对象方式
上面的写法已经可以满足大部分需求,但是当一个页面特别复杂,我们需要做一套组件。仅仅用这个就不行了。首先的问题就是,这种写法太灵活了,写单个组件还可以。如果我们需要做一套风格相近的组件,而且是多个人同时在写。那真的是噩梦。

在编程的圈子里,面向对象一直是被认为最佳的编写代码方式。比如Java,就是因为把面向对象发挥到了极致,所以多个人写出来的代码都很接近,维护也很方便。但是很不幸的是,javascript不支持class类的定义。但是我们可以模拟。
先实现一个简单的js类,作为base类。
var Class = (function() {
  var _mix = function(r, s) {
    for (var p in s) {
      if (s.hasOwnProperty(p)) {
        r[p] = s[p]
      }
    }
  }

  var _extend = function() {

    //开关 用来使生成原型时,不调用真正的构成流程init
    this.initPrototype = true
    var prototype = new this()
    this.initPrototype = false

    var items = arguments.slice() || []
    var item

    //支持混入多个属性,并且支持{}也支持 Function
    while (item = items.shift()) {
      _mix(prototype, item.prototype || item)
    }


    // 这边是返回的类,其实就是我们返回的子类
    function SubClass() {
      if (!SubClass.initPrototype && this.init)
        this.init.apply(this, arguments)//调用init真正的构造函数
    }

    // 赋值原型链,完成继承
    SubClass.prototype = prototype

    // 改变constructor引用
    SubClass.prototype.constructor = SubClass

    // 为子类也添加extend方法
    SubClass.extend = _extend

    return SubClass
  }
  //超级父类
  var Class = function() {}
  //为超级父类添加extend方法
  Class.extend = _extend

})()
基类的使用方式为:
/继承超级父类,生成个子类Animal,并且混入一些方法。这些方法会到Animal的原型上。
//另外这边不仅支持混入{},还支持混入Function
var Animal = Class.extend({
  init:function(opts){
    this.msg = opts.msg
    this.type = "animal"
  },
  say:function(){
    alert(this.msg+":i am a "+this.type)
  }
})


//继承Animal,并且混入一些方法
var Dog = Animal.extend({
  init:function(opts){
    //并未实现super方法,直接简单使用父类原型调用即可
    Animal.prototype.init.call(this,opts)
    //修改了type类型
    this.type = "dog"
  }
})

//new Animal({msg:'hello'}).say()

new Dog({msg:'hi'}).say()
其实就是重新覆盖或者定义了基类中的抽象出的方法。

本文实例请参考原文链接,

抽象出base

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

  • 比如init用来初始化属性。
  • 比如render用来处理渲染的逻辑。
  • 比如bind用来处理事件的绑定。
抽象出这三个方法,都按照这个约定写,开发大规模组件库就变得更加规范,相互之间配合也更容易。
事实上,这边的init,bind,render就已经有了点生命周期的影子,但凡是组件都会具有这几个阶段,初始化,绑定事件,以及渲染。当然这边还可以加一个destroy销毁的方法,用来清理现场。
在组件开发中,引入事件机制 ,以便向外部报出组件当前状态。
观察者模式
想象一下base是个机器人会说话,他会一直监听输入的字数并且汇报出去(通知)。而你可以把耳朵凑上去,听着他的汇报(监听)。发现字数超过5个字了,你就做些操作。
//辅组函数,获取数组里某个元素的索引 index
var _indexOf = function(array,key){
  if (array === null) return -1
  var i = 0, length = array.length
  for (; i < length; i++) if (array[i] === item) return i
  return -1
}

var Event = Class.extend({
  //添加监听
  on:function(key,listener){
    //this.__events存储所有的处理函数
    if (!this.__events) {
      this.__events = {}
    }
    if (!this.__events[key]) {
      this.__events[key] = []
    }
    if (_indexOf(this.__events,listener) === -1 && typeof listener === 'function') {
      this.__events[key].push(listener)
    }

    return this
  },
  //触发一个事件,也就是通知
  fire:function(key){

    if (!this.__events || !this.__events[key]) return

    var args = Array.prototype.slice.call(arguments, 1) || []

    var listeners = this.__events[key]
    var i = 0
    var l = listeners.length

    for (i; i < l; i++) {
      listeners[i].apply(this,args)
    }

    return this
  },
  //取消监听
  off:function(key,listener){

    if (!key && !listener) {
      this.__events = {}
    }
    //不传监听函数,就去掉当前key下面的所有的监听函数
    if (key && !listener) {
      delete this.__events[key]
    }

    if (key && listener) {
      var listeners = this.__events[key]
      var index = _indexOf(listeners, listener)

      (index > -1) && listeners.splice(index, 1)
    }

    return this;
  }
})


var a = new Event()

//添加监听 test事件
a.on('test',function(msg){
  alert(msg)
})

//触发 test事件
a.fire('test','我是第一次触发')
a.fire('test','我又触发了')

a.off('test')

a.fire('test','你应该看不到我了')

实现起来并不复杂,只要使用this.__events存下所有的监听函数(on时候存下监听)。在fire的时候去找到并且执行相应的监听函数就行了。
这个时候面向对象的好处就来了,如果我们希望base拥有事件机制。只需要这么写:
var Base = Class.extend(Event,{
  ...
  destroy:function(){
    //去掉所有的事件监听
    this.off()
  }
})

是的只要extend的时候多混入一个Event,这样Base或者它的子类生成的对象都会自动具有事件机制。

有了事件机制我们可以把组件内部很多状态暴露出来,比如我们可以在set方法中抛出一个事件,这样每次属性变更的时候我们都可以监听到。

到这里为止,我们的base类已经像模像样了,具有了init,bind,render,destroy方法来表示组件的各个关键过程,并且具有了事件机制。基本上已经可以很好的来开发组件了。

对基类的更进一步的丰富目前就不在讨论了,主要考虑三个方面:

1.事件代理:不在是dom元素的事件绑定监听,也不需要用户去关心什么时候销毁。

2。模板渲染:用户不需要覆盖render方法,而是覆盖实现setUp方法。可以通过在setUp里面调用render来达到渲染对应html的目的。直接使用模板解析渲染。

3.数据单项绑定:无需操作dom,后面要改动内容,不需要操作dom,只需要调用setChuckdata(key,新的值),选择性的更新某个数据,相应的html会自动重新渲染。

 

 



 

 

posted @ 2017-03-15 16:04  夏日雪  阅读(9558)  评论(1编辑  收藏  举报