我的angularjs源碼學習之旅2——依賴註入

来源:http://www.cnblogs.com/chuaWeb/archive/2016/03/10/angular-dependency-injection-in.html
-Advertisement-
Play Games

依賴註入起源於實現控制反轉的典型框架Spring框架,用來削減電腦程式的耦合問題。簡單來說,在定義方法的時候,方法所依賴的對象就被隱性的註入到該方法中,在方法中可以直接使用,而不需要在執行該函數的時候再參數中添加這些依賴對象。 理解很簡單,我們以一個例子說明 var $name = "chua",


  依賴註入起源於實現控制反轉的典型框架Spring框架,用來削減電腦程式的耦合問題。簡單來說,在定義方法的時候,方法所依賴的對象就被隱性的註入到該方法中,在方法中可以直接使用,而不需要在執行該函數的時候再參數中添加這些依賴對象。

  理解很簡單,我們以一個例子說明

var $name = "chua",$age = 26;
function myInfo($name,$age){
  alert($name + ":" + $age );
};

//普通情況下應該執行
myInfo($name,$age);//"chua:26"
//實現依賴註入以後
myInfo();//本人希望列印的結果是"chua:26",但是毫無疑問在沒有實現依賴註入之前是不好使的

那麼怎麼樣實現依賴註入呢?


  首先,需要有一個函數來接管myInfo的函數定義,不然我們沒法拿到myInfo的依賴對象名稱加以保存。

function injector(fn){
    //處理fn,保存依賴對象
    ...
    return function(){
      fn.apply({},xxx);//xxx是處理過後的參數
    }
}

//myInfo的定義變成
var myInfo = injector(function ($name,$age){
  alert($name + ":" + $age );
});
myInfo();

  injector函數是怎麼獲取到myInfo的依賴對象$name、$age的?我們可以通過傳入參數的函數fn.toString()來看,

//fn.toString()的結果為下麵的字元串
"function myInfo($name,$age){
  alert($name + ":" + $age );
}"

  參考angular的處理:從字元串中讀取函數myInfo的參數還是能讀取。

  STRIP_COMMENTS =/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,
  FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m,
  FN_ARG =/^\s*(\S+?)\s*$/;
  function injector(fn){
    //處理fn,保存依賴對象
    var fnText = fn.toString().replace(STRIP_COMMENTS, '');//"function myInfo($name,$age){ alert($name + ":" + $age ); }"
    var argDecl = fnText.match(FN_ARGS);//["function myInfo($name,$age)", "$name,$age"]
    var args = argDecl[1].split(",");//["$name","$age"]
    for(var i = 0; i < args.length; i++){
      args[i] = args[i].replace(FN_ARG,"$1");
    }
    ... 
return function(){ fn.apply({},xxx);//xxx是處理過後的參數 } }

  但是有這個還不夠,我們雖然拿到了要依賴的對象名稱,但是我們並沒有給這些名稱指定對應的對象。所以應當有一個給這些要依賴的對象註冊的過程。

  function injector(fn){
    ...
    var args = argDecl[1].split(",");
    for(var i = 0; i < args.length; i++){
      args[i] = injector.cache[args[i].replace(FN_ARG,"$1")];
    }    
    return function(){
      fn.apply({},args);
    }
  }
  injector.cache = {};
  injector.register = function(key,resource){
    injector.cache[key] = resource;
  }
//先註冊要依賴的對象 injector.register(
"$name",$name); injector.register("$age",$age);

  這個時候我們可以使用injector來聲明函數了

  var myInfo = injector(function($name,$age){
    alert($name + ":" + $age );
  });
//沒有任何參數,得到期望的結果 myInfo();//chua:26

  只要是已經註冊過的對象就多可以註入到使用injector函數來聲明的函數中使用。完整的測試代碼如下

<script>
  var $name = "chua",
  $age = 26,
  STRIP_COMMENTS =/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,
  FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m,
  FN_ARG =/^\s*(\S+?)\s*$/;
  function injector(fn){
    //處理fn,保存依賴對象
    var fnText = fn.toString().replace(STRIP_COMMENTS, '');//"function myInfo($name,$age){ alert($name + ":" + $age ); }"
    var argDecl = fnText.match(FN_ARGS);//["function myInfo($name,$age)", "$name,$age"]
    var args = argDecl[1].split(",");//["$name","$age"]
    for(var i = 0; i < args.length; i++){
      args[i] = injector.cache[args[i].replace(FN_ARG,"$1")];
    }
    return function(){
      fn.apply({},args);
    }
  }
  injector.cache = {};
  injector.register = function(key,resource){
    injector.cache[key] = resource;
  }
  //先註冊要依賴的對象
  injector.register("$name",$name);
  injector.register("$age",$age);


  //下麵這個奇跡出現的時刻
  var myInfo = injector(function($name,$age){
    alert($name + ":" + $age );
  });
  myInfo();//chua:26
</script>
View Code

   

  好了簡單的實現了一個依賴註入。但是依賴註入有一個問題,依賴註入是基於依賴對象的名稱字元串的

injector(function($name,$age){...});

  那麼上面的代碼通過一般的壓縮工具壓縮以後會變成

a(function(b,c){...});

  已經無法找到依賴對象是"$name"/"$age"對應的對象了。

  

  所以angular想了幾個辦法。 

  angular簡單註入方式為

myModule.controller('myCtrl',function($scope) {... });

  數組註釋法

 myModule.controller('myCtrl', ['$scope', function($scope) {... }]);

   顯式註入法

function myCtrl(){...};
myCtrl.$inject = ["$scope"];
myModule.controller('myCtrl',myCtrl);

   有一個第三方的插件ng-min,它可以幫你將以簡單方式註入的代碼自動轉換成數組註釋的方式,即能滿足你寫簡潔代碼的願望,又能避免壓縮出錯問題。ng-min地址:https://github.com/btford/ngmin

  

angular實現依賴註入的方式


  應該說,js的依賴註入實現方式都是類似的。angular實現的依賴註入對象結構如下

injector = {
  annotate: function annotate(fn, strictDi, name),
  get: function getService(serviceName, caller),
  has: function(name),
  instantiate: function  instantiate(Type, locals, serviceName),
  invoke: function invoke(fn, self, locals, serviceName)
}

 

1.需要有註入的對象(依賴的對象)保存的地方。

  不知道還記不記得我的angular源碼學習之旅1中提到angularModule = setupModuleLoader(window)給angular添加了模塊聲明(添加模塊)、載入(獲取模塊)的方法angular.module。而在創建註入對象的時候(createInjector函數中)有這麼一段代碼

forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });

  其中loadModules(modulesToLoad)就是去載入依賴模塊,在loadModules函數中真正去載入模塊的是這段代碼

        if (isString(module)) {
          moduleFn = angularModule(module);
          ...
        }

  angularModule即angular.module。前面也分析過這些模塊實際都保存在一個外部變數modules

  

  所以通過angular.module方法就是通過模塊名去modules上添加或取出其中的模塊。

  

