天天看點

JQuery源碼解析(十)

預設回調對象設計

不傳入任何參數,調用add的時候将函數add到内部的list中,調用fire的時候順序觸發list中的回調函數:

function fn1(val) {
  console.log('fn1 says:' + val);
}

function fn2(val) {
  console.log('fn2 says ' + val);
}
var cbs = $.Callbacks();
cbs.add(fn1);
cbs.fire('foo');
console.log('........')
cbs.add(fn2);
cbs.fire('bar')      

 結果就是按照順序疊加觸發,如下清單:

  

fn1 says:foo 
………………………
fn1 says:bar 
fn2 says bar      

 這種就是最簡單的處理了,可以直接模拟,代碼如下:

function Callbacks() {
  var list = [];
  var self;
  self = {
    add: function(fn) {
      list.push(fn)
    },
    fire: function(args) {
      list.forEach(function(fn) {
        fn(args);
      })
    }
  }
  return self;
}      

 代碼:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>

<title></title>
</head>
<body>

<script type="text/javascript">


function Callbacks() {
  var list = [];
  var self;
  self = {
    add: function(fn) {
      list.push(fn)
    },
    fire: function(args) {
      list.forEach(function(fn) {
        fn(args);
      })
    }
  }
  return self;
}

function fn1(val) {
  show('fn1 says:' + val);
}
function fn2(val) {
  show('fn2 says ' + val);
}

var cbs = Callbacks();
cbs.add(fn1);
cbs.fire('foo');
cbs.add(fn2);
cbs.fire('bar')



</script>

</body>
</html>      

once的設計

once的作用確定回調清單隻執行(.fire())一次(像一個遞延 Deferred),如下代碼:

function fn1(val){
    console.log('fn1 says ' + val);
}
var cbs = $.Callbacks('once');
cbs.add(fn1);
cbs.fire('foo');
cbs.fire('foo');      

 結果你會發現cbs.fire('foo')隻執行了一次。

fn1 says foo  //隻顯示一次      

 once定義是很明确的,確定這個回調清單隻執行( .fire() )一次(像一個遞延 Deferred),是以針對這種once的處理可以有多種不同的途徑實作。

1、add的時候抛棄

2、在fire的時候抛棄多個。

但是jQuery是在執行第一個fire的時候直接給清空list清單了,然後在add的地方給判斷下list是否存在,進而達到這樣的處理。

function Callbacks(options) {
  var list = [];
  var self;
  self = {
    add: function(fn) {
      list.push(fn)
    },
    fire: function(args) {
      if (list) {
        list.forEach(function(fn) {
          fn(args);
        })
        if (options === 'once') {
          list = undefined;
        }
      }
    }
  }
  return self;
}      

 在fire之後,判斷參數是否為once,直接把list給清理掉,是以之後的所有fire都被抛棄掉了,而從達到了once的效果。

jQuery.Callbacks的處理

在fire中調用了 self.disable(); 方法

// 禁用回調清單中的回調。
disable: function() {
    list = stack = memory = undefined;
    return this;
},      

 執行個體代碼:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>

<title></title>
</head>
<body>

<script type="text/javascript">


function Callbacks(options) {
  var list = [];
  var self;
  self = {
    add: function(fn) {
      list.push(fn)
    },
    fire: function(args) {
      if(list){
        list.forEach(function(fn) {
          fn(args);
        })
        if(options === 'once'){
          list = undefined;
        }       
      }
    }
  }
  return self;
}


function fn1(val) {
  show('fn1 says:' + val);
}
function fn2(val) {
  show('fn2 says ' + val);
}


var cbs = Callbacks('once');
cbs.add(fn1);
cbs.fire('foo');
cbs.fire('foo');


</script>

</body>
</html>      

memory的設計

memory:保持以前的值,将添加到這個清單的後面的最新的值立即執行調用任何回調 (像一個遞延 Deferred)。

回調函數是從異步隊列Deferred分離出來的,是以很多的接口設計都是為了契合Deferred接口,memory用的很多,這個緩存的設計這裡提及一下

主要是用來實作deferred的異步收集與pipe管道風格的資料傳遞的,具體在Deferred有詳解,這裡大概了解下作用範圍。

memory這個有點不好了解,我們還是通過列子說明下,看下面的代碼:

var cbs = Callbacks('once');
cbs.add(fn1);
cbs.fire('foo');
cbs.fire('foo');

