36

私が最も慣れている言語である C++ では、通常、次のようにオブジェクトを宣言します。

class foo
{
public:
    int bar;
    int getBar() { return bar; }
}

呼び出しgetBar()は正常に機能します (初期化されていない可能性があるという事実を無視しbarます)。bar内の変数getBar()は class のスコープ内にあるため、パラメーターではなくクラスを参照していることを本当に明確にする必要がない限りfoo、言う必要はありません。this->barbar

今、私はJavascriptでOOPを始めようとしています。だから、私はクラスを定義する方法を調べて、同じ種類のことを試します:

function foo()
{
     this.bar = 0;
     this.getBar = function() { return bar; }
}

そして、それは私に与えますbar is undefined。を に変更するbarthis.bar問題は解決しますが、すべての変数に対してこれを行うと、コードがかなり乱雑になります。これはすべての変数に必要ですか? これに関する質問が見つからないので、根本的に間違ったことをしているように感じます。


編集:そうです、コメントから私が得ているのは、オブジェクトのプロパティであるがローカル変数this.barとは異なるものを参照していることです。barスコープとオブジェクトに関して、これが正確になぜなのか、また、これが不要なオブジェクトを定義する別の方法があるかどうかを誰かが言うことができますか?

4

6 に答える 6

34

JavaScript にはクラスクラス ベースのオブジェクト モデルはありません。クラスを模倣できる強力なプロトタイプの継承を使用しますが、これにはあまり適していません。すべてがオブジェクトであり、オブジェクトは他のオブジェクトから継承[できます]。

コンストラクターは、新しく作成されたオブジェクトにプロパティを割り当てる単なる関数です。オブジェクト (キーワード を使用した呼び出しによって作成されるnew)は、thisキーワード(関数に対してローカル) を介して参照できます。

メソッドは、オブジェクトに対して呼び出される単なる関数でもあります。これthisも、オブジェクトを指すことによって行われます。少なくとも、その関数がオブジェクトのプロパティとして呼び出されたときは、メンバー演算子(ドット、角かっこ) を使用します。これは初心者に多くの混乱を引き起こします。なぜなら、その関数を (イベント リスナーなどに) 渡すと、アクセスされたオブジェクトから「分離」されるからです。

今、遺産はどこにありますか?「クラス」のインスタンスは、同じプロトタイプ オブジェクトから継承します。メソッドは、そのオブジェクトの関数プロパティとして定義されます (インスタンスごとに 1 つの関数ではなく)。メソッドを呼び出すインスタンスは、そのプロパティを継承するだけです。

例:

function Foo() {
    this.bar = "foo"; // creating a property on the instance
}
Foo.prototype.foo = 0; // of course you also can define other values to inherit
Foo.prototype.getBar = function() {
    // quite useless
    return this.bar;
}

var foo = new Foo; // creates an object which inherits from Foo.prototype,
                   // applies the Foo constructor on it and assigns it to the var
foo.getBar(); // "foo" - the inherited function is applied on the object and
              // returns its "bar" property
foo.bar; // "foo" - we could have done this easier.
foo[foo.bar]; // 0 - access the "foo" property, which is inherited
foo.foo = 1;  // and now overwrite it by creating an own property of foo
foo[foo.getBar()]; // 1 - gets the overwritten property value. Notice that
(new Foo).foo;     // is still 0

そのため、そのオブジェクトのプロパティのみを使用し、満足しています。しかし、それらはすべて「公開」されており、上書き/変更/削除することができます! それが気にならなければ、あなたは幸運です。プロパティの名前の前にアンダースコアを付けることで、プロパティの「プライベート性」を示すことができますが、これは他の開発者へのヒントにすぎず、従わない可能性があります (特にエラーの場合)。

そのため、賢い人は、コンストラクター関数をクロージャーとして使用して、プライベートな「属性」を作成できるソリューションを見つけました。JavaScript 関数を実行するたびに、ローカル変数用の新しい変数環境が作成され、実行が終了するとガベージ コレクションが行われる場合があります。そのスコープ内で宣言されたすべての関数も、これらの変数にアクセスできます。これらの関数が (たとえば、イベント リスナーによって) 呼び出される限り、環境は存続する必要があります。したがって、コンストラクターからローカルに定義された関数をエクスポートすることにより、これらの関数のみがアクセスできるローカル変数でその変数環境を保持します。

実際に見てみましょう:

function Foo() {
    var bar = "foo"; // a local variable
    this.getBar = function getter() {
        return bar; // accesses the local variable
    }; // the assignment to a property makes it available to outside
}

