亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

深入理解Javascript里的依賴注入

2019-11-20 20:55:54
字體:
來源:轉載
供稿:網友


遲早你需要用到其他開發人員的抽象成果――即你依靠別人的代碼。我喜歡依賴自由(無依賴)的模塊,但那是難以實現的。甚至你創建的那些漂亮的黑盒子組件也或多或少會依賴一些東西。這正是依賴注入大顯身手的之處。現在有效地管理依賴的能力是絕對必要的。本文總結了我對問題探索和一些的解決方案。

一、目標
設想我們有兩個模塊。第一個是負責Ajax請求服務(service),第二個是路由(router)。

復制代碼 代碼如下:

var service = function() {
    return { name: 'Service' };
}
var router = function() {
    return { name: 'Router' };
}

我們有另一個函數需要用到這兩個模塊。
復制代碼 代碼如下:

var doSomething = function(other) {
    var s = service();
    var r = router();
};

為使看起來更有趣,這函數接受一個參數。當然,我們完全可以使用上面的代碼,但這顯然不夠靈活。如果我們想使用ServiceXML或ServiceJSON呢,或者如果我們需要一些測試模塊呢。我們不能僅靠編輯函數體來解決問題。首先,我們可以通過函數的參數來解決依賴性。即:
復制代碼 代碼如下:

var doSomething = function(service, router, other) {
    var s = service();
    var r = router();
};

我們通過傳遞額外的參數來實現我們想要的功能,然而,這會帶來新的問題。想象如果我們的doSomething 方法散落在我們的代碼中。如果我們需要更改依賴條件,我們不可能更改所有調用函數的文件。

我們需要一個能幫我們搞定這些的工具。這就是依賴注入嘗試解決的問題。讓我們寫下一些我們的依賴注入解決辦法應該達到的目標:

我們應該能夠注冊依賴關系
1.注入應該接受一個函數,并返回一個我們需要的函數
2.我們不能寫太多東西――我們需要精簡漂亮的語法
3.注入應該保持被傳遞函數的作用域
4.被傳遞的函數應該能夠接受自定義參數,而不僅僅是依賴描述
5.堪稱完美的清單,下面 讓我們實現它。
三、RequireJS / AMD的方法
你可能對RequireJS早有耳聞,它是解決依賴注入不錯的選擇。

復制代碼 代碼如下:

define(['service', 'router'], function(service, router) {      
    // ...
});

這種想法是先描述需要的依賴,然后再寫你的函數。這里參數的順序很重要。如上所說,讓我們寫一個叫做injector的模塊,能接受相同的語法。
復制代碼 代碼如下:

var doSomething = injector.resolve(['service', 'router'], function(service, router, other) {
    expect(service().name).to.be('Service');
    expect(router().name).to.be('Router');
    expect(other).to.be('Other');
});

doSomething("Other");
再繼續之前我應該解釋清楚doSomething函數體內容,我使用expect.js (斷言方面的庫)僅是為了保證我寫的代碼的行為和我期望的是一樣的,體現一點點TDD(測試驅動開發)方法。
下面開始我們的injector模塊,這是非常棒的一個單例模式,所以它能在我們程序的不同部分工作的很好。
復制代碼 代碼如下:

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function(deps, func, scope) {

    }
}


這是一個非常簡單的對象,有兩個方法,一個用來存儲的屬性。我們要做的是檢查deps數組并在dependencies變量中搜索答案。剩下的只是調用.apply方法并傳遞之前的func方法的參數。
復制代碼 代碼如下:

resolve: function(deps, func, scope) {
    var args = [];
    for(var i=0; i<deps.length, d=deps[i]; i++) {
        if(this.dependencies[d]) {
            args.push(this.dependencies[d]);
        } else {
            throw new Error('Can/'t resolve ' + d);
        }
    }
    return function() {
        func.apply(scope || {}, args.concat(Array.prototype.slice.call(arguments, 0)));
    }       
}

scope是可選的,Array.prototype.slice.call(arguments, 0)是必須的,用來將arguments變量轉換為真正的數組。到目前為止還不錯。我們的測試通過了。這種實現的問題是,我們需要寫所需部件兩次,并且我們不能混淆他們的順序。附加的自定義參數總是位于依賴之后。

四、反射方法
根據維基百科的定義反射是指一個程序在運行時檢查和修改一個對象的結構和行為的能力。簡單的說,在JavaScript的上下文里,這具體指讀取和分析的對象或函數的源代碼。讓我們完成文章開頭提到的doSomething函數。如果你在控制臺輸出doSomething.tostring()。你將得到如下的字符串:

復制代碼 代碼如下:

"function (service, router, other) {
    var s = service();
    var r = router();
}"

通過此方法返回的字符串給我們遍歷參數的能力,更重要的是,能夠獲取他們的名字。這其實是Angular 實現它的依賴注入的方法。我偷了一點懶,直接截取Angular代碼中獲取參數的正則表達式。
復制代碼 代碼如下:

/^function/s*[^/(]*/(/s*([^/)]*)/)/m

我們可以像下面這樣修改resolve 的代碼:
復制代碼 代碼如下:

