[ AngularJS ] 本身實現一個簡單的依賴注入

咱們開始使用AngularJS的時候,它的雙向數據綁定是最讓咱們印象深入的,那第二個就應該算是它的那神奇的依賴注入的功能了。javascript

舉個栗子

function myController = ($scope, $http){
    $http.get('users/users.json').success(function(data){
        $scope.users = data;
    })
}

這是一個典型的angularjs的控制器,他發送了一個http請求,從後臺獲取json數據,而後把他傳遞給當前做用域。你會發現,咱們並無執行這個myController函數(咱們沒有機會給它傳遞參數),其實,是angular這個框架幫咱們作了;那麼,$scope, $http這些變量從哪裏來的呢? 這是angular的一個很是酷的特性,咱們一步步來實現一個簡單的注入器,從而知道它是怎麼實現的。html

傳統的方式

如今,咱們有一個js函數去把用戶列表展現在網頁上,那麼這個函數就須要從ajax獲取過來的數據和用於展現數據的DOM元素。爲了簡單點,咱們直接用靜態數據了來代替ajax的http請求:java

var data = ['John', 'Steve', 'David'];
var body = document.querySelector('body');
var ajaxFn = {
  get: function(path, cb) {
    console.log(path + ' requested');
    cb(data);
  }
}

咱們把body標籤做爲列表的容器,ajaxFn是模擬ajax請求的對象,data是一個包含全部用戶的數組。下面來使用:angularjs

var displayUsers = function(domEl, ajax) {
  ajax.get('/api/users', function(users) {
    var html = '';
    for(var i=0; i < users.length; i++) {
      html += '<p>' + users[i] + '</p>';
    }
    domEl.innerHTML = html;
  });
}

很明顯,咱們運行displayUsers(body, ajaxFn),咱們將會看到三個用戶名顯示在網頁上,同時控制檯輸出了/api/users requested。那這樣的話,咱們能夠認爲咱們的displayUsers函數有兩個依賴,bodyajaxFn.ajax

那麼如今,咱們的目的是使displayUsers這個函數在不傳參數的狀況下照樣工做,即,運行displayUsers()要獲得和上面同樣的結果,若是咱們直接運行,你會發現報錯了:正則表達式

Uncaught TypeError: Cannot read property 'get' of undefined

很明顯是由於ajax這個參數沒有定義。json

來實現依賴注入

如今大部分的框架都提供依賴注入機制的模塊,可能會叫作:injector。爲了在某個地方使用一個依賴,咱們須要在那個地方註冊那個依賴:api

來建立咱們的injector數組

var injector = {
  storage: {},
  register: function(name, resource) {
    this.storage[name] = resource;
  },
  resolve: function(target) {

  }
};

咱們只須要兩個方法,第一個是register, 接收咱們的依賴並將它存儲起來。第二個方法是resolve,這個方法接收的參數是咱們咱們要注入依賴的對象,這裏的關鍵的點就是這個注入器不該該調用咱們的函數,因此咱們在resolve方法中返回一個閉包來包裹咱們的target,而後再調用它:閉包

resolve: function(target) {
  return function() {
    target();
  };
}

那麼到如今呢,咱們的注入器依然是不可用的:

displayUsers = injector.resolve(displayUsers);
displayUsers();

咱們依然獲得一樣的錯誤,你以爲少了什麼呢?絕壁就是依賴項了,因此咱們下一步就是要找出這個函數的依賴項,如何找? 這是比較棘手的問題,不過咱們能夠參考angularjs的方式:

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
...
function annotate(fn) {
  ...
  fnText = fn.toString().replace(STRIP_COMMENTS, '');
  argDecl = fnText.match(FN_ARGS);
  ...
}
We purpo

這裏省略了一些細節部分,咱們須要注意的就是annotate這個函數,它負責將目標函數轉換爲一個字符串,刪除它的註釋(若是有),而後提取他的參數(依賴項):

resolve: function(target) {
  var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
  fnText = target.toString().replace(STRIP_COMMENTS, '');
  argDecl = fnText.match(FN_ARGS);
  console.log(argDecl);
  return function() {
    target();
  }
}

上面主要是經過正則表達式來找到依賴項,來看看結果:

clipboard.png

你會看到返回的數組中包含了咱們須要的依賴項,咱們找到它們並用注入器註冊和存儲:

resolve: function(target) {
  var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
  fnText = target.toString().replace(STRIP_COMMENTS, '');
  argDecl = fnText.match(FN_ARGS)[1].split(/, ?/g);
  var args = [];
  for(var i=0; i&lt;argDecl.length; i++) {
    if(this.storage[argDecl[i]]) {
      args.push(this.storage[argDecl[i]]);
    }
  }
  return function() {
    target.apply({}, args);
  }
}

如今,咱們可使用咱們的注入器了:

injector.register('domEl', body);
injector.register('ajax', ajaxFn);

displayUsers = injector.resolve(displayUsers);
displayUsers();

你會發現獲得了和剛開始同樣的結果。

這樣作有什麼優勢呢?有點就是咱們能夠把DOM元素和ajaxFn注入到任何一個函數中,咱們甚至能夠將咱們的應用程序配置成這樣的方式,那咱們就不用經過繼承來傳遞這些對象了。這僅僅是injector的register和resolve方法而已,咱們的注入器還不夠完美,還有擴展空間,好比定義做用域scope.

而angularjs中的依賴注入更增強大:

displayUsers = injector.resolve(['domEl', 'ajax', displayUsers]);

和displayUsers 不一樣,它傳遞的是真實的依賴名稱。

譯自:http://www.sitepoint.com/revealing-magic-javascript/

有錯誤還請指正!

相關文章
相關標籤/搜索