可以使用哪些技术来定义JavaScript中的类,它们的权衡是什么?

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

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

334635 次浏览

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

// 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中具有自己的近似类功能风格。您至少需要查看原型jQuery

决定哪一个是“最好的”是对Stack Overflow发起圣战的好方法。如果你正在着手一个更大的JavaScript密集型项目,那么学习一个流行的库并按照他们的方式去做绝对是值得的。我是一个原型的人,但Stack Overflow似乎更倾向于jQuery。

就只有“一种方法来做这件事”而言,不依赖外部库,我写的方式差不多就是这样。

如果你想要简单,你可以完全避免“new”关键字,只使用工厂方法。有时我更喜欢这样,因为我喜欢使用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");

不过,我不确定大型物体的性能受到了什么影响。

JavaScript是面向对象,但它与其他OOP语言(如Java、C#或C++)完全不同。不要试图这样理解它。扔掉旧知识,重新开始。JavaScript需要不同的思维。

我建议买一本好的手册或关于这个主题的东西。我自己找到了ExtJS教程对我来说是最好的,尽管我在阅读之前或之后没有使用过这个框架。但它确实很好地解释了JavaScript世界中的内容。对不起,该内容似乎已被删除。这是archive.org副本的链接。今天有效。: P

简单的方法是:

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的原因是,如果您将方法作为事件处理程序提供,this可以绑定到其他东西,因此您可以在实例化期间保存该值并在以后使用它。

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

我认为你应该读道格拉斯·克罗克福德的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的网站上找到许多例子。

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

认真点。

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

  • 基于类的OO(最初由Smalltalk引入)
  • 基于原型的OO(最初由self引入)
  • 基于多方法的OO(我认为是由Common Loops首次引入的)
  • 基于谓词的OO(不知道)

可能还有其他我不知道的。

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

换句话说:没有课。

JavaScript实际上对该模型进行了一个很好的调整:构造函数。你不仅可以通过复制现有对象来创建对象,你也可以“凭空”构造它们。如果你使用new关键字调用一个函数,该函数将成为一个构造函数,this关键字不会指向当前对象,而是指向一个新创建的“空”对象。因此,你可以随心所欲地配置一个对象。通过这种方式,JavaScript构造函数可以承担传统基于类的OO中类的角色之一:充当新对象的模板或蓝图。

现在,JavaScript是一种非常强大的语言,所以如果你想实现基于类的OO系统在javascript是非常容易的。但是,只有当你真的需要它时,你才应该这样做,而不仅仅是因为这是Java的方式。

因为我不会承认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'});

理想情况下,类型测试是在第一个方法原型上

MooTools(我的面向对象工具)以的思想为中心。您甚至可以使用继承进行扩展和实现。

掌握后,它会产生可笑的可重用、强大的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'});

我更喜欢使用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中定义类的最佳方式。

以下是在javascript中创建对象的方法,到目前为止我已经使用过了

示例1:

obj = new Object();
obj.name = 'test';
obj.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(定制的CrockfordObject.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());

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

    // 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的类型。它将具有名为切换文本的内部私有字段。它还将具有这些公开成员:字段计数数字;属性切换文本长度;方法按计数递增数字调整

这里详细介绍了折叠模式: Javascript折叠模式

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几乎所有人都支持),但是你现在可以使用这些功能,比如巴别塔

资源

具有继承性的基于对象的类

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

简单,甜蜜,并得到'er完成。

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的方式。

为@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());

一个基地

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"

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

    // 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的东西。

//new way using this and new
function Persons(name) {
this.name = name;
this.greeting = function() {
alert('Hi! I\'m ' + this.name + '.');
};
}


var gee=new Persons("gee");
gee.greeting();


var gray=new Persons("gray");
gray.greeting();


//old way
function createPerson(name){
var obj={};
obj.name=name;
obj.greeting = function(){
console.log("hello I am"+obj.name);
};
return obj;
}


var gita=createPerson('Gita');
gita.greeting();