JavaScriptでクラスを定義するために使用できるテクニックと、そのトレードオフは何ですか?


686

私は、現在取り組んでいるプロジェクトのような大規模プロジェクトでOOPを使用することを好みます。JavaScriptでいくつかのクラスを作成する必要がありますが、誤解しない限り、それを行うには少なくともいくつかの方法があります。構文はどのようになり、なぜそのように行われるのでしょうか?

サードパーティのライブラリの使用は避けたい-少なくとも最初は。
他の答えを探していると、JavaScriptによるオブジェクト指向プログラミング、パートI:継承-JavaScriptでのオブジェクト指向プログラミングについて説明しているドキュメントJavaScriptを見つけました。継承を行うより良い方法はありますか?


注:これはの複製であるstackoverflow.com/questions/355848
ジェイソンS

3
個人的には、関数本体内でクラスメンバーを宣言するのが好きです。「this thisの修正」手法を使用して、クラスのように動作するクロージャーを作成します。私は私のブログに詳細な例を持っている:ncombo.wordpress.com/2012/12/30/...
ジョン

ほとんどのC ++ OOP機能を単純で自然な構文でJavaScriptに移植しました。ここで私の答えを参照してください:stackoverflow.com/a/18239463/1115652

JavaScriptにはクラスはありません。ただし、JSでクラスに似た動作をシミュレートする場合は、可能です。詳細については、symfony-world.blogspot.com
2013/10

回答:


743

外部ライブラリを使用せずにそれを行う方法は次のとおりです。

// 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ライブラリが数多くあります。少なくともPrototypejQueryをチェックアウトする必要があります。

これらのうちのどれが「最良」であるかを決定することは、スタックオーバーフローで聖戦を開始するための優れた方法です。大規模なJavaScriptを多用するプロジェクトに着手している場合は、人気のあるライブラリを学び、それを自分のやり方で行うことは間違いなく価値があります。私はプロトタイプの人ですが、スタックオーバーフローはjQueryに傾いているようです。

外部ライブラリに依存せずに、「それを行う1つの方法」しかない限り、私が書いた方法はほとんどそれです。


48
しかし、オブジェクトインスタンスの作成に使用される
Thingyが機能

2
developer.mozilla.org/en-US/docs/Web/JavaScript/…によると、プロパティもプロトタイプに追加する必要があります( "Person.prototype.name = '';")
DaveD

1
@DaveD-多分それはしましたが、もうそうではないようです。
Kieren Johnstone 2014

6
jQueryはクラスのような機能を作成する方法も提供しませんか??? (このクラスはすべてCSSクラスです)回答のその部分から削除する必要があります。
ベルギ14

7
私はそれを新しい方法(あまりきれいとeaser)を行うことを提案するように、2015年のECMAScript 6の新しい標準のsecound半分からは、リリースされましたes6-features.org/#ClassDefinition
DevWL

213

JavaScriptでクラスを定義する最良の方法は、クラスを定義しないことです。

真剣に。

オブジェクト指向にはいくつかの異なるフレーバーがあり、そのうちのいくつかは次のとおりです。

  • クラスベースのOO(Smalltalkによって最初に導入)
  • プロトタイプベースのOO(Selfによって最初に導入)
  • マルチメソッドベースのオブジェクト指向(最初はCommonLoopsによって導入されたと思います)
  • 述語ベースのオブジェクト指向(アイデアなし)

そしておそらく私が知らない他の人たち。

JavaScriptはプロトタイプベースのオブジェクト指向を実装しています。プロトタイプベースのオブジェクト指向では、新しいオブジェクトは(クラステンプレートからインスタンス化されるのではなく)他のオブジェクトをコピーすることによって作成され、メソッドはクラスではなくオブジェクトに直接存在します。継承は委譲を介して行われます。オブジェクトにメソッドまたはプロパティがない場合、オブジェクトはそのプロトタイプ(つまり、クローン元のオブジェクト)、次にプロトタイプのプロトタイプなどで検索されます。

つまり、クラスはありません。

