目录
依赖注入做了什么
依赖注入怎么做?
添加依赖信息
服务管理
模块创建
总结
参考资料
首页 开发工具 VSCode 简单聊聊VSCode中依赖注入的原理

简单聊聊VSCode中依赖注入的原理

Feb 07, 2023 pm 06:18 PM
vscode 前端 visual studio code

本篇文章给大家浅析VSCode中依赖注入的原理,聊聊依赖注入做了什么?依赖注入怎么做?希望对大家有所帮助!

简单聊聊VSCode中依赖注入的原理

团队推行 「依赖注入」有一段时间了,但每次使用时都觉得很陌生,有很多概念总是不知所云:服务id,服务描述符,服务装饰器等等。

可能是因为不懂得其中原理,使用时都有种「虚」的感觉,最近通过阅读 VS Code 源码,拜读团队大佬的分享文章,力图理清其中的原理,在这里做一个简单的核心逻辑介绍。

依赖注入做了什么

假设以下情况:

  • 服务模块 A,依赖服务 B;

  • 服务模块 B;

  • 功能模块 Feature,依赖服务 A 和 B;

按照普通的写法就是:

class B {}

class A {
    constructor() {
        // 在 A 的构造器中 new B
        this.b = new B();
    }
}

class Feature {
    constructor() {
        this.a = new A();
        this.b = new B();
    }
}

// 使用时
const feature = new Feature();

代码简单明了,存在一些问题,比如:如果 A 和 Feature 依赖的 B 需要是同一个实例,以上的写法将会初始化两个 B 实例。【推荐学习:vscode教程编程教学

简单修改一下:

class A {
    constructor(b: B) {
        this.b = b;
    }
}

class Feature {
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }
}

// 使用时
const b = new B();
const a = new A(b);
const feature = new Feature(a, b);

某个模块初始化时,先在外部将其所依赖的模块创建出来,通过参数的形式传入功能模块。这样的写法就是「依赖注入」。

现在这种写法的问题在于:手动传参的形式,必须人工保证 new 的顺序,即必须获得 a, b 实例才能执行 new Feature。

当依赖关系变得复杂时,创建一个功能模块之前很有可能需要无数个基础模块,这时候复杂度将会非常高。类似于这种感觉:

杂乱的依赖关系

想象一种模式:存在一个模块控制器,或者说「服务管理器」来管理这些依赖关系:

class Feature {
    // 声明这个模块依赖 idA, idB
    idA
    idB
}

// 告知「服务管理器」,怎么找对应的模块
services[idA] = A;
services[idB] = B;

// 使用时
const feature = services.createInstance(Feature);

这个 services 承载的不就是之前的「手工」过程吗?
在 createInstance(Feature) 时,分析 Feature 所依赖的模块:

  • 如果所依赖的模块还未创建出实例,递归创建出该服务实例,最终返回;

  • 如果所依赖的模块已有实例,返回该实例;

  • 找齐后通过参数注入 Feature,完成初始化;
    VSCode 实现的正是这么一套「依赖注入体系」。

依赖注入怎么做?

要实现这样一套功能,大致需要:

  • 一个类如何声明其依赖的服务 id,即给定一个 类,外部如何知道他依赖了哪些服务?

  • 如何管理管理服务?

  • 如何创建某个模块?

下文会实现一个最简单的模型,涵盖主体流程。

添加依赖信息

如何给一个 类 打上烙印,声明它所依赖的服务呢?
将问题再次抽象:如何给一个类加上额外的信息?
其实,每个类在 es5 下都是 Function,而每个 Function 说到底也只是 Object ,只要给 Object 加上几个字段来标识所需要的服务 id,就可以完成所需要的功能。
通过 「参数装饰器」的写法,可以很容易做到这一点:

// 参数装饰器 
const decorator = (
    target: Object, // 被装饰的目标,这里为 Feature
    propertyName: string, 
    index: number // 参数的位置索引
) => {
    target['deps'] = [{        index,        id: 'idA',    }];
}
class Feature {
    name = 'feature';
    a: any;
    constructor(
        // 参数装饰器
        @decorator a: any,
    ) {
        this.a = a;
    }
}
console.log('Feature.deps', Feature['deps']);
// [{ id: 'idA', index: 0 }]

通过这种方式,通过 Feature (之后会称之为 构造器 ctor)就可以获取到 serviceId。

服务管理

使用 Map 来进行管理,一个 id 对应一个 服务 ctor。

class A {
    name = 'a';
}

// 服务集
class ServiceCollection {
    // 服务集合
    // key 为服务标识
    // value 为 服务ctor
    private entries = new Map<string, any>();

    set(id: string, ctor: any) {
        this.entries.set(id, ctor);   
    }

    get(id: string): any {
        return this.entries.get(id);
    }
}

const services = new ServiceCollection();

// 声明服务 A id 为 idA
services.set(&#39;idA&#39;, A);

示意图如下:

现在,就可以通过 Feature 来找到所依赖的服务的构造器了

// 通过 Feature 找到所依赖的 A
const serviceId = Feature[&#39;deps&#39;][0].id; // idA
console.log(
    &#39;Feature.deps&#39;, 
    services.get(serviceId) // A
);

模块创建

具体思路为:

  • 如果所依赖的模块还未创建出实例,递归创建出该服务实例,最终返回;

  • 如果所依赖的模块已有实例,返回该实例;

  • 找齐后通过参数注入 Feature,完成初始化;

这里先上一个简单的 demo,只有一层的依赖(即所依赖的服务没有依赖其他服务),简单的讲,就是没有递归能力:

class InstantiationService {
    services: ServiceCollection;

    constructor(services: ServiceCollection) {
        this.services = services;
    }

    createInstance(ctor: any) {
        // 1. 获取 ctor 依赖的 服务id
        // 结果为: [&#39;idA&#39;]
        const depIds = ctor[&#39;deps&#39;].map((item: any) => item.id);

        // 2. 获取服务 id 对应的 服务构造器
        // 结果为:[A]
        const depCtors = depIds.map((id: string) => services.get(id));

        // 3. 获取服务实例
        // 结果为: [ A { name: &#39;a&#39;} ]
        const args = depCtors.map((ctor: any) => new ctor());

        // 4. 依赖的服务作为参数注入,实例化所需要模块
        // 结果为:[ Feature { name: &#39;feature&#39;, a }]
        const result = new ctor(...args);

        return result;
    }
}

const instantiation = new InstantiationService(services);

// 使用时
const feature = instantiation.createInstance(Feature);

至此,依赖注入的核心流程实现完毕,要使用 Feature 时,只需要调用 createInstance,不用管他所依赖的服务是否被初始化,instantiation 帮我们做了这个事情。

总结

本文简单实现一个 demo 级别的「依赖注入」模型,简单实现了:

  • 模块声明所需要的依赖;

  • 服务管理;

  • 模块创建;

以此为基础,可以拓展出一些高级功能:

  • 模块创建(递归):VSCode 用了 栈 + 图 做了这件事,算法也不复杂;

  • 依赖收集:可用于分析每个模块的依赖,且可以检测是否存在「循环依赖」;

  • 模块销毁:当模块销毁时,递归销毁他所依赖的服务实例;

  • 延迟初始化:创建依赖的服务时,选择创建一个 proxy ,当真正使用时才真正创建实例;

  • 异步依赖:当依赖的服务的创建过程是异步的情况下,如何执行创建逻辑;

源码地址 本文代码看这里。
完整功能 参考 VSCode 整个依赖注入体系写的代码,进阶可以看这里。

参考资料

VS Code 源码 位置:src/vs/platform/instantiation/common
本文借鉴了代码思路,且命名也高度一致(手动狗头

更多关于VSCode的相关知识,请访问:vscode教程!!

以上是简单聊聊VSCode中依赖注入的原理的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

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

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何在Windows上安装VSCODE 如何在Windows上安装VSCODE Jul 27, 2025 am 03:16 AM

Gotohttps://code.visualstudio.comanddownloadtheWindowsUserInstaller.2.Runthe.exefile,allowchanges,andselectrecommendedoptionsincludingaddingtoPATHandcreatingadesktopshortcut.3.ClickFinishtolaunchVSCodeafterinstallation.4.Optionallyinstallusefulextens

如何将VSCODE与WSL(Linux的Windows子系统)一起使用 如何将VSCODE与WSL(Linux的Windows子系统)一起使用 Aug 01, 2025 am 06:26 AM

InstallWSLandaLinuxdistributionbyrunningwsl--installinPowerShellasAdministrator,thenrestartandsetuptheLinuxdistribution.2.Installthe"Remote-WSL"extensioninVSCodetoenableintegrationwithWSL.3.OpenaprojectinWSLbylaunchingtheWSLterminal,navigat

Vscode和Visual Studio之间有什么区别 Vscode和Visual Studio之间有什么区别 Jul 30, 2025 am 02:38 AM

VSCodeisalightweight,cross-platformcodeeditorwithIDE-likefeaturesviaextensions,idealforwebandopen-sourcedevelopment;2.VisualStudioisafull-featured,Windows-onlyIDEdesignedforcomplex.NET,C ,andenterpriseapplications;3.VSCodeperformsfasteronlower-endma

如何在VSCODE中使用参数运行Python脚本 如何在VSCODE中使用参数运行Python脚本 Jul 30, 2025 am 04:11 AM

TorunaPythonscriptwithargumentsinVSCode,configurelaunch.jsonbyopeningtheRunandDebugpanel,creatingoreditingthelaunch.jsonfile,andaddingthedesiredargumentsinthe"args"arraywithintheconfiguration.2.InyourPythonscript,useargparseorsys.argvtoacce

如何更改VSCODE中的字体大小? 如何更改VSCODE中的字体大小? Aug 02, 2025 am 02:37 AM

TochangethefontsizeinVSCode,useoneofthesemethods:1.OpenSettingsviaCtrl ,(orCmd ,onMac),searchfor"fontsize",andadjustthe"Editor:FontSize"value.2.OpenSettings(JSON)fromtheCommandPalette,thenaddormodify"editor.fontSize":e.g

如何更改VSCODE中的字体大小 如何更改VSCODE中的字体大小 Jul 26, 2025 am 04:13 AM

tochangetheTsizeInvScode,gotofile>“首选项”>“设置”,搜索“ fontsize”,andmodifythe“ editor:fontsize” value.2

如何在VSCODE中调试单元测试 如何在VSCODE中调试单元测试 Aug 01, 2025 am 06:12 AM

CreateModifyLaunch.JSONINVSCODEBYOPENEDTHERUNANDDEBUGVIEW,SELECTingYourenVironment(例如Python,Node.js)和ConconfiguringItfo ryourtestframework(例如,pytest,jest)。2。setbreakpointsinyourtestfile,selectthedebuggconfiguration,andstartdebuggingwithf5topaus

如何在VSCODE中使用Markdown预览 如何在VSCODE中使用Markdown预览 Jul 29, 2025 am 02:05 AM

要使用VSCode中的Markdown预览,无需额外安装,1.打开或创建一个.md文件;2.使用Ctrl Shift V(Windows/Linux)或Cmd Shift V(Mac)快捷键,或通过右键菜单、命令面板打开预览;3.预览默认实时更新,确保开启自动保存以保持同步;4.可通过右键预览区域切换同步滚动功能,预览支持GitHub风格Markdown和数学公式(需安装MarkdownAllinOne扩展),并可自定义CSS样式,操作简便且功能完整。

See all articles