与行动中的inheritance的angularjs
抽象
我正在使用angular度作为客户端框架的应用程序,棱angular分明的岩石,我真的很高兴使用它,但现在我发现,我用它来复制和粘贴代码,我想组织成类层次结构。 例如,对话框共享一组共同的function,它们需要被打开,closures,提供typeahead
function的代码也是从某些父类BaseTypeaheadClassinheritance的第一个候选者,尽pipe我没有在angular度中find的一件事是一种标准的方法组织这些层次结构。 控制器,服务,提供者都使用普通的javascript函数,下面的函数可以通过prototype
来扩展,所以我的问题是:
题
组织我的类函数的angular度是什么?是否有任何标准的机制可以从另一个类派生出一个类
PS
我猜想这个问题:
- 将基类的实现定义为服务,因此它们将被轻松注入到需要该特定类的任何控制器或其他服务中
- 定义
OOP
服务并提供将用于创build基类/派生类的方法,如define
,derive
等
编辑
从最开始问我的问题的时候已经过去了一段时间。 从那以后,我就开始采用我在几个项目中成功使用的方法,我非常喜欢,想和大家分享。
目前angular并没有提供任何用于组织类层次结构的构造,可惜的是,或多或less的大型应用程序不能满足Model / View / Controller / …结构,它必须将代码组织到OOP对象中。
我已经在web开发领域工作了很长一段时间,甚至没有见过一个企业项目正在大量利用JavaScript的OOP优势。 我看到的是巨大的,很好的组织服务器端/数据库端逻辑+接近无限的JavaScript意大利面与客户端的框架和库动物园。
没有MVVM,MVP框架,如knockout.js,骨干,其他…都能够替代OOP。 如果你不使用类,对象,inheritance,抽象,多态等面向对象编程的核心原则,那么你将面临很大的麻烦,最终你会得到一个超长的javascript意大利面条。
对于Angular而言,我认为它是一个与knockout.js / backbone.js /任何其他MVV-nothing框架非常不同的框架,但根据我的实践,它也不是能够替代OOP的银弹。 当我试图不使用Angular OOP时,我最终得到了大部分位于控制器中的重复逻辑。 不幸的是,没有(我没有find)清楚和angular度的方式来打这个问题。
但我已经成功(我认为)解决了这个问题。
我使用了紧凑的零依赖关系库,它只是实现了John Resig's Simple JavaScript Inheritance
( https://github.com/tracker1/core-js/blob/master/js-extensions/040-Class.js )。 在这个库的帮助下,我能够创build/inheritance/创build抽象方法/覆盖它们,换句话说就是在服务器端完成我所习惯的一切。
以下是一个示例用法:
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 + Angular一起玩非常好,在angular上下文中创build的对象可以利用服务自动dependency injection的优势,所以你不必注入实例到你的OOP构造函数中,这使得你的OOP层次结构非常苗条,没有不相关的东西需要(并且)由angular.js处理
所以玩这个方法,并在这里给你反馈结果你遇到的问题或遇到的问题,
另一个编辑
最近我遇到了一些原来的Class.js实现的问题,如下所示:
1)如果你将传递一个对你的实例方法的引用作为其他方法的callback,这些方法可能不会以你期望的方式工作。 他们会放松this
参考。 在这种情况下,您将期望看到您当前的对象,但它将是顶层Window
或其他上下文对象,取决于callback如何调用您的方法。 这发生在JavaScript架构。 为了解决这个问题,提供了一个特殊的ClassMember
函数,它指示Class
在创build对象上下文时将你的方法绑定到对象上下文(查看下面的Usage
以获得进一步的指导)。
2)很明显原来的Class.js
实现并不知道控制器方法声明的angular度types
Class.extend('YourClassDisplayName', { ctor: function () { // Some useful constructor logic }, controller: ['$scope', '$attrs', function ($scope, $attrs) { // Do something with $scope and $attrs }] });
当前的实现理解上面的语法
3)当使用上面的方法没有适当的处理它会打破angular$$annotate
'上面的例子,所以参考上面的例子将不可能注入到ClassMember
方法,或重写的方法是使用this.base(...)
电话。 所以这也是固定的。
陷阱:
1)在asynchronous操作处理程序(如$http.get(..., function() { self.base(...); })
)中使用$http.get(..., function() { self.base(...); })
请注意this.base(...)
调用有一个有限的生命周期,只要方法返回this.base(...)
停止存在。 因此,如果您打算以asynchronous方式调用基本方法,则应该明确地保存对基本方法的引用。 即:
... 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` })
我已经解决了所有上述问题(除了一个由于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, y, z]); }, myClassMemberMethod: ClassMember(function (x, y, z) { console.log([this.name, x, y, z]); }) }); var Y = Class.extend('Y', { ctor: function () { this.name = "I'm Y"; }, myOrdinaryMethod: function (x, y, z) { console.log([this.name, x, y, z]); }, myClassMemberMethod: ClassMember(function (x, y, z) { console.log([this.name, x, 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', 'b', 'c'); // ["I'm X", "a", "b", "c"] y.myOrdinaryMethod('u', 'v', 'm'); // ["I'm Y", "u", "v", "m"] y.theirOrdinaryMethod = x.myOrdinaryMethod; y.theirClassMemberMethod = x.myClassMemberMethod; y.theirOrdinaryMethod('a', 'b', 'c'); // ["I'm Y", "a", "b", "c"] y.theirClassMemberMethod('u', 'v', 'm'); // ["I'm X", "u", "v", "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', 'fn', '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相关的问题,这个问题与angular的注释过程有关(用于dependency injection),它使用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', { ctor: function () { this.name = "I'm X"; }, myOrdinaryMethod: function (x, y, z) { console.log([this.name, x, y, z]); }, myClassMemberMethod: ClassMember(function (x, y, z) { console.log([this.name, x, y, z]); }) }); var Y = Class.extend('Y', { ctor: function () { this.name = "I'm Y"; }, myOrdinaryMethod: function (x, y, z) { console.log([this.name, x, y, z]); }, myClassMemberMethod: ClassMember(function (x, y, z) { console.log([this.name, x, 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', 'b', 'c'); // ["I'm X", "a", "b", "c"] y.myOrdinaryMethod('u', 'v', 'm'); // ["I'm Y", "u", "v", "m"] y.theirOrdinaryMethod = x.myOrdinaryMethod; y.theirClassMemberMethod = x.myClassMemberMethod; y.theirOrdinaryMethod('a', 'b', 'c'); // ["I'm Y", "a", "b", "c"] y.theirClassMemberMethod('u', 'v', 'm'); // ["I'm X", "u", "v", "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', '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 (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", "members", "FN_ARGS", "STRIP_COMMENTS", "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);\n\ if (fn.fire) {\n\ property.fire = bind(this, fn.fire);\n\ }\n\ if (fn.listeners) {\n\ property.listeners = fn.listeners;\n\ }\n\ }\n\ this.ctor.apply(this, arguments);\n\ }\n\ }"))(runtime, eventsArray, membersArray, 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; });
你的猜测听起来完全适用。
您可以通过简单地调用附加到父范围的方法来重用在父控制器中定义的function:
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 }; }
如果你想用原型inheritance的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); }());
另外检查这个讨论和关于在我发布的AngularJS的inheritance的博客文章 。
让我给你我对angular度/inheritance情况的看法。
你不会在Angular.js中做类/原型inheritance。 这可能很难testing,这是一个问题。 对于那些在Angular中寻找“inheritance”的人,我推荐:
你的基类是控制器 。 控制器无论如何是一个抽象的模型,所以它是完美的。 在你的控制器中使用$ scope.init()函数,但不要从那里调用它!
如果你想'扩展'你的控制器的function,使用指令 。 在你的指令link()函数中,调用控制器的$ scope.init()。 (编译时,先运行angular度控制器,然后是指令链接函数)。 如果scope有一个
$scope.name='base'
,那么在指令链接中你可以重新定义$scope.name=child
,然后运行$ scope.init()。可是等等! 但是这只允许单层inheritance。 – 是的,这是真的。 但是,如果你正在寻找多级inheritance,你应该使用服务 。
多级inheritance不是别的,而是在分层结构中共享相同的代码。 为此,使用服务 ,并将这些服务与dependency injection器放入您的指令中。 太容易了。 这应该很容易完成,易于理解,testing运行顺利。
指令是非常强大的工具,因为您可以将部分和控制器dynamic组合。
我认为你的猜测是相当不错的,我用这样的几个方法来演奏,但是他们都变得比我所希望的更加冗长。
我遇到了一个问题,就是在我们的pipe理界面中开发了一个复杂的对话框,但是我希望在用户部分的popup窗口中有一个几乎相同的对话框,但是数据将从不同的来源填充,并且会有一些额外的button。 基本上是古典inheritance的一个很好的候选人。 对于UI方面,我使用了包含在两个不同控制器的地方的模板。 但为了避免在控制器中复制复杂的UI逻辑,我想使用inheritance。
范围inheritance方法在某种程度上依赖于应用程序的结构,因为这两个UI在不同的应用程序中是不合适的。 将重用代码放入服务的方法最终会变得冗长,因为我需要让每个控制器方法调用服务上的等效方法。 所以我用了下面这个简单的JavaScriptinheritance方法:
/** * Effective base class for Thing Controllers. * This should be considered abstract since it does not define * $scope.readData() or $scope.saveData() which may be called from its * other functions. */ function BaseThingController($scope, $http){ $scope.data = []; // local data store; $scope.validateForm(){...} $scope.edit(){...} $scope.cancel(){...} $scope.reset(){...} $scope.otherMethod1(){...} $scope.otherMethod2(){...} $scope.otherMethod3(){...} } /** * AdminThingController effectively extends BaseThingController */ function AdminThingController($scope, $http){ // Calling BaseThingController as a function defines all the needed // functions and properties in our scope. BaseThingController($scope, $http) $scope.readData(){ // $scope.data = data from admin data source } $scope.saveData(newData){ // save to special admin service } // initialize local data $scope.readData() } /** * UserThingController effectively extends BaseThingController */ function UserThingController($scope, $http){ // Calling BaseThingController as a function defines all the needed // functions and properties in our scope. BaseThingController($scope, $http) $scope.readData(){ // $scope.data = data from user data source } $scope.saveData(newData){ // save to user service } /** * Overriding base class behaviour here */ $scope.otherMethod1(){...} // initialize local data $scope.readData() }
所以我没有使用原型inheritance作为$范围是随时可用的。 但是我已经从基本控制器中获得了所有的行为,只添加或覆盖了我想要的内容。 我的意见可以用任何一个控制器进行configuration,并且可以不做任何修改。