使用 oop 继承的 angularjs [英] angularjs with oop inheritance in action

查看:33
本文介绍了使用 oop 继承的 angularjs的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

摘要

我正在开发一个使用 angular 作为客户端框架的应用程序,angular 目前很流行,我很高兴使用它,但现在我发现我使用了很多我想组织的复制和粘贴代码进入类层次结构.例如,对话框共享一组通用的功能,它们需要被打开、关闭,提供 typeahead 功能的代码也是从某些父 BaseTypeaheadClass 继承的第一个候选者,尽管有一点我没有find in angular 是组织这些层次结构的标准方法.控制器、服务、提供者都在下面使用普通的 javascript 函数,这些函数可以通过 prototype 的方式进行扩展,所以我的问题是:

问题

组织类函数的角度方式是什么,是否有任何标准机制允许从另一个类派生一个类

附注

我对问题的猜测:

  • 将基类的实现定义为服务,因此它们将很容易注入到需要该特定类的任何控制器或其他服务中
  • 定义OOP服务并提供诸如definederive等用于创建基类/派生类的方法
<小时>

编辑

从我最初提出问题以来已经过去了一段时间.从那以后,我提出了我在几个项目中成功使用的方法,我非常喜欢并想与大家分享.

目前 angular 不提供任何用于组织类层次结构的构造,很遗憾,因为或多或少的大型应用程序仅能满足 Model/View/Controller/... 构造,它必须将其代码组织到 OOP 对象中.

我已经在 Web 开发领域工作了很长时间,我还没有看到一个企业项目在 JavaScript 中大量利用 OOP.我看到的是巨大且组织良好的服务器端/数据库端逻辑 + 接近无限的 javascript 意大利面,在客户端涂有大量框架和库.

没有 MVVM、MVP 框架(例如knockout.js、backbone、其他……)能够替代 OOP.如果你没有使用面向编程的核心原则,比如类、对象、继承、抽象、多态,你就会陷入困境,你最终会得到一个超长的 javascript 意大利面.

关于 Angular,我认为它是一个与 Knockout.js/backbone.js/任何其他 MVV-anything 框架非常不同的框架,但根据我的实践,它也不是能够取代 OOP 的灵丹妙药.当我试图不将 OOP 与 Angular 一起使用时,我最终会得到主要位于控制器中的重复逻辑.不幸的是,没有(我发现没有)解决这个问题的干净利落的方式.

但我已经成功(我认为)解决了这个问题.

我使用了紧凑的零依赖库,它只实现了 John Resig 的简单 JavaScript 继承 (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() {//构造函数this._super.init(123, 231);//调用基本构造函数},someFunction:函数(){//请注意,您的 OOP 现在知道可以注入 angular 服务的所有内容,这非常酷:)$http({method: 'GET', url: '/someUrl'}).then(function(){this._super.someFunction();//调用基函数实现});}});//返回新的 SomeChildClass();//我们没有在这里返回实例!返回 SomeChildClass;//服务是一个函数定义而不是一个对象的实例}]);//所以现在我们既可以在 angular 中使用这个服务,也可以使用 `extend` 方法调用来扩展它,就像这样:Application.controller('MegaController', ['$scope', 'SomeChildClass', function ($scope, SomeChildClass) {$scope.someObject = new SomeChildClass();}]);

OOP + Angular 可以很好地结合在一起,在 Angular 上下文下创建的对象可以通过服务自动利用依赖注入,因此您不必将实例注入到您的 OOP 构造函数中,这一事实使您的 OOP 层次结构非常纤薄和自由需要(并且正在)由 angular.js 处理的无关内容

因此,请尝试使用这种方法,并在此处就您获得的结果或遇到的问题提供反馈,

另一个编辑

最近,我在使用原始 Class.js 实现时遇到了一些问题,如下所示:

1) 如果您要将实例方法的引用作为对其他方法的回调传递,则这些方法的工作方式可能与您期望的方式不同.他们将失去对this 的引用.在这种情况下,您将期望在 this 中看到您的当前对象,但它将是顶级 Window 或其他一些上下文对象,具体取决于回调如何调用您的方法.这是由于 JavaScript 架构造成的.为了解决这个问题,提供了一个特殊的 ClassMember 函数,该函数指示 Class 在创建时将您的方法绑定到对象上下文(检查 Usage> 下面是进一步的指导).

2) 显然,原始的 Class.js 实现对控制器方法声明的角度类型一无所知,即

Class.extend('YourClassDisplayName', {ctor:函数(){//一些有用的构造函数逻辑},控制器:['$scope','$attrs',函数($scope,$attrs){//用 $scope 和 $attrs 做一些事情}]});

当前实现理解上述语法

3) 在没有适当处理的情况下使用上述方法时,它会破坏 angular $$annotate'on 进程,因此参考上面的示例,将无法注入 $scope$attrs 进入 ClassMember 方法,或使用 this.base(...) 调用的重写方法.所以这也是固定的.

问题:

1) 在异步操作处理程序中使用 this.base(...) 时(类似于 $http.get(..., function() { self.base(...); })) 请注意 this.base(...) 调用的生命周期是有限的,并且只要该方法返回 this.base(...) 停止存在.因此,如果您计划以异步方式调用基方法,则应显式保存对基方法的引用.即:

<预><代码>...var self = this;var base = this.base;...$http.get(..., function () {base.call(self, ...);//或者 base.apply(self, ...) 或者 base() 如果你不关心 `this`})

以上问题我都解决了(除了一个由于JavaScript架构无法解决的问题),想分享给大家,希望你能从中受益:

/* 简单的 JavaScript 继承* 作者:John Resig http://ejohn.org/* MIT 许可.** 受 base2 和 Prototype 的启发* Denis Yaremov 的 Angular 改编 http://github.com/lu4* 用法:---------------------------------var X = Class.extend('X', {ctor:函数(){this.name = "我是 X";},myOrdinaryMethod: 函数 (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:函数(){this.name = "我是Y";},myOrdinaryMethod: 函数 (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');//["我是 X", "a", "b", "c"]y.myClassMemberMethod('u', 'v', 'm');//["我是 Y", "u", "v", "m"]x.myOrdinaryMethod('a', 'b', 'c');//["我是 X", "a", "b", "c"]y.myOrdinaryMethod('u', 'v', 'm');//["我是 Y", "u", "v", "m"]y.theirOrdinaryMethod = x.myOrdinaryMethod;y.theirClassMemberMethod = x.myClassMemberMethod;y.theirOrdinaryMethod('a', 'b', 'c');//["我是 Y", "a", "b", "c"]y.theirClassMemberMethod('u', 'v', 'm');//["我是 X", "u", "v", "m"]*/angular.module('app').factory('ClassMember', function () {返回函数类成员(fn){如果(ClassMember 的这个实例){this.fn = fn;} 别的 {返回新的类成员(fn);}};});angular.module('app').factory('Class', function (ClassMember) {var 运行时 = { 初始化:false },fnTest =/xyz/.test(function() { xyz; }) ?/\bbase\b/:/.*/,FN_ARGS =/^function\s*[^\(]*\(\s*([^\)]*)\)/m,STRIP_COMMENTS =/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;var toString = Object.prototype.toString;//基类实现(什么都不做)函数类(){};Class.members = { };//创建一个继承自该类的新类Class.extend = 函数扩展(显示名称,属性){var 数组;var targetMembers = {};var sourceMembers = this.members;for (var memberName in sourceMembers) {如果(sourceMembers.hasOwnProperty(memberName)){targetMembers[memberName] = sourceMembers[memberName];}}var base = this.prototype;//实例化一个基类(但只创建实例,//不要运行ctor构造函数)runtime.initializing = true;varprototype = new this();runtime.initializing = false;//将属性复制到新原型上for(属性中的变量名){如果(properties.hasOwnProperty(名称)){//检查我们是否覆盖了现有的函数var 属性 = 属性[名称];//支持 angular 的控制器/服务/工厂声明符号if (toString.call(property) === '[对象数组]') {数组 = 属性;var item = array[array.length - 1];if (toString.call(item) === '[object Function]' || item instanceof ClassMember) {property = array[array.length - 1];} 别的 {数组 = 空;}} 别的 {数组 = 空;}var isClassMember = 属性 instanceof ClassMember;如果(isClassMember){财产=财产.fn;}if (typeof property === "function") {if (typeof base[name] === "function" && fnTest.test(property)) {属性=(函数(属性名称,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\//添加一个与方法相同的新 .base() 方法\n\//但在超类上\n\\n\this.base = base[propertyName];\n\\n\//该方法只需要临时绑定,所以我们\n\//当我们完成执行后删除它\n\var ret = fn.call(this' + (!!args ? (', ' + args) : args) + ');\n\\n\if (hasBase) {\n\this.base = prevBase;\n\} else {\n\删除这个["base"];\n\}\n\返回 ret;\n\}'))(propertyName, fn, base);})(姓名,财产);}如果(isClassMember){目标成员[名称] = 属性;} else if (name in targetMembers) {删除目标成员[名称];}如果(数组){数组[array.length - 1] = 属性;属性 = 数组;}原型[名称] = 属性;} 别的 {原型[名称] = 属性;}}}var membersArray = [];for (var i in targetMembers) {如果(targetMembers.hasOwnProperty(i)){membersArray.push({ name: i, fn: targetMembers[i] });}}//所有的构造实际上都是在ctor方法中完成的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\返回参数?(new Function('me', 'fn', 'return function (' + args + ') { return fn.call(me, ' + args + '); }'))(me, fn) : function (){ 返回 fn.call(me);};\n\})(this, item.fn);\n\\n\}\n\this.ctor.apply(this, arguments);\n\}\n\}"))(运行时,membersArray,FN_ARGS,STRIP_COMMENTS);ChildClass.members = targetMembers;//填充我们构造的原型对象ChildClass.prototype = 原型;//强制构造函数符合我们的预期ChildClass.prototype.constructor = ChildClass;//并使这个类可扩展ChildClass.extend = 扩展;返回子类;};返回类;});

<小时>

另一个编辑

最终我偶然发现了另一个与原始 John Resig 的 Angular 实现相关的问题,该问题与 angular 的注释过程(用于依赖注入)有关,它使用 Function.prototype.toString() 和一些 Regex'es 用于提取依赖项的名称.原始实现的问题在于它不期望这一点,因此您无法声明接受依赖项的方法,因此我稍微调整了实现以处理先前描述的问题,这里是:

/* 简单的 JavaScript 继承* 作者:John Resig http://ejohn.org/* MIT 许可.** 受 base2 和 Prototype 的启发* Denis Yaremov 的 Angular 改编 http://github.com/lu4* 用法:---------------------------------var X = Class.extend('X', {ctor:函数(){this.name = "我是 X";},myOrdinaryMethod: 函数 (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:函数(){this.name = "我是Y";},myOrdinaryMethod: 函数 (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');//["我是 X", "a", "b", "c"]y.myClassMemberMethod('u', 'v', 'm');//["我是 Y", "u", "v", "m"]x.myOrdinaryMethod('a', 'b', 'c');//["我是 X", "a", "b", "c"]y.myOrdinaryMethod('u', 'v', 'm');//["我是 Y", "u", "v", "m"]y.theirOrdinaryMethod = x.myOrdinaryMethod;y.theirClassMemberMethod = x.myClassMemberMethod;y.theirOrdinaryMethod('a', 'b', 'c');//["我是 Y", "a", "b", "c"]y.theirClassMemberMethod('u', 'v', 'm');//["我是 X", "u", "v", "m"]*/angular.module('homer').factory('Class', function () {函数类成员(fn){如果(ClassMember 的这个实例){this.fn = fn;返回这个;} 别的 {返回新的类成员(fn);}}函数类事件(){如果(ClassEvent 的这个实例){返回这个;} 别的 {返回新的类事件();}}var 运行时 = { 初始化:false },fnTest =/xyz/.test(function () { xyz; }) ?/\bbase\b/:/.*/,fnArgs =/^function\s*[^\(]*\(\s*([^\)]*)\)/m,stripComments =/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;var toString = Object.prototype.toString;//基类实现(什么都不做)函数类(){};Class.events = {};Class.members = {};//创建一个继承自该类的新类Class.extend = 函数扩展(显示名称,属性){var 数组;var targetEvents = {};var sourceEvents = this.events;var targetMembers = {};var sourceMembers = this.members;for (var eventName in sourceEvents) {如果(sourceEvents.hasOwnProperty(事件名称)){targetEvents[eventName] = sourceEvents[eventName];}}for (var memberName in sourceMembers) {如果(sourceMembers.hasOwnProperty(memberName)){targetMembers[memberName] = sourceMembers[memberName];}}var base = this.prototype;//实例化一个基类(但只创建实例,//不要运行ctor构造函数)runtime.initializing = true;varprototype = new this();runtime.initializing = false;//将属性复制到新原型上for(属性中的变量名){如果(properties.hasOwnProperty(名称)){//检查我们是否覆盖了现有的函数var 属性 = 属性[名称];//支持 angular 的控制器/服务/工厂声明符号if (toString.call(property) === '[对象数组]') {数组 = 属性;var item = array[array.length - 1];if (toString.call(item) === '[object Function]' || item instanceof ClassMember) {property = array[array.length - 1];} 别的 {数组 = 空;}} 别的 {数组 = 空;}var isClassMember = 属性 instanceof ClassMember;如果(isClassMember){财产=财产.fn;}var isClassEvent = 属性 instanceof ClassEvent;如果(isClassEvent){属性=(函数(){功能订阅者(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) 返回结果;}返回无效0;}返回订阅者;})();}if (typeof property === "function") {if (typeof base[name] === "function" && fnTest.test(property)) {属性=(函数(属性名称,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\//添加一个与方法相同的新 .base() 方法\n\//但在超类上\n\\n\this.base = base[propertyName];\n\\n\//该方法只需要临时绑定,所以我们\n\//当我们完成执行后删除它\n\var ret = fn.call(this' + (!!args ? (', ' + args) : args) + ');\n\\n\if (hasBase) {\n\this.base = prevBase;\n\} else {\n\删除这个["base"];\n\}\n\返回 ret;\n\}'))(propertyName, fn, base);})(姓名,财产);}如果(isClassEvent){目标事件[名称] = 属性;} 别的 {删除目标事件[名称];}如果(isClassMember){目标成员[名称] = 属性;} else if (name in targetMembers) {删除目标成员[名称];}如果(数组){数组[array.length - 1] = 属性;属性 = 数组;}原型[名称] = 属性;} 别的 {原型[名称] = 属性;}}}var eventsArray = [];for (var targetEventName in targetEvents) {如果(targetEvents.hasOwnProperty(targetEventName)){eventsArray.push({ name: targetEventName, fn: targetEvents[targetEventName] });}}var membersArray = [];for (var targetMemberName in targetMembers) {如果(targetMembers.hasOwnProperty(targetMemberName)){membersArray.push({ name: targetMemberName, fn: targetMembers[targetMemberName] });}}//所有的构造实际上都是在ctor方法中完成的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\无功结果 = args ?(new Function('me', '$$fn$$', 'return function (' + args + ') { return $$fn$$.apply(me, arguments); }'))(me, $$fn$$) : function () { return $$fn$$.apply(me, arguments);};\n\返回结果;\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\如果(fn.fire){\n\property.fire = bind(this, fn.fire);\n\}\n\如果(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\如果(fn.fire){\n\property.fire = bind(this, fn.fire);\n\}\n\如果(fn.listeners){\n\property.listeners = fn.listeners;\n\}\n\}\n\this.ctor.apply(this, arguments);\n\}\n\}"))(运行时、eventsArray、membersArray、fnArgs、stripComments);ChildClass.members = targetMembers;//填充我们构造的原型对象ChildClass.prototype = 原型;//强制构造函数符合我们的预期ChildClass.prototype.constructor = ChildClass;//并使这个类可扩展ChildClass.extend = 扩展;ChildClass.event = ClassEvent;ChildClass.member = ClassMember;返回子类;};Class.member = ClassMember;Class.event = ClassEvent;返回类;});

解决方案

你的猜测听起来完全适用.

您可以通过简单地调用附加到父作用域的方法来重用父控制器中定义的功能:

HTML

<!-- 这里有些东西...--><div ng-controller="ChildCtrl"><!-- 这里有些东西...-->

<!-- 这里有些东西...-->

JavaScript

function ParentCtrl($scope) {$scope.parentMethod = function () {//方法体};}函数 ChildCtrl($scope) {$scope.childMethod = 函数 () {//功能$scope.parentMethod();//功能};}

如果你想使用带有原型继承的 JavaScript 方法,你可以使用:

var myApp = angular.module('myApp',[]);函数父($scope){$scope.name = '超级英雄';$scope.clickParent = function() {$scope.name = '从基础控制器点击';}}函数子($scope,$injector){调试器;$injector.invoke(Parent, this, {$scope: $scope});$scope.name = '超级英雄的孩子';$scope.clickChild = function(){$scope.clickParent();}}Child.prototype = Object.create(Parent.prototype);

http://jsfiddle.net/mhevery/u6s88/12/

例如,对于服务,您可以使用:

(函数(){函数父服务(arg1){this.arg1 = arg1;}函数 ChildService(arg1, arg2) {ParentService.call(this, arg1);this.arg2 = arg2;}ChildService.prototype = new ParentService();app.service('ChildService', ChildService);}());

还要检查这个 讨论和 关于 AngularJS 中的继承的博文 我发布了.

Abstract

I'm working on an application that uses angular as a client side framework, angular currently rocks and I'm really happy using it, though now I find that I use to much copy and paste code that I would like to organize into class hierarchy. For example dialogs share a common set of functionality, they need to be opened, closed, the code that provides typeahead functionality is also a first candidate to inherit from some parent BaseTypeaheadClass, though one thing I didn't find in angular is a standard way of organising these hierarchies. Both controllers, services, providers use ordinary javascript functions underneath which can be extended by the means of prototype, so my question is:

Question

What is the angular way of organising my class functions, are there any standard mechanisms that will allow to derive one class from another

P.S.

My guesses on the problem:

  • Define implementation of base classes as services, as a result they will be easily injected into any controller or other services where that specific class will be needed
  • Define OOP service and provide methods such as define, derive, etc. that will be used to create base / derived classes

Edit

Some time has passed from time when I was initially asking my question. Since then I have come out with approach that I'm successfully using in several projects, that I like very much and want to share with everyone.

Currently angular doesn't provide any constructs for organising class hierarchies and it's a pity since more or less large application can't suffice only Model/View/Controller/... constructs, it has to organise it's code into OOP objects.

I'm working in the field of web-development for quite a long time already and I haven't seen even one enterprise project that was taking advantage of OOP with JavaScript massively. What I seen was huge and nicely organised server side / database side logic + close to infinite javascript spaghetti greased with zoo of frameworks and libraries on client side.

No MVVM, MVP frameworks such as knockout.js, backbone, other... are capable of replacing the OOP as such. If you are not using core principles of oriented programming such as Classes, Objects, Inheritance, Abstraction, Polymorphism you are in deep trouble, what you will end up is a mega long javascript spaghetti.

Regarding Angular I think it is a framework very much different from knockout.js / backbone.js / any other MVV-anything frameworks but according to my practice also it is not a silver bullet capable of replacing OOP. When I'm trying not to use the OOP with Angular I end up with duplicate logic located mostly in controllers. And unfortunately there is no (I have found no) clean and angular-way of beating that problem.

But I have successfully (I think) solved that problem.

I've used compact, zero-dependency lib that just implements John Resig's Simple JavaScript Inheritance (https://github.com/tracker1/core-js/blob/master/js-extensions/040-Class.js). With the help of that library I was able to create / inherit / create abstract methods / override them, in other words do everything that I've accustomed to on server side.

Here is an example usage:

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 play together very nicely, objects created under angular context can take advantage of dependency injection via services automatically, so you don't have to inject instances into your OOP constructors and this fact makes your OOP hierarchy very slim and free of irrelevant stuff that needs to be (and is) handled by angular.js

So play with this approach and give feedback here with results you gained or problems you encountered,

Another edit

Recently I've faced few problems with original Class.js implementation, as follows:

1) If you will be passing a reference to your instance methods as callbacks to other methods, these methods might work not the way you expect them to work. They will loose reference to this. In such case you will be expecting to see your current object inside this but it will be either top level Window or some other context object depending on how the callback calls your method. It happens due to JavaScript architecture. In order to fight this problem a special ClassMember function is provided which instructs Class to bind your method to object context when it is being created (check Usage below for further guidance).

2) Obviously original Class.js implementation doesn't know anything about angular type of controller method declarations i.e.

Class.extend('YourClassDisplayName', {
    ctor: function () {
        // Some useful constructor logic
    },
    controller: ['$scope', '$attrs', function ($scope, $attrs) {
        // Do something with $scope and $attrs
    }]
});

Current implementation understands above syntax

3) When using above approach without appropriate handling it would break angular $$annotate'on process so referring to above example it would make impossible to inject $scope and $attrs into into ClassMember method, or overridden method which is using this.base(...) calls. So this is also fixed.

Gotchas:

1) When using this.base(...) within async operation handler (something like $http.get(..., function() { self.base(...); })) please note that this.base(...) call has a limited lifetime and as soon as the method returns this.base(...) stops existing. So you should save reference to base method explicitly if you are planning to call base methods in asynchronous fashion. i.e:

...
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`
})

I've resolved all of the above problems (except one gotcha which can not be resolved due to JavaScript architecture) and would like to share with everyone, hope you will benefit from it:

/* 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;
});


Another edit

Eventually I've stumbled upon another problem related to original John Resig's implementation in relation to angular, and the problem is related to angular's annotation process (used for dependency injection) which uses Function.prototype.toString() and some Regex'es for the purpose of extracting the names of dependencies. And the problem with original implementation is that it doesn't expect this and so you are not able to declare methods that accept dependencies, so I've tweaked the implementation a little bit to deal with previously described problem and here it is:

/* 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;
});

解决方案

Your guesses sounds perfectly applicable.

You can reuse functionality defined in parent controllers by simply calling methods attached to the parent scope:

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
    };
}

If you want to use the JavaScript approach with prototype inheritance you can use:

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/

For services, for example, you can use:

(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);

}());

Also check this discussion and the blog post about inheritance in AngularJS I posted.

这篇关于使用 oop 继承的 angularjs的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