JavaScript类

我了解基本的JavaScript伪类:

function Foo(bar) { this._bar = bar; } Foo.prototype.getBar = function() { return this._bar; }; var foo = new Foo('bar'); alert(foo.getBar()); // 'bar' alert(foo._bar); // 'bar' 

我也理解模块模式,它可以模拟封装:

 var Foo = (function() { var _bar; return { getBar: function() { return _bar; }, setBar: function(bar) { _bar = bar; } }; })(); Foo.setBar('bar'); alert(Foo.getBar()); // 'bar' alert(Foo._bar); // undefined 

但是对于这两种模式都有类似于OOP的属性。 前者不提供封装。 后者不提供实例化。 两种模式都可以被修改以支持伪inheritance。

我想知道的是,如果有任何模式允许:

  • 遗产
  • 封装(支持“私有”属性/方法)
  • 实例化(可以有多个“类”实例,每个实例都有自己的状态)

那这个呢 :

 var Foo = (function() { // "private" variables var _bar; // constructor function Foo() {}; // add the methods to the prototype so that all of the // Foo instances can access the private static Foo.prototype.getBar = function() { return _bar; }; Foo.prototype.setBar = function(bar) { _bar = bar; }; return Foo; })(); 

现在我们有实例化,封装和inheritance。
但是,还是有问题的。 privatevariables是static因为它在Foo所有实例中共享。 快速演示:

 var a = new Foo(); var b = new Foo(); a.setBar('a'); b.setBar('b'); alert(a.getBar()); // alerts 'b' :( 

一个更好的方法可能是使用约定的私有variables:任何私有variables应该以下划线开始。 这个约定是众所周知和广泛使用的,所以当另外一个程序员使用或者改变你的代码并且看到一个以下划线开头的variables时,他会知道它是私有的,仅供内部使用,他不会修改它。
这是使用这个约定重写的:

 var Foo = (function() { // constructor function Foo() { this._bar = "some value"; }; // add the methods to the prototype so that all of the // Foo instances can access the private static Foo.prototype.getBar = function() { return this._bar; }; Foo.prototype.setBar = function(bar) { this._bar = bar; }; return Foo; })(); 

现在我们有实例化,inheritance,但是我们已经失去了封装来支持约定:

 var a = new Foo(); var b = new Foo(); a.setBar('a'); b.setBar('b'); alert(a.getBar()); // alerts 'a' :) alert(b.getBar()); // alerts 'b' :) 

但私人汽车可以访问:

 delete a._bar; b._bar = null; alert(a.getBar()); // alerts undefined :( alert(b.getBar()); // alerts null :( 

我认为你要找的是“显示原型图案”。

Dan Wahlin有一个很棒的博客文章: http ://weblogs.asp.net/dwahlin/archive/2011/08/03/techniques-strategies-and-patterns-for-structuring-javascript-code-revealing-prototype-pattern 。 ASPX

甚至更好的Pluralsight当然在这个和其他相关的JavaScript结构: http ://pluralsight.com/training/courses/TableOfContents?courseName=structuring – javascript&highlight=dan-wahlin_structuring-javascript-module1!dan-wahlin_structuring-javascript-module2!dan -wahlin_structuring-JavaScript的单词数!丹wahlin_structuring-JavaScript的单词数!丹wahlin_structuring-JavaScript的单词数#结构,JavaScript的模块1

Javascript当然是面向对象的。 你总是有多态性,但是你必须牺牲封装或实例化,这是你遇到的问题。

试试这个只是刷上你的select。 http://www.webmonkey.com/2010/02/make_oop_classes_in_javascript/另外一个老问题,我已经书签:; 是JavaScript面向对象?

closures是你的朋友!

只需将以下小函数添加到顶级命名空间,即可完成OOP

  • 封装,静态和实例,私有和公共variables和方法
  • 遗产
  • (例如单身服务)
  • 没有约束,没有框架,只是普通的旧的Javascript

 function clazz(_class, _super) { var _prototype = Object.create((_super || function() {}).prototype); var _deps = Array.isArray(_class) ? _class : [_class]; _class = _deps.pop(); _deps.push(_super); _prototype.constructor = _class.apply(_prototype, _deps) || _prototype.constructor; _prototype.constructor.prototype = _prototype; return _prototype.constructor; } 

上面的函数只是连接给定的类的原型和最终的父构造函数,并返回结果的构造函数,准备实例化。

现在,您可以最自然地用几行代码声明您的基类(即,扩展{}),并完成静态,实例,公共和私有属性和方法:

 MyBaseClass = clazz(function(_super) { // class closure, 'this' is the prototype // local variables and functions declared here are private static variables and methods // properties of 'this' declared here are public static variables and methods return function MyBaseClass(arg1, ...) { // or: this.constructor = function(arg1, ...) { // local variables and functions declared here are private instance variables and methods // properties of 'this' declared here are public instance variables and methods }; }); 

扩展一个类? 所有更自然的:

 MySubClass = clazz(function(_super) { // class closure, 'this' is the prototype // local variables and functions are private static variables and methods // properties of this are public static variables and methods return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) { // local variables and functions are private instance variables and methods _super.apply(this, arguments); // or _super.call(this, arg1, ...) // properties of 'this' are public instance variables and methods }; }, MyBaseClass); // extend MyBaseClass 

换句话说,将父类构造函数传递给clazz函数,并将_super.call(this, arg1, ...)到子类的构造函数,该构造函数使用所需的参数调用父类的构造函数。 与任何标准的inheritancescheme一样,父构造函数调用必须先在子构造函数中进行。

请注意,您可以自由显式地使用this.constructor = function(arg1, ...) {...}this.constructor = function MyBaseClass(arg1, ...) {...}如果您需要从构造函数中的代码中简单访问构造函数,或者甚至简单地返回具有return function MyBaseClass(arg1, ...) {...}的构造return function MyBaseClass(arg1, ...) {...}如上面的代码中所示。 无论你觉得最舒服。

只需从构造函数中实例化这些类的对象: myObj = new MyBaseClass();

请注意,闭包很好地封装了一个类的所有function,包括它的原型和构造函数,为静态和实例,私有和公共属性和方法提供了一个自然的命名空间。 类closures中的代码完全没有约束。 没有框架,没有约束,只是普通的旧的Javascript。 closures规则!

哦,如果你想注入单例依赖(例如服务)到你的类(即原型), clazz将为你做AngularJS:

 DependentClass = clazz([aService, function(_service, _super) { // class closure, 'this' is the prototype // the injected _service dependency is available anywhere in this class return function MySubClass(arg1, ...) // or: this.constructor = function(arg1, ...) { _super.apply(this, arguments); // or _super.call(this, arg1, ...) // the injected _service dependency is also available in the constructor }; }], MyBaseClass); // extend MyBaseClass 

正如上面的代码试图说明的那样,要将singleton注入到类中,只需将类closure作为最后一项放入具有所有依赖项的数组中即可。 还要将相应的参数添加到_super参数前面的类闭包中,并按照与数组中相同的顺序。 clazz会将数组中的依赖作为参数注入到类closures中。 依赖关系可以在类closures中的任何地方使用,包括构造函数。

实际上,由于依赖关系被注入到原型中,所以甚至在从类中实例化任何对象之前,它们都可用于静态方法。 这对于连接应用程序或单元以及端到端testing非常有用。 它也消除了向构造函数中注入单例的需要,否则会不必要地破坏构造函数的代码。

检查这个小提琴: http : //jsfiddle.net/5uzmyvdq/1/

反馈和build议最受欢迎!

最近我正在思考这个问题,以及各种方法的局限性。 我已经能够提出的最佳解决scheme如下。

它似乎解决了inheritance问题,实例化和ecapsulation(至less从谷歌浏览器v.24testing),虽然可能在内存使用成本。

 function ParentClass(instanceProperty) { // private var _super = Object.create(null), privateProperty = "private " + instanceProperty; // public var api = Object.create(_super); api.constructor = this.constructor; api.publicMethod = function() { console.log( "publicMethod on ParentClass" ); console.log( privateProperty ); }; api.publicMethod2 = function() { console.log( "publicMethod2 on ParentClass" ); console.log( privateProperty ); }; return api; } function SubClass(instanceProperty) { // private var _super = ParentClass.call( this, instanceProperty ), privateProperty = "private sub " + instanceProperty; // public var api = Object.create(_super); api.constructor = this.constructor; api.publicMethod = function() { _super.publicMethod.call(this); // call method on ParentClass console.log( "publicMethod on SubClass" ); console.log( privateProperty ); } return api; } var par1 = new ParentClass(0), par2 = new ParentClass(1), sub1 = new SubClass(2), sub2 = new SubClass(3); par1.publicMethod(); par2.publicMethod(); sub1.publicMethod(); sub2.publicMethod(); par1.publicMethod2(); par2.publicMethod2(); sub1.publicMethod2(); sub2.publicMethod2(); 

有很多JS类的问题是,他们不保证他们的领域和方法,这意味着任何人使用它可能会意外地取代一个方法。 例如代码:

 function Class(){ var name="Luis"; var lName="Potter"; } Class.prototype.changeName=function(){ this.name="BOSS"; console.log(this.name); }; var test= new Class(); console.log(test.name); test.name="ugly"; console.log(test.name); test.changeName(); test.changeName=function(){ console.log("replaced"); }; test.changeName(); test.changeName(); 

会输出:

 ugly BOSS replaced replaced 

正如你所看到的,changeName函数被覆盖。 下面的代码将确保类方法和字段的安全,并且将使用getter和setter来访问它们,从而使其他语言中的“常规”类更多。

 function Class(){ var name="Luis"; var lName="Potter"; function getName(){ console.log("called getter"); return name; }; function setName(val){ console.log("called setter"); name = val }; function getLName(){ return lName }; function setLName(val){ lName = val; }; Object.defineProperties(this,{ name:{ get:getName, set:setName, enumerable:true, configurable:false }, lastName:{ get:getLName, set:setLName, enumerable:true, configurable:false } }); } Class.prototype.changeName=function(){ this.name="BOSS"; }; Object.defineProperty(Class.prototype, "changeName", { writable:false, configurable:false }); var test= new Class(); console.log(test.name); test.name="ugly"; console.log(test.name); test.changeName(); test.changeName=function(){ console.log("replaced") }; test.changeName(); test.changeName(); 

这输出:

 called getter Luis called setter called getter ugly called setter called setter called setter 

现在你的类方法不能被随机的值或函数replace,并且getters和setter中的代码在尝试读取或写入字段时总是运行。

JavaScript类是在ECMAScript 6中引入的,并且是基于JavaScript的现有基于原型的inheritance的语法糖。 类语法没有为JavaScript引入一个新的面向对象的inheritance模型。 JavaScript类提供了一个更简单,更清晰的语法来创build对象并处理inheritance。

你可以在这个链接中看到更多的Mozilla社区

Github上

这个闭包允许实例化和封装,但没有inheritance。

 function Foo(){ var _bar = "foo"; return { getBar: function() { return _bar; }, setBar: function(bar) { _bar = bar; } }; }; a = Foo(); b = Foo(); a.setBar("bar"); alert(a.getBar()); // "bar" alert(b.getBar()); // "foo"