2.獲取依賴註入列表(即要依賴的對象的名稱)

  獲取方式也是通過fn.toString()來獲取的。angular的實現是injector.annotate,函數體為

function annotate(fn, strictDi, name) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn === 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        ...
        fnText = fn.toString().replace(STRIP_COMMENTS, '');
        argDecl = fnText.match(FN_ARGS);
        forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
          arg.replace(FN_ARG, function(all, underscore, name) {
            $inject.push(name);
          });
        });
      }
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    ...
  } else {
    ...
  }
  return $inject;
}

  只是應為angular接受多種註入方式所以處理代碼有多個分支,而且也只是保存了依賴對象名稱,並沒有返回執行函數,執行函數是統一的injector.invoke。

  先前我們自己的實現執行是直接調用的myInfo();angular的執行函數是injector.invoke,函數體如下

    function invoke(fn, self, locals, serviceName) {
      if (typeof locals === 'string') {
        serviceName = locals;
        locals = null;
      }

      var args = [],
          $inject = createInjector.$$annotate(fn, strictDi, serviceName),
          length, i,
          key;

      for (i = 0, length = $inject.length; i < length; i++) {
        key = $inject[i];
        ...
//獲取依賴對象
args.push( locals && locals.hasOwnProperty(key) ? locals[key] : getService(key, serviceName) ); } if (isArray(fn)) { fn = fn[length]; } // http://jsperf.com/angularjs-invoke-apply-vs-switch // #5388 返回執行結果 return fn.apply(self, args); }

 

   好了angularjs的依賴註入實現分析到此結束。更詳細的細節可以斷點跟蹤。

 

   如果覺得本文不錯,請點擊右下方【推薦】! 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 創建對象 var box = new Object();//創建對象 box.name = 'Lee'; //添加屬性 box.age = 100; box.run = function(){ return this.name + this.age + "運行中"; //this 表示當前作用域下對
  • javascript得到客戶端IP的新方法 很久以來,我都是經過http://fw.qq.com/ipaddress來得到客戶端用戶的IP,這個方法簡單、快速、實用 。 我們調用它的寫法是: <script type="text/javascript" src="http://fw.qq.com/i
  • 在HTML頁面完成展現之後,動態改變頁面元素或調整CSS樣式都會引起瀏覽器重繪,性能的損耗直接取決於動態改變的範圍:如果只是改變一個元素的顏色之類的信息則只會重繪該元素;而如果是增刪節點或調整節點位置則會引起其兄弟節點也一併重繪。 減少重繪並不是說不要重繪,而是要註意重繪範圍:①改...
  • jQuery Easy是一組基於jQuery的UI插件集合,EasyUI的目標就是幫助web開發者更輕鬆的打造出功能豐富並且美觀的UI界面。開發者不需要編寫複雜的javascript,也不需要對css樣式有深入的瞭解,開發者需要瞭解的只有一些簡單的html標簽。 註:jQuery的特點 基於jQue
  • 在ECMAScript5中對Object新增的些方法,學習以及demo
  • HTML代碼: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://
  • HTML代碼: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://
  • 一、框架介紹 RequireJS 資料:http://www.requirejs.cn/RequireJS的目標是鼓勵代碼的模塊化,它使用了不同於傳統<script>標簽的腳本載入步驟。可以用它來加速 、優化代碼,但其主要目的還是為了代碼的模塊化。它鼓勵在使用腳本時以module ID替代URL地址
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...