1. 程式人生 > >30行程式碼讓你理解angular依賴注入:angular 依賴注入原理

30行程式碼讓你理解angular依賴注入:angular 依賴注入原理

依賴注入(Dependency Injection,簡稱DI)是像C#,java等典型的面嚮物件語言框架設計原則控制反轉的一種典型的一種實現方式,angular把它引入到js中,介紹angular依賴注入的使用方式的文章很多,
angular官方的文件,也有很詳細的說明。但介紹原理的較少,angular程式碼結構較複雜,文章實現了一簡化版本的DI,核心程式碼只有30行左右,相看實現效果(可能需FQ)或檢視原始碼

這篇文章用盡量簡單的方式說一說 angular依賴注入的實現。

簡化的實現原理

要實現注入,基本有三步:

  1. 得到模組的依賴項
  2. 查詢依賴項所對應的物件
  3. 執行時注入

1. 得到模組的依賴項

javascript 實現DI的核心api是Function.prototype.toString,對一個函式執行toString,它會返回函式的原始碼字串,這樣我們就可以通過正則匹配的方式拿到這個函式的引數列表:

function extractArgs(fn) { //angular 這裡還加了註釋、箭頭函式的處理
            var args = fn.toString().match(/^[^\(]*\(\s*([^\)]*)\)/m);
            return args[1].split(',');
        }

2. 查詢依賴項所對應的物件

java與.net通過反射來獲取依賴物件,js是動態語言,直接一個object[name]

就可以直接拿到物件。所以只要用一個物件儲存物件或函式列表就可以了

function createInjector(cache) {
            this.cache = cache;

        }
angular.module = function () {
            modules = {};
            injector = new createInjector(modules);
            return {
                injector: injector,
                factory: function (name, fn) {
                    modules[name.trim()] = this.injector.invoke(fn); 
                    return this;
                }
            }
        };

3. 執行時注入

最後通過 fn.apply方法把執行上下文,和依賴列表傳入函式並執行:


createInjector.prototype = {
            invoke: function (fn, self) {
                argsString = extractArgs(fn);
                args = [];
                argsString.forEach(function (val) {
                    args.push(this.cache[val.trim()]);
                }, this);
                return fn.apply(self, args);
            }
        };

這裡是簡化的版本,實際angular的實現考慮了很多問題,如模組管理,延遲執行等

angular 的實現

為了簡單,我們也按這三步來介紹angular DI

  1. 得到模組的依賴項
  2. 查詢依賴項所對應的物件
  3. 執行時注入

注:以下程式碼行數有就可能變

1. 得到模組的依賴項

var ARROW_ARG = /^([^\(]+?)=>/;
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

function extractArgs(fn) {
  var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
      args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
  return args;
}

2. 查詢依賴項所對應的物件

    function getService(serviceName, caller) {
      if (cache.hasOwnProperty(serviceName)) {
        if (cache[serviceName] === INSTANTIATING) {
          throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
                    serviceName + ' <- ' + path.join(' <- '));
        }
        return cache[serviceName];
      } else {
        try {
          path.unshift(serviceName);
          cache[serviceName] = INSTANTIATING;
          return cache[serviceName] = factory(serviceName, caller);
        } catch (err) {
          if (cache[serviceName] === INSTANTIATING) {
            delete cache[serviceName];
          }
          throw err;
        } finally {
          path.shift();
        }
      }
    }

3. 執行時注入

得到引數:

    function injectionArgs(fn, locals, serviceName) {
      var args = [],
          $inject = createInjector.$$annotate(fn, strictDi, serviceName);

      for (var i = 0, length = $inject.length; i < length; i++) {
        var key = $inject[i];
        if (typeof key !== 'string') {
          throw $injectorMinErr('itkn',
                  'Incorrect injection token! Expected service name as string, got {0}', key);
        }
        args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
                                                         getService(key, serviceName));
      }
      return args;
    }

呼叫

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

      var args = injectionArgs(fn, locals, serviceName);
      if (isArray(fn)) {
        fn = fn[fn.length - 1];
      }

      if (!isClass(fn)) {
        // http://jsperf.com/angularjs-invoke-apply-vs-switch
        // #5388
        return fn.apply(self, args);
      } else {
        args.unshift(null);
        return new (Function.prototype.bind.apply(fn, args))();
      }
    }

