【 js 基礎 】【 源碼學習 】backbone 源碼閱讀(二)

来源:http://www.cnblogs.com/lijiayi/archive/2017/08/06/backbone2.html
-Advertisement-
Play Games

最近看完了 backbone.js 的源碼,這裡對於源碼的細節就不再贅述了,大家可以 star 我的源碼閱讀項目(https://github.com/JiayiLi/source-code-study)進行參考交流,有詳細的源碼註釋,以及知識總結,同時 google 一下 backbone 源碼, ...


最近看完了 backbone.js 的源碼,這裡對於源碼的細節就不再贅述了,大家可以 star 我的源碼閱讀項目(https://github.com/JiayiLi/source-code-study)進行參考交流,有詳細的源碼註釋,以及知識總結,同時 google 一下 backbone 源碼,也有很多優秀的文章可以用來學習。

我這裡主要記錄一些偏設計方向的知識點。這篇文章主要講 控制反轉

 

一、控制反轉

上篇文章有說到控制反轉,但只是簡略的舉了個例子,在這裡我們詳細說一下這個知識點,它其實並沒有那麼簡單。

控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低電腦代碼之間的耦合度。其中最常見的方式叫做依賴註入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被註入到對象中。 -----------來自 wiki (https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)

 

圍繞著概念來學習一下:

首先來解釋一下什麼是耦合度,這樣才能知道 控制反轉到底解決了什麼問題。
耦合度:指一程式中,模塊及模塊之間信息或參數依賴的程度。
舉個例子:
一個程式有20個函數,當你改動其中 1 個函數的時候,其它 19 個函數都需要修改,這就是高耦合,顯然不是我們希望的。

再舉個例子:
在採用面向對象的設計中,程式的實現都是由 n 個對象組成的,這些對象通過彼此的合作,最終實現業務邏輯。就像下麵這個圖:

類似於機械手錶,齒輪之間互相帶動,互相影響,在這種方式的協同工作中,若一個齒輪出現問題不轉了,那麼其他齒輪也會受到影響停止轉動。
對象之間的耦合關係是無法避免的,因為他們要互相配合才能完成工作,當程式功能越來越龐大,對象之間的依賴關係也就越複雜,會出現對象之間的多重依賴關係,就像下麵這個圖,關係是錯綜複雜的:

這個時候如果一個對象的改變,需要和其相關的所有對象都作出改變,牽一發而動全身,一是關係不好理清,二是工作量加大,三是模塊的可復用性低。

為瞭解決這一問題,降低對象模塊之間的低耦合,控制反轉(IoC)理論誕生了。
這個理論希望我們把複雜的功能需求,業務邏輯,拆分成相互合作的對象,這些對象通過封裝以後,可以更加靈活地被重用和擴展,然後藉助“第三方”實現具有依賴關係,但是又是低耦合的合作方式:

 

通過“第三方”,即 IoC 容器,對象之間的耦合明顯降低,各個齒輪的轉動都是依靠 “第三方”,所有對象的控制權也都是 “第三方” IoC 容器 來管理。正是 IoC 容器把所有對象粘合在一起發揮作用,如果沒有它,對象與對象之間彼此會失去聯繫。

 

 

咱們來比較一下 有無引入 IoC 容器 的區別:
  A、對於沒有引入 IoC 容器的設計來說,就像第一張圖

 

Object A 依賴於 Object B,當 Object A 在初始化或者運行到某一點需要 Object B 支持的時候,Object A 必須主動去創建 Object B 或者使用已經創建的 Object B。無論是創建還是使用已經創建了的 Object B,控制權都在 Object  A 自己手上。

 

   B、而對於引入 IoC 容器的設計來說,就像第三張圖

 

由於 IoC 容器 的加入,Object A 與 Object B 之間失去了直接聯繫,當 Object A 運行到需要 Object B 的時候,IoC 容器 會主動創建一個 Object B 註入到 Object A 需要的地方。

 

通過比較可以看出來,Object A 獲得依賴 Object B 的過程,由主動行為變為了被動行為,控制權顛倒過來了,這也就是 控制反轉 ,反轉的是獲得依賴對象的過程。

 

那麼到底具體是通過什麼方法來實現控制反轉,降低耦合度的呢,這個 IoC 到底是什麼呢?
這裡就要提到概念里出現的兩種實現 IoC 的方式:依賴註入(Dependency Injection,簡稱DI)和 依賴查找(Dependency Lookup)。

  1、依賴註入(DI):就是由 IoC 容器 在運行期間,動態地將某種依賴關係註入到對象之中。類似於一個對象製造工廠,你需要什麼,它會給你送去,你直接使用就行了,而再也不用去關心你所用的東西是如何製成的,也不用關心最後是怎麼被銷毀的,這一切全部由IOC容器包辦。

來舉個例子來看看技術上的實現:例子來自(http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript
假設我們有兩個模塊。第一個是使Ajax請求的服務,第二個是路由器。我們還有另一個需要這些模塊的功能 doSomething,當然它也可以接受額外的參數來使用其他模塊。

 1 var service = function() {
 2     return { name: 'Service' };
 3 }
 4 var router = function() {
 5     return { name: 'Router' };
 6 }
 7 var doSomething = function(service,router,other) {
 8     var s = service();
 9     var r = router();
10 };

想象一下如果我們的 doSomething 方法散落在我們的代碼中,這時我們需要更改它的依賴條件,我們需要更改所有調用這個函數的地方。

 

我們把上面的代碼改成 依賴註入 的方式:
  A、RequireJS / AMD 的方法:( 關於 RequireJS / AMD、模塊化的知識,大家可以看我的另一篇文章 http://www.cnblogs.com/lijiayi/p/js_node_module.html

1 define(['service', 'router'], function(service, router) {       
2     // ……
3 });

RequireJS 的 define 方法先描述模塊所需要的依賴,然後再寫模塊的要實現的函數方法。非常好的 依賴註入 的實現。

 

我們來簡單實現一下 RequireJS / AMD 依賴註入的方法,命名為 injector :

 1 var injector = {
 2     dependencies: {},
 3     register: function(key, value) {
 4         this.dependencies[key] = value;
 5     },
 6     resolve: function(deps, func, scope) {
 7         var args = [];
 8         for (var i = 0; i < deps.length, d = deps[i]; i++) {
 9             if (this.dependencies[d]) {
10                 args.push(this.dependencies[d]);
11             } else {
12                 throw new Error('Can\'t resolve ' + d);
13             }
14         }
15         return function() {
16             func.apply(scope || {},
17             args.concat(Array.prototype.slice.call(arguments, 0)));
18         }
19     }
20 }

 

這是一個非常簡單的對象,有兩個方法。register 方法用來註冊所有可以依賴的模塊 。resolve 用來將模塊所需依賴在註冊過的依賴列表dependencies變數中找到並將找到的依賴傳入到 func 參數中。其中依賴的順序不能打亂。

 

injector的使用:

1 var doSomething = injector.resolve(['service', 'router'], function(service, router, other) {
2     console.log(service().name) // ‘Service'
3     console.log(router().name) // 'Router'
4     console.log(other) // 'Other'
5 });
6 doSomething("Other");

 

 

  B、反射方法(angular 實現依賴註入的方法)
反射:在電腦科學中,反射是指電腦程式在運行時(Run time)可以訪問、檢測和修改它本身狀態或行為的一種能力。 -------------來自wiki

在 JavaScript 中,具體指讀取和分析的對象或函數的源代碼。我們可以通過分析代碼,來獲取函數所需要的依賴,然後進行註入。這裡我們就需要使用到 toString() 方法。

當我們調用 doSomething.tostring() 你會得到如下:

1 "function (service, router, other) {
2     var s = service();
3     var r = router();
4 }"

 

這樣我們就可以遍歷這個字元串,得到其需要的參數,也就是所需要的依賴。

我們重新修改一下 上面 injector 方法,主要變化在 resolve 方法上:

 1 var injector = {
 2     dependencies: {},
 3     register: function(key, value) {
 4         this.dependencies[key] = value;
 5     },
 6     resolve: function() {
 7         var func, deps, scope, args = [],
 8         self = this;
 9         func = arguments[0];
10 
11         // 這裡的正則幫我們提取出所需要的依賴,正則匹配結果 ["function (service, router, other)", "service, router, other"]
12         deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',’);  
13         scope = arguments[1] || {};
14         return function() {
15             var a = Array.prototype.slice.call(arguments, 0);
16             // 遍歷dependencies數組,如果發現缺失項則嘗試從arguments對象中獲取
17             for (var i = 0; i < deps.length; i++) {
18                 var d = deps[i];
19                 args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
20             }
21             func.apply(scope || {},
22             args);
23         }
24     }
25 }

 

新版的 injector 的使用:

1 var doSomething = injector.resolve(function(service, other, router) {
2     console.log(service().name) // ‘Service'
3     console.log(router().name) // 'Router'
4     console.log(other) // ‘Other'
5 });
6 doSomething("Other");


與第一個的方式的區別 :只有一個參數(第一種方法有兩個參數,需要依賴數組),依賴的順序可以打亂。

也證實因為這兩個區別導致這個方法有個問題,當你壓縮了代碼之後,就會改變參數的名字,這樣就不能夠保證 正確的映射關係。例如 doSometing()壓縮後可能看起來像這樣:

1 var doSomething=function(e,t,n){var r=e();var i=t()}

 

Angular團隊提出的解決方案,傳入這樣形式的參數:

1 var doSomething = injector.resolve(['service', 'router', function(service, router) {
2 
3 }]);

 

我們結合第一種和第二種方案,修改一下  injector 方法 :

 1 var injector = {
 2     dependencies: {},
 3     register: function(key, value) {
 4         this.dependencies[key] = value;
 5     },
 6     resolve: function() {
 7         var func, deps, scope, args = [], self = this;
 8         if(typeof arguments[0] === 'string') {
 9             func = arguments[1];
10             deps = arguments[0].replace(/ /g, '').split(',');
11             scope = arguments[2] || {};
12         } else {
13             func = arguments[0];
14             deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
15             scope = arguments[1] || {};
16         }
17         return function() {
18             var a = Array.prototype.slice.call(arguments, 0);
19             for(var i=0; i<deps.length; i++) {
20                 var d = deps[i];
21                 args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
22             }
23             func.apply(scope || {}, args);
24         }        
25     }
26 }

 

新版的 injector 的使用:

1 var doSomething = injector.resolve('router,,service', function(a, b, c) {
2    console.log(a().name)  //'Router’
3    console.log(b)  //'Other’
4    console.log(c().name)  //'Service'
5 });
6 doSomething("Other");

 

  C、直接註入Scope
上面代碼認真看的童鞋會發現,我們的 resolve 方法是有一個參數叫 scope,這其實就是當前作用域,也就是通常意義上的 this 對象。我們可以將依賴綁定到 this 對象上,實現註入。

 1 var injector = {
 2     dependencies: {},
 3     register: function(key, value) {
 4         this.dependencies[key] = value;
 5     },
 6     resolve: function(deps, func, scope) {
 7         var args = [];
 8         scope = scope || {};
 9         for(var i=0; i<deps.length, d=deps[i]; i++) {
10             if(this.dependencies[d]) {
11                 scope[d] = this.dependencies[d];
12             } else {
13                 throw new Error('Can\'t resolve ' + d);
14             }
15         }
16         return function() {
17             func.apply(scope || {}, Array.prototype.slice.call(arguments, 0));
18         }        
19     }
20 }

 

新版的 injector 的使用:

1 var doSomething = injector.resolve(['service', 'router'], function(other) {
2     console.log(this.service().name) // ‘Service'
3     console.log(this.router().name) // 'Router'
4     console.log(other) // ‘Other’
5 });
6 doSomething("Other");

 


  2、依賴查找:模塊 利用 IoC 容器提供的回調介面和上下文條件 來找到依賴。
這種情況下模塊就必須使用容器提供的API來查找資源和協作對象,僅有的控制反轉體現在回調方法上:容器將調用回調方法,從而讓模塊獲得所需要的依賴。

對於依賴註入和依賴查找來說,兩者的區別在於:前者是被動的接收對象,在類A的實例創建過程中即創建了依賴的B對象,通過類型或名稱來判斷將不同的對象註入到不同的屬性中,而後者是主動索取相應類型的對象,獲得依賴對象的時間也可以在代碼中自由控制。

依賴查找 相對於 依賴註入來說,用到的比較少,這裡不再詳細講解,大家瞭解一下還有這種方式就可以。

 

 

以上,在上篇關於 backbone 的知識總結文章中,我們有提到 backbone 用到了控制反轉,在events.on和events.listenTo 以及 events.once和events.listenToOnce,但其實他只是用到了很小的方面,只是思想的符合,而真正意義上的控制反轉則大面積的運用到了依賴管理中,通過這篇文章,你應該可以有個系統的認識了。 

 

 

學習並感謝: 

https://my.oschina.net/1pei/blog/492601   控制反轉IOC與依賴註入DI http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript  Dependency injection in JavaScript

http://yanhaijing.com/program/2016/09/01/about-coupling/    圖解7種耦合關係 (推薦大家閱讀一下具體的有幾種耦合方式)

 


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

-Advertisement-
Play Games
更多相關文章
  • 今天我分享一個技術點,利用Spring初始化+線程接管進行程式啟動後保持會話狀態。 先來一段@test單元測試註解,後臺開發的很熟悉,這是測試局部代碼用的: RunWith和ContextConfiguration的源碼和功能就不細解釋了,不熟悉的可以去翻翻源碼。 1:我來一段@Test單元測試操作 ...
  • 工具:eclipse-nion、jdk8、python3.6、pydev eclipse -》 help -》 eclipse marketplace -》 輸入 python,install pydev 接著配置一下eclipse的解釋器以及編碼方式 新建一個項目:new -》project -》 ...
  • /** * 剪切一個文件夾,且文件夾中包含內容,有問題的地方大家可以指出 */import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileI ...
  • 本節作業: 熟練使用類和模塊,寫一個交互性強、有衝突的程式。 一、作業目的 1、規範程式寫法,要按照模塊來規範書寫; 2、類的使用,文件之間的調用練習; 3、思路的開闊,自己編寫衝突,實現調用; 4、對基礎知識的熟練掌握。 本文寫了一個決鬥系統,兩個男的為了一個女的進行決鬥,獲勝者贏得美女放心,失敗 ...
  • 本文介紹將各種Spring的配置方式,幫助您瞭解配置Spring應用的複雜性。Spring是一個非常受歡迎的Java框架,它用於構建web和企業應用。不像許多其他框架只關註一個領域,Spring框架提供了各種功能,通過項目組合來滿足當代業務需求。Spring框架提供了多種靈活的方式配置Bean。例如... ...
  • mybatis 詳解(四)------properties以及別名定義 ...
  • 目錄 · 總述 · 記憶 · 效果 · 面向對象設計原則 · 創建型模式 · 單例模式(Singleton) · 效果 · 分類 · 代碼(餓漢式) · 代碼(懶漢式) · 代碼(雙重檢測鎖式) · 代碼(靜態內部類式) · 代碼(枚舉單例) · 代碼(使用反射的破解與防禦) · 代碼(使用序列化的 ...
  • 定義(百度百科): 責任鏈模式是一種設計模式。在責任鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪一個對象最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...