Giter Site home page Giter Site logo

uioc's People

Contributors

exodia avatar otakustay avatar srhb18 avatar strwind avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

uioc's Issues

依赖node做组件的声明式注册

我们知道,在Spring中组件是可以声明式注册和注入的

@Service
public class Knife implements Weapon {
    // ...
}

public class Character {
    @Autowired
    public Knife setWeapon(Weapon weapon) {
        // ...
    }
}

但是JavaScript在前端环境中,由于并没有类似ClassLoader的机制在运行前扫描所有的组件,所以纯粹前端环境是无法做自动注入的(根本不知道有哪些组件存在)

但是如果我们在构建过程或者后端有一定的工作可以扫描所有的文件,显然可以做到声明式的配置,因此后续可以考虑这样的工具,作用是扫描所有的.js文件查找所有注册的组件和注入关系

这个工具可以在构建过程中用以自动生成IoC的配置对象,也可以运行在后端动态输出ioc-config.js之类的文件(甚至内联在HTML中)

为edp单独设置main

和以前一样,edpnodemain判断不同,edp会自动加上src

我建议edp就用src下的好了,不需要理会dist

插件机制是否有必要

现在遇到这样一个问题,有些模块会依赖DOM元素,这些交由IoC管理能提升可测性,所以现在是这样的:

{
    account: {
        module: 'common/account',
        scope: 'static',
        properties: {
            userName: {
                creator: function () { return document.getElementById('user-name'); }
            }
        }
    }
}

感觉就是要写很多creatordocument.getElementById略麻烦,如果通过插件机制做扩展,写成creator: '#user-name'会不会有价值?

完善对es6模块的异步加载支持

现在的情况:

// module.js
export default class DefaultClass {};

export class Class1 {}

export class Class2 {}

// ioc.js
ioc.addComponent('component', {module: 'module'});

以上情况通过配置module关键字获取component时,获取到的仅仅是DefaultClass实例,想要配置获取Class1和Class2目前简单的方式得通过es6 import + creator的方式进行。或者要自己写插件支持,这个场景比较常见,打算内置支持。

1.0.0-beta todo

  1. 对外不暴露default export, 全部以named的形式暴露。

    2. setLoaderFunction改名为setProvider,参数和返回值暂时不变,兼容SystemJS环境。

  2. 移除getComponentConfig方法。

  3. 获取未注册组件时返回rejected promise

  4. 注册组件同名时抛异常

    5. 注册组件时,module与creator都设置时抛异常

  5. 移除factory method的支持, creator中不再支持字符串的配置,即如下实例化组件方式不再支持:

// app/List.js
define(
    function (require) {
        return {
            create: function() {
                return {};
            }
        }
    }
);

var config = {
    components: {
        list: {
            module: 'app/List',
            creator: 'create'
        }
    }
};

es6场景可以使用named export+systemJS支持,后期根据场景需求再考虑增加其他配置项支持。

aop 配置语法支持

目前设计的配置语法如下:

let log = {
    start: () => console.log(Date.now()),
    end: () => console.log(Date.now())
};

let iocConfig = {
    components: {
        myComponent: {
            creator() {
                this.doSomething = function () {};
                this.doSomething1 = function () {};
            },
            // aop 配置
            aopConfig: {
                // 通知器
                advisors: [
                    {
                        // 字符串匹配
                        matcher: 'doSomething',
                        // 具体通知
                        advices: {
                            // doSomething 执行前执行 log.start 方法
                            before: log.start,
                            // doSomething 执行完执行 log.end 方法
                            afterReturning: log.end
                        }
                    },
                    {
                        matcher: 'doSomething1',
                        advices: {
                            before: (...args) => console.log(args)
                        }
                    },
                    {
                        matcher: /doSomething/,
                        advices: {
                            after: () => console.log('after doSomething')
                        }
                    }
                ]
            }
        }
    }
};

// 打印 start end after doSomething
ioc.getComponent('myComponent').then(myComponent => myComponent.doSomething());

依赖的继承和组合

在JAVA等语言中,有这样的方式:

public interface Actable {
    void setWeapon(Weapon weapon);

    Weapon getWeapon();
}

pubilc class Wizard implements Actable {
    // ...实现
}

public class Amazon impolments Actable {
    // ...实现
}

当需要所有的Actable都有固定的Weapon对象时,可以按照接口来进行配置:

<interface type="Actable">
    <property name="weapon" type="Sword" />
</interface>

这样无论是创建Wizard还是创建Amazon都会有Sword对象作为其weapon属性

这个功能可以节省大量的配置,对应我们的现状,如果一个Model对象有setGlobalData就意味着其需要GlobalData的实例,但每一个都这么配置有2个问题:

  1. 配置太多,到处要写一样的配置
  2. 如果一个Model从不依赖变成了依赖GlobalData,或者反过来,那么就要改配置

同时,在JAVA中,除了接口外,继承关系也一样会造成配置的继承


针对js,因为没有显式的接口这个概念,所以可能要分为类型的“定义”和类型的“组合”两种,其中“定义”依旧是普通的ioc配置,随后再通过一些判断来确认是否需要注入

ioc.configInterface(
    'requireGlobalData',
    function (instance) {  return instance.setGlobalData; },
    {
        properties: {
            globalData: { $ref: 'GlobalData' }
        }
    }
); 

对于继承,由于js并不容易判断继承链,JAVA也并不是子类一定会有父类的注入配置,所以使用一个importmix配置即可:

var config = {
    foo: {
        properties: {
            x: { $ref: 'x' }
        }
    },
    bar: {
        mix: 'foo',
        properties: {
            y: { value: 2 }
        }
    }
};

支持类的高阶转换

我希望ioc支持这样一个功能:

{
    foo: {
        module: 'foo',
        scope: 'static'
    },
    bar: {
        module: 'bar',
        scope: 'static'
    },
    main: {
        module: 'common/Main',
        transformers: [
            {$ref: 'foo'},
            {$ref: 'bar'}
        ]
    }
}

此时如果ioc.getComponent('main')得到的是new bar(foo(Main))()

这能让我不用在每个框架中都支持基于高阶转换的extension实现,让ioc统一负责这一功能

并行获取同个单例组件时,会创建不同的实例

sample code:

ioc.addComponent('single', {scope: 'singleton', creator: Object});
Promise.all([
new Promise(resolve => ioc.getComponent('single', resolve)),
new Promise(resolve => ioc.getComponent('single', resolve))
]).then([instance1, instance2] => alert(instance1 === instance2));

期望是弹出 true, 但实际是 false, 内部实现的时候遗漏了创建实例的异步场景。

循环依赖处理策略

构造函数注入时,有循环依赖

场景demo