JavaScriptには、実際にはそのモデルのすばらしい調整、つまりコンストラクターがあります。既存のオブジェクトをコピーしてオブジェクトを作成できるだけでなく、いわば「薄い空気から」オブジェクトを構築することもできます。あなたが関数を呼び出す場合はnew、キーワード、その関数はコンストラクタになり、thisキーワードが現在のオブジェクトにではなく、新しく作成された「空」1を指しています。したがって、オブジェクトは好きなように設定できます。このようにして、JavaScriptコンストラクターは、従来のクラスベースのオブジェクト指向におけるクラスの役割の1つを引き受けることができます。新しいオブジェクトのテンプレートまたは青写真として機能します。

現在、JavaScriptは非常に強力な言語であるため、必要に応じて、JavaScript内にクラスベースのOOシステムを実装するのは非常に簡単です。ただし、これを行う必要があるのは、Javaがそのように実行しているためだけではなく、本当にそれが必要な場合だけです。


「newキーワードで関数を呼び出すと、その関数はコンストラクターになり、thisキーワードは現在のオブジェクトではなく、新しく作成された「空の」オブジェクトを指します。新しいキーワードなしで関数を呼び出す場合、これは呼び出しコンテキスト、デフォルトではグローバルオブジェクト(ウィンドウ)を参照します。strictモードでは、undefinedがデフォルトです。call、apply、bindは、最初のパラメーターとして呼び出しコンテキストを受け取ります。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
エリアスハスレ

