angularjs模块覆盖的问题(转)
Angular中的模块机制
module
在AngularJS中,有module的概念,但是它这个module,跟我们通常在AMD里面看到的module是完全不同的两种东西,大致可以相当于是一个namespace,或者package,表示的是一堆功能单元的集合。
一个比较正式的Angular应用,需要声明一个module,供初始化之用。比如说:
angular.module("test", [])
.controller("TestCtrl", ["$scope", function($scope) {
$scope.a = 1;
}]);
随后,可以在HTML中指定这个module:
<div ng-app="test" ng-controller="TestCtrl">
{{a}}
</div>
这样,就是以这个div为基准容器,实例化了刚才定义的module。
或者,也可以等价地这样,在这里,我们很清楚地看到,module的意义是用于标识在一个页面中可能包含的多个Angular应用。
angular.element(document).ready(function() {
angular.bootstrap(document.getElementById("app1"), ["test"]);
angular.bootstrap(document.getElementById("app2"), ["test"]);
});
<div id="app1" ng-controller="TestCtrl">
{{a}}
</div>
<div id="app2" ng-controller="TestCtrl">
{{a}}
</div>
这样可以在同一个页面中创建同一module的不同实例。两个应用互不干涉,在各自的容器中运行。
module的依赖项
除此之外,我们可以看到,在module声明的时候,后面带一个数组,这个数组里面可以指定它所依赖的module。比如说:
angular.module("moduleB", [])
.service("GreetService", function() {
return {
greet: function() {
return "Hello, world";
}
};
});
angular.module("moduleA", ["moduleB"])
.controller("TestCtrl", ["$scope", "GreetService", function($scope, GreetService) {
$scope.words = "";
$scope.greet = function() {
$scope.words = GreetService.greet();
};
}]);
然后对应的HTML是:
<div ng-app="moduleA">
<div ng-controller="TestCtrl">
<span ng-bind="words"></span>
<button ng-click="greet()">Greet</button>
</div>
</div>
好了,注意到这个例子里面,创建了两个module,在页面上只直接初始化了moduleA,但是从moduleA的依赖关系中,引用到了moduleB,所以,moduleA下面的TestCtrl,可以像引用同一个module下其他service那样,引用moduleB中定义的service。
到这里,我们是不是就可以把module当作一种namespace那样的组织方式呢,很可惜,它远远没有想的那么好。
这种module真的有用吗?
看下面这个例子:
angular.module("moduleA", [])
.factory("A", function() {
return "a";
})
.factory("B", function() {
return "b";
});
angular.module("moduleB", [])
.factory("A", function() {
return "A";
})
.factory("B", function() {
return "B";
});
angular.module("moduleC", ["moduleA", "moduleB"])
.factory("C", ["A", "B", function(A, B) {
return A + B;
}])
.controller("TestCtrl", ["$scope", "C", function($scope, C) {
$scope.c = C;
}]);
angular.module("moduleD", ["moduleB", "moduleA"])
.factory("C", ["A", "B", function(A, B) {
return A + B;
}])
.controller("TestCtrl", ["$scope", "C", function($scope, C) {
$scope.c = C;
}]);
angular.module("moduleE", ["moduleA"])
.factory("A", function() {
return "AAAAA";
})
.factory("C", ["A", "B", function(A, B) {
return A + B;
}])
.controller("TestCtrl", ["$scope", "C", function($scope, C) {
$scope.c = C;
}]);
<div id="app1" ng-controller="TestCtrl">
<span ng-bind="c"></span>
</div>
<div id="app2" ng-controller="TestCtrl">
<span ng-bind="c"></span>
</div>
<div id="app3" ng-controller="TestCtrl">
<span ng-bind="c"></span>
</div>
angular.element(document).ready(function() {
angular.bootstrap(document.getElementById("app1"), ["moduleC"]);
angular.bootstrap(document.getElementById("app2"), ["moduleD"]);
angular.bootstrap(document.getElementById("app3"), ["moduleE"]);
});
我们在moduleA和moduleB中,分别定义了两个A跟B,然后,在moduleC和moduleD的时候中,分别依赖这两个module,但是依赖的顺序不同,其他所有代码完全一致,再看看结果,会发现两边的结果居然是不一致的。
再看看moduleE,它自己里面有一个A,然后结果跟前两个例子也是不同的。
照理说,我们对module会有一种预期,也就是把它当作命名空间来使用,但实际上它并未起到这种作用,只是一个简单的复制,把依赖的module中定义的东西全部复制到自己里面了,后面进来的会覆盖前面的,比如:
- moduleC里面,来自moduleA的两个变量被来自moduleB的覆盖了
- moduleD里面,来自moduleB的两个变量被来自moduleA的覆盖了
- moduleE里面,来自moduleA的A被moduleE自己里面的A覆盖了,因为它的A是后加进来的
整个覆盖过程没有任何提示。
我们可以把module设计的初衷理解为:供不同的开发团队,或者不同的业务模块做归类约束用,但实际上完全没有起到这种作用。结果,不得不在下级组织单元的命名上继续做文章,不然在多项目集成的时候,就要面临冲突的风险。
更多的坑
不仅如此,这种module机制还为大型应用造成了不必要的麻烦。比如说,module不支持运行时添加依赖,看下面的例子:
angular.module("some.components", [])
//这里定义了一些组件
;
假设上面是一个组件库,集中存放于components.js中,我们要在自己的应用中使用,必须:
angular.module("our.app", ["some.components"]);
现在假设这个components.js较大,我们不打算在首页引入,想在某个时候动态加载,就会出现这样的尴尬局面:
- 主应用our.app启动的时候,必须声明所有依赖项
- 但是它所依赖的module "some.components"的声明还在另外一个未加载的文件components.js中
关键问题就在于它不存在一个在our.app启动之后向其中添加some.components依赖的方式。我们预期的代码方式是类似这样:
angular.module("our.app", []);
require("components.js", function() {
// angular.module("our.app").addDependency("some.components");
// ready to use
});
也就是这段代码中注释掉的那句。但从现在看来,它基本没法做这个,因为他用的是复制的方式,而且对同名的业务单元不做提示,也就是可能出现覆盖了已经在使用的模块,导致同一个应用中的同名业务单元出现行为不一致的情况,对排错很不利。
在一些angular最佳实践中,建议各业务模块使用module来组织业务单元,基于以上原因,我个人是不认同的,我推荐在下一级的controller,service,factory等东西上,使用标准AMD的那种方式定义名称,而彻底放弃module的声明,比如所有业务代码都适用同一个module。详细的介绍,我会在另外一篇文章中给出。
此外,考虑到在前端体系中,JavaScript是需要加载到浏览器才能使用的,module的机制自身也至少应当包括异步加载机制,很可惜,没有。没有模块加载机制,意味着什么呢?意味着做大型应用有麻烦。这个可以用一些变通的方式去处理,在这里先不提了。
可以看到,Angular中的module并未起到预期作用,相反,还造成了一些麻烦。因此,我认为这是Angular当前版本中唯一一块弊大于利的东西,在2.0中,这部分已经做了重新规划,会把这些问题解决,也加入动态加载的考虑。
原文博客地址:https://github.com/xufei/blog/issues/17