function fn1(val) {
  console.log('fn1 says ' + val);
}
function fn2(val) {
  console.log('fn2 says ' + val);
}
function fn3(val) {
  console.log('fn3 says ' + val);
}

var cbs = $.Callbacks('memory');
cbs.add(fn1);
cbs.fire('foo');

console.log('..........')

cbs.add(fn2);
cbs.fire('bar');

console.log('..........')
cbs.add(fn3);
cbs.fire('aaron');      

 結果可以看出,我們在執行cbs.add(fn2);的時候,此時除了把fn2添加到了回調隊列之外而且還立刻執行了這個方法,唯一的差別就是,參數是用的之前的。是以解釋就叫“保持以前的值”。 

fn1 says foo 
.......... 
fn2 says foo 
fn1 says bar 
fn2 says bar 
.......... 
fn3 says bar 
fn1 says aaron 
fn2 says aaron 
fn3 says aaron      

是以這個​

​memory​

​設計需要解決的問題就是:

1:如何取到上一個參數

2:add後如何執行

看看我們實作的代碼:

function Callbacks(options) {
  var list = [];
  var self;
  var firingStart;
  var memory;

  function _fire(data) {
    memory = options === 'memory' && data;
    firingIndex = firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    for (; list && firingIndex < firingLength; firingIndex++) {
      list[firingIndex](data)
    }
  }

  self = {
    add: function(fn) {
      var start = list.length;
      list.push(fn)
      if (memory) {
        firingStart = start; //擷取最後一值
        _fire(memory);
      }
    },
    fire: function(args) {
      if (list) {
        _fire(args)
      }
    }
  }
  return self;
}      

首先add之後要能觸發fire的動作,是以我們把fire作為内部的一個私有方法實作_fire,比較合邏輯,這樣外部的fire隻是一個門面方法的調用。

私有變量memory緩存這上一個參數的屬性,我們靠firingStart用來定位最後通過add增加的回調資料的索引。在周遊的時候直接通過firingStart的起始索引定位,然後傳遞memory的參數,而且實作這種“保持以前的值”的設計。

執行個體代碼:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>
<title></title>
</head>
<body>

<script type="text/javascript">


function Callbacks(options) {
  var list = [];
  var self;
  var firingStart;
  var memory;

  function _fire(data) {
    memory = options === 'memory' && data;
    firingIndex = firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    for (; list && firingIndex < firingLength; firingIndex++) {
      list[firingIndex](data)
    }
  }

  self = {
    add: function(fn) {
      var start = list.length;
      list.push(fn)
      if (memory) {
        firingStart = start; //擷取最後一值
        _fire(memory);
      }
    },
    fire: function(args) {
      if (list) {
        _fire(args)
      }
    }
  }
  return self;
}


function fn1(val) {
  show('fn1 says ' + val);
}

function fn2(val) {
  show('fn2 says ' + val);
}

function fn3(val) {
  show('fn3 says ' + val);
}

var cbs = Callbacks('memory');
cbs.add(fn1);
cbs.fire('foo');



cbs.add(fn2);
cbs.fire('bar');


cbs.add(fn3);
cbs.fire('aaron')



</script>

</body>
</html>      

unique的設計

Unique:確定一次隻能添加一個回調(是以在清單中沒有重複的回調)

function fn1(val) {
  console.log('fn1 says ' + val);
}
var callbacks = $.Callbacks( "unique" );
callbacks.add( fn1 );
callbacks.add( fn1 ); // repeat addition
callbacks.add( fn1 );
callbacks.fire( "foo" );      

 結果:過濾了相同的add操作

fn1 says foo      

 過濾重複的比較簡單,因為是數組的儲存方式,我們可以在入口處通過indexOf判斷即可

function Callbacks(options) {
  var list = [];
  var self;
  var firingStart;
  var memory;

  function _fire(data) {
    memory = options === 'memory' && data;
    firingIndex = firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    for (; list && firingIndex < firingLength; firingIndex++) {
      list[firingIndex](data)
    }
  }

  self = {
    add: function(fn) {
      var start = list.length;
      if (options == 'unique') {
        if (-1 === list.indexOf(fn)) {
          list.push(fn)
        }
      } else {
        list.push(fn)
      }
      if (memory) {
        firingStart = start; //擷取最後一值
        _fire(memory);
      }
    },
    fire: function(args) {
      if (list) {
        _fire(args)
      }
    }
  }
  return self;
}      

 執行個體代碼:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>
