我正在开发一个应用程序,使用角度作为一个客户端框架,角度当前岩石,我真的很高兴使用它,虽然现在我发现我使用了很多复制和粘贴代码,我想组织成类层次。例如对话框共享一组通用的功能,他们需要被打开,关闭,提供typahead功能的代码也是从一些父BaseTypeaheadClass继承的第一个候选人,虽然一个我没有找到在角度是一个标准的方式组织这些层次结构。两个控制器,服务,提供者使用普通的javascript函数下面可以通过原型的方式扩展,所以我的问题是:
题
什么是组织我的类函数的角度方式,有什么标准机制,将允许从另一个派生一个类
P.S。
我猜这个问题:
>定义基类的实现作为服务,因此它们将很容易注入任何控制器或其他需要该特定类的服务
>定义OOP服务,并提供将用于创建基础/派生类的方法,如define,derived等
谢谢,
编辑
从我开始问我的问题的时间过去了一段时间。从那时起,我已经出来了方法,我成功地使用在几个项目,我非常喜欢,想与大家分享。
目前angular不提供任何用于组织类层次结构的构造,并且很遗憾,因为或多或少的大应用程序不能满足只有Model / View / Controller / …结构,它必须将它的代码组织成OOP对象。
我在网络开发领域已经工作了很长时间,我还没有看到一个企业项目,大量利用JavaScript的OOP。我看到的是巨大的,良好的组织的服务器端/数据库侧的逻辑接近无限的javascript意粉润滑脂动物园的框架和库在客户端。
没有MVVM,MVP框架,如knockout.js,骨干,其他…能够取代OOP本身。如果你不使用定向编程的核心原则,如类,对象,继承,抽象,多态性,你是在深处,你会得到一个巨大的长javascript的意大利面。
关于Angular我认为它是一个框架非常不同于knockout.js / backbone.js /任何其他MVV任何框架,但根据我的实践也不是一个银弹能够取代OOP。当我试图不使用OOP与Angular I结束了重复逻辑大多在控制器。不幸的是没有(我没有发现没有)干净和有角度的方式打那个问题。
但我已成功(我认为)解决了这个问题。
我使用紧凑,zerro依赖性lib,只是实现了John Resig的Simple JavaScript Inheritance(https://github.com/tracker1/core-js/blob/master/js-extensions/040-Class.js)。在这个库的帮助下,我能够创建/继承/创建抽象方法/覆盖它们,换句话说,做我在服务器端习惯的一切。
这里是一个使用示例:
Application.factory('SomeChildobject',['$http','SomeParentClass',function ($http,SomeParentClass) {
var SomeChildClass = SomeParentClass.extend({
init: function() { // Constructor
this._super.init(123,231); // call base constructor
},someFunction: function() {
// Notice that your OOP Now kNows everything that can be injected into angular service,which is pretty cool :)
$http({method: 'GET',url: '/someUrl'}).then(function(){
this._super.someFunction(); // call base function implementation
});
}
});
// return new SomeChildClass(); // We are not returning instance here!
return SomeChildClass; // Service is a function deFinition not an instance of an object
}]);
// So Now we can both use this service in angular and have the ability to extend it using the `extend` method call,like so:
Application.controller('MegaController',['$scope','SomeChildClass',function ($scope,SomeChildClass) {
$scope.someObject = new SomeChildClass();
}]);
OOP角度游戏一起非常好,在角度上下文创建的对象可以利用通过服务自动依赖注入,所以你不必注入实例到你的OOP构造函数,这个事实使你的OOP层次结构非常苗条,没有不相关的东西,需要(和是)由angular.js处理
所以玩这种方法,并提供反馈在这里与您获得的结果或您遇到的问题,
谢谢,
另一个编辑
最近我已经遇到了很少的原始Class.js实现的问题,如下所示:
1)如果你将一个对你的实例方法的引用传递给其他方法的回调,这些方法可能不会按你期望的方式工作。他们会放弃参考。在这种情况下,你会期望看到你的当前对象在这里,但它将是顶级窗口或一些其他上下文对象,这取决于回调如何调用你的方法。它发生是由于JavaScript架构。为了解决这个问题,提供了一个特殊的ClassMember函数,指示Class在创建时将方法绑定到对象上下文(查看下面的使用进一步的指导)。
2)显然原始的Class.js实现不知道有关角度类型的控制器方法声明。
Class.extend('YourClassdisplayName',{
ctor: function () {
// Some useful constructor logic
},controller: ['$scope','$attrs',$attrs) {
// Do smth. with $scope and $attrs
}]
});
当前实现理解上述语法
3)当使用上面的方法没有适当的处理它会打破角度$$注释的过程,所以参考上面的例子,将不可能注入$ scope和$ attrs到ClassMember方法,或overriden方法,使用this.base …)。所以这也是固定的。
诀窍:
1)当在异步操作处理程序(类似$ http.get(…,function(){self.base(…);})中使用this.base(…)时请注意this.base (…)调用有一个有限的生命周期,一旦方法返回this.base(…)停止现有。因此,如果您计划以异步方式调用基本方法,则应明确保存对基本方法的引用。即:
...
var self = this;
var base = this.base;
...
$http.get(...,function () {
base.call(self,...); // or base.apply(self,...),or base() if you don't care about `this`
})
我已经解决了所有上述问题(除了一个gotcha,由于JavaScript架构无法解决),并希望与大家分享,希望你会从中受益:
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*
* Inspired by base2 and Prototype
* Angular adaptations by Denis Yaremov http://github.com/lu4
* Usage:
---------------------------------
var X = Class.extend('X',{
ctor: function () {
this.name = "I'm X";
},myOrdinaryMethod: function (x,y,z) {
console.log([this.name,x,z]);
},myClassMemberMethod: ClassMember(function (x,z]);
})
});
var Y = Class.extend('Y',{
ctor: function () {
this.name = "I'm Y";
},z]);
})
});
var x = new X();
var y = new Y();
x.myClassMemberMethod('a','b','c'); // ["I'm X","a","b","c"]
y.myClassMemberMethod('u','v','m'); // ["I'm Y","u","v","m"]
x.myOrdinaryMethod('a',"c"]
y.myOrdinaryMethod('u',"m"]
y.theirOrdinaryMethod = x.myOrdinaryMethod;
y.theirClassMemberMethod = x.myClassMemberMethod;
y.theirOrdinaryMethod('a','c'); // ["I'm Y","c"]
y.theirClassMemberMethod('u','m'); // ["I'm X","m"]
*/
angular.module('app').factory('ClassMember',function () {
return function ClassMember(fn) {
if (this instanceof ClassMember) {
this.fn = fn;
} else {
return new ClassMember(fn);
}
};
});
angular.module('app').factory('Class',function (ClassMember) {
var runtime = { initializing: false },fnTest = /xyz/.test(function() { xyz; }) ? /\bbase\b/ : /.*/,FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m,STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var toString = Object.prototype.toString;
// The base Class implementation (does nothing)
function Class() { };
Class.members = { };
// Create a new Class that inherits from this class
Class.extend = function extend(displayName,properties) {
var array;
var targetMembers = {};
var sourceMembers = this.members;
for (var memberName in sourceMembers) {
if (sourceMembers.hasOwnProperty(memberName)) {
targetMembers[memberName] = sourceMembers[memberName];
}
}
var base = this.prototype;
// Instantiate a base class (but only create the instance,// don't run the ctor constructor)
runtime.initializing = true;
var prototype = new this();
runtime.initializing = false;
// copy the properties over onto the new prototype
for (var name in properties) {
if (properties.hasOwnProperty(name)) {
// Check if we're overwriting an existing function
var property = properties[name];
// Support angular's controller/service/factory declaration notation
if (toString.call(property) === '[object Array]') {
array = property;
var item = array[array.length - 1];
if (toString.call(item) === '[object Function]' || item instanceof ClassMember) {
property = array[array.length - 1];
} else {
array = null;
}
} else {
array = null;
}
var isClassMember = property instanceof ClassMember;
if (isClassMember) {
property = property.fn;
}
if (typeof property === "function") {
if (typeof base[name] === "function" && fnTest.test(property)) {
property = (function (propertyName,fn) {
var args = fn.toString().replace(STRIP_COMMENTS,'').match(FN_ARGS)[1];
return (new Function('propertyName','fn','base','return function (' + args + ') {\n\
var prevBase = this.base;\n\
var hasBase = "base" in this;\n\
\n\
// Add a new .base() method that is the same method\n\
// but on the super-class\n\
\n\
this.base = base[propertyName];\n\
\n\
// The method only need to be bound temporarily,so we\n\
// remove it when we\'re done executing\n\
var ret = fn.call(this' + (!!args ? (',' + args) : args) + ');\n\
\n\
if (hasBase) {\n\
this.base = prevBase;\n\
} else {\n\
delete this["base"];\n\
}\n\
return ret;\n\
}'))(propertyName,fn,base);
})(name,property);
}
if (isClassMember) {
targetMembers[name] = property;
} else if (name in targetMembers) {
delete targetMembers[name];
}
if (array) {
array[array.length - 1] = property;
property = array;
}
prototype[name] = property;
} else {
prototype[name] = property;
}
}
}
var membersArray = [];
for (var i in targetMembers) {
if (targetMembers.hasOwnProperty(i)) {
membersArray.push({ name: i,fn: targetMembers[i] });
}
}
// All construction is actually done in the ctor method
var ChildClass = (new Function("runtime","members","FN_ARGS","STRIP_COMMENTS","return function " + (displayName || "Class") + "() {\n\
if (!runtime.initializing && this.ctor)\n\
{\n\
var length = members.length;\n\
for (var i = 0; i < length; i++)\n\
{\n\
var item = members[i];\n\
this[item.name] = (function (me,fn) {\n\
var args = fn.toString().replace(STRIP_COMMENTS,'').match(FN_ARGS)[1];\n\
return args ? (new Function('me','return function (' + args + ') { return fn.call(me,' + args + '); }'))(me,fn) : function () { return fn.call(me); };\n\
})(this,item.fn);\n\
\n\
}\n\
this.ctor.apply(this,arguments);\n\
}\n\
}"))(runtime,membersArray,FN_ARGS,STRIP_COMMENTS);
ChildClass.members = targetMembers;
// Populate our constructed prototype object
ChildClass.prototype = prototype;
// Enforce the constructor to be what we expect
ChildClass.prototype.constructor = ChildClass;
// And make this class extendable
ChildClass.extend = extend;
return ChildClass;
};
return Class;
});
另一个编辑
最后,我偶然发现了另一个与原始的John Resig相关的角度相关的问题,这个问题与angular的注解过程(用于依赖注入)有关,它使用Function.prototype.toString()和一些Regex’es提取依赖的名称的目的。原始实现的问题是它不期望这样,所以你不能声明接受依赖的方法,所以我已经调整了实现一点处理以前描述的问题,这里是:
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*
* Inspired by base2 and Prototype
* Angular adaptations by Denis Yaremov http://github.com/lu4
* Usage:
---------------------------------
var X = Class.extend('X',"m"]
*/
angular.module('homer').factory('Class',function () {
function ClassMember(fn) {
if (this instanceof ClassMember) {
this.fn = fn;
return this;
} else {
return new ClassMember(fn);
}
}
function ClassEvent() {
if (this instanceof ClassEvent) {
return this;
} else {
return new ClassEvent();
}
}
var runtime = { initializing: false },fnTest = /xyz/.test(function () { xyz; }) ? /\bbase\b/ : /.*/,fnArgs = /^function\s*[^\(]*\(\s*([^\)]*)\)/m,stripComments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var toString = Object.prototype.toString;
// The base Class implementation (does nothing)
function Class() { };
Class.events = {};
Class.members = {};
// Create a new Class that inherits from this class
Class.extend = function Extend(displayName,properties) {
var array;
var targetEvents = {};
var sourceEvents = this.events;
var targetMembers = {};
var sourceMembers = this.members;
for (var eventName in sourceEvents) {
if (sourceEvents.hasOwnProperty(eventName)) {
targetEvents[eventName] = sourceEvents[eventName];
}
}
for (var memberName in sourceMembers) {
if (sourceMembers.hasOwnProperty(memberName)) {
targetMembers[memberName] = sourceMembers[memberName];
}
}
var base = this.prototype;
// Instantiate a base class (but only create the instance,// don't run the ctor constructor)
runtime.initializing = true;
var prototype = new this();
runtime.initializing = false;
// copy the properties over onto the new prototype
for (var name in properties) {
if (properties.hasOwnProperty(name)) {
// Check if we're overwriting an existing function
var property = properties[name];
// Support angular's controller/service/factory declaration notation
if (toString.call(property) === '[object Array]') {
array = property;
var item = array[array.length - 1];
if (toString.call(item) === '[object Function]' || item instanceof ClassMember) {
property = array[array.length - 1];
} else {
array = null;
}
} else {
array = null;
}
var isClassMember = property instanceof ClassMember;
if (isClassMember) {
property = property.fn;
}
var isClassEvent = property instanceof ClassEvent;
if (isClassEvent) {
property = (function() {
function Subscriber(fn) {
Subscriber.listeners.push(fn.bind(this));
};
Subscriber.listeners = [];
Subscriber.fire = function() {
var listeners = Subscriber.listeners;
for (var i = 0; i < listeners.length; i++) {
var result = listeners[i].apply(this,arguments);
if (result !== undefined) return result;
}
return void 0;
}
return Subscriber;
})();
}
if (typeof property === "function") {
if (typeof base[name] === "function" && fnTest.test(property)) {
property = (function (propertyName,fn) {
var args = fn.toString().replace(stripComments,'').match(fnArgs)[1];
return (new Function('propertyName',property);
}
if (isClassEvent) {
targetEvents[name] = property;
} else {
delete targetEvents[name];
}
if (isClassMember) {
targetMembers[name] = property;
} else if (name in targetMembers) {
delete targetMembers[name];
}
if (array) {
array[array.length - 1] = property;
property = array;
}
prototype[name] = property;
} else {
prototype[name] = property;
}
}
}
var eventsArray = [];
for (var targetEventName in targetEvents) {
if (targetEvents.hasOwnProperty(targetEventName)) {
eventsArray.push({ name: targetEventName,fn: targetEvents[targetEventName] });
}
}
var membersArray = [];
for (var targetMemberName in targetMembers) {
if (targetMembers.hasOwnProperty(targetMemberName)) {
membersArray.push({ name: targetMemberName,fn: targetMembers[targetMemberName] });
}
}
// All construction is actually done in the ctor method
var ChildClass = (new Function("runtime","events","return function " + (displayName || "Class") + "() {\n\
if (!runtime.initializing && this.ctor)\n\
{\n\
var length = members.length;\n\
var bind = function (me,$$fn$$) {\n\
var args = $$fn$$.toString().replace(STRIP_COMMENTS,'').match(FN_ARGS)[1];\n\
var result = args ? (new Function('me','$$fn$$','return function (' + args + ') { return $$fn$$.apply(me,arguments); }'))(me,$$fn$$) : function () { return $$fn$$.apply(me,arguments); };\n\
return result;\n\
};\n\
for (var i = 0; i < length; i++)\n\
{\n\
var item = members[i];\n\
var fn = item.fn;\n\
var name = item.name;\n\
var property = this[name] = bind(this,fn);\n\
if (fn.fire) {\n\
property.fire = bind(this,fn.fire);\n\
}\n\
if (fn.listeners) {\n\
property.listeners = fn.listeners;\n\
}\n\
}\n\
\n\
var length = events.length;\n\
for (var i = 0; i < length; i++)\n\
{\n\
var item = events[i];\n\
var fn = item.fn;\n\
var name = item.name;\n\
var property = this[name] = bind(this,fn.fire);\n\
}\n\
if (fn.listeners) {\n\
property.listeners = fn.listeners;\n\
}\n\
}\n\
this.ctor.apply(this,eventsArray,fnArgs,stripComments);
ChildClass.members = targetMembers;
// Populate our constructed prototype object
ChildClass.prototype = prototype;
// Enforce the constructor to be what we expect
ChildClass.prototype.constructor = ChildClass;
// And make this class extendable
ChildClass.extend = Extend;
ChildClass.event = ClassEvent;
ChildClass.member = ClassMember;
return ChildClass;
};
Class.member = ClassMember;
Class.event = ClassEvent;
return Class;
});
您可以通过简单地调用附加到父作用域的方法来重用父控制器中定义的功能:
HTML
<div ng-controller="ParentCtrl">
<!-- Something here ... -->
<div ng-controller="ChildCtrl">
<!-- Something here ... -->
</div>
<!-- Something here ... -->
</div>
JavaScript
function ParentCtrl($scope) {
$scope.parentMethod = function () {
//method body
};
}
function ChildCtrl($scope) {
$scope.childMethod = function () {
//functionality
$scope.parentMethod();
//functionality
};
}
如果你想使用JavaScript方法与原型继承,你可以使用:
var myApp = angular.module('myApp',[]);
function Parent($scope) {
$scope.name = 'Superhero';
$scope.clickParent = function() {
$scope.name = 'Clicked from base controller';
}
}
function Child($scope,$injector) {
debugger;
$injector.invoke(Parent,this,{$scope: $scope});
$scope.name = 'Superhero Child';
$scope.clickChild = function(){
$scope.clickParent();
}
}
Child.prototype = Object.create(Parent.prototype);
http://jsfiddle.net/mhevery/u6s88/12/
对于服务,例如,您可以使用:
(function () {
function ParentService(arg1) {
this.arg1 = arg1;
}
function ChildService(arg1,arg2) {
ParentService.call(this,arg1);
this.arg2 = arg2;
}
ChildService.prototype = new ParentService();
app.service('ChildService',ChildService);
}());
还检查this讨论和blog post about inheritance in AngularJS我贴。