var foo = new Foo; // an object with one method, inheriting from a [currently] empty prototype
foo.getBar(); // "foo" - receives us the value of the "bar" variable in the constructor

コンストラクタ内で定義されるこのゲッター関数は、「プライベート」(ローカル)「属性」(変数)にアクセスできるため、 「特権メソッド」と呼ばれるようになりました。の値barは決して変わりません。もちろん、セッター関数を宣言することもできます。それにより、検証などを追加できます。

プロトタイプ オブジェクトのメソッドは、コンストラクターのローカル変数にアクセスできませんが、特権メソッドを使用する可能性があることに注意してください。1つ追加しましょう:

Foo.prototype.getFooBar = function() {
    return this.getBar() + "bar"; // access the "getBar" function on "this" instance
}
// the inheritance is dynamic, so we can use it on our existing foo object
foo.getFooBar(); // "foobar" - concatenated the "bar" value with a custom suffix

したがって、両方のアプローチを組み合わせることができます。異なるスコープ チェーン (同じコード) を持つ個別の関数オブジェクトを作成するため、特権メソッドにはより多くのメモリが必要であることに注意してください。非常に大量のインスタンスを作成する場合は、プロトタイプでのみメソッドを定義する必要があります。

ある「クラス」から別の「クラス」への継承を設定する場合は、さらに複雑になります。基本的に、子プロトタイプ オブジェクトを親オブジェクトから継承させ、子インスタンスに親コンストラクターを適用して「プライベート属性」を作成する必要があります。 "。正しい javascript 継承継承されたプロトタイプのプライベート変数、JAVASCRIPTモジュール パターンでのプライベート フィールド メンバーと継承の定義、およびJS で継承を実装する方法 プロトタイプ パターンを明らかにする方法をご覧ください。

于 2012-11-16T14:52:31.727 に答える
7

明示的に言うことは、(よく理解しているように)によって参照されている現在のオブジェクトthis.fooのプロパティに関心があることを意味します。したがって、次を使用する場合: equalsによって参照される現在のオブジェクトのプロパティを に設定します。foothisthis.foo = 'bar';foothisbar

JavaScriptのthisキーワードは、C++ のように常に同じことを意味するとは限りません。ここで例を挙げることができます:

function Person(name) {
   this.name = name;
   console.log(this); //Developer {language: "js", name: "foo"} if called by Developer
}

function Developer(name, language) {
   this.language = language;
   Person.call(this, name);
}

var dev = new Developer('foo', 'js');

Person上記の例では、関数のコンテキストで関数を呼び出しているDeveloperためthis、 によって作成されるオブジェクトを参照していますDeveloperconsole.log結果からわかるように、 からthis来ていDeveloperます。メソッドの最初の引数でcall、関数が呼び出されるコンテキストを指定します。

単純に使用しない場合this、作成したプロパティはローカル変数になります。ご存じかもしれませんが、JavaScript には関数スコープがあるため、変数はローカルになり、変数が宣言されている関数に対してのみ表示されます (もちろん、親内で宣言されているすべての子関数です)。次に例を示します。

function foo() {
    var bar = 'foobar';
    this.getBar = function () {
        return bar;
    }
}

var f = new foo();
console.log(f.getBar());  //'foobar'

varこれは、キーワードを使用する場合に当てはまります。これは、残念ながらグローバルになるbarことを忘れた場合、ローカル変数として定義していることを意味します。varbar

function foo() {
    bar = 'foobar';
    this.getBar = function () {
        return bar;
    }
}

var f = new foo();
console.log(window.bar);  //'foobar'

まさにローカル スコープは、OOP の最大の利点の 1 つであるプライバシーとカプセル化を実現するのに役立ちます。

実際の例:

function ShoppingCart() {
    var items = [];

    this.getPrice = function () {
       var total = 0;
       for (var i = 0; i < items.length; i += 1) {
          total += items[i].price;
       }
       return total;
    }

    this.addItem = function (item) {
        items.push(item);
    }

    this.checkOut = function () {
        var serializedItems = JSON.strigify(items);
        //send request to the server...
    }
}

var cart = new ShoppingCart();
cart.addItem({ price: 10, type: 'T-shirt' });
cart.addItem({ price: 20, type: 'Pants' });
console.log(cart.getPrice()); //30

JavaScript スコープの利点のもう 1 つの例は、モジュール パターンです。モジュール パターンでは、JavaScript のローカル機能スコープを使用してプライバシーをシミュレートできます。このアプローチでは、プライベート プロパティとメソッドの両方を持つことができます。次に例を示します。

