2

オブジェクト内で setInterval を使用しようとすると、奇妙な動作が発生します。

これが私のコードです:

var Person = {
    speech: null,

    tryToSpeak: function ()
    {
        this.speech = "hello";
        self.setTimeout (this.speak, 1000);
    },

    speak: function ()
    {
        // prints out undefined
        console.log (this.speech);
    }
}

Person.tryToSpeak ();

speak()を介して実行される場合setTimeout()、 などのオブジェクト データにはアクセスできませんspeech。一体何が起こっているのですか?これは避けられない行動ですか?

4

5 に答える 5

2

最初にいくつかのメモ:

  • new一般に、javascriptの規則では、キーワードを使用して作成された「インスタンス」を持つ「クラス」定義の大文字の名前を予約します。私の答えでは、personの代わりに使用しPersonます。
  • setTimeoutは、windowオブジェクトのメソッドです。self.setTimeout正しくありません。一部のJavaScript実装selfではウィンドウですが、他の実装ではそうではないため、信頼性がありません。
  • this いつ、どのように発生しているかに関係なく、常に現在の実行コンテキストを参照します。setTimeout呼び出しは、関数を通常のオブジェクトコンテキストから完全に削除します。これは、その時点での関数であるため、実行すると、予期される「this」オブジェクトはありません。

これを回避するためのいくつかのオプションを見ることができます。

  1. bindを使用して、「呼び出されたときに、 thisキーワードが指定された値に設定される新しい関数を作成します。」:

    window.setTimeout(this.speak.bind(this), 1000);
    
  2. thissetTimeoutに渡された関数を、動的に設定する無名関数でラップします。

    window.setTimeout(function(thisobj) {
       return function() {
          thisobj.speak.call(thisobj);
       }
    }(this), 1000);
    

    thisobj関数を使用して、パラメーターのクロージャーを作成しています。このパラメーターthis は、setTimeoutの呼び出し時に現在のこのオブジェクトを使用して呼び出されていました。次に、関数自体がsetTimeoutが呼び出す関数を返し、その関数はを使用してcall(または、を使用して) 、の呼び出し用のオブジェクトapplyを設定します。ここで実際に行ったのは、パラメーターのサポートなしでの機能を複製することだけです。したがって、完全なクロスブラウザーと古いブラウザーのサポートが必要な場合を除いて、代わりにバインドを使用してください(この場合、これを行うか、JavaScriptをシュガーアップできます。バインドは機能します。私の答えの下部にあるコードを使用します)。しかし、私はあなたに何が起こっているのかを隠して見て欲しかった。thisspeakbind

  3. 個人オブジェクトのメンバーに明示的にアクセスします。

    speak: function () {
        console.log (person.speech); // instead of this.speech
    }
    
  4. Personをオブジェクトコンストラクター関数に変更します。これを使用して、新しい「person」インスタンスを作成できます。

    function Person() {
        var me = this;
        this.speech = null;
        this.tryToSpeak = function () {
            me.speech = "hello";
            window.setTimeout(me.speak, 1000);
        };
        this.speak = function () {
            console.log(me.speech);
        };
    }
    
    var person = new Person();
    person.tryToSpeak();
    

    のコピーをコンストラクター関数内thisのプライベート変数としてキャプチャすることにより、後で他のメソッドで使用できます。me実行すると実行コンテキストが存在するため、キャプチャされたthisものは理にかなっていnew Person()ます(オブジェクトを単純に宣言した場合とは異なり、実行コンテキストはwindow)。このメソッドの大きな欠点は、オブジェクトのプロトタイプの一部にすることができないため、オブジェクトのすべてのインスタンスが関数の独自のコピーを持つことです(プライベート変数にアクセスするためme)。

私はそれを処理するために可能な他の方法があると確信しています。そもそもなぜオブジェクトになる必要があるのか​​説明していpersonなかったので、目標を達成するための最良のパターンがわかりません。複数の人物のオブジェクトは必要ないかもしれませんが、それでも必要な場合があります。進行中の文脈と目的についてのより広い理解は、私があなたをよりよく導くのに役立つでしょう。

注:を持たないjavascript実装を「シム」するbindために、関数でバインドをより簡単に使用できます。これを行うことができます(ネイティブ実装とはいくつかの違いがあることに注意してください。詳細については、上記と同じバインドリンクを参照してください)。

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}
于 2012-11-26T18:54:03.917 に答える
1

これは避けられません。そのように関数を渡すと、実行コンテキストが失われます。

解決策は、「this」変数をキャプチャすることです。

var self = this;
setTimeout(function() { self.speak(); }, 1000);
于 2012-11-26T18:23:33.487 に答える
0

ErikE の 4 番目の提案の多少異なるバージョンは、ほぼ同じ仕事をしますが、より単純なコードだと思います。

function Person() {
    this.speech = null;
}
Person.prototype.tryToSpeak = function () {
    this.speech = "hello";
    var person = this;
    window.setTimeout(function() {person.speak();}, 1000);
};
Person.prototype.speak = function () {
    console.log(this.speech);
};


var person = new Person();
person.tryToSpeak();

Erik が言ったように、複数のオブジェクトが必要Personかどうかは明確ではありませんが、複数のオブジェクトが必要な場合は、この種の手法が最も簡単かもしれません。


更新(ErikE のコメントに基づく): このバージョンでは、 に名前が追加され、 がパラメーターとしてPerson渡さspeechれて、誰が何を言っているのかが明確になります。基本的な構造はそのままです。

function Person(name) {
    this.name = name;
}
Person.prototype.tryToSpeak = function (speech) {
    var person = this;
    window.setTimeout(function() {person.speak(speech);}, 1000);
};
Person.prototype.speak = function (speech) {
    console.log(this.name + " says, '" + speech + "'.");
};

var lincoln = new Person("Abraham Lincoln");
var churchill = new Person("Winston Churchill");
lincoln.tryToSpeak("Four score and seven years ago...");
churchill.tryToSpeak("Never, never, never give up.");

要点は、person変数 intryToSpeakはクロージャーに格納されたローカル変数であり、シングルトンへの参照ではないということです。

于 2012-11-26T20:27:17.650 に答える
-2

なぜマイナスになったのかわからない。これは典型的なスコープ/参照エラーです。ちょっと考えてみてください、これは何ですか?答え:ナッツ。

したがって、次のように、オブジェクトを直接参照できます。

 // prints out Hello
 console.log (Person.speach);

または、次のように、それを話す属性として渡します。

self.setTimeout (this.speak(this), 1000);

両方のケースで Jsfiddle :

http://jsfiddle.net/eukQH/

于 2012-11-26T18:28:29.717 に答える