angular模組管理,深坑

angular在每次應用啟動時,初始化一個Injector例項:

var injector = createInjector(modules, config.strictDi);

由此程式碼可以看出對每一個Angular應用來說,無論是哪個模組,所有的"provider"都是存在相同的providerCache或cache中

所以會導致一個被譽為angular模組管理的坑王的問題:
module 並沒有什麼名稱空間的作用,當依賴名相同的時候,後面引用的會覆蓋前面引用的模組。

具體的示例可以檢視:

注:angular di用本文的呼叫方式壓縮程式碼會出問題:可以用g-annotate轉為安全的呼叫方式。

到此angular di的實現原理已完成簡單的介紹,angular用了專案中幾乎不會用到的api:Function.prototype.toString 實現依賴注入,思路比較簡單,但實際框架中考慮的問題較多,更加詳細的實現可以直接看angular的原始碼

以後會逐步介紹angular其它原理。

相關推薦

30程式碼理解angular依賴注入angular 依賴注入原理

依賴注入(Dependency Injection,簡稱DI)是像C#,java等典型的面嚮物件語言框架設計原則控制反轉的一種典型的一種實現方式,angular把它引入到js中,介紹angular依賴注入的使用方式的文章很多, angular官方的文件,也有很詳細的說明。但介紹原理的較少,angular程式碼

設計模式三段程式碼理解 裝飾者模式

package com.zx.b_decorator; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader;

Python 十程式碼秒變撩妹達人

做微信聊天機器人,實現步驟: 1.獲取微信的使用權,即python指令碼能控制微信收發資訊。 2.python指令碼收到聊天資訊後,要對該資訊進行處理,返回機器人的迴應資訊。 簡易版程式碼 from wxpy import * #apikey在http://www.t

30 js 幫理解日曆是如何生成的

javascript的日期物件Date 是程式設計中非常常用的物件之一,這篇文章旨在幫助大家熟悉日期物件,並理解使用日期物件來建立一個日曆。 言歸正傳 先看看簡單的效果,後面還有複雜的 來看程式碼[javascript] var weeks = "一二三四五六日

C#--三程式碼理解神祕的拆箱和裝箱

一、在說拆箱和裝箱之前的準備知識 首先,我們需要知道C#中有兩種型別:值型別和引用型別 名稱 值型別 引用型別 表示型別 基本型別 類,陣列,介面 ,C#特有的委託. 儲存內容 值 值的引用 儲存位

10程式碼秒變撩妹達人用Python做一個聊天機器人

導讀:用Python做一個聊天機器人,這樣你就可以邊寫程式碼邊撩妹了~作者:大鄧來源:大鄧和他的

用著短短的30程式碼,帶一起去看朋友們的人生態度!

微信是現在人們生活中不可或缺的一部分,通過微信的朋友圈,你能看到朋友們的生活動態,能看到他們激勵自己或朋友的簽名,你能看許許多多的正能量。   今天就用 Python 把你微信朋友的簽名收集起來做成詞雲。從簽名大致能看出一個人的人生態度,今天就來看看你的微信朋友態度。 專

資料結構 | 30程式碼,手把手帶實現Trie樹

本文始發於個人公眾號:**TechFlow**,原創不易,求個關注 今天是演算法和資料結構專題的第28篇文章,我們一起來聊聊一個經典的字串處理資料結構——Trie。 在之前的4篇文章當中我們介紹了關於博弈論的一些演算法,其中應用最廣也是最重要的就是最後的SG函式。瞭解到這些之後,足夠我們應付常見的博弈

一分鐘內理解什麽是產品經理?

產品經理前幾天在某論壇上看見一個段子,生動形象地描繪出了產品經理、程序員、需求者三者之間那無以言表的關系圖。看完這個段子也深刻體會到今年剛上線的大大神平臺責任重大,該平臺主要以產品經理為特色推出,平臺又要怎樣為三者之間創造出一個和諧共處的環境,讓我很是期待!1.劉大爺有塊地,想蓋個平房養老,聘請小明全權負責蓋

30程式碼實現Javascript中的MVC

