ecomfe / uioc Goto Github PK
View Code? Open in Web Editor NEWIoC Framework for us
License: MIT License
IoC Framework for us
License: MIT License
我们知道,在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
和node
的main
判断不同,edp
会自动加上src
我建议edp
就用src
下的好了,不需要理会dist
现在遇到这样一个问题,有些模块会依赖DOM元素,这些交由IoC管理能提升可测性,所以现在是这样的:
{
account: {
module: 'common/account',
scope: 'static',
properties: {
userName: {
creator: function () { return document.getElementById('user-name'); }
}
}
}
}
感觉就是要写很多creator
和document.getElementById
略麻烦,如果通过插件机制做扩展,写成creator: '#user-name'
会不会有价值?
现在的情况:
// 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的方式进行。或者要自己写插件支持,这个场景比较常见,打算内置支持。
function A() {
}
A.prototype.setName = function (name) {
// xxxxx
}
ioc.addComponent(
'a',
{
creator: A
}
);
ioc.getComponent('a');
在 ioc.getComponent('a')时, A#setName会被调用,但 name 未注册为组件,本质上不应该调用。
对外不暴露default export, 全部以named的形式暴露。
2. setLoaderFunction改名为setProvider,参数和返回值暂时不变,兼容SystemJS环境。
移除getComponentConfig方法。
获取未注册组件时返回rejected promise
注册组件同名时抛异常
5. 注册组件时,module与creator都设置时抛异常
移除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支持,后期根据场景需求再考虑增加其他配置项支持。
目前设计的配置语法如下:
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个问题:
Model
从不依赖变成了依赖GlobalData
,或者反过来,那么就要改配置同时,在JAVA中,除了接口外,继承关系也一样会造成配置的继承
针对js,因为没有显式的接口这个概念,所以可能要分为类型的“定义”和类型的“组合”两种,其中“定义”依旧是普通的ioc配置,随后再通过一些判断来确认是否需要注入
ioc.configInterface(
'requireGlobalData',
function (instance) { return instance.setGlobalData; },
{
properties: {
globalData: { $ref: 'GlobalData' }
}
}
);
对于继承,由于js并不容易判断继承链,JAVA也并不是子类一定会有父类的注入配置,所以使用一个import
或mix
配置即可:
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, 内部实现的时候遗漏了创建实例的异步场景。
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){ //... });
ioc.create({
A: {
creator: function() { // ... },
properties: { b: { $ref: 'B' } }
},
B: {
creator: function() { // ... },
properties: { a: { $ref: 'A' } }
}
});
ioc.getComponent('A', function(a){ //... });
控制台发警告,同时创建好依赖实例后,调用对应 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带有逻辑可能产生一些副作用
场景:仅在dev环境下做循环依赖检测,生产环境不做循环依赖检测,初步估计性能能提升20%-100%
假设foo
依赖bar
,但在创建foo
的时候无法获得bar
,这种场景下无法直接使用IoC组合出一个合理的foo
对象,而是要在foo
对象中使用ioc.getComponent
去获取bar
(保证获取的时候已经有了)
这就需要,在foo
中注入整个容器对象,才可以拿到ioc
这个属性
目前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 容器和当前配置作为参数, 可以在此拦截容器级的配置,返回一个新的容器级配置提供给 ioc 使用, ioc将基于新的配置做后续操作。
场景:基于 ioc 的插件需要在容器级配置上获取必要的配置信息。
如: aop 插件需要从容器级别的配置上解析 aop 配置
注册组件时调用,传入 ioc 容器,当前组件 id ,可以在此拦截组件级的配置,返回一个新的组件配置提供给 ioc 使用,ioc 将基于此配置做后续操作。
场景:插件需要在组件级的配置上获取必要的配置信息
如:语法糖插件可以针对组件配置做些静态的映射转换
获取组件时调用,传入 ioc 容器,当前组件 id,可以在此拦截组件级的配置,返回一个新的组件配置提供给 ioc 使用,ioc 将基于此配置做后续操作。
场景:插件需要在组件级的配置上获取必要的配置信息,而某些信息需要在获取组件时才能方便的获得。
如:目前所有的依赖解析都在这个时机进行,因为此时所有的依赖组件信息最全。
创建组件实例前调用,传入 ioc 容器,当前组件 id,和当前已经创建的实例(可能没有),返回一个值为实例的 promise 给 ioc 使用,若返回值不为 promise,则不会覆盖现有实例, 若最终无实例,ioc 内部将根据组件配置创建实例,ioc 将基于此做后续操作。
场景:插件可以在此返回缓存实例,根据上下文信息做实例创建前的拦截操作。
如: 单例插件会在实例创建前检测是有缓存实例,有的话则返回。
创建组件实例后调用,传入 ioc 容器,当前组件 id,和当前已经创建的实例,返回一个值为实例的 promise 给 ioc 使用,若返回值不为 promise,则不会覆盖现有实例。
场景:插件可以在此缓存实例,根据上下文信息做实例创建后的操作。
如:
销毁容器时调用。
场景:插件需要在容器销毁时释放资源。
如:缓存型/单例型插件需要在容器销毁时释放缓存资源。
实现了 ILifeCircleHook 接口的子类
在容器指定位置上插入一系列插件实例,未传入指定位置时,追加到插件列表末尾。若插件名称已存在则抛出异常。
返回容器当前的插件列表
容器实例化时传入的插件配置项
reduce 过程:FIFO,返回结果作为后续输入的一部分。
把没用的都去掉,只要src
、dist
和package.json
就够了
uioc一直没增加这个接口支持的原因是只要有一个组件模块是异步加载的,那么获取该组件或者有对该组件依赖的组件就会出现非预期情况。
同步接口的初步想法是递归检测该组件以及依赖的组件模块是否已经加载,有任一未加载则抛异常,否则正常返回组件。
让ioc的核心只保持模块的加载和插件的协调功能,将对象的加载和创建定义一个明确的分阶段生命周期,在各个阶段都可以应用插件,比如:
setXxx
和set 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 的配置中
RT
一个对象不是ioc创建出来的,但需要注入其属性
麻烦在于,不知道对应的module id,没有ioc里的config id,也不知道类名,毕竟js没有元数据
// 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
});
在 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;
};
想要这样:
ioc.getComponent(['foo', 'bar'], function (foo, bar) {
// ...
});
现在要写2层的嵌套callback
,或者转为Promise
后控制,麻烦
希望的效果是,如果有一个对象在IoC中设置了名字叫globalData
,另一个对象有一个setGlobalData
方法,那么不需要特别的配置会自动注入这个对象
对于这个,有几个特性需要实现:
setXxx
方法,依次和容器内的组件关联auto: true/flase
的配置,默认为false
,仅当为true
时才有自动注入的效果,同时auto
配置建议还有容器级别的开关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;
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.