var module = (function {

    var privateProperty = 42;

    function privateMethod() {
        console.log('I\'m private');
    }
    return {

       publicMethod: function () {
           console.log('I\'m public!');
           console.log('I\'ll call a private method!');
           privateMethod();
       },

       publicProperty: 1.68,

       getPrivateProperty: function () {
           return privateProperty;
       },

       usePublicProperty: function () {
           console.log('I\'ll get a public property...' + this.publicProperty);
       }

    }
}());

module.privateMethod(); //TypeError
module.publicProperty(); //1.68
module.usePublicProperty(); //I'll get a public property...1.68
module.getPrivateProperty(); //42
module.publicMethod(); 
/*
 * I'm public!
 * I'll call a private method!
 * I'm private
 */

匿名関数を親なしでラップするという少し奇妙な構文がありますが、しばらく忘れてください (初期化された後に関数を実行しているだけです)。機能は使用例から見ることができますが、利点は主に、すべての実装の詳細に関与しない単純なパブリック インターフェイスを提供することに関連しています。パターンの詳細な説明については、上記のリンクを参照してください。


this:-) の情報で、JavaScript のいくつかの基本的なトピックを理解するのに役立つことを願っています。

于 2012-11-16T15:12:32.430 に答える
4
function Foo() {
  this.bar = 0;
  this.getBar = function () { return this.bar };
}

上記の関数をnewキーワードで呼び出すと-このように...

var foo = new Foo();

...-いくつかのことが起こります:

1)オブジェクトが作成されます。2)そのオブジェクトを参照するキーワード
を使用して関数が実行されます。 3)そのオブジェクトが返されます。 this

foo、次に、このオブジェクトになります。

{
    bar: 0,
    getBar: function () { return this.bar; }
};

では、次のようにしてください。

var foo = {
    bar: 0,
    getBar: function () { return this.bar; }
};

それがたった1つの単純なオブジェクトであるなら、あなたはそうするでしょう。

しかし、コンストラクターを使用してオブジェクトを作成すると(そのように呼ばれます)、複数の「同じ」オブジェクトを作成する上で大きな利点があります。

javascriptでは、すべての関数がプロトタイププロパティ[オブジェクト]で作成され、その関数で作成されたすべてのオブジェクトが(newキーワードで呼び出すことによって)そのプロトタイプオブジェクトにリンクされていることを参照してください。これがとてもクールな理由です-すべての一般的なメソッド(および必要に応じてプロパティ)をプロトタイプオブジェクトに格納し、多くのメモリを節約できます。仕組みは次のとおりです。

function Foo( bar, bob ) {
   this.bar = bar;
   this.bob = bob;
}

Foo.prototype.calculate = function () {
  // 'this' points not to the 'prototype' object 
  // as you could've expect, but to the objects
  // created by calling Foo with the new keyword.
  // This is what makes it work.
  return this.bar - this.bob;  
};

var foo1 = new Foo(9, 5);
var foo2 = new Foo(13, 3);
var result1 = foo1.calculate();
var result2 = foo2.calculate();

console.log(result1); //logs 4
console.log(result2); //logs 10

それでおしまい!

于 2012-11-16T15:57:09.420 に答える
2

JavaScript で OOP に近づくには、モジュールの設計パターン (たとえば、ここで説明されている) を調べることをお勧めします。

クロージャ効果に基づいて、このパターンはオブジェクトのプライベート プロパティをエミュレートできます。

「プライベート」プロパティを使用すると、その識別子によって直接参照できます (つまり、thisコンストラクタのようなキーワードはありません)。

とにかく、JS のクロージャとデザイン パターン - 高度なトピックです。そのため、基本に慣れてください (前述の本でも説明されています)。

于 2012-11-16T15:06:34.737 に答える
2

JavaScript ではthis、常に関数の所有者オブジェクトを参照します。たとえばfoo()、ページで関数を定義する場合、所有者は javascript オブジェクトwindowsです。foo()またはon html 要素を定義した場合<body>、所有者は html 要素の本体です。同様に、 element の関数 onclick を定義すると<a>、所有者がアンカーになります。

あなたの場合、bar最初に「所有者」オブジェクトにプロパティを割り当て、ローカル変数を返そうとしていますbar

ローカル varialbebarを定義したことがないため、 bar が定義されていないことがわかります。

var bar;理想的には、値ゼロを返したいかのように、コードで変数を定義する必要があります。

于 2012-11-16T15:08:01.600 に答える