uioc 是一个轻量级的前端 ioc 容器,因为不知道叫什么名字好,所以就是 unnamed ioc 了。! uioc 能够很好的与 AMD/CMD 模块加载器配合工作,将依赖的创建转移给 ioc,能够更大程度的提高模块复用性。
以下的例子是常见的一个 mvc 业务功能的简化实现:model,view,action,分别演示了在无依赖注入,有依赖注入,和使用 ioc 容器管理依赖的写法
以下例子中:Action 中依赖了 Model 和 View,通过loader加载了两个依赖,并在构造函数中实例化 Model 和 View,将依赖硬编码至 Action 中。 这使得要对 Action 进行测试时,难以对 View 和 Model 进行 mock。也使得要对 Action 进行复用,必然需要一并使用绑定的 View 和 Model, 而 View 是经常变化的,这很难满足复用的目的。
// Action.js
define(function () {
var View = require('./View');
var Model = require('./Model');
function Action() {
this.model = new Model();
this.view = new View();
this.view.model = this.model;
}
Action.prototype.enter = function () {
this.model.load();
this.view.render();
};
return Action;
});
// View.js
define(function () {
function View() {
}
View.prototype.render = function () {
document.body.innerHTML = this.model.get('name');
};
return View;
});
// Model.js
define(function () {
function Model() {
this.stores = {};
}
Model.prototype.load = function () {
this.set('name', 'Hello');
};
return Model;
});
// main.js
require(
['Action'],
function(Action){
var action = new Action();
action.enter();
}
);
为了使得 Action 更好的复用和测试,以下例子将 View 和 Model 作为 Action 构造函数的参数注入,Action 内部将不再承担 Model 和 View 的创建职责。 Model 和 View 的创建交给了外部主程序代码,这使得 View 和 Model 的实现可以由外部进行替换。Action 仅针对 View 和 Model 的接口编程。
// Action.js
define(function () {
function Action(model, view) {
this.model = model;
this.view = view;
this.view.model = this.model;
}
Action.prototype.enter = function () {
this.model.load();
this.view.render();
};
return Action;
});
// View.js
define(function () {
function View() {
}
View.prototype.render = function () {
document.body.innerHTML = this.model.get('name');
};
return View;
});
// Model.js
define(function () {
function Model() {
this.stores = {};
}
Model.prototype.load = function () {
this.set('name', 'Hello');
};
return Model;
});
// main.js
require(
['Action', 'View', 'Model'],
function(Action, View, Model){
var view = new View();
var model = new Model();
var action = new Action(model, view);
action.enter();
}
);
第二个例子解决了模块与依赖创建分离的问题,将依赖的创建丢给了 main,这会导致 main 中的实例化代码膨胀,而 ioc 很好的承当了依赖的注入与创建。 仅需要一份构件配置文件,ioc 容器会自动解析配置和各个构件的依赖关系,在获取实例时,会将依赖自动注入。当 View 和 Model 的实现变化时,仅需要更改配置即可。
// config.js
define({
components: {
action: {
module: 'Action',
args: [
{ $ref: 'view' },
{ $ref: 'model' },
]
},
view: { module: 'view' },
model: { module: 'model' }
}
});
// Action.js
define(function () {
function Action(model, view) {
this.model = model;
this.view = view;
this.view.model = this.model;
}
Action.prototype.enter = function () {
this.model.load();
this.view.render();
};
return Action;
});
// View.js
define(function () {
function View() {
}
View.prototype.render = function () {
document.body.innerHTML = this.model.get('name');
};
return View;
});
// Model.js
define(function () {
function Model() {
this.stores = {};
}
Model.prototype.load = function () {
this.set('name', 'Hello');
};
return Model;
});
// main.js
require(
['ioc', 'config'],
function(IoC, config){
var ioc = new IoC(config);
ioc.getComponent('action', function(action){
action.enter();
});
}
);
ioc 配置
构件批量配置对象, 其中每个key 为构件id,值为构建配置,配置选项见IoC.prototype.addComponent
ioc 的模块加载器,默认使用全局的 require,需要符合 AMD 接口规范
给容器添加一个构件配置
构件id,不能重复
单个构件配置对象
模块加载的路径,将传给 loader,对 AMD loader,不可使用相对路径(使用的是全局 require)
*** 若配置了config.creator 函数,此配置无效。 ***
构件的构造函数或工厂函数,若未设置为函数,则使用config.module配置的返回值作为creator; 若 creator 为字符串,则使用 config.module 配置的模块返回值的creator属性值作为 creator;
var myComponentConfig = {
// 最终为 new A.method();
module: 'A',
creator: 'method'
};
构件实例的管理方式,默认为 transient:
- 为 transient,每次获取构件实例时,都会调用 creator返回一个新的实例;
- 为 singleton,表示构件为单例,仅在第一次获取实例时调用一次 creator,之后返回同一个实例;
- 为 static,直接返回 creator。
标识 creator 是否为工厂函数,若为工厂,则在创建实例时,直接调用,否则用 new 进行调用。
调用 creator 时传入的参数,完成构造函数依赖注入。 若在单个参数的对象中设置了 $ref 操作符,则表示依赖某个构件,此时将此参数替换为$ref对应的构件实例
var myComponentConfig = {
creator: function (name, b){
this.name = name;
this.b = b;
},
// new creator('string', new B())
args: [ 'string', { $ref: 'B' } ]
};
完成构件实例创建后,需要注入的属性,key 为属性名,值为要注入的属性值,若值为对象且有$ref,则该属性值会被替换为$ref对应的构件实例。
var myComponentConfig = {
creator: function (){
this.setB = function(b) {
this.b = b;
}
},
properties: {
// 实例化 a 后,执行 aInstance.name = 'string';
name: 'string',
// 实例化 a 后,执行 aInstance.setB(b)
B: { $ref: 'B' }
}
};
设置是否自动注入,若为 true,则 ioc 容器会在创建构件实例后,寻找实例的 setter(set${Name}), 之后会调用该 setter,将对应的 ${name}依赖作为参数传入 setter。 注意:properties中的配置优先级高于自动注入
var myComponentConfig = {
creator: function () {
this.setB = function(b) {
this.b = b;
}
this.setC = function(c) {
this.c = c;
}
},
// 会自动查找 setter的依赖,这里会查询到 setB 和 setC对应的依赖为'b','c',
// 由于 c 在 propeties 中已经配置,优先级 properties 更高,因此这里不会去实例化构件 c,仅会实例化构件 b,并调用setB(b);
auto: true,
properties: {
c: 'c' // 优先级高于自动注入,因此最终aInstance.c为'c'
}
};
给容器批量添加构件配置
键为构件id,值为构件配置
获取构件实例
需要获取的构件 id,可为数组或字符串; 为字符串时,获取id 对应的构件,为数组时,批量获取数组中每个字符串 id 对应的构件
构件获取完毕后的回调函数,会将实例按照 id 的传入顺序作为参数依次传递给 cb:
ioc.getComponent(['a', 'b'], function(a, b){
});
设置 ioc 实例的模块加载器
销毁容器,若 scope 为 singleton 的构件有 dispose 方法,ioc会自动调用。
- 支持循环依赖的注入