咱們開始使用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函數有兩個依賴,body
和ajaxFn
.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(); } }
上面主要是經過正則表達式來找到依賴項,來看看結果:
你會看到返回的數組中包含了咱們須要的依賴項,咱們找到它們並用注入器註冊和存儲:
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<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/
有錯誤還請指正!