欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript 組件之旅(二)編碼實現(xiàn)和算法

 更新時間:2009年10月28日 14:47:52   作者:  
話說上期我們討論了隊列管理組件的設(shè)計,并且給它取了個響亮而獨特的名字:Smart Queue. 這次,我們要將之前的設(shè)計成果付諸實踐,用代碼來實現(xiàn)它。

首先,我們要考慮一下它的源文件布局,也就是決定代碼如何拆分到獨立的文件中去。為什么要這么做呢?還記得上期結(jié)尾處我提到這個組件會使用“外部代碼”嗎?為了區(qū)分代碼的用途,決定將代碼至少分成兩部分:外部代碼文件和 Smart Queue 文件。
區(qū)分用途只是其一,其二,分散到獨立文件有利于代碼的維護。試想,以后的某一天你決定要在現(xiàn)有的隊列管理基本功能之上,添加一些新的擴展功能,或是把它包裝成某個實現(xiàn)特定任務(wù)的組件,而又希望保持現(xiàn)有功能(內(nèi)部實現(xiàn))和調(diào)用方式(對外接口)不變,那么將新的代碼寫到單獨的文件是最好的選擇。

嗯,下期會重點談?wù)勎募季值脑掝},現(xiàn)在要開始切入正題了。第一步,當(dāng)然是要為組件創(chuàng)建自己的命名空間,組件所有的代碼都將限制在這個頂層命名空間內(nèi):

var SmartQueue = window.SmartQueue || {};
SmartQueue.version = '0.1';

初始化的時候,如果碰到命名空間沖突就把它拉過來用。通常這個沖突是由重復(fù)引用組件代碼導(dǎo)致的,因此“拉過來用”會將對象以同樣的實現(xiàn)重寫一次;最壞的情況下,如果碰巧頁面上另一個對象也叫 SmartQueue, 那不好意思了,我會覆蓋你的實現(xiàn)——如果沒有進一步的命名沖突,基本上兩個組件可以相安無事地運行。同時順便給它一個版本號。

接著,按三個優(yōu)先級為 SmartQueue 創(chuàng)建三個隊列:

var Q = SmartQueue.Queue = [[], [], []];

每個都是空數(shù)組,因為還沒有任務(wù)加進去嘛。又順便給它建個“快捷方式”,后面要訪問數(shù)組直接寫 Q[n] 就可以啦。

接下來,我們的主角 Task 隆重登場——怎么 new 一個 Task, 定義在這里:

  var T = SmartQueue.Task = function(fn, level, name, dependencies) {
    if(typeof fn !== FUNCTION) {
      throw new Error('Invalid argument type: fn.');
    }
    this.fn = fn;
    this.level = _validateLevel(level) ? level : LEVEL_NORMAL;

    // detect type of name
    this.name = typeof name === STRING && name ? name : 't' + _id++;

    // dependencies could be retrieved as an 'Object', so use instanceof instead.
    this.dependencies = dependencies instanceof Array ? dependencies : [];
  };

里面的具體細節(jié)就不說了,有必要的注釋,一般我們的代碼也能做到自我描述,后面代碼也是這樣。這里告訴客戶(使用者):你想新建一個 SmartQueue.Task 實例,就要至少傳一個參數(shù)給這個構(gòu)造函數(shù)(后 3 個都可以省略進行缺省處理),否則拋出異常伺候。

但是這還不夠,有時候,客戶希望從已有 Task 克隆一個新實例,或是從一個“殘廢體”(具有部分 Task 屬性的對象)修復(fù)出“健康體”(真正的 Task 對象實例),通過上面的構(gòu)造方式就有點不爽了——客戶得這樣寫:

var task1 = new SmartQueue.Task(obj.fn, 1, '', obj.dependencies);