83

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.`;
  }
}

利点

主な利点は、静的分析ツールを使用すると、この構文をターゲットにするのが簡単になることです。また、クラスベースの言語を使用する他の人がその言語をポリグロットとして使用する方が簡単です。

注意事項

現在の制限に注意してください。プライベートプロパティを実現するには、SymbolsまたはWeakMaps使用する必要があります。今後のリリースでは、これらの不足している機能を含めるようにクラスが拡張される可能性が高くなります。

サポート

現在のところ、ブラウザーのサポートはあまり良くありません(IEを除くほとんどすべてのユーザーがサポートしています)が、これらの機能をBabelのようなトランスパイラーで使用できるようになりました。

資源


56

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);
        });
      }
    },
  };
}

personクラスの例には、バインド可能なモジュールが含まれます。

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.ムーアで、これが私の{SUPER: SYSTEM}です。これは、JavaScriptでクラスを定義する最良の方法です。


@DanielXMoore「インスタンス変数はクラスの個々のインスタンス間で共有されます」これらはインスタンス変数ではなく、静的/クラス変数です。
JAB 2013

2
@JAB不正解です。静的/クラス変数は、クラスのすべてのインスタンス間で共有されます。各インスタンスには独自のインスタンス変数があります。
Daniel X Moore

(言い換えると、「インスタンス変数」という用語の通常の意味を使用すると、変数が1であるかどうかは、変数のアクセス可能性のレベルに直角になります。)
JAB

2
あなたは最高のxDを主張するスーパーヒーローのように聞こえました
Dadan

JavaScriptオブジェクトを使用してJavascriptクラスを定義する簡単な方法:wapgee.com/story/i/203
Ilyas karim

41
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'});

これは、何もインポートせずに使用可能なオブジェクト構造を構築する非常にエレガントな方法です。私はResigのクラスシステムを使用していましたが、このほうが好きかもしれません。ありがとうございました。
Tim Scollick、2013

29
このアプローチの問題は、新しいAnimalインスタンスを作成するたびに、プロトタイプで一度だけ定義するのではなく、関数を再定義することです。
ジャスティン

33

以下は、これまで使用してきた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(カスタマイズされた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());

1
@ジャスティン:無効なものを教えてください。
Amol M Kulkarni

これらの表記法を検討しているときに、this.set()にも出会いました。例:this.set( 'port'、3000)。私の推測では、これはオブジェクトのポートプロパティを設定するために使用されます。もしそうなら、なぜ直接使用しないのですか:{ポート:3000}。詳細を確認できるドキュメントはありますか?
adityah 2017年

24

JavaScriptでのDouglas Crockfordのプロトタイプ継承と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;
});

私は彼のビデオもお勧めしますAdvanced JavaScript

彼のページでさらに多くのビデオを見つけることができます:http : //javascript.crockford.com/ John Reisigの本では、Douglas CrockforのWebサイトから多くの例を見つけることができます。


25
私だけでしょうか?一体これはもっとエレガントですか?'strings'多くのことを実際の名前で関数定義と呼びますが、エレガントはそれらの1つではありません...
fgysinがモニカを復活させ

4
@JAB、しかしリフレクションは例外であり、ルールではありません。上記のメソッドでは、すべてのメソッドを文字列で宣言する必要があります。
Kirk Woll、

16

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テストはプロトタイプ化された最初のメソッド


私はそれが好きです。私はほとんどの場合、JSの標準構文を使用しています。これは、各オブジェクトインスタンスに関数をコピーするという考えが気に入らないためです。しかし、私は常に自己完結型のソリューションの美しさを見逃しており、これはそれをかなりうまく解決します。
Lukasz Korzybski、2011

1
わかりませんが、ガベージコレクターがクラスのインスタンスにアクセスできないため、関数のスコープ内でプロトタイプ関数を(ある程度クロージャとして)定義すると、メモリリークが発生することを理解しました。
サンネ2014

15

単純な場合は、「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");

ただし、大きなオブジェクトのパフォーマンスへの影響はわかりません。


内部オブジェクトはgetSomeObj()のパラメーターにアクセスできるため、obj.instancevar1 = var1行は不要です。
トリプティク

ワオ。それは私の脳を傷つけますが、それには優雅さがあります。したがって、「obj.instancevar1 = var1」の部分は一種のコンストラクタの始まりだと思いますか?
Karim、

Triptychのコメントを見たところです。そうですか。したがって、内部オブジェクトがインスタンス化されている「instancevar1:var1」のようなことを実行できます。
Karim、

まさに... {}を使用してオブジェクトを定義すると、現在スコープ内にある変数にアクセスできます。
サム

10
このアプローチを使用すると、継承する機能が失われ、obj.prototype.somethingを使用していないため、objectを使用するたびに関数を定義している=より多くのメモリと低速になります。
いくつかの

12
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のコンストラクターを使用してクラスをコンパイルする方法です。


10

簡単な方法は次のとおりです。

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

その理由thatthis、メソッドをイベントハンドラーとして指定した場合に何かにバインドされる可能性があるため、インスタンス化中に値を保存し、後で使用するためです。

編集:それは間違いなく最良の方法ではなく、単純な方法です。私も良い答えを待っています!


1
that = thisコンストラクトはここでは必要ありません。また、add()メソッドとavg()メソッドは、クラス間で共有されるのではなく、クラスFooのすべての「インスタンス」に対してコピーされます。
トリプティク

1
その場合は(sorta)が必要ですが、指定した単純なケースではありませんか?
トリプティク

9

おそらく、折りたたみパターンを使用してタイプを作成する必要があります。

    // 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というタイプが得られます。これには、toggleおよびtextと呼ばれる内部プライベートフィールドがあります。また、これらの公開メンバーも含まれます。フィールドの。プロパティtoggletext、およびnumberLength。メソッドincrementNumbersByCountおよびtweak

折りたたみパターンについて詳しくは、こちらをご覧ください: JavaScriptの折りたたみパターン


3

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



2

継承されたオブジェクトベースのクラス

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

シンプルで甘い、そしてやり遂げる。


1

ベース

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"

1

トリプティクの例に基づいて、これはさらに簡単かもしれません:

    // 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


0

JavaScriptはオブジェクト指向ですが、Java、C#、C ++などの他のOOP言語とは根本的に異なります。そのように理解しようとしないでください。その古い知識を捨てて、新たに始めましょう。JavaScriptには別の考え方が必要です。

この件については、良いマニュアルか何かを入手することをお勧めします。私自身、ExtJSチュートリアルが最適だと思いましたが、それを読んだ前後にこのフレームワークを使用したことはありません。しかし、それはJavaScriptの世界で何が何であるかについての良い説明を与えます。そのコンテンツは削除されたようです。代わりに、archive.orgコピーへのリンクがあります。今日動作します。:P


2
オブジェクト指向?機能的だと思いました。
Peter Mortensen 2013

「ExtJSチュートリアル」リンクが壊れています。
Peter Mortensen 2013

javascriptの関数はオブジェクトであり、javascriptのブラケットスコープルールは各関数ブロックをカプセル化することを説明するほうがわかりやすいと思います。
mibbit 2018

-1

//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();

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.