我們大家近幾年都接觸過一個前端框架叫做 angularjs,里面對它對描述非常簡單,MVW(Model-View-Whatever)、模塊化、自動化雙向數據綁定、語義化標簽、依賴注入,每一個標簽都非常都高大上,就跟我們看名片一樣,某某集團公司總經理/中國xxx理事會成員/世界xxx組織干事, 看起來不明覺厲 好高大上都樣子 但是到底是特么什么玩應 我們還是很迷糊都,就覺得很厲害都樣子
最近正好無聊 跟大家吹牛逼 動不動就是源碼啦 底層了,博客 github地址啦, 讓我懷疑這個社會大家都技術都到達了好幾層樓樓都高度,然后一看~尼瑪 全特么是你吵我 我抄你都文章 , 為了以后能來個小十三 也偶爾寫寫自己對框架底層和源碼對編寫。
這里首先講講什么是依賴注入,先上圖為證
現實世界中,當我們要買一件或幾件商品時候,首先需要的是金錢,當沒有金錢的時候會向父母索取,那么我們最后實現的目的 也就是買東西的多少和價錢 取決於父母給予我們的多少.
程序同樣如此,我們要實現一個功能,如以加密格式對數據進行加密,就要使用md5這個插件或其他加密工具, 在這里 程序的結果 和 加密算法 就是一個依賴關系,只有有加密算法 我們才能又一個加密的數據。
那依賴我們簡單知道了,什么是注入呢? 還是看代碼,以angular為例
<script> var person = {name:"黎明",age:18} window.onload = function() { alert(person.name+"說我今年"+person.age) } </script>
在上面代碼中,alert程序需要依賴與person對象,但是在程序運行完以后person對象就沒有存在的價值了,在這個時候,person還是會繼續暫居在內存中,這時候我們就有兩種處理方式 1.等待瀏覽器或程序關閉,自動清除內存, 2 手動刪除程序
我們換一種編寫方式,將person對象只有在程序需要的時候再將其創建,當引用完成再對它刪除
1 <script> 2 3 4 5 var createPerson = function() { 6 var person = {name:"黎明",age:18} 7 return person; 8 } 9 10 11 window.onload = function() { 12 13 var obj = createPerson(); 14 15 alert(obj.name+"說我今年"+obj.age) 16 17 } 18 19 20 </script>
在這里,創建person對函數為createPerson,如果程序沒有運行就是一個函數表達式而已,占據很少對內存效果,在window.onload中 我們需要使用調用createPerson創建對象,在使用完成后,函數內部自動進行GC銷毀,這個過程就是注入,當然有人會說我看到對是顯示聲明創建對象 沒有注入啊。 如果我們把 創建var obj = createPerson() 的過程 以自動加載的方式表示出來,並提供緩存/隊列/單例 等功能 那么就是一個注入等過程了 ,因為有依賴 所以我們要調用,在調用等過程又不想顯示聲明,想自動化處理 所以提供了注入等思想概念。
我們首先以angularjs等思維方式簡單編寫一個依賴注入程序
var angular = function() { } angular.module = function() { return this; }
//其實更建議使用prototype編寫,但是怕麻煩 大家看不懂就放棄了 angular.$httpProvider = function() { } angular.controller = function(name,fn) { fn(); console.info("控制器結束"); } var app = angular.module("a"); app.controller("indexCtrl",function() { console.info("運行控制器"); });
上面是以我個人理解對ng簡單編寫的一個小函數,在上面中,我們很容易看到 controller運行流程是: 1.運行控制器 2.控制器結束
我們接下來完善$httpProvider服務,並在controller中進行引用
angular.$httpProvider = function() {
//ajax封裝 => 代碼意思意思就好 不要較真 var ajax = function(obj) { var xhr = new XMLHttpRequest(); xhr.open(obj.method,obj.uri,obj.async); xhr.onreadystatechange = function() { if(xhr.readyState==4 && xhr.status == 200) { var data = JSON.parse(xhr.responseText); obj.callBack(xhr.responseText); } } xhr.send(); } this.$http = ajax; }
app.controller("indexCtrl", function() {
console.info(this, this.$httpProvider , "打印數據" );
})
在最終打印我們可以看到 結果是 ( window對象,undefined , '打印數據' ),也就是說 this指針不能直接指向app,那有的小伙伴說了,我們可以直接使用app.$httpProvider 進行調用,不是說不可以,但是如果大家接觸過設計模式和面向對象的編程方式就知道是不可取的,而且 我們要盡量做到讓程序智能化,也就是說 進行引用的時候 讓程序內部去進行調用,我們只需要使用現成的就好了,我們繼續更改代碼
//錯誤代碼
angular.controller = function(name,fn) { fn(); console.info("控制器結束"); }
//正確代碼
angular.controller = function(name,fn) {
var http = angular.$httpProvider(); //步驟1 調用依賴的 ajax
fn(http); //步驟2 注入進ajax
}
app.controller("indexCtrl",function($httpProvider) {
$httpProvider.get({
method:"get",
uri:"https://www.baidu.com"
})
});
那有些小伙伴看到這里又要炸鍋了,什么破玩應,為啥在angular.controller里直接就創建ajax對象了,不應該是我注入什么創建什么么,所以我們需要在 上面代碼的步驟1中 將 傳入進來的函數進行拆分截取獲取對應的服務名稱
angular.controller = function(name,fn) { var fnStr = fn.toString(); //將調用controller傳入進來的匿名函數轉化為字符串,通過正則 獲取里面傳入的參數名稱 //為了簡單我們直接索引獲取 ()內部的參數 var startI = fnStr.indexOf('(')+1,lastI = fnStr.indexOf(')'); var params = fnStr.substring(startI,lastI).split(',');
//params 就是我們傳入進去的服務名稱,很神奇吧 其實這里我們要獲取的是它字符串 //接下來就是通過特定的函數去尋找到對應的服務,這里面在ng中成為注入器 inject var injectArr = [];
params.forEach(function(vl) { //循環調用每一個注入器需要的實例對象 var injectObj = angular[vl]() injectArr.push(injectObj) //臨時保存數組中 后期注入進去 }); //apply替換參數,因為不是確定有多少個參數傳遞進去 以為數組 apply的方式傳值 fn.apply(this,injectArr) }
上面代碼看起來雖然突然間多了一點,但是還是可以看明白多,也就是說 我們傳入進去多函數 獲取參數名稱,根據參數名稱尋找到對應到服務去創建,然后將服務到實例對象傳如匿名函數中運行就好了。這樣就簡單多了,我們重新梳理下理解多邏輯。
當然,我這里面只是寫了一個非常簡單到依賴注入關系,並沒有對單例/緩存/隊列做出處理,一個完整對依賴注入還是要考慮到這些到。
既然東西我們寫完了,我們在回首下依賴注入到標准化定義:
依賴注入: 將程序創建到過程交給框架進行處理,管理程序對聲明周期和模型,自動注入到需要使用到另一個程序中到過程,成為依賴注入。
我們可以看到一個最大到特點,依賴注入這種程序到編寫和理解方式對我們對代碼初期造成了極大對復雜度,但是如果程序越來越大,特別是呈幾何倍對增長時,依賴注入管理程序帶來對好處與所損耗對性能來說是不值一提對。
本來就是做個隨筆 大家簡單清除這個流程就好了,我們稍微不填點代碼 將注入對對象緩存在對象中,這樣我們就好理解為什么在ng對程序中服務只創建一次(其實叫注冊),為什么服務沒有對應對會報錯了
var angular = function() { } angular.module = function() { return this; } angular.$httpProvider = function() { //服務只創建一次 單例模式 console.info("初始化$http服務"); var ajax = function(obj) { var xhr = new XMLHttpRequest(); xhr.open(obj.method,obj.uri,obj.async); xhr.onreadystatechange = function() { if(xhr.readyState==4 && xhr.status == 200) { var data = JSON.parse(xhr.responseText); obj.callBack(xhr.responseText); } } xhr.send(); } return { get:ajax, post:ajax, test:"測試" } } angular.cacheProvider = {} angular.controller = function(name,fn) { var fnStr = fn.toString(); var startI = fnStr.indexOf('(')+1,lastI = fnStr.indexOf(')'); var params = fnStr.substring(startI,lastI).split(','); var injectArr = []; params.forEach(function(vl) { var injectObj; //看看是否存在 不存在創建 存在直接走緩存 if(angular.cacheProvider[vl]) { injectObj = angular.cacheProvider[vl]; } else {//沒有創建過 新創建 try { injectObj = angular[vl]() angular.cacheProvider[vl] = injectObj }catch(e) { console.error("沒有映射到對服務名稱"); return; } } injectArr.push(injectObj) }); fn.apply(this,injectArr) } var app = angular.module("a"); var fn = function($httpProvider) { console.info("運行控制器",$httpProvider,$httpProvider.test); } app.controller("indexCtrl",fn); app.controller("secondCtrl",fn);