【 js 基础 】【 源码学习 】backbone 源码阅读(二)
2021-06-10 22:05
标签:条件 描述 回调 制造 array sim 利用 gis 组成 最近看完了 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) 围绕着概念来学习一下: 首先来解释一下什么是耦合度,这样才能知道 控制反转到底解决了什么问题。 再举个例子: 类似于机械手表,齿轮之间互相带动,互相影响,在这种方式的协同工作中,若一个齿轮出现问题不转了,那么其他齿轮也会受到影响停止转动。 这个时候如果一个对象的改变,需要和其相关的所有对象都作出改变,牵一发而动全身,一是关系不好理清,二是工作量加大,三是模块的可复用性低。 为了解决这一问题,降低对象模块之间的低耦合,控制反转(IoC)理论诞生了。 通过“第三方”,即 IoC 容器,对象之间的耦合明显降低,各个齿轮的转动都是依靠 “第三方”,所有对象的控制权也都是 “第三方” IoC 容器 来管理。正是 IoC 容器把所有对象粘合在一起发挥作用,如果没有它,对象与对象之间彼此会失去联系。 咱们来比较一下 有无引入 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 到底是什么呢? 1、依赖注入(DI):就是由 IoC 容器 在运行期间,动态地将某种依赖关系注入到对象之中。类似于一个对象制造工厂,你需要什么,它会给你送去,你直接使用就行了,而再也不用去关心你所用的东西是如何制成的,也不用关心最后是怎么被销毁的,这一切全部由IOC容器包办。 来举个例子来看看技术上的实现:例子来自(http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript) 想象一下如果我们的 doSomething 方法散落在我们的代码中,这时我们需要更改它的依赖条件,我们需要更改所有调用这个函数的地方。 我们把上面的代码改成 依赖注入 的方式: RequireJS 的 define 方法先描述模块所需要的依赖,然后再写模块的要实现的函数方法。非常好的 依赖注入 的实现。 我们来简单实现一下 RequireJS / AMD 依赖注入的方法,命名为 injector : 这是一个非常简单的对象,有两个方法。register 方法用来注册所有可以依赖的模块 。resolve 用来将模块所需依赖在注册过的依赖列表dependencies变量中找到并将找到的依赖传入到 func 参数中。其中依赖的顺序不能打乱。 injector的使用: B、反射方法(angular 实现依赖注入的方法) 在 JavaScript 中,具体指读取和分析的对象或函数的源代码。我们可以通过分析代码,来获取函数所需要的依赖,然后进行注入。这里我们就需要使用到 toString() 方法。 当我们调用 doSomething.tostring() 你会得到如下: 这样我们就可以遍历这个字符串,得到其需要的参数,也就是所需要的依赖。 我们重新修改一下 上面 injector 方法,主要变化在 resolve 方法上: 新版的 injector 的使用: 也证实因为这两个区别导致这个方法有个问题,当你压缩了代码之后,就会改变参数的名字,这样就不能够保证 正确的映射关系。例如 doSometing()压缩后可能看起来像这样: Angular团队提出的解决方案,传入这样形式的参数: 我们结合第一种和第二种方案,修改一下 injector 方法 : 新版的 injector 的使用: C、直接注入Scope 新版的 injector 的使用: 对于依赖注入和依赖查找来说,两者的区别在于:前者是被动的接收对象,在类A的实例创建过程中即创建了依赖的B对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取相应类型的对象,获得依赖对象的时间也可以在代码中自由控制。 依赖查找 相对于 依赖注入来说,用到的比较少,这里不再详细讲解,大家了解一下还有这种方式就可以。 以上,在上篇关于 backbone 的知识总结文章中,我们有提到 backbone 用到了控制反转,在events.on和events.listenTo 以及 events.once和events.listenToOnce,但其实他只是用到了很小的方面,只是思想的符合,而真正意义上的控制反转则大面积的运用到了依赖管理中,通过这篇文章,你应该可以有个系统的认识了。 学习并感谢: http://yanhaijing.com/program/2016/09/01/about-coupling/ 图解7种耦合关系 (推荐大家阅读一下具体的有几种耦合方式) 【 js 基础 】【 源码学习 】backbone 源码阅读(二) 标签:条件 描述 回调 制造 array sim 利用 gis 组成 原文地址:http://www.cnblogs.com/lijiayi/p/backbone2.html
耦合度:指一程序中,模块及模块之间信息或参数依赖的程度。
举个例子:
一个程序有20个函数,当你改动其中 1 个函数的时候,其它 19 个函数都需要修改,这就是高耦合,显然不是我们希望的。
在采用面向对象的设计中,程序的实现都是由 n 个对象组成的,这些对象通过彼此的合作,最终实现业务逻辑。就像下面这个图:
对象之间的耦合关系是无法避免的,因为他们要互相配合才能完成工作,当程序功能越来越庞大,对象之间的依赖关系也就越复杂,会出现对象之间的多重依赖关系,就像下面这个图,关系是错综复杂的:
这个理论希望我们把复杂的功能需求,业务逻辑,拆分成相互合作的对象,这些对象通过封装以后,可以更加灵活地被重用和扩展,然后借助“第三方”实现具有依赖关系,但是又是低耦合的合作方式:
A、对于没有引入 IoC 容器的设计来说,就像第一张图
这里就要提到概念里出现的两种实现 IoC 的方式:依赖注入(Dependency Injection,简称DI)和 依赖查找(Dependency Lookup)。
假设我们有两个模块。第一个是使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 };
A、RequireJS / AMD 的方法:( 关于 RequireJS / AMD、模块化的知识,大家可以看我的另一篇文章 http://www.cnblogs.com/lijiayi/p/js_node_module.html )1 define([‘service‘, ‘router‘], function(service, router) {
2 // ……
3 });
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 ) {
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 }
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");
反射:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。 -------------来自wiki1 "function (service, router, other) {
2 var s = service();
3 var r = router();
4 }"
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 ) {
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 }
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");
与第一个的方式的区别 :只有一个参数(第一种方法有两个参数,需要依赖数组),依赖的顺序可以打乱。1 var doSomething=function(e,t,n){var r=e();var i=t()}
1 var doSomething = injector.resolve([‘service‘, ‘router‘, function(service, router) {
2
3 }]);
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
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");
上面代码认真看的童鞋会发现,我们的 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
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来查找资源和协作对象,仅有的控制反转体现在回调方法上:容器将调用回调方法,从而让模块获得所需要的依赖。
文章标题:【 js 基础 】【 源码学习 】backbone 源码阅读(二)
文章链接:http://soscw.com/essay/93335.html