Js 组件化基础

需求: 我们需要制作一个输入框,并显示出输入字符的个数

Step1:
  • 最初始者的写法(使用各种全局变量 缺点:变量混乱难以维护)

Js bin 查看地址

    $(function(){
        var input = $('#j-input');
        var num = $('#j-num');

        function addEvent(){
            input.on('keyup', function(){
             render();
            });
        };

       function getNum(){
              return input.val().length;
        }

        //渲染变化的数字
        function render(){
               num.html(getNum());
        }


        function init(){
            addEvent();
        };

        init();
})()
Step2:
  • 使用单个变量作为命名空间,这时候我们会把我们的控制写在一个一个单独的变量里面 (优点:结构清晰 缺点:没有私有方法,私有变量的概念) Js bin 查看地址
$(function(){
            var inputNumController = {
                init: function(){
                    this.getNode();
                    this.bindEvent();
                },
                bindEvent: function(){
                    this.input.on('keyup', function(){
                        var num = this.input.val().length;
                        this.num.html(num);
                    }.bind(this));
                },
                getNode: function(){
                    this.input = $('#j-input');
                    this.num = $('#j-num');
                }
            };

            inputNumController.init();
}());
Step4:
  • 通过函数闭包实现私有变量,公有变量的控制, return 出公有接口 (优点:大部分jquery的插件写法 可以满足大部分的需求 缺点:适用于单个组件的写法,但是要做一套组件时,很难维护,因为每个人的风格会不一样,且不利于重用) Js bin 查看地址
    <input type="text" id="j-input-1"/>
    <input type="text" id="j-input-2"/>
        //利用闭包
        var TextNumCom = (function(){
            //私有方法
            var _bindNumControl = function(that){
                  that.input.after('统计:<span class="j-num"></span>');
                  that.input.on('keyup', function(){
                      that.render();
                  });
            };

            var TextCountFun = function(config){
                this.input = config.input;
            };

            TextCountFun.prototype = {
                //暴露出去的公有接口
                init: function(){

                    _bindNumControl(this);
                    return this;
                },
                render: function(){
                    this.num = this.input.next('.j-num');
                    this.num.html(this.input.val().length);
                }
            };


            return TextCountFun;
        }());


        $(function(){
            var text1 = new TextNumCom({
                input:$('#j-input-1')
            });
            var text2 = new TextNumCom({
                input:$('#j-input-2')
            });


            text1.init().render();
            text2.init().render();
        });
Step5:
  • 使用面向对象的方式去写组件,让代码有统一的风格
  • 假设使用 John Resig 的 class
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 = Array.prototype.slice.call(arguments) || []
    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

  return Class
})()

然后我们就可以通过下面的代码:

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"
  });
})
Step6:
  • 抽象出Base代码,组件有些方法,是大部分组件都会有的
    • init 初始化属性
    • render 渲染
    • bind 事件绑定
    • destroy 销毁
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(){

  }
})
Step7:

自定义事件可以参考

  • 引入事件机制 观察者模式 fire on
    • 通过on监听
    • 通过fire触发
   //辅组函数,获取数组里某个元素的索引 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;
  }
})
Step8: 希望Base拥有事件机制
  var Base = Class.extend(Event,{
  init: function(){},
  get: function(){},
  set: function(){},
  ...
  destroy:function(){
    //去掉所有的事件监听
    this.off()
  }
})
Step9: 需要进一步扩展Base 形成RichBase
  • 事件代理:不需要用户自己去找dom元素绑定监听直接绑定在parentNode身上,也不需要用户去关心什么时候销毁。
  • 模板渲染:用户不需要覆盖render方法,而是覆盖实现setUp方法。可以通过在setUp里面调用render来达到渲染对应html的目的。
  • 单向绑定:通过setChuckdata方法,更新数据,同时会更新html内容,不再需要dom操作。

results matching ""

    No results matching ""