ioc.create({
       A: {  
                creator: function(instanceB) { this.b = instanceB }
                args: [{  $ref :  'B' }]
       },

       B: {  
                creator: function(instanceA) { this.a = instanceA }
                args: [{  $ref :  'A' }]
       }
});

 ioc.getComponent('A', function(a){ //...  });

可能的策略

  1. 抛异常
  2. 将依赖设置为 null传入, 控制台丢警告

setter 注入时,有循环依赖

场景demo

ioc.create({
       A: {  
                creator: function() { // ... },
                properties: {  b: { $ref: 'B' } }
       },

       B: {  
                creator: function() { // ... },
                properties: {  a: { $ref: 'A' } }
       }
});

 ioc.getComponent('A', function(a){ //...  });

可能的策略

控制台发警告,同时创建好依赖实例后,调用对应 setter 传入实例即可,注入这块不会存在无限递归问题。

支持ES5 setter

我们需要让uioc支持ES5通过Object.defineProperty定义的setter,一个setter的判断方法比较简单:

var descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
return descriptor && descriptor.set

一个问题是有可能一个对象同时有foo这个setter又有setFoo这个方法,我的建议是都注入一次

同时要支持auto的分析能把setter也找出来,这个逻辑也不难,配合Object.getOwnPropertyDescriptor就行了

我想周一是不是有可能完成这个功能,包括线下沟通的因为ES6 class默认成员不能枚举导致的for .. in失效问题

这里是一个已经写完的代替for .. in的方法

Setter.prototype.findAllSetters = function(config, instance) {
    var exclude = config.properties || {};
    var deps = [];

    if (Object.getPrototypeOf && Object.getOwnPropertyDescriptors) {
        var prototype = Object.getPrototypeOf(instance);

        while (prototype) {
            var keys = Object.getOwnPropertyKeys(keys);
            for (var i = 0, len = keys.length; i < len; i++) {
                var prop = this.getPropertyFromSetter(keys[i]);

                // 有属性,未和属性配置冲突,且组件已注册
                prop && !u.hasOwn(exclude, prop) && this.context.hasComponent(prop) && deps.push(prop);
            }
            prototype = Object.getPrototypeOf(prototype);
        }
    }
    else {
        for (var k in instance) {
            if (typeof instance[k] === 'function') {
                var prop = this.getPropertyFromSetter(k);

                // 有属性,未和属性配置冲突,且组件已注册
                prop && !u.hasOwn(exclude, prop) && this.context.hasComponent(prop) && deps.push(prop);
            }
        }
    }
};

可以在这个基础上改造加上对Object.getOwnPropertyDesriptor的使用,并将返回值由字符串改为{proptyName, setterType = 'accessor | method'应该能解决问题

另外有一个很关键的需要注意的是,因为有getter的存在,我们要尽量避免访问一个属性,因为getter带有逻辑可能产生一些副作用

注入容器自己

假设foo依赖bar,但在创建foo的时候无法获得bar,这种场景下无法直接使用IoC组合出一个合理的foo对象,而是要在foo对象中使用ioc.getComponent去获取bar(保证获取的时候已经有了)

这就需要,在foo中注入整个容器对象,才可以拿到ioc这个属性

aop拦截时机和实现

目前uioc是基于object的拦截,在组件实例化后对实例上的方法拦截,那么在构造函数中,如果有arrow函数调用,这个this是原有的object,而不是拦截后的object,造成混乱,如下NodeJS代码:by @otakustay

class Foo {
    constructor(context) {
        this.print = () => console.log(this.x);
    }
    foo() {
    }
}

let ioc = new (require('uioc').IoC)();

let config = {
    foo: {
        creator: Foo,
        properties: {
            x: 1
        },
        aopConfig: {
            advisors: [
                {
                    matcher: 'foo',
                    advices: {
                        around({proceed}) {
                            console.log('aop');
                        }
                    }
                }
            ]
        }
    }
};
ioc.addComponent(config);
ioc.getComponent('foo').then(f => f.print());
ioc.getComponent('foo').then(f => console.log(f.x));

期望输出:1 1
实际输出:undefined 1

但完全切换成class拦截,在实例上的方法则会无法拦截,因此这里暂时考虑的方案是提供配置项由使用者决定是用class还是object拦截。

IoC 插件机制

当前的容器生命周期

  1. 实例化容器
  2. 注册组件
  3. 获取组件
    3.1 调用各个操作符解析当前组件配置,设置静态化的依赖
    3.2 根据组件配置加载所需模块
    3.3 创建实例
    • 3.3.1 根据组件配置获取构造函数依赖
    • 3.3.2 回到 3 获取构造函数依赖实例
    • 3.3.3 调用组件构造函数,传入构造函数依赖,创建组件实例
    • 3.3.4 回到 3 获取 property 依赖实例并注入
    • 3.3.5 根据实例获取 setter 依赖
    • 3.3.6 回到 3 获取setter 依赖实例并注入
  4. 销毁容器

ILifeCircleHook: 生命周期钩子接口

string : get name()

钩子名称

<? exends IoCConfig> : onContainerInit(IoC : ioc, IoCConfig : config)

容器实例化时调用,传入 ioc 容器和当前配置作为参数, 可以在此拦截容器级的配置,返回一个新的容器级配置提供给 ioc 使用, ioc将基于新的配置做后续操作。

场景:基于 ioc 的插件需要在容器级配置上获取必要的配置信息。

如: aop 插件需要从容器级别的配置上解析 aop 配置

<? extends ComponentConfig> : onAddComponent(IoC: ioc, string: componentId)

注册组件时调用,传入 ioc 容器,当前组件 id ,可以在此拦截组件级的配置,返回一个新的组件配置提供给 ioc 使用,ioc 将基于此配置做后续操作。

场景:插件需要在组件级的配置上获取必要的配置信息

如:语法糖插件可以针对组件配置做些静态的映射转换

<? extends ComponentConfig> : onGetComponent(IoC: ioc, string: componentId)

获取组件时调用,传入 ioc 容器,当前组件 id,可以在此拦截组件级的配置,返回一个新的组件配置提供给 ioc 使用,ioc 将基于此配置做后续操作。

场景:插件需要在组件级的配置上获取必要的配置信息,而某些信息需要在获取组件时才能方便的获得。

如:目前所有的依赖解析都在这个时机进行,因为此时所有的依赖组件信息最全。

Promise<Any> : beforeCreateInstance(IoC : ioc, string : id, Any : instance)

创建组件实例前调用,传入 ioc 容器,当前组件 id,和当前已经创建的实例(可能没有),返回一个值为实例的 promise 给 ioc 使用,若返回值不为 promise,则不会覆盖现有实例, 若最终无实例,ioc 内部将根据组件配置创建实例,ioc 将基于此做后续操作。

场景:插件可以在此返回缓存实例,根据上下文信息做实例创建前的拦截操作。

如: 单例插件会在实例创建前检测是有缓存实例,有的话则返回。

Promise<Any> : afterCreateInstance(IoC : ioc, string : id, Any : instance)

创建组件实例后调用,传入 ioc 容器,当前组件 id,和当前已经创建的实例,返回一个值为实例的 promise 给 ioc 使用,若返回值不为 promise,则不会覆盖现有实例。

场景:插件可以在此缓存实例,根据上下文信息做实例创建后的操作。

如:

  • 单例插件会在此缓存实例。
  • 自动注入插件会在此基于实例做自动注入
  • 属性注入插件在此注入属性依赖

void : onContainerDispose(IoC: ioc)

销毁容器时调用。

场景:插件需要在容器销毁时释放资源。

如:缓存型/单例型插件需要在容器销毁时释放缓存资源。

容器插件

实现了 ILifeCircleHook 接口的子类

容器对插件的支持接口

IoC#addPlugins(ILifeCircleHook[]: plugins, [, number : pos])

在容器指定位置上插入一系列插件实例,未传入指定位置时,追加到插件列表末尾。若插件名称已存在则抛出异常。

ILifeCircleHook[] : IoC#getPlugins()

返回容器当前的插件列表

ILifeCircleHook[] : IoCConfig.plugins

容器实例化时传入的插件配置项

多个插件实例在同个钩子函数上的执行顺序

reduce 过程:FIFO,返回结果作为后续输入的一部分。

增加.npmignore

把没用的都去掉,只要srcdistpackage.json就够了

[讨论]同步获取组件的接口支持

uioc一直没增加这个接口支持的原因是只要有一个组件模块是异步加载的,那么获取该组件或者有对该组件依赖的组件就会出现非预期情况。

同步接口的初步想法是递归检测该组件以及依赖的组件模块是否已经加载,有任一未加载则抛异常,否则正常返回组件。

定义一个插件模型

让ioc的核心只保持模块的加载和插件的协调功能,将对象的加载和创建定义一个明确的分阶段生命周期,在各个阶段都可以应用插件,比如:

  • 对象模块路径获取,可以指定自动化模块路径计算的插件,减小配置
  • 对象的创建,构造函数注入作为插件实现
  • 对象创建后,属性注入是一个插件,可以将setXxxset xxx作为不同插件实现(可能工作比较复杂)

后续的AOP同样使用插件的模式实现,也存在使用多个阶段的插件,如singleton就是在对象创建阶段增加判断,并在对象创建后进行单例管理

匿名组件配置

项目中遇到的场景:

var compoment = {
         appRequestStrategy: {
                module: 'common/RequestStrategy',
                args: ['app', 'app']
        },
        appRequestStrategy: {
                module: 'common/RequestStrategy',
                args: ['creative', 'creative']
        },
       appData: {
              // ....
             properties: {
                    requestStrategy: { $ref: 'appRequestStrategy' }
             }
       },
      creativeData: {
              // ....
             properties: {
                    requestStrategy: { $ref: 'creativeRequestStrategy' }
             }
       }
}

可改成:

var compoment = {
         requestStrategy: {
                module: 'common/RequestStrategy'
        },
       appData: {
              // ....
             properties: {
                    requestStrategy: { 
                            $import: 'requestStrategy',
                            args: ['app', 'app']
                    }
             }
       },
      creativeData: {
              // ....
             properties: {
                    requestStrategy: { 
                            $import: 'requestStrategy',
                            args: ['creative', 'creative']
                    }
             }
       }
}

增加$import操作符,将导入对应的组件配置,同时加上自定义的配置,相同的配置,自定义配置优先。
$import与$ref一样,适用于 args 和 properties 的配置中

能否对一个已经存在的对象做注入

一个对象不是ioc创建出来的,但需要注入其属性

麻烦在于,不知道对应的module id,没有ioc里的config id,也不知道类名,毕竟js没有元数据

增加$array与$map操作符,以支持注入数组和对象形式的依赖

// component.js
function Component(depList, depObject) {
    this.depList = depList;
    this.depObject = depObject;
}

Component.prototype.setArrayProp = function (arrayProp) {
    this.arrayProp = arrayProp;
};

Component.prototype.setObjectProp = function (objectProp) {
    this.objectProp = objectProp;
};

// main.js
var component = {
    module: 'component',
    args: [
        {
            $array: [
                {$ref: 'dep1'},
                {$ref: 'dep2'},
                'normal value'
            ]
        },
        {
            $map: {
                prop1: {$ref: 'dep3'},
                prop2: {$ref: 'dep4'},
                prop3: 'normal property value'
            }
        }
    ],
    properties: {
        arrayProp: {
            $array: [
                {$ref: 'dep5'},
                {$ref: 'dep6'},
                'normal value'
            ]
        },
        objectProp: {
            $object: {
                prop1: {$ref: 'dep7'},
                prop2: {$ref: 'dep8'},
                prop3: 'normal property value'
            }
        }
    }
};

var IoC = require('uioc');
var ioc = IoC({
    component: component,
    dep1: {
        creator: function Dep() {
            // xxx
        }
    },
    dep2: {}
    // ...
});

ioc.getComponent('component', function (component) {
    console.log(component.depList instanceof Array === true); // true
    console.log(component.arrayProp instanceof Array === true); // true
    console.log(typeof component.depObject === 'object'); // true
    console.log(typeof component.objectProp === 'object'); // true
});

es5 property getter 与 自动注入&属性注入冲突的问题

在 property getter 中会存在逻辑去获取实例上的 ioc 依赖,但这个时候实例的依赖可能并没有注入完成,此时的依赖为 undefined,导致异常, 代码如下:

ioc.addComponents({
     a: {
           module: 'a',
           auto: true
     },
    b: {
         creator: {
              fn() {}
         }, 
        scope: 'static'
    }
});

class A {
    constructor() {
         let a = this.a;
         // xxxx
    }
     get a () {
            let c = this.getB().fn();
     }
     setB(b) {
          this.b = b;
     }
    getB() {
         return this.b;
    }
}

另外:ioc/Setter.js下的 Setter#getPropertyFromSetter方法检测属性逻辑存在缺陷:

Setter.prototype.getPropertyFromSetter = function (instance, name) {
     var prop = null;
      // 这行代码会导致 property getter 执行,具有一定的副作用
      if (typeof instance[name] === 'function' && SETTER_REGEX.test(name)) {
             prop = name.charAt(SET_LEGTH).toLowerCase() + name.slice(SET_LEGTH + 1);
       }

        return prop;
};

能一次获取多个component吗?

想要这样:

ioc.getComponent(['foo', 'bar'], function (foo, bar) {
    // ...
});

现在要写2层的嵌套callback,或者转为Promise后控制,麻烦

支持属性的自动注入

希望的效果是,如果有一个对象在IoC中设置了名字叫globalData,另一个对象有一个setGlobalData方法,那么不需要特别的配置会自动注入这个对象

对于这个,有几个特性需要实现:

  1. 要自动查找被注入对象的setXxx方法,依次和容器内的组件关联
  2. 要在配置上支持一个auto: true/flase的配置,默认为false,仅当为true时才有自动注入的效果,同时auto配置建议还有容器级别的开关
  3. 考虑到每次一个对象拿到都要遍历所有属性找setter会有性能的消耗,容器应该假设类型是静态的(即运行时不会随便加减方法),因此每个组件都会缓存(或者叫预编译)注入关系

具体类似:

ioc.addComponent(
    'globalData',
    { module: 'common/GlobalData' }
);

ioc.addComponent(
    'XxxModel',
    {
        module: 'xxx/Model',
        auto: true
    }
);

创建构造函数依赖时,未把其依赖的属性注入

var config = {
    components: {
              a: {
                      creator: function (b) {
                             this.b = b;
                      },
                      args: [ { $ref: 'b' } ]
              },
              b: {
                     creator: function () {},
                     properties: {
                           prop: 'prop'
                     }
             }
     }
};

ioc.getComponent('a', function (a) {
      alert(a.b.prop);
});

此时a.b.prop应该为 'prop',实际为undefined;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.