從09年左右開始,MVC逐漸在前端領域大放異彩,並終於在剛剛過去的2015年隨著React Native的推出而迎來大爆發:AngularJS、EmberJS、Backbone、ReactJS、RiotJS、VueJS…… 一連串的名字走馬觀花式的出現和更迭,它們中一些已經漸漸淡出了大家的視

一個最簡單的程式理解多徑通道

原文地址:https://wenku.baidu.com/view/f4bb76fe941ea76e58fa044d.html 時變、多徑是無線通道的特點,相信很多人在看了很多書之後,對無線通道感覺還是一頭霧水。為什麼多徑導致頻率選擇性?為什麼多普勒頻移反映了通道的時變性?對這些問題感覺困惑的肯

30程式碼實現微信自動回覆機器人

一、寫在前面 前段時間寫過一篇微信好友大揭祕,很多朋友對itchat非常感興趣,今天下午又學到了itchat另一種有趣的玩法---微信自動回覆機器人。程式很簡單僅僅三十行程式碼左右,實現了機器人自動與你的微信好友聊天。   二、程式介紹   本程式通過itch

一個例子理解重定向

        重定向過程好比有個綽號叫“瀏覽器”的人寫信找張三借錢,張三回信說沒有錢,讓“瀏覽器”去找李四借,並將李四現在的通訊地址告訴給了“瀏覽器 ”。於是,“瀏覽器”又按張三提供通訊地址給李四寫信借錢,李四收到信後就把錢匯給了“瀏覽器”。可見,“瀏覽器”

用3程式碼Python資料處理指令碼獲得4倍提速!

Python是一門非常適合處理資料和自動化完成重複性工作的程式語言,我們在用資料訓練機器學習模型之前,通常都需要對資料進行預處理,而Python就非常適合完成這項工作,比如需要重新調整幾十萬張影象的尺寸,用Python沒問題!你幾乎總是能找到一款可以輕鬆完成資料處理工作的Python庫。 然而,

如何用 30 程式碼實現微信自動回覆機器人?

作者 | Ahab 責編 | 胡巍巍 寫在前面 很多朋友對itchat非常感興趣,近日又學到了itchat另一種有趣的玩法——微信自動回覆機器人。 程式很簡單僅僅三十行程式碼左右,實現了機器人自動與你的微信好友聊天,下面是我的機器人小籠包跟自己微

一圖理解Linux中的環境變數設定

本文首先介紹了環境變數的通俗理解,然後給出了幾種不同設定方式的詳細解釋,與其他部落格不同,本部落格通過一張圖總體描述不同設定方式的作用範圍,生效時間。最後通過一個具體的例子說明了環境變數的設定格式。 什麼是環境變數 所謂的環境變數有時候是一種“快捷方式”,有時候又是

從R-CNN到RFBNet,深度目標檢測5年縱覽,文章+程式碼從入門到精通(轉)

mark一下,最早是從機器學習研究會上看到,收藏之後一直沒時間細讀。現開始閱讀並分享出來。最後感謝作者分享! 文章名 | Deep Learning for Generic Object Detection: A Survey 文章地址 | https://arxiv.org/abs/1

threejs第一課 30程式碼實現旋轉的立方體

需要電子檔書籍可以Q群:828202939   希望可以和大家一起學習、一起進步!! 所有的課程原始碼在我上傳的資源裡面,本來想設定開源,好像不行!部落格和專欄同步! 如有錯別字或有理解不到位的地方,可以留言或者加微信15250969798,在下會及時修改!!!!!

12程式碼教會用python讀excel檔案,提取資料,生成條形碼

  一、需求分析 條形碼應用廣泛,尤其是人事、財務和庫管等等崗位,常需根據excel檔案成批生成條碼,如果是經常性天天做,用excel的自己控制元件還是很枯燥煩人的。 當然在學習Python的道路上肯定會困難,沒有好的學習資料,怎麼去學習呢?  

10程式碼用Python掃描Excel表格,自動生成條形碼!

  一、需求分析 條形碼應用廣泛,尤其是人事、財務和庫管等等崗位,常需根據excel檔案成批生成條碼,如果是經常性天天做,用excel的自己控制元件還是很枯燥煩人的。 當然在學習Python的道路上肯定會困難,沒有好的學習資料,怎麼去學習呢? 學習Python