关于oop:可以使用哪些技术在JavaScript中定义类,以及它们的权衡取舍?

What techniques can be used to define a class in JavaScript, and what are their trade-offs?

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

我想避免使用第三方库——至少在一开始是这样。在寻找其他答案时,我发现了一篇关于用Javascript进行面向对象编程的文章,第一部分:继承-DocJavascript,它讨论了用Javascript进行面向对象编程。有更好的继承方式吗?


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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。

决定哪一个是"最好的",是对堆栈溢出发起神圣战争的一个好方法。如果你正在着手一个更大的JavaScript重的项目,那绝对值得学习一个流行的库并按他们的方式来做。我是一个原型,但堆栈溢出似乎倾向于jquery。

因为只有"一种方法",不依赖于外部库,所以我写的方法差不多就是它。


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

说真的。

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

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

可能还有其他我不认识的人。

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

换句话说:没有课程。

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

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


es2015类

在你可以使用AISI规范es2015,which is just the class的句法糖over the prototype system。P></

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.`;
  }
}

利益

is that the main和静态分析工具easier this to find /目标表。它也easier is for其他对象类的语言,从语言的使用在polyglot as to the。P></caveats

好wary of its current的局限性。一个是私人性质的,使用前必须weakmaps度假村的符号。在未来的新闻稿中,将包括扩大Most likely to be missing,这些特点。P></支持

浏览器支持不好的时刻(at the甚近每一个人除了负载模式你可以使用IE浏览器),但现在,这些特点与类transpiler巴贝尔。P></资源

  • 在ECMAScript中6(最终的语义)
  • 什么?等待。真的吗?哦,不!(后es6 about类和隐私)
  • –兼容性表类
  • 巴贝尔–类

我更喜欢用丹尼尔·X·摩尔的《江户记》1(2)。这是一个提供诸如真实实例变量、基于特征的继承、类层次结构和配置选项等好处的规程。下面的例子说明了使用真正的实例变量,我认为这是最大的优势。如果您不需要实例变量,只使用公共或私有变量,那么可能会有更简单的系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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"

哇,这本身并不是很有用,但是看看添加子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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"

另一个优势是拥有模块和基于特征的继承的能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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);
        });
      }
    },
  };
}

包含Person类的示例包括可绑定模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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!

披露:我是丹尼尔·X·摩尔,这是我的埃多克斯。这是用JavaScript定义类的最佳方法。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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:

1
2
3
4
5
obj = new Object();
obj.name = 'test';
obj.sayHello = function() {
    console.log('Hello '+ this.name);
}

例2:

1
2
3
4
5
6
obj = {};
obj.name = 'test';
obj.sayHello = function() {
    console.log('Hello '+ this.name);
}
obj.sayHello();

例3:

1
2
3
4
5
6
var obj = function(nameParam) {
    this.name = nameParam;
}
obj.prototype.sayHello = function() {
    console.log('Hello '+ this.name);
}

示例4:object.create()的实际好处。请参阅[此链接]

1
2
3
4
5
6
7
8
9
10
11
12
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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更新答案

类的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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());


我认为您应该阅读DouglasCrockford在javascript中的原型继承和在javascript中的经典继承。

本页示例:

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

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

1
2
3
4
5
6
7
8
function Parenizor(value) {
    this.setValue(value);
}

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

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

你可以在他的网页上找到更多视频:http://javascript.crockford.com/在JohnReisig的书中,你可以从道格拉斯·克罗克福的网站上找到很多例子。


因为我不接受Yui/Crockford工厂计划,而且因为我喜欢保持事物的独立性和可扩展性,这是我的变体:

1
2
3
4
5
6
7
8
9
10
11
12
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'});

在理想情况下,测试类型是基于类似于第一个原型方法的


如果你想简单点,你可以完全避免"new"关键字,只使用工厂方法。有时我更喜欢这样,因为我喜欢使用JSON创建对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function getSomeObj(var1, var2){
  var obj = {
     instancevar1: var1,
     instancevar2: var2,
     someMethod: function(param)
     {  
          //stuff;
     }
  };
  return obj;
}

var myobj = getSomeObj("var1","var2");
myobj.someMethod("bla");

不过,我不确定大型对象的性能影响是什么。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 compiles with to JavaScript构造函数。P></


简单的方法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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可以绑定到其他对象,因此在实例化期间保存该值,稍后使用。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
    // 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的类型。它将有称为切换和文本的内部私有字段。它还具有这些公开的成员:字段计数和数字;属性切换、文本和数字长度;方法incrementNumbersByCount和tweak。

折叠图案在这里详细说明:javascript折叠模式


S for @高尔夫liammclennan'回答队列。P></

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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());

P></


基于继承的类与对象P></

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

单,二和甜蜜,是做得到的。P></


moootools(我的面向对象工具)以类的概念为中心。甚至可以使用继承来扩展和实现。

当掌握后,它会产生荒谬的可重用、强大的javascript。


基于P></

1
2
3
function Base(kind) {
    this.kind = kind;
}

在类P></

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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);
}

actionP></

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


基于triptych the example of this might:嗯,甚至简单的内部P></

1
2
3
4
5
6
7
8
9
10
11
    // 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"

这只单曲创源到对象实例,但仍然是有用的,如果你想帮encapsulate of names for a class中的变量和方法。我normally not be there"鲍勃,M"Arguments for example to the构造函数方法,if the摇篮电话系统与自己的数据或网络数据库,such as a。P></

我还看到过纽约和JS使用this does not the prototype为什么事。P></


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//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();

P></


JavaScript是面向对象的,但它与其他的OOP语言(如Java、C语言或C++)截然不同。不要那样理解它。抛开旧知识,重新开始。JavaScript需要不同的思考。

我建议你找一本好的手册或是有关这个问题的东西。我自己发现extjs教程对我来说是最好的,尽管我在阅读之前或之后都没有使用过这个框架。但它确实很好地解释了JavaScript世界中的内容。抱歉,内容似乎已被删除。这里有一个到archive.org copy的链接。今天工作。P