<title></title>
</head>
<body>

<script type="text/javascript">


function Callbacks(options) {
  var list = [];
  var self;
  var firingStart;
  var memory;

  function _fire(data) {
    memory = options === 'memory' && data;
    firingIndex = firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    for (; list && firingIndex < firingLength; firingIndex++) {
      list[firingIndex](data)
    }
  }

  self = {
    add: function(fn) {
      var start = list.length;
      if (options == 'unique') {
        if (-1 === list.indexOf(fn)) {
          list.push(fn)
        }
      } else {
        list.push(fn)
      }
      if (memory) {
        firingStart = start; //擷取最後一值
        _fire(memory);
      }
    },
    fire: function(args) {
      if (list) {
        _fire(args)
      }
    }
  }
  return self;
}


function fn1(val) {
  show('fn1 says ' + val);
}
var callbacks = Callbacks( "unique" );
callbacks.add( fn1 );
callbacks.add( fn1 ); // 重複添加
callbacks.add( fn1 );
callbacks.fire( "foo" );



</script>

</body>
</html>      

stopOnFalse

stopOnFalse: 當一個回調傳回false 時中斷調用

function fn1(value) {
  console.log(value);
  return false;
}

function fn2(value) {
  fn1("fn2 says: " + value);
  return false;
}

var callbacks = $.Callbacks("stopOnFalse");
callbacks.add(fn1);
callbacks.fire("foo");

callbacks.add(fn2);
callbacks.fire("bar");      

結果雖然fn1被添加到了回調清單,但是因為fn1傳回了false,那麼意思之後的回調都不會被調用了。如果還有fn3,在f2上傳回false,fn3也将不會被調用。

foo
bar      

 這個設計我們隻要控制好函數傳回的處理的布爾值,通過這個值用來判斷是否需要下一個周遊

if (list[firingIndex](data) === false && options === 'stopOnFalse') {
  break;
}      

 源碼如下:

function Callbacks(options) {
  var list = [];
  var self;
  var firingStart;
  var memory;

  function _fire(data) {
    memory = options === 'memory' && data;
    firingIndex =
      firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    for (; list && firingIndex < firingLength; firingIndex++) {
      if (list[firingIndex](data) === false && options === 'stopOnFalse') {
        break;
      }
    }
  }

  self = {
    add: function(fn) {
      var start = list.length;
      if (options == 'unique') {
        if (-1 === list.indexOf(fn)) {
          list.push(fn)
        }
      } else {
        list.push(fn)
      }
      if (memory) {
        firingStart = start; //擷取最後一值
        _fire(memory);
      }
    },
    fire: function(args) {
      if (list) {
        _fire(args)
      }
    }
  }
  return self;
}      

以上是幾種單獨的處理情況的用法,我們可以看到jQuery都是組合使用的,最常見的就是

jQuery.Callbacks("once memory")的組合了,其實以上的思路都講解過了,無非就是組合起來的時候要考慮一些判斷了。

代碼示例:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>
<title></title>
</head>
<body>

<script type="text/javascript">

function Callbacks(options) {
  var list = [];
  var self;
  var firingStart;
  var memory;

  function _fire(data) {
    memory = options === 'memory' && data;
    firingIndex =
      firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    for (; list && firingIndex < firingLength; firingIndex++) {
      if (list[firingIndex](data) === false && options === 'stopOnFalse') {
        break;
      }
    }
  }

  self = {
    add: function(fn) {
      var start = list.length;
      if (options == 'unique') {
        if (-1 === list.indexOf(fn)) {
          list.push(fn)
        }
      } else {
        list.push(fn)
      }
      if (memory) {
        firingStart = start; //擷取最後一值
        _fire(memory);
      }
    },
    fire: function(args) {
      if (list) {
        _fire(args)
      }
    }
  }
  return self;
}


function fn1( value ){
    show( value );
    return false;
}
 
function fn2( value ){
    fn1( "fn2 says: " + value );
    return false;
}
 
var callbacks = Callbacks('stopOnFalse');
callbacks.add(fn1);
callbacks.fire("foo1");

callbacks.add(fn2);
callbacks.fire("foo2");




</script>

</body>
</html>      

繼續閱讀