我很懶,我只想傳 fn 和 dependencies 兩個屬性,不想做額外的事情。好吧,我們來重構(gòu)一下構(gòu)造函數(shù):

  var _setupTask = function(fn, level, name, dependencies) {
    if(typeof fn !== FUNCTION) {
      throw new Error('Invalid argument type: fn.');
    }
    this.fn = fn;
    this.level = _validateLevel(level) ? level : LEVEL_NORMAL;

    // detect type of name
    this.name = typeof name === STRING && name ? name : 't' + _id++;

    // dependencies could be retrieved as an 'Object', so use instanceof instead.
    this.dependencies = dependencies instanceof Array ? dependencies : [];
  };

  var T = SmartQueue.Task = function(task) {
    if(arguments.length > 1) {
      _setupTask.apply(this, arguments);
    } else {
      _setupTask.call(this, task.fn, task.level, task.name, task.dependencies);
    }

    // init context/scope and data for the task.
    this.context = task.context || window;
    this.data = task.data || {};
  };

如此一來,原來的構(gòu)造方式可以繼續(xù)工作,而上面的懶人可以這樣傳入一個“殘廢體”:

var task1 = new SmartQueue.Task({fn: obj.fn, dependencies: obj.dependencies});

當(dāng)構(gòu)造函數(shù)收到多個參數(shù)時,按之前的方案等同處理;否則,視唯一的參數(shù)為 Task 對象或“殘廢體”。這里通過 JavaScript 中的 apply/call 方法將新實例傳給重構(gòu)出來的 _setupTask 方法,作為該方法的上下文 (context, 也有稱為 scope), apply/call 是 JavaScript 在方法之間傳遞上下文的法寶,要用心體會哦。同時,允許用戶定義 task.fn 在執(zhí)行時的上下文,并將自定義的數(shù)據(jù)傳遞給執(zhí)行中的 fn.

經(jīng)典的 JavaScript 對象三段式是什么?

  1. 定義對象的構(gòu)造函數(shù)
  2. 在原型上定義屬性和方法
  3. new 對象,拿來用

所以,下面要為 SmartQueue.Task 對象的原型定義屬性和方法。上期分析過 Task (任務(wù))有幾個屬性和方法,部分屬性我們已經(jīng)在 _setupTask 中定義了,下面是原型提供的屬性和方法:

  T.prototype = {
    enabled: true,
    register: function() {
      var queue = Q[this.level];
      if(_findTask(queue, this.name) !== -1) {
        throw new Error('Specified name exists: ' + this.name);
      }
      queue.push(this);
    },
    changeTo: function(level) {
      if(!_validateLevel(level)) {
        throw new Error('Invalid argument: level');
      }
      level = parseInt(level, 10);
      if(this.level === level) {
        return;
      }
      Q[this.level].remove(this);
      this.level = level;
      this.register();
    },
    execute: function() {
      if(this.enabled) {
        // pass context and data
        this.fn.call(this.context, this.data);
      }
    },
    toString: function() {
      var str = this.name;
      if(this.dependencies.length) {
        str += ' depends on: [' + this.dependencies.join(', ') + ']';
      }
      return str;
    }
  };

如你所見,邏輯非常簡單,也許你已經(jīng)在一分鐘內(nèi)掃過了代碼,嘴角不經(jīng)意間露出一絲心領(lǐng)神會。不過,這里要說的是簡單而且通常最不被重視的 toString 方法。在一些高級語言中,為自定義對象實現(xiàn) toString 方法被作為最佳實踐準(zhǔn)則而推薦,為什么呢?因為 toString 可以很方便地在調(diào)試器中提供有用的信息,可以方便地將對象基本信息寫入日志;在統(tǒng)一的編程模式中,實現(xiàn) toString 可以讓你少寫一些代碼。

嗯,我們繼續(xù)推進,我們要實現(xiàn) SmartQueue 的具體功能。上期分析過,SmartQueue 只有一個實例,因此我們決定直接在 SmartQueue 下面創(chuàng)建方法:

  SmartQueue.init = function() {
    Q.forEach(function(queue) {
      queue.length = 0;
    });
  };

這里用到 JavaScript 1.6 為 Array 對象提供的遍歷方法 forEach. 之所以這樣寫是因為我們假定“外部代碼”已經(jīng)在前面運行過了。設(shè)置 Array 對象的 length 屬性為 0 導(dǎo)致,它被清空并且釋放所有的項(數(shù)組單元)。

最后一個方法 fire, 是整個組件最主要的方法,它負責(zé)對所有任務(wù)隊列進行排序,并逐個執(zhí)行。由于代碼稍長了一點,這里只介紹排序使用的算法和實現(xiàn)方式,完整代碼在這里。

var _dirty = true, // A flag indicates weather the Queue need to be fired.
  _sorted = [], index;
// Sort all Queues.
// ref: http://en.wikipedia.org/wiki/Topological_sorting
var _visit = function(queue, task) {
    if(task._visited >= 1) {
      task._visited++;
      return;
    }
    task._visited = 1;
    // find out and visit all dependencies.
    var dependencies = [], i;
    task.dependencies.forEach(function(dependency) {
      i = _findTask(queue, dependency);
      if(i != -1) {
        dependencies.push(queue[i]);
      }
    });
    dependencies.forEach(function(t) {
      _visit(queue, t);
    });
    if(task._visited === 1) {
      _sorted[index].push(task);
    }
  },
  _start = function(queue) {
    queue.forEach(function(task) {
      _visit(queue, task);
    });
  },
  _sort = function(suppress) {
    for(index = LEVEL_LOW; index <= LEVEL_HIGH; index++) {
      var queue = Q[index];
      _sorted[index] = [];
      _start(queue);
      if(!suppress && queue.length > _sorted[index].length) {
        throw new Error('Cycle found in queue: ' + queue);
      }
    }
  };

我們將按任務(wù)指定的依賴關(guān)系對同一優(yōu)先級內(nèi)的任務(wù)進行排序,確保被依賴的任務(wù)在設(shè)置依賴的任務(wù)之前運行。這是一個典型的深度優(yōu)先的拓撲排序問題,維基百科提供了一個深度優(yōu)先排序算法,大致描述如下:

圖片來自維基百科

圖片來自維基百科

  1. 訪問待排序的每一個節(jié)點
    1. 如果已經(jīng)訪問過了,則返回
    2. 否則標(biāo)記為已訪問
    3. 找出它連接(在這里是依賴)的每個節(jié)點
    4. 跳到內(nèi)層1遞歸訪問這些節(jié)點
    5. 訪問完了就把當(dāng)前節(jié)點加入已排序列表
  2. 繼續(xù)訪問下一個

如果 A 依賴 B, B 依賴 C, C 依賴 A, 那么這 3 個節(jié)點形成了循環(huán)依賴。 文中指出這個算法并不能檢測出循環(huán)依賴。通過標(biāo)記節(jié)點是否已訪問,可以解決循環(huán)依賴造成的遞歸死循環(huán)。我們來分析一下循環(huán)依賴的場景:

從節(jié)點 A 出發(fā)的時候,它被標(biāo)記為已訪問,當(dāng)從節(jié)點 C 再回到節(jié)點 A 的時候,它已經(jīng)被訪問過了。不過這個時候 C 并不知道 A 是否在自己的上游鏈上,所以不能直接判定發(fā)生了循環(huán)依賴,因為 A 可能是其他已“處理”(跑完了內(nèi)層遞歸)過的節(jié)點。如果我們知道節(jié)點是不是第一次被訪問過,就可以判斷是哪一種情況。

改造一下上面的算法,將“是否已訪問”改成“訪問計數(shù)” (task._visited++)。僅當(dāng)節(jié)點被訪問過 1 次的時候 (task._visited === 1),才將其加入到已排序列表,全部遍歷完之后,如果待排序的節(jié)點數(shù)比已排序的多 (queue.length > _sorted[index].length),則表明待排序中多出的節(jié)點發(fā)生了循環(huán)依賴。

至此,隊列管理組件的編碼實現(xiàn)已經(jīng)完成。什么?怎么使用?很簡單啦:

var t1 = new SmartQueue.Task(function() {
    alert("Hello, world!");
  }), t2 = new SmartQueue.Task(function() {
    alert("High level task has name");
  }, 2, 'myname');
t1.register(); t2.register();
SmartQueue.fire();

更多功能,如任務(wù)的依賴,等待你去發(fā)掘哦。

本期貼出的代碼都是一些局部片段,部分 helper 方法代碼沒有貼出來。查看完整的代碼請訪問這里。后面我們將介紹如何管理組件文件,以及構(gòu)建組件,下期不見不散哦。

相關(guān)文章

最新評論