55

抽象的

我正在开发一个使用 Angular 作为客户端框架的应用程序,Angular 目前很稳定,我很高兴使用它,尽管现在我发现我经常复制和粘贴我想组织到类层次结构中的代码. 例如,对话框共享一组通用功能,它们需要打开、关闭,提供typeahead功能的代码也是从某些父 BaseTypeaheadClass 继承的第一个候选对象,尽管我在 Angular 中找不到的一件事是标准的组织这些层次结构。控制器,服务,提供者都使用普通的javascript函数,可以通过 扩展prototype,所以我的问题是:

问题

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

附言

我对这个问题的猜测:

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

编辑

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

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

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

没有任何 MVVM、MVP 框架(如 knockout.js、backbone、other...)能够替代 OOP。如果你没有使用诸如类、对象、继承、抽象、多态等面向编程的核心原则,你就会陷入大麻烦,你最终会得到一个超长的 javascript 意大利面条。

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

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

我使用了刚刚实现的紧凑、零依赖库John Resig's Simple JavaScript Inheritancehttps://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 + Angular 配合得非常好,在 Angular 上下文下创建的对象可以通过服务自动利用依赖注入,因此您不必将实例注入您的 OOP 构造函数,这一事实使您的 OOP 层次结构非常纤细并且没有不相关的东西需要(并且是)由 angular.js 处理

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

另一个编辑

最近我在原来的 Class.js 实现中遇到了一些问题,如下:

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

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

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

当前实现理解上述语法

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

陷阱:

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

...
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 的实现相关的问题,该问题与使用 Function.prototype.toString() 和一些正则表达式的 angular 的注释过程(用于依赖注入)有关目的是提取依赖项的名称。原始实现的问题在于它不期望这样,因此您无法声明接受依赖项的方法,因此我对实现进行了一些调整以处理先前描述的问题,这里是:

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

3 回答 3

25

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

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

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

}());

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

于 2013-06-30T10:24:23.170 回答
6

让我给你我对 Angular / 继承情况的看法。

你不会在 Angular.js 中进行类/原型继承。它可能很难测试,这是一个问题。对于那些在 Angular 中寻找“继承”的人,我推荐这个:

您的基类是控制器。无论如何,控制器都是一个抽象模型,所以它非常适合这个目的。在控制器中使用 $scope.init() 函数,但不要从那里调用它!

如果您想“扩展”控制器的功能,请使用指令。在你的指令 link() 函数中,调用控制器的 $scope.init()。(编译时,角度首先运行控制器,然后是指令链接功能)。如果 scope 有$scope.name='base', 在指令链接中你将能够重新定义$scope.name=child,然后运行 ​​$scope.init()。

可是等等!但这仅允许单级继承。- 是的,这是真的。但是如果你正在寻找多级继承,你应该使用Services

多级继承只不过是在分层类结构中共享相同的代码。为此,请使用 Services并将这些服务与依赖注入器一起放入您的指令中。太容易了 这应该很容易完成,易于理解,并且测试运行顺利。

指令是非常强大的工具,因为您可以动态地将部分与控制器结合起来。

于 2014-02-18T11:00:00.390 回答
3

我认为您的猜测非常好,并且我使用了一些类似的方法,但结果都比我希望的要冗长。

我有一个问题,我在管理界面中开发了一个复杂的对话框作为选项卡,但我希望在用户部分的弹出窗口中有一个几乎相同的对话框,但是数据将从不同的来源填充,并且会有一些附加按钮。基本上是经典继承的绝佳候选者。对于 UI 方面,我使用了一个模板,该模板包含在两个带有不同控制器的地方。但是为了避免在控制器中重复复杂的 UI 逻辑,我想使用继承。

范围继承方法在一定程度上依赖于应用程序的结构并且不合适,因为这两个 UI 位于实际上不同的应用程序中。将重用代码放入服务的方法最终会变得冗长,因为我需要让每个控制器方法调用服务上的等效方法。所以我使用了以下简单的 JavaScript 继承方法:

/**
 * 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()

}

所以我没有使用原型继承,因为 $scope 很容易获得。但是我已经从基本控制器获得了所有行为,并且只添加或覆盖了我想要的。我的视图可以使用任一控制器进行配置,并且无需修改即可工作。

于 2013-08-21T05:54:06.527 回答