resolve: function() {
    var func, deps, scope, args = [], self = this;
    func = arguments[0];
    deps = func.toString().match(/^function/s*[^/(]*/(/s*([^/)]*)/)/m)[1].replace(/ /g, '').split(',');
    scope = arguments[1] || {};
    return function() {
        var a = Array.prototype.slice.call(arguments, 0);
        for(var i=0; i<deps.length; i++) {
            var d = deps[i];
            args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
        }
        func.apply(scope || {}, args);
    }       
}

我們執行正則表達式的結果如下:
復制代碼 代碼如下:

["function (service, router, other)", "service, router, other"]

看起來,我們只需要第二項。一旦我們清楚空格并分割字符串就得到deps數組。只有一個大的改變:
復制代碼 代碼如下:

var a = Array.prototype.slice.call(arguments, 0);
...
args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());

我們循環遍歷dependencies數組,如果發現缺失項則嘗試從arguments對象中獲取。謝天謝地,當數組為空時,shift方法只是返回undefined,而不是拋出一個錯誤(這得益于web的思想)。新版的injector 能像下面這樣使用:
復制代碼 代碼如下:

var doSomething = injector.resolve(function(service, other, router) {
    expect(service().name).to.be('Service');
    expect(router().name).to.be('Router');
    expect(other).to.be('Other');
});
doSomething("Other");

不必重寫依賴并且他們的順序可以打亂。它仍然有效,我們成功復制了Angular的魔法。

然而,這種做法并不完美,這就是反射類型注射一個非常大的問題。壓縮會破壞我們的邏輯,因為它改變參數的名字,我們將無法保持正確的映射關系。例如,doSometing()壓縮后可能看起來像這樣:

復制代碼 代碼如下:

var doSomething=function(e,t,n){var r=e();var i=t()}
Angular團隊提出的解決方案看起來像:

var doSomething = injector.resolve(['service', 'router', function(service, router) {

}]);


這看起來很像我們開始時的解決方案。我沒能找到一個更好的解決方案,所以決定結合這兩種方法。下面是injector的最終版本。
復制代碼 代碼如下:

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function() {
        var func, deps, scope, args = [], self = this;
        if(typeof arguments[0] === 'string') {
            func = arguments[1];
            deps = arguments[0].replace(/ /g, '').split(',');
            scope = arguments[2] || {};
        } else {
            func = arguments[0];
            deps = func.toString().match(/^function/s*[^/(]*/(/s*([^/)]*)/)/m)[1].replace(/ /g, '').split(',');
            scope = arguments[1] || {};
        }
        return function() {
            var a = Array.prototype.slice.call(arguments, 0);
            for(var i=0; i<deps.length; i++) {
                var d = deps[i];
                args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
            }
            func.apply(scope || {}, args);
        }       
    }
}

resolve訪客接受兩或三個參數,如果有兩個參數它實際上和文章前面寫的一樣。然而,如果有三個參數,它會將第一個參數轉換并填充deps數組,下面是一個測試例子:
復制代碼 代碼如下:

var doSomething = injector.resolve('router,,service', function(a, b, c) {
    expect(a().name).to.be('Router');
    expect(b).to.be('Other');
    expect(c().name).to.be('Service');
});
doSomething("Other");

你可能注意到在第一個參數后面有兩個逗號――注意這不是筆誤。空值實際上代表“Other”參數(占位符)。這顯示了我們是如何控制參數順序的。

五、直接注入Scope
有時我會用到第三個注入變量,它涉及到操作函數的作用域(換句話說,就是this對象)。所以,很多時候不需要使用這個變量。

復制代碼 代碼如下:

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function(deps, func, scope) {
        var args = [];
        scope = scope || {};
        for(var i=0; i<deps.length, d=deps[i]; i++) {
            if(this.dependencies[d]) {
                scope[d] = this.dependencies[d];
            } else {
                throw new Error('Can/'t resolve ' + d);
            }
        }
        return function() {
            func.apply(scope || {}, Array.prototype.slice.call(arguments, 0));
        }       
    }
}

我們所做的一切其實就是將依賴添加到作用域。這樣做的好處是,開發人員不用再寫依賴性參數;它們已經是函數作用域的一部分。
復制代碼 代碼如下:

var doSomething = injector.resolve(['service', 'router'], function(other) {
    expect(this.service().name).to.be('Service');
    expect(this.router().name).to.be('Router');
    expect(other).to.be('Other');
});
doSomething("Other");

六、結束語
其實我們大部分人都用過依賴注入,只是我們沒有意識到。即使你不知道這個術語,你可能在你的代碼里用到它百萬次了。希望這篇文章能加深你對它的了解。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
情事1991在线| 国产精品第8页| 欧美精品在线观看91| 中文字幕欧美精品日韩中文字幕| 亚洲国产天堂久久国产91| 亚洲国产精品久久| 精品国产一区二区三区久久狼5月| 久久视频在线观看免费| 欧美在线视频一区二区| 国产mv久久久| 北条麻妃一区二区在线观看| 91在线免费视频| 亚洲男人天堂2024| 国产91精品视频在线观看| 亚洲欧洲第一视频| 久久久人成影片一区二区三区观看| 亚洲欧美在线磁力| 久热爱精品视频线路一| 亚洲福利在线视频| 亚洲在线观看视频网站| 欧美精品在线免费播放| 欧美高清视频在线| 亚洲国产精品人人爽夜夜爽| 国外成人在线播放| 亚洲xxxx在线| 国产日韩在线免费| 欧美最猛黑人xxxx黑人猛叫黄| 精品视频久久久久久| 隔壁老王国产在线精品| 欧美最顶级丰满的aⅴ艳星| 国产91色在线|| 日本免费在线精品| 国产精品91视频| 免费av在线一区| 亚洲最大av在线| 亚洲精品自拍视频| 欧美精品在线免费观看| 日韩av日韩在线观看| 国产精品吹潮在线观看| 中文字幕视频在线免费欧美日韩综合在线看| 538国产精品一区二区在线| 成人信息集中地欧美| 亚洲精品av在线播放| 成人激情视频在线播放| 亚洲欧洲美洲在线综合| 国产在线精品成人一区二区三区| 精品久久在线播放| 亚洲一区二区三区久久| 亚洲欧美在线免费| 亚洲精品一区二区在线| 国内偷自视频区视频综合| 国产精品自产拍在线观看中文| 欧美视频在线免费看| 国产69精品久久久久99| 日韩av免费在线播放| 精品久久久久久久久久ntr影视| 最近2019中文字幕mv免费看| 国产成人综合一区二区三区| 国产美女精彩久久| 91精品视频网站| 欧美黑人xxxⅹ高潮交| 久久婷婷国产麻豆91天堂| 亚洲黄色www| 久久综合久久八八| 91亚洲精华国产精华| 亚洲黄色在线观看| 亚洲黄页视频免费观看| 亚洲第一视频网站| 欧美午夜激情视频| 久久久久久国产精品美女| 欧美国产第一页| 日韩资源在线观看| 国产精品99久久99久久久二8| 亚洲欧美国产高清va在线播| 茄子视频成人在线| 操91在线视频| 亚洲精品电影在线观看| 中文日韩在线观看| 亚洲男人天堂视频| 亚洲国产美女精品久久久久∴| 国产精品视频精品视频| 亚洲韩国欧洲国产日产av| 亚洲国产天堂网精品网站| 国产精品对白刺激| 欧美最猛性xxxxx免费| 亚洲网站在线观看| 国内精品久久久| 日韩成人在线观看| 综合网中文字幕| 午夜精品福利在线观看| 韩国v欧美v日本v亚洲| 国产视频观看一区| 久久久久久久久电影| 国产成人自拍视频在线观看| 国产精品av免费在线观看| 亚洲一区二区精品| 欧美午夜片欧美片在线观看| 精品久久中文字幕| 国产一区在线播放| 精品久久久久久久久久ntr影视| 亚洲免费av网址| 亚洲精品按摩视频| 日本电影亚洲天堂| 日韩专区在线播放| 爱福利视频一区| 亚洲天堂2020| 国产成人aa精品一区在线播放| 亚洲影院色在线观看免费| 亚洲欧美中文字幕| 久久精品国产亚洲| 欧美激情图片区| 成人性生交大片免费观看嘿嘿视频| 精品国产乱码久久久久久婷婷| 国产精品成人va在线观看| 欧美电影《睫毛膏》| 国产美女扒开尿口久久久| 国产美女扒开尿口久久久| 日韩精品中文字幕在线播放| 中文字幕在线日韩| 97激碰免费视频| 欧美黑人狂野猛交老妇| 一本一本久久a久久精品牛牛影视| 日韩av快播网址| 一区二区三区视频免费在线观看| 亚洲欧美激情另类校园| 国产欧美一区二区三区视频| 国产成人精品网站| 欧美一级片一区| 亚洲曰本av电影| 国产午夜精品美女视频明星a级| 亚洲偷熟乱区亚洲香蕉av| 成人a级免费视频| 57pao国产成人免费| 久久久女人电视剧免费播放下载| 国产91色在线| 欧美香蕉大胸在线视频观看| 日韩欧美精品网站| 岛国av一区二区在线在线观看| 欧美另类第一页| 国产精品一区二区久久精品| 欧美精品激情在线观看| 另类专区欧美制服同性| 欧美影院在线播放| 日韩美女激情视频| 亚洲欧美国内爽妇网| 亚洲成年人在线| 中文字幕精品一区二区精品| 日本精品一区二区三区在线播放视频| 91国产精品视频在线| 国产精品久久久久影院日本| 日韩精品视频在线免费观看| 国产成人啪精品视频免费网| 欧美性xxxx极品hd欧美风情| 69av视频在线播放| 欧美成人久久久| 亚洲综合中文字幕在线观看| 狠狠做深爱婷婷久久综合一区| 久久久久女教师免费一区| 亲爱的老师9免费观看全集电视剧| 国产精品av网站| 亚洲欧美精品伊人久久| 在线亚洲男人天堂| 亚洲欧美国产精品专区久久| 国产精品扒开腿做爽爽爽的视频|