搜索
首页web前端js教程详细解析JavaScript中依赖注入示例代码

计算机编程的世界其实就是一个将简单的部分不断抽象,并将这些抽象组织起来的过程。JavaScript也不例外,在我们使用JavaScript编写应用时,我们是不是都会使用到别人编写的代码,例如一些著名的开源库或者框架。随着我们项目的增长,我们需要依赖的模块变得越来越多,这个时候,如何有效的组织这些模块就成了一个非常重要的问题。依赖注入解决的正是如何有效组织代码依赖模块的问题。你可能在一些框架或者库种听说过“依赖注入”这个词,比如说著名的前端框架AngularJS,依赖注入就是其中一个非常重要的特性。但是,依赖注入根本就不是什么新鲜玩意,它在其他的编程语言例如PHP中已经存在已久。同时,依赖注入也没有想象种那样复杂。在本文中,我们将一起来学习JavaScript中的依赖注入的概念,深入浅出的讲解如何编写“依赖注入风格”的代码。

目标设定

假设我们现在拥有两个模块。第一个模块的作用是发送Ajax请求,而第二个模块的作用则是用作路由

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

这时,我们编写了一个函数,它需要使用上面提到的两个模块:

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

在这里,为了让我们的代码变得有趣一些,这个参数需要多接收几个参数。当然,我们完全可以使用上面的代码,但是无论从哪个方面来看上面的代码都略显得不那么灵活。要是我们需要使用的模块名称变为Service<a href="http://www.php.cn/wiki/1527.html" target="_blank">XML</a>或者Service<a href="http://www.php.cn/wiki/1488.html" target="_blank">JSON</a>该怎么办?或者说如果我们基于测试的目的想要去使用一些假的模块改怎么办。这时,我们不能仅仅去编辑函数本身。因此我们需要做的第一件事情就是将依赖的模块作为参数传递给函数,代码如下所示:

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

在上面的代码中,我们完全传递了我们所需要的模块。但是这又带来了一个新的问题。假设我们在代码的哥哥部分都调用了doSomething方法。这时,如果我们需要第三个依赖项该怎么办。这个时候,去编辑所有的函数调用代码并不是一个明智的方法。因此,我们需要一段代码来帮助我们做这件事情。这就是依赖注入器试图去解决的问题。现在我们可以来定下我们的目标了:

  • 我们应该能够去注册依赖项

  • 依赖注入器应该接收一个函数,然后返回一个能够获取所需资源的函数

  • 代码不应该复杂,而应该简单友好

  • 依赖注入器应该保持传递的函数作用域

  • 传递的函数应该能够接收自定义的参数,而不仅仅是被描述的依赖项

requirejs/AMD方法

或许你已经听说过了大名鼎鼎的requirejs,它是一个能够很好的解决依赖注入问题的库:

define([&#39;service&#39;, &#39;router&#39;], function(service, router) {       
    // ...
});

requirejs的思想是首先我们应该去描述所需要的模块,然后编写你自己的函数。其中,参数的顺序很重要。假设我们需要编写一个叫做injector的模块,它能够实现类似的语法。

var doSomething = injector.resolve([&#39;service&#39;, &#39;router&#39;], function(service, router, other) {
    expect(service().name).to.be(&#39;Service&#39;);
    expect(router().name).to.be(&#39;Router&#39;);
    expect(other).to.be(&#39;Other&#39;);
});
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(&#39;Can\&#39;t resolve &#39; + d);
        }
    }
    return function() {
        func.apply(scope || {}, args.concat(Array.prototype.slice.call(arguments, 0)));
    }        
}

如果你需要指定一个作用域,上面的代码也能够正常的运行。

在上面的代码中,Array.prototype.slice.call(arguments, 0)的作用是将arguments变量转换为一个真正的数组。到目前为止,我们的代码可以完美的通过测试。但是这里的问题是我们必须要将需要的模块写两次,而且不能够随意排列顺序。额外的参数总是排在所有的依赖项之后。

反射(reflection)方法

根据维基百科中的解释,反射(reflection)指的是程序可以在运行过程中,一个对象可以修改自己的结构和行为。在JavaScript中,简单来说就是阅读一个对象的源码并且分析源码的能力。还是回到我们的doSomething方法,如果你调用doSomething.to<a href="http://www.php.cn/wiki/57.html" target="_blank">String</a>()方法,你可以获得下面的字符串:

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

这样一来,只要使用这个方法,我们就可以轻松的获取到我们想要的参数,以及更重要的一点就是他们的名字。这也是AngularJS实现依赖注入所使用的方法。在AngularJS的代码中,我们可以看到下面的正则表达式

/^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, &#39;&#39;).split(&#39;,&#39;);
    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 != &#39;&#39; ? 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 != &#39;&#39; ? self.dependencies[d] : a.shift());

在上面的代码中,我们遍历了依赖项目,如果其中有缺失的项目,如果依赖项目中有缺失的部分,我们就从arguments对象中获取。如果一个数组是空数组,那么使用shift方法将只会返回undefined,而不会抛出一个错误。到目前为止,新版本的injector看起来如下所示:

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

在上面的代码中,我们可以随意混淆依赖项的顺序。

但是,没有什么是完美的。反射方法的依赖注入存在一个非常严重的问题。当代码简化时,会发生错误。这是因为在代码简化的过程中,参数的名称发生了变化,这将导致依赖项无法解析。例如:

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

因此我们需要下面的解决方案,就像AngularJS中那样:

var doSomething = injector.resolve([&#39;service&#39;, &#39;router&#39;, function(service, router) {

}]);

这和最一开始看到的AMD的解决方案很类似,于是我们可以将上面两种方法整合起来,最终代码如下所示:

var injector = {
    dependencies: {},
    register: function(key, value) {
        this.dependencies[key] = value;
    },
    resolve: function() {
        var func, deps, scope, args = [], self = this;
        if(typeof arguments[0] === &#39;string&#39;) {
            func = arguments[1];
            deps = arguments[0].replace(/ /g, &#39;&#39;).split(&#39;,&#39;);
            scope = arguments[2] || {};
        } else {
            func = arguments[0];
            deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, &#39;&#39;).split(&#39;,&#39;);
            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 != &#39;&#39; ? self.dependencies[d] : a.shift());
            }
            func.apply(scope || {}, args);
        }        
    }
}

这一个版本的resolve方法可以接受两个或者三个参数。下面是一段测试代码:

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

你可能注意到了两个逗号之间什么都没有,这并不是错误。这个空缺是留给Other这个参数的。这就是我们控制参数顺序的方法。

结语

在上面的内容中,我们介绍了几种JavaScript中依赖注入的方法,希望本文能够帮助你开始使用依赖注入这个技巧,并且写出依赖注入风格的代码。

以上是详细解析JavaScript中依赖注入示例代码的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

了解JavaScript引擎:实施详细信息了解JavaScript引擎:实施详细信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python vs. JavaScript:学习曲线和易用性Python vs. JavaScript:学习曲线和易用性Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

Python vs. JavaScript:社区,图书馆和资源Python vs. JavaScript:社区,图书馆和资源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

从C/C到JavaScript:所有工作方式从C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

JavaScript引擎:比较实施JavaScript引擎:比较实施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

超越浏览器:现实世界中的JavaScript超越浏览器:现实世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在现实世界中的应用包括服务器端编程、移动应用开发和物联网控制:1.通过Node.js实现服务器端编程,适用于高并发请求处理。2.通过ReactNative进行移动应用开发,支持跨平台部署。3.通过Johnny-Five库用于物联网设备控制,适用于硬件交互。

使用Next.js(后端集成)构建多租户SaaS应用程序使用Next.js(后端集成)构建多租户SaaS应用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境