什么技术可以用来在JavaScript中定义一个类,以及它们的权衡是什么?

我更喜欢在大型项目中使用OOP,比如我现在正在使用的项目。 我需要用JavaScript创建几个类,但是如果我没有弄错的话,至少有几个方法可以做到这一点。 语法是什么,为什么要这样做呢?

我想避免使用第三方库 – 至少在一开始。
寻找其他的答案,我找到了文章面向对象的JavaScript编程,第一部分:继承 – 文档JavaScript讨论JavaScript中的面向对象编程。 有没有更好的方法来继承?

下面是不使用任何外部库的方法:

// Define a class like this function Person(name, gender){ // Add object properties like this this.name = name; this.gender = gender; } // Add methods like this. All Person objects will be able to invoke this Person.prototype.speak = function(){ alert("Howdy, my name is" + this.name); }; // Instantiate new objects with 'new' var person = new Person("Bob", "M"); // Invoke methods like this person.speak(); // alerts "Howdy, my name is Bob" 

现在真正的答案比这个复杂得多。 比如,JavaScript中没有类。 JavaScript使用基于prototype的继承方案。

另外,还有许多流行的JavaScript库,它们都有自己的在JavaScript中类似类似功能的风格。 你至少要检查一下Prototype和jQuery 。

决定哪一个是“最好的”是一个伟大的方式来启动堆栈溢出的圣战。 如果你正在着手一个更大型的JavaScript项目,那么肯定值得学习一个流行的图书馆,并按照自己的方式去做。 我是一个原型人,但堆栈溢出似乎倾向于jQuery。

就“只有一个办法”而言,没有任何外部图书馆的依赖,我所写的就是这样。

在JavaScript中定义类的最好方法是不定义类。

认真。

有几种不同的面向对象的风格,其中一些是:

  • 基于类的OO(首先由Smalltalk引入)
  • 基于原型的OO(由Self首先引入)
  • 基于多方法的面向对象(首先由CommonLoops引入,我认为)
  • 基于谓词的OO(不知道)

也可能是其他人我不知道。

JavaScript实现了基于原型的面向对象。 在基于原型的OO中,通过复制其他对象(而不是从类模板实例化)来创建新对象,并且方法直接存在于对象中而不是类中。 继承是通过委托来完成的:如果一个对象没有方法或者属性,就会在其原型(即从中克隆的对象),原型的原型等等上查找。

换句话说:没有类。

JavaScript实际上有一个很好的调整模型:构造函数。 您不仅可以通过复制现有的对象来创建对象,还可以“轻松地”构建它们,可以这么说。 如果使用new关键字调用函数,那么该函数将成为构造函数,而this关键字不会指向当前对象,而是指向新创建的“空”对象。 所以,你可以用任何你喜欢的方式配置一个对象。 这样,JavaScript构造函数就可以在传统的基于类的OO中承担类中的一个角色:用作新对象的模板或蓝图。

现在,JavaScript是一种非常强大的语言,所以如果你愿意的话, 在JavaScript中实现一个基于类的OO系统是相当容易的。 但是,如果您确实需要这样做,那么您只应该这样做,而不仅仅是因为Java这样做。

ES2015课程

在ES2015规范中,您可以使用类原型系统上的类语法。

 class Person { constructor(name) { this.name = name; } toString() { return `My name is ${ this.name }.`; } } class Employee extends Person { constructor(name, hours) { super(name); this.hours = hours; } toString() { return `${ super.toString() } I work ${ this.hours } hours.`; } } 

优点

主要的好处是静态分析工具更容易定位这个语法。 来自基于类的语言的其他人也更容易使用该语言作为多语言。

注意事项

警惕目前的局限性。 要实现私有财产,必须诉诸使用符号或弱图 。 在将来的版本中,类很可能会扩展到包含这些缺失的功能。

支持

浏览器支持目前并不是很好(几乎所有人都支持除IE以外),但是现在可以使用像Babel这样的翻译工具来使用这些功能。

资源

  • ECMAScript 6中的类(最终语义)
  • 什么? 等待。 真? 不好了! (关于ES6课程和隐私的文章)
  • 兼容性表 – 类
  • Babel – 课程

我更喜欢用Daniel X. Moore的{SUPER: SYSTEM} 。 这是一个提供诸如真实实例变量,基于特征的继承,类层次结构和配置选项等优点的学科。 下面的例子说明了使用真实的实例变量,我认为这是最大的优势。 如果你不需要实例变量,并且只对公共或私有变量感到满意,那么可能有更简单的系统。

 function Person(I) { I = I || {}; Object.reverseMerge(I, { name: "McLovin", age: 25, homeState: "Hawaii" }); return { introduce: function() { return "Hi I'm " + I.name + " and I'm " + I.age; } }; } var fogel = Person({ age: "old enough" }); fogel.introduce(); // "Hi I'm McLovin and I'm old enough" 

哇,这对自己并不是很有用,但是看看添加一个子类:

 function Ninja(I) { I = I || {}; Object.reverseMerge(I, { belt: "black" }); // Ninja is a subclass of person return Object.extend(Person(I), { greetChallenger: function() { return "In all my " + I.age + " years as a ninja, I've never met a challenger as worthy as you..."; } }); } var resig = Ninja({name: "John Resig"}); resig.introduce(); // "Hi I'm John Resig and I'm 25" 

另一个优点是具有模块和基于特征的继承。

 // The Bindable module function Bindable() { var eventCallbacks = {}; return { bind: function(event, callback) { eventCallbacks[event] = eventCallbacks[event] || []; eventCallbacks[event].push(callback); }, trigger: function(event) { var callbacks = eventCallbacks[event]; if(callbacks && callbacks.length) { var self = this; callbacks.forEach(function(callback) { callback(self); }); } }, }; } 

具有人员类别的示例包括可绑定模块。

 function Person(I) { I = I || {}; Object.reverseMerge(I, { name: "McLovin", age: 25, homeState: "Hawaii" }); var self = { introduce: function() { return "Hi I'm " + I.name + " and I'm " + I.age; } }; // Including the Bindable module Object.extend(self, Bindable()); return self; } var person = Person(); person.bind("eat", function() { alert(person.introduce() + " and I'm eating!"); }); person.trigger("eat"); // Blasts the alert! 

披露:我是Daniel X. Moore,这是我的{SUPER: SYSTEM} 。 这是在JavaScript中定义一个类的最好方法。

 var Animal = function(options) { var name = options.name; var animal = {}; animal.getName = function() { return name; }; var somePrivateMethod = function() { }; return animal; }; // usage var cat = Animal({name: 'tiger'}); 

以下是在JavaScript中创建对象的方法,我迄今使用过

例1:

 obj = new Object(); obj.name = 'test'; obj.prototype.sayHello = function() { console.log('Hello '+ this.name); } 

例2:

 obj = {}; obj.name = 'test'; obj.sayHello = function() { console.log('Hello '+ this.name); } obj.sayHello(); 

例3:

 var obj = function(nameParam) { this.name = nameParam; } obj.prototype.sayHello = function() { console.log('Hello '+ this.name); } 

例4: Object.create()的实际好处 请参考[此链接]

 var Obj = { init: function(nameParam) { this.name = nameParam; }, sayHello: function() { console.log('Hello '+ this.name); } }; var usrObj = Object.create(Obj); // <== one level of inheritance usrObj.init('Bob'); usrObj.sayHello(); 

示例5(定制的Crockford的Object.create):

 Object.build = function(o) { var initArgs = Array.prototype.slice.call(arguments,1) function F() { if((typeof o.init === 'function') && initArgs.length) { o.init.apply(this,initArgs) } } F.prototype = o return new F() } MY_GLOBAL = {i: 1, nextId: function(){return this.i++}} // For example var userB = { init: function(nameParam) { this.id = MY_GLOBAL.nextId(); this.name = nameParam; }, sayHello: function() { console.log('Hello '+ this.name); } }; var bob = Object.build(userB, 'Bob'); // Different from your code bob.sayHello(); 

保持与ES6 / ES2015更新的答案

一个类是这样定义的:

 class Person { constructor(strName, numAge) { this.name = strName; this.age = numAge; } toString() { return '((Class::Person) named ' + this.name + ' & of age ' + this.age + ')'; } } let objPerson = new Person("Bob",33); console.log(objPerson.toString()); 

我认为你应该阅读道格拉斯·克罗克福德的JavaScript中的原型继承和JavaScript中的 古典继承 。

他的页面中的示例:

 Function.prototype.method = function (name, func) { this.prototype[name] = func; return this; }; 

影响? 它将允许您以更优雅的方式添加方法:

 function Parenizor(value) { this.setValue(value); } Parenizor.method('setValue', function (value) { this.value = value; return this; }); 

我也推荐他的视频: 高级JavaScript 。

你可以在他的页面上找到更多的视频: http ://javascript.crockford.com/在John Reisig书中,你可以找到很多来自Douglas Crockfor网站的例子。

因为我不会承认YUI / Crockford工厂计划,而且因为我喜欢把东西包含进去并且可以扩展,所以这是我的变化:

 function Person(params) { this.name = params.name || defaultnamevalue; this.role = params.role || defaultrolevalue; if(typeof(this.speak)=='undefined') //guarantees one time prototyping { Person.prototype.speak = function() {/* do whatever */}; } } var Robert = new Person({name:'Bob'}); 

在理想情况下,typeof测试就像第一种方法的原型

如果你想要简单,你可以完全避免使用“新”关键字,只使用工厂方法。 我更喜欢这个,有时候,因为我喜欢使用JSON来创建对象。

 function getSomeObj(var1, var2){ var obj = { instancevar1: var1, instancevar2: var2, someMethod: function(param) { //stuff; } }; return obj; } var myobj = getSomeObj("var1", "var2"); myobj.someMethod("bla"); 

不过,我不确定大型物体的性能如何。

 var Student = (function () { function Student(firstname, lastname) { this.firstname = firstname; this.lastname = lastname; this.fullname = firstname + " " + lastname; } Student.prototype.sayMyName = function () { return this.fullname; }; return Student; }()); var user = new Student("Jane", "User"); var user_fullname = user.sayMyName(); 

这就是TypeScript将带有构造函数的类编译成JavaScript的方式。

您可能想要使用折叠模式创建一个类型:

  // Here is the constructor section. var myType = function () { var N = {}, // Enclosed (private) members are here. X = this; // Exposed (public) members are here. (function ENCLOSED_FIELDS() { N.toggle = false; N.text = ''; }()); (function EXPOSED_FIELDS() { X.count = 0; X.numbers = [1, 2, 3]; }()); // The properties below have access to the enclosed fields. // Careful with functions exposed within the closure of the // constructor, each new instance will have it's own copy. (function EXPOSED_PROPERTIES_WITHIN_CONSTRUCTOR() { Object.defineProperty(X, 'toggle', { get: function () { var before = N.toggle; N.toggle = !N.toggle; return before; } }); Object.defineProperty(X, 'text', { get: function () { return N.text; }, set: function (value) { N.text = value; } }); }()); }; // Here is the prototype section. (function PROTOTYPE() { var P = myType.prototype; (function EXPOSED_PROPERTIES_WITHIN_PROTOTYPE() { Object.defineProperty(P, 'numberLength', { get: function () { return this.numbers.length; } }); }()); (function EXPOSED_METHODS() { P.incrementNumbersByCount = function () { var i; for (i = 0; i < this.numbers.length; i++) { this.numbers[i] += this.count; } }; P.tweak = function () { if (this.toggle) { this.count++; } this.text = 'tweaked'; }; }()); }()); 

该代码会给你一个名为myType的类型。 它将有内部的私人领域称为切换文字 。 它也将有这些暴露的成员:字段数字 ; 属性切换文本numberLength ; 方法incrementNumbersByCount调整

这里的折叠模式是完全详细的: Javascript折叠模式

简单的方法是:

 function Foo(a) { var that=this; function privateMethod() { .. } // public methods that.add = function(b) { return a + b; }; that.avg = function(b) { return that.add(b) / 2; // calling another public method }; } var x = new Foo(10); alert(x.add(2)); // 12 alert(x.avg(20)); // 15 

其原因是,如果您将方法作为事件处理程序提供that则可以将其绑定到其他位置,以便在实例化期间保存该值并在以后使用。

编辑:这绝对不是最好的方法,只是一个简单的方法。 我也在等待好的答案!

代码高尔夫@ liammclennan的答案 。

 var Animal = function (args) { return { name: args.name, getName: function () { return this.name; // member access }, callGetName: function () { return this.getName(); // method call } }; }; var cat = Animal({ name: 'tiger' }); console.log(cat.callGetName()); 

MooTools (我的面向对象的工具)是集中在类的想法。 你甚至可以继承和继承。

当掌握,它使可笑的可重复使用,功能强大的JavaScript。

基于对象的类与继承

 var baseObject = { // Replication / Constructor function new : function(){ return Object.create(this); }, aProperty : null, aMethod : function(param){ alert("Heres your " + param + "!"); }, } newObject = baseObject.new(); newObject.aProperty = "Hello"; anotherObject = Object.create(baseObject); anotherObject.aProperty = "There"; console.log(newObject.aProperty) // "Hello" console.log(anotherObject.aProperty) // "There" console.log(baseObject.aProperty) // null 

简单,甜美,并得到完成。

JavaScript是面向对象的 ,但与Java,C#或C ++等其他OOP语言截然不同。 不要试图去理解它。 抛弃旧的知识,重新开始。 JavaScript需要一个不同的想法。

我会建议得到一个很好的手册或关于这个问题的东西。 我自己发现ExtJS教程对我来说是最好的,尽管在阅读之前或之后我还没有使用过这个框架。 但是,它确实给出了JavaScript世界中什么是什么的一个很好的解释。 对不起,内容似乎已被删除。 这里是一个链接到archive.org副本,而不是。 今天工作。 :P

基地

 function Base(kind) { this.kind = kind; } 

一类

 // Shared var var _greeting; (function _init() { Class.prototype = new Base(); Class.prototype.constructor = Class; Class.prototype.log = function() { _log.apply(this, arguments); } _greeting = "Good afternoon!"; })(); function Class(name, kind) { Base.call(this, kind); this.name = name; } // Shared function function _log() { console.log(_greeting + " Me name is " + this.name + " and I'm a " + this.kind); } 

行动

 var c = new Class("Joe", "Object"); c.log(); // "Good afternoon! Me name is Joe and I'm a Object" 

基于三联的例子,这可能更简单:

  // Define a class and instantiate it var ThePerson = new function Person(name, gender) { // Add class data members this.name = name; this.gender = gender; // Add class methods this.hello = function () { alert('Hello, this is ' + this.name); } }("Bob", "M"); // this instantiates the 'new' object // Use the object ThePerson.hello(); // alerts "Hello, this is Bob" 

这只会创建一个单一的对象实例,但是如果你想为一个类中的变量和方法封装一堆名字,它仍然是有用的。 通常情况下,构造函数不会有“Bob,M”参数,例如,如果方法是使用自己的数据(如数据库或网络)调用系统。

我仍然对JS太新,看看为什么这不使用prototype东西。