前端学 IOC(3)- 什么是作用域(scope or lifetime)?

153 阅读3分钟

今天我们来学习,什么是 ioc 中的 作用域 scope (或者叫 lifetime 这俩概念差不多)
有的库中称之为 scope ,例如 inversify inversify.io/
有的称之为 lifetime ,例如 awilix github.com/jeffijoe/aw…

那么,究竟是什么呢?
最好理解的场景是,单例 Singleton,即,不管什么时候从容器中 get,总是返回同一个对象,不会每次都 new 一个新的给你

例如:

// 按照 inversify 的 api,
// 声明这个 Editor 类是单例的
container.bind('Editor').to(Editor).inSingletonScope();

const editor1 = constainer.get('Editor') // 缓存中没有的话,会 new 一个新的返回
const editor2 = constainer.get('Editor') // 再次get也不会new新的,而是从 缓存中获取
editor1 === editor2 // true

单例的场景没啥好说的

TRANSIENT (瞬态)这个是第二种类型,其实就是,每次 get 都会 new 一个新的给你,一般来说这种是默认的scope

例如

// 还是拿 inversify 的 api 举例,
// 绑定作用域为 瞬态的 TRANSIENT
container.bind('Node').to(Node).inTransientScope();

const node1 = constainer.get('Node') // 每次都 new 一个新的 node 给你
const node2 = constainer.get('Node') //
node1 === node2 // false

下一种类型是 request
这个 scope 比较有说法了,这个意思是,每次 get 的时候,都会按照对应的依赖图,创建一系列对象,而在创建的过程中,如果这个类型的对象已经创建过了,就用缓存的,不再创建新的
同样是缓存,区别于单例的是,单例是全局缓存,只要在这个 container 里创建过,就有缓存
而这个 request 是,本次 get 的过程中,有缓存,而当 get 结束之后,缓存也就清除了

举个例子

// 假设这个场景: A 依赖 B 和 C, 而 B 依赖 C
A => B   而  B => C
  => C

// 如果 C 瞬态的,则每次请求 A,会先创建 B 和 C,
// 而在创建 B 的时候,又会再次创建一次 C,而这两个 C 是不相等的
  
class A {
    b
    c
    contructor(@Inject('B') b, @Inject('C') c) {
        this.b = b
        this.c = c
    }
}

class B {
    c
    contructor(@Inject('C') c) {
        this.c = c
    }
}

class C {
   
}

// 假设我们将这三个类,都设置为 瞬态的
container.bind('A').to(A).inTransientScope();
container.bind('B').to(B).inTransientScope();
container.bind('C').to(C).inTransientScope();

const a = constainer.get('A') // 此时会遍历 A 的依赖图

// 依赖图类似
A => B => C
  => C

// 根据依赖图 依次构造,因此会创建两次 c 
a.c === a.b.c // false

// 而如果我们将 C 的scope 设置为 request 后
container.bind('C').to(C).inRequestScope();
const a = constainer.get('A') 
// 此时,在遍历依赖图的时候,第一次创建的 C 会生成缓存,
// 在解析 B 的依赖的时候,会直接拿之前缓存的 c ,不会再创建新的
a.c === a.b.c // true

request 这种还是比较有用的

最后一种是 childScope
意思是, 创建一个小的 “局部范围” ,生成的对象只在这个范围内生效
一个非常主要的场景是,node 里的请求,每个请求都创建一个 “局部范围” ,我们可以在这个 “局部范围” 内放置一些对象(权限,上下文,token,用户等等),这样每个请求之间都互不干扰

这个在 inversify.js 里是不支持的,我们来看下 awilix 的语法

// Increments the counter every time it is resolved.
let counter = 1
// 这个是,将 counterValue 注册为 “工厂类” (有的称之为 factory,有的称之为 function)
container.register({
  counterValue: asFunction(() => counter++).scoped(),
})

// 创建两个 scope
const scope1 = container.createScope()
const scope2 = container.createScope()

// scope 还可以再创建子 scope
const scope1Child = scope1.createScope()

scope1.cradle.counterValue === 1
// 第二次访问 counterValue , 因为前一次在 scope1 内有缓存,因而还是1
scope1.cradle.counterValue === 1 

// 首次访问 scope2 里的 counterValue , 没有缓存,获得的值为 2
scope2.cradle.counterValue === 2
// 再次访问 ,读取缓存 2
scope2.cradle.counterValue === 2

// 读取 子 scope 的值
scope1Child.cradle.counterValue === 3

OK,就看到这里,下次见