久久久久久久av_日韩在线中文_看一级毛片视频_日本精品二区_成人深夜福利视频_武道仙尊动漫在线观看

基于javascript的組件開發方式

作為一名前端工程師,寫組件的能力至關重要。雖然javascript經常被人嘲笑是個小玩具,但是在一代代大牛的前仆后繼的努力下,漸漸的也摸索了一套組件的編寫方式。下面我們來談談,

  作為一名前端工程師,寫組件的能力至關重要。雖然javascript經常被人嘲笑是個小玩具,但是在一代代大牛的前仆后繼的努力下,漸漸的也摸索了一套組件的編寫方式。

  下面我們來談談,在現有的知識體系下,如何很好的寫組件。

  比如我們要實現這樣一個組件,就是一個輸入框里面字數的計數。這個應該是個很簡單的需求。


  

  我們來看看,下面的各種寫法。

  為了更清楚的演示,下面全部使用jQuery作為基礎語言庫。



  最簡陋的寫法

  嗯 所謂的入門級寫法呢,就是完完全全的全局函數全局變量的寫法。(就我所知,現在好多外包還是這種寫法)

  代碼如下:




  
  test
  
  




  

  這段代碼跑也是可以跑的,但是呢,各種變量混亂,沒有很好的隔離作用域,當頁面變的復雜的時候,會很難去維護。目前這種代碼基本是用不了的。當然少數的活動頁面可以簡單用用。


  作用域隔離

  讓我們對上面的代碼作些改動,使用單個變量模擬命名空間。


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('');
    };

    $('#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('');
    };

    $('#J_input_count').html(num+'個字');
  };
  //返回構造函數
  return TextCountFun;

})();

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

  

  這種寫法,把所有的東西都包在了一個自動執行的閉包里面,所以不會受到外面的影響,并且只對外公開了TextCountFun構造函數,生成的對象只能訪問到init,render方法。這種寫法已經滿足絕大多數的需求了。事實上大部分的jQuery插件都是這種寫法。


  面向對象

  上面的寫法已經可以滿足絕大多數需求了。

  但是呢,當一個頁面特別復雜,當我們需要的組件越來越多,當我們需要做一套組件。僅僅用這個就不行了。首先的問題就是,這種寫法太靈活了,寫單個組件還可以。如果我們需要做一套風格相近的組件,而且是多個人同時在寫。那真的是噩夢。

  在編程的圈子里,面向對象一直是被認為最佳的編寫代碼方式。比如java,就是因為把面向對象發揮到了極致,所以多個人寫出來的代碼都很接近,維護也很方便。但是很不幸的是,javascript不支持class類的定義。但是我們可以模擬。

  下面我們先實現個簡單的javascript類:


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
})()

  

  這是拿John Resig的class簡單修改了下。

這邊只是很簡陋的實現了類的繼承機制。如果對類的實現有興趣可以參考我另一篇文章javascript oo實現

  我們看下使用方法:


//繼承超級父類,生成個子類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()

  

  使用很簡單,超級父類具有extend方法,可以繼承出一個子類。子類也具有extend方法。

  這邊要強調的是,繼承的父類都是一個也就是單繼承。但是可以通過extend實現多重混入。詳見下面用法。

  有了這個類的擴展,我們可以這么編寫代碼了:


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('');
    };

    $('#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"
  });
})

  這邊可能還沒看見class的真正好處,不急我們繼續往下。


  抽象出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('');
    };

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

  }
})

$(function() {
  new TextCount({
  //這邊直接傳input的節點了,因為屬性的賦值都是自動的。
    input:$("#J_input")
  });
})

  

  可以看到我們直接實現一些固定的方法,bind,render就行了。其他的base會自動處理(這里只是簡單處理了配置屬性的賦值)。

  事實上,這邊的init,bind,render就已經有了點生命周期的影子,但凡是組件都會具有這幾個階段,初始化,綁定事件,以及渲染。當然這邊還可以加一個destroy銷毀的方法,用來清理現場。

  此外為了方便,這邊直接變成了傳遞input的節點。因為屬性賦值自動化了,一般來說這種情況下都是使用getter,setter來處理。這邊就不詳細展開了。


  引入事件機制(觀察者模式)

  有了base應該說我們編寫組件更加的規范化,體系化了。下面我們繼續深挖。

  還是上面的那個例子,如果我們希望輸入字的時候超過5個字就彈出警告。該怎么辦呢。

  小白可能會說,那簡單啊直接改下bind方法:


var TextCount = Base.extend({
  ...
  bind:function(){
    var self = this;
    self.get('input').on('keyup',function(){
      if(self._getNum() > 5){
        alert('超過了5個字了。。。')
      }
      self.render();
    });
  },
  ...
})

  

  的確也是一種方法,但是太low了,代碼嚴重耦合。當這種需求特別特別多,代碼會越來越亂。

  這個時候就要引入事件機制,也就是經常說的觀察者模式。

注意這邊的事件機制跟平時的瀏覽器那些事件不是一回事,要分開來看。

  什么是觀察者模式呢,官方的解釋就不說了,直接拿這個例子來說。

  想象一下base是個機器人會說話,他會一直監聽輸入的字數并且匯報出去(通知)。而你可以把耳朵湊上去,聽著他的匯報(監聽)。發現字數超過5個字了,你就做些操作。

  所以這分為兩個部分,一個是通知,一個是監聽。

  假設通知是 fire方法,監聽是on。于是我們可以這么寫代碼:


var TextCount = Base.extend({
  ...
  bind:function(){
    var self = this;
    self.get('input').on('keyup',function(){
      //通知,每當有輸入的時候,就報告出去。
      self.fire('Text.input',self._getNum())
      self.render();
    });
  },
  ...
})

$(function() {
  var t = new TextCount({
    input:$("#J_input")
  });
  //監聽這個輸入事件
  t.on('Text.input',function(num){
    //可以獲取到傳遞過來的值
    if(num>5){
       alert('超過了5個字了。。。')
    }
  })
})

  

  fire用來觸發一個事件,可以傳遞數據。而on用來添加一個監聽。這樣組件里面只負責把一些關鍵的事件拋出來,至于具體的業務邏輯都可以添加監聽來實現。沒有事件的組件是不完整的。

  下面我們看看怎么實現這套事件機制。

  我們首先拋開base,想想怎么實現一個具有這套機制的類。


//輔組函數,獲取數組里某個元素的索引 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存下所有的監聽函數。在fire的時候去找到并且執行就行了。

  這個時候面向對象的好處就來了,如果我們希望base擁有事件機制。只需要這么寫:


var Base = Class.extend(Event,{
  ...
  destroy:function(){
    //去掉所有的事件監聽
    this.off()
  }
})
//于是可以
//var a  = new Base()
// a.on(xxx,fn)
//
// a.fire()

  

  是的只要extend的時候多混入一個Event,這樣Base或者它的子類生成的對象都會自動具有事件機制。

  有了事件機制我們可以把組件內部很多狀態暴露出來,比如我們可以在set方法中拋出一個事件,這樣每次屬性變更的時候我們都可以監聽到。

  到這里為止,我們的base類已經像模像樣了,具有了init,bind,render,destroy方法來表示組件的各個關鍵過程,并且具有了事件機制。基本上已經可以很好的來開發組件了。


  更進一步,richbase

  我們還可以繼續深挖。看看我們的base,還差些什么。首先瀏覽器的事件監聽還很落后,需要用戶自己在bind里面綁定,再然后現在的TextCount里面還存在dom操作,也沒有自己的模板機制。這都是需要擴展的,于是我們在base的基礎上再繼承出一個richbase用來實現更完備的組件基類。

  主要實現這些功能:

  • 事件代理:不需要用戶自己去找dom元素綁定監聽,也不需要用戶去關心什么時候銷毀。
  • 模板渲染:用戶不需要覆蓋render方法,而是覆蓋實現setUp方法。可以通過在setUp里面調用render來達到渲染對應html的目的。
  • 單向綁定:通過setChuckdata方法,更新數據,同時會更新html內容,不再需要dom操作。

  我們看下我們實現richbase后怎么寫組件:


var TextCount = RichBase.extend({
  //事件直接在這里注冊,會代理到parentNode節點,parentNode節點在下面指定
  EVENTS:{
    //選擇器字符串,支持所有jQuery風格的選擇器
    'input':{
      //注冊keyup事件
      keyup:function(self,e){
        //單向綁定,修改數據直接更新對應模板
        self.setChuckdata('count',self._getNum())

      }
    }
  },
  //指定當前組件的模板
  template:'<%= count %>個字',
  //私有方法
  _getNum:function(){
    return this.get('input').val().length || 0
  },
  //覆蓋實現setUp方法,所有邏輯寫在這里。最后可以使用render來決定需不需要渲染模板
  //模板渲染后會append到parentNode節點下面,如果未指定,會append到document.body
  setUp:function(){
    var self = this;

    var input = this.get('parentNode').find('#J_input')
    self.set('input',input)

    var num = this._getNum()
    //賦值數據,渲染模板,選用。有的組件沒有對應的模板就可以不調用這步。
    self.render({
      count:num
    })

  }
})

$(function() {
  //傳入parentNode節點,組件會掛載到這個節點上。所有事件都會代理到這個上面。
  new TextCount({
    parentNode:$("#J_test_container")
  });
})

/**對應的html,做了些修改,主要為了加上parentNode,這邊就是J_test_container

*/

  

  看下上面的用法,可以看到變得更簡單清晰了:

  • 事件不需要自己綁定,直接注冊在EVENTS屬性上。程序會自動將事件代理到parentNode上。
  • 引入了模板機制,使用template規定組件的模板,然后在setUp里面使用render(data)的方式渲染模板,程序會自動幫你append到parentNode下面。
  • 單向綁定,無需操作dom,后面要改動內容,不需要操作dom,只需要調用setChuckdata(key,新的值),選擇性的更新某個數據,相應的html會自動重新渲染。

  下面我們看下richebase的實現:


var RichBase = Base.extend({
  EVENTS:{},
  template:'',
  init:function(config){
    //存儲配置項
    this.__config = config
    //解析代理事件
    this._delegateEvent()
    this.setUp()
  },
  //循環遍歷EVENTS,使用jQuery的delegate代理到parentNode
  _delegateEvent:function(){
    var self = this
    var events = this.EVENTS || {}
    var eventObjs,fn,select,type
    var parentNode = this.get('parentNode') || $(document.body)

    for (select in events) {
      eventObjs = events[select]

      for (type in eventObjs) {
        fn = eventObjs[type]

        parentNode.delegate(select,type,function(e){
          fn.call(null,self,e)
        })
      }

    }

  },
  //支持underscore的極簡模板語法
  //用來渲染模板,這邊是抄的underscore的。非常簡單的模板引擎,支持原生的js語法
  _parseTemplate:function(str,data){
    /**
     * http://ejohn.org/blog/javascript-micro-templating/
     * https://github.com/jashkenas/underscore/blob/0.1.0/underscore.js#L399
     */
    var fn = new Function('obj',
        'var p=[],print=function(){p.push.apply(p,arguments);};' +
        'with(obj){p.push(\'' + str
            .replace(/[\r\t\n]/g, " ")
            .split("<%").join("\t")
            .replace(/((^|%>)[^\t]*)'/g, "$1\r")
            .replace(/\t=(.*?)%>/g, "',$1,'")
            .split("\t").join("');")
            .split("%>").join("p.push('")
            .split("\r").join("\\'") +
        "');}return p.join('');")
    return data ? fn(data) : fn
  },
  //提供給子類覆蓋實現
  setUp:function(){
    this.render()
  },
  //用來實現刷新,只需要傳入之前render時的數據里的key還有更新值,就可以自動刷新模板
  setChuckdata:function(key,value){
    var self = this
    var data = self.get('__renderData')

    //更新對應的值
    data[key] = value

    if (!this.template) return;
    //重新渲染
    var newHtmlNode = $(self._parseTemplate(this.template,data))
    //拿到存儲的渲染后的節點
    var currentNode = self.get('__currentNode')
    if (!currentNode) return;
    //替換內容
    currentNode.replaceWith(newHtmlNode)

    self.set('__currentNode',newHtmlNode)

  },
  //使用data來渲染模板并且append到parentNode下面
  render:function(data){
    var self = this
    //先存儲起來渲染的data,方便后面setChuckdata獲取使用
    self.set('__renderData',data)

    if (!this.template) return;

    //使用_parseTemplate解析渲染模板生成html
    //子類可以覆蓋這個方法使用其他的模板引擎解析
    var html = self._parseTemplate(this.template,data)

    var parentNode = this.get('parentNode') || $(document.body)

    var currentNode = $(html)
    //保存下來留待后面的區域刷新
    //存儲起來,方便后面setChuckdata獲取使用
    self.set('__currentNode',currentNode)
    parentNode.append(currentNode)
  },
  destroy:function(){

    var self = this
    //去掉自身的事件監聽
    self.off()
    //刪除渲染好的dom節點
    self.get('__currentNode').remove()
    //去掉綁定的代理事件
    var events = self.EVENTS || {}
    var eventObjs,fn,select,type
    var parentNode = self.get('parentNode')

    for (select in events) {
      eventObjs = events[select]

      for (type in eventObjs) {
        fn = eventObjs[type]

        parentNode.undelegate(select,type,fn)
      }

    }

  }
})

  

  主要做了兩件事,一個就是事件的解析跟代理,全部代理到parentNode上面。另外就是把render抽出來,用戶只需要實現setUp方法。如果需要模板支持就在setUp里面調用render來渲染模板,并且可以通過setChuckdata來刷新模板,實現單向綁定。


  結語

  有了richbase,基本上組件開發就沒啥問題了。但是我們還是可以繼續深挖下去。

  比如組件自動化加載渲染,局部刷新,比如父子組件的嵌套,再比如雙向綁定,再比如實現ng-click這種風格的事件機制。

  當然這些東西已經不屬于組件里面的內容了。再進一步其實已經是一個框架了。實際上最近比較流行的react,ploymer還有我們的brix等等都是實現了這套東西。受限于篇幅,這個以后有空再寫篇文章詳細分析下。

  2015-3-23 更新

  鑒于有人跟我要完整代碼,其實上面都列出來了。好吧,那我就再整理下,放在github了包含具體的demo,請點這里。不過僅僅作為理解使用,最好不要用于生產環境。如果覺得有幫助就給我個star吧。


來源:http://purplebamboo.github.io/2015/03/16/javascript-component/

【網站聲明】本站除付費源碼經過測試外,其他素材未做測試,不保證完整性,網站上部分源碼僅限學習交流,請勿用于商業用途。如損害你的權益請聯系客服QQ:2655101040 給予處理,謝謝支持。

相關文檔推薦

由于實際運行環境是在瀏覽器中,因此性能還取決于JavaScript解釋器的效率,指定的FPS幀速在低性能解釋器中可能不會達到,所以這部分不是開發者能夠決定的,開發者能作的是盡可能通
本文將使用HTML5提供的VideoAPI做一個自定義的視頻播放器,需要用到HTML5提供的video標簽、以及HTML5提供的對JavascriptAPI的擴展。,HTML5中國,中國最大的HTML5中文門戶。
隨著 Hybrid 應用的豐富,HTML5 工程師們已經不滿足于把桌面端體驗簡單移植到移動端,他們覬覦移動原生應用人性化的操作體驗,特別是原生應用與生俱來的豐富的手勢系統。HTML5 沒有提
你想要在自己網站上分享一個產品,或者是一個作品集,又或者僅僅只是一個靈感。在你發布到網上之前,你想讓它看起來有吸引力,專業,或者至少得看起來像那么回事。那么你接下
H5廣告,包括H5廣告的設計流程,究竟有什么講究,和階段。為了能幫助更多的人了解H5廣告,我專門做了一個講義。同時,也讓我意外的收到了非常好反饋和認!這是對我的極大鼓勵!我的
本文主要內容有:框架與組件、構建生態、開發技巧與調試、html、css與重構、native/hybrid/桌面開發、前端/H5優化、全棧/全端開發、研究實驗、數據分析與監控、其它軟技能、前端技術網
主站蜘蛛池模板: 亚洲一区二区三区在线观看免费 | 午夜视频在线观看视频 | 国产高清视频在线播放 | 亚洲精品久久久久久一区二区 | 毛片免费视频 | 国产精品99久久久久久久vr | 久久精品成人 | 日韩欧美中文字幕在线观看 | 91天堂| 日韩欧美一区二区三区在线播放 | 久久国产精品一区二区 | 国产精品久久久久久婷婷天堂 | 国家一级黄色片 | 亚洲看片网站 | 国产乱肥老妇国产一区二 | 欧美日韩亚洲国产综合 | 奇米四色在线观看 | 国产99久久精品一区二区永久免费 | 国产福利免费视频 | 国产欧美精品一区二区三区 | 日韩欧美不卡 | 91视视频在线观看入口直接观看 | 一区二区三区中文字幕 | 久久精品日产第一区二区三区 | 韩国av一区二区 | 亚洲欧美在线免费观看 | 91毛片网| 狠狠综合网 | 中文字幕在线视频免费观看 | 亚洲精品在线观看视频 | 自拍第1页 | 九色国产| 色播久久 | 天天操天天干天天爽 | 国产一区二区三区久久久久久久久 | 91在线精品一区二区 | 一区二区三区国产精品 | 99热这里都是精品 | 免费中文字幕 | 大乳boobs巨大吃奶挤奶 | 久久黄网 |