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