oop - 使用 oop 继承的 angularjs

标签 oop angularjs

抽象的

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



组织我的类功能的 Angular 方式是什么,是否有任何标准机制允许从另一个类派生一个类

附言

我对这个问题的猜测:

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


  • 编辑

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

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

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

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

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

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

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

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

    另一个编辑

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

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

    2)显然是原创Class.js实现对 Controller 方法声明的 Angular 类型一无所知,即
    Class.extend('YourClassDisplayName', {
        ctor: function () {
            // Some useful constructor logic
        },
        controller: ['$scope', '$attrs', function ($scope, $attrs) {
            // Do something with $scope and $attrs
        }]
    });
    

    当前实现理解上述语法

    3)如果使用上述方法而没有适当的处理,它会破坏 Angular $$annotate '在处理过程中,因此引用上面的示例将无法注入(inject) $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 与 angular 相关的实现相关的问题,该问题与 angular 的注释过程(用于依赖注入(inject))有关,它使用 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;
    });
    

    最佳答案

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

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

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

    如果你想使用带有原型(prototype)继承的 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我已经发布。

    关于oop - 使用 oop 继承的 angularjs,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17389291/

    相关文章:

    javascript - ionic 模式无法打开

    java - SUPER 类应该从它的 SUBCLASS 中获取函数和属性。在Java中有可能吗?

    excel - 在 VBA 中实现接口(interface)时,实现的功能需要是私有(private)的还是公有的?

    javascript - AngularJS 监视服务变量

    php - 我无法使用 php 和 angularjs 使用更新功能将记录更新到数据库中

    angularjs - "base href"使用 ui-router 在路由 url 中添加目录路径

    java - 如何将数据存储在对象而不是类中

    java - Lwjgl GUI 库

    java - 面向对象项目的例子帮助程序程序员

    java - 即使我刷新网页我也想要我的旧数据