Почему * это * не * это *? - PullRequest
       37

Почему * это * не * это *?

6 голосов
/ 12 апреля 2011

Я только что написал этот фрагмент кода, чтобы представить эту ошибку, которая убивает меня (Grrr!)

Интересно, почему, когда я получаю ошибку: метод не определен Я проверил в Safari, и эта переменная внутри метода parserDidStart () не относится к типу EpisodeController, а имеет тип EpisodeFeedParser. Почему это так?

<html>
<head>
<script type="text/javascript">
var EpisodeFeedParser = function(url){
    this.url = url;
    this.didStartCallback = null;
};
EpisodeFeedParser.prototype.parse = function(doc){
    this.didStartCallback(this);
};

var EpisodeController = function(){
    this.episodes = new Array();
    this.parser = null; //lazy
};
EpisodeController.prototype.parserDidStart = function(parser){
    console.log("here *this* is not of type EpisodeController but it is EpisodeFeedParser Why?");
    this.testEpi(); //**********ERROR HERE!***********
};
EpisodeController.prototype.fetchEpisodes = function(urlString){
    if(urlString !== undefined){
        if(parser === undefined){
            var parser = new EpisodeFeedParser(urlString);
            parser.didStartCallback = this.parserDidStart;
            this.parser = parser;
        }
        this.parser.parse();
    }
};
EpisodeController.prototype.testEpi = function(){
console.log("it worked!");
};

function testEpisode(){
    var controller = new EpisodeController();
    controller.fetchEpisodes("myurl");
}
</script>
</head>
<body>
<button type="button" onclick="testEpisode()">press me</button>
</body>
</html> 

Ответы [ 5 ]

9 голосов
/ 17 апреля 2011

это часто неправильно понимаемый аспект Javascript.(и под «этим» я подразумеваю this)

Вы можете думать о this как о другом параметре, который незаметно передается вашим функциям.Поэтому, когда вы пишете такую ​​функцию, как

function add (a,b) {
   return a+b;
}

, вы действительно пишете

function add(this, a, b) {
    return a+b;
}

Это, вероятно, очевидно, а то, что не очевидно, точно , что передается и называется как "это".Правила для этого следующие.Существует четыре способа вызова функции, и каждый из них связывает свою вещь с this.

классический вызов функции

add(a,b);

в классическом вызове функции this привязан к глобальному объекту.Это правило теперь повсеместно воспринимается как ошибка, и в будущих версиях, вероятно, будет установлено значение null.

вызов конструктора

new add(a,b);

в вызове конструктора, this устанавливается равнымсвежий новый объект, внутренний (и недоступный) указатель прототипа которого установлен на add.prototype

вызов метода

someobject.add(a,b);

в вызове метода, this получает значение someobject.не имеет значения, где вы изначально определили add, находился ли он внутри конструктора, части прототипа конкретного объекта или чего-то еще.Если вы вызываете функцию таким образом, this устанавливается для любого объекта, для которого вы ее вызывали.Это правило, с которым вы сталкиваетесь.

вызов / применение вызова

 add.call(someobject,a,b);

в вызове / применении вызова, this устанавливается равным тому, что вы передаете в видимый теперь.Первый параметр метода вызова.

, что происходит в вашем коде:

 this.parser.didStartCallback = this.parserDidStart;

, пока вы пишете parserDidStart, ожидая, что его this будет EpisodeController при вызове методаэто ... что на самом деле происходит, так это то, что вы теперь меняете this с EpisodeController на this.parser.Это не происходит в этой конкретной строке кода.Переключение физически не происходит до тех пор, пока здесь:

this.didStartCallback(this);

, где this в этом случае - EpisodeParser, и к тому времени, когда этот код выполняется, вы присвоили parserDidStart имя didStartCallback.Когда вы вызываете didStartCallback здесь, с этим кодом вы, по сути, говорите ...

didStartCallback.call (this, this);

, говоря this.didStartCallback (), выустановив для this значение .. ну .. this при его вызове.

Вы должны знать о функции, называемой bind, которая объясняется здесь: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

Bind создает новую функцию из существующей функции, чья this фиксирована (привязана) к любому объекту, который вы явно передаете.

2 голосов
/ 12 апреля 2011

Попробуйте:

var that = this;
parser.didStartCallback = function(parser) {
  that.parserDidStart(parser);
};

Это создает замыкание, которое передается в правильной области действия parserDidStart. В настоящее время, когда вы вызываете this.parser.parse(), он передает EpisodeFeedParser в качестве контекста, откуда он вызывается. Это одна из причуд в области видимости в JavaScript, и она может быть довольно неприятной.

2 голосов
/ 12 апреля 2011

this передано didStartCallback в

EpisodeFeedParser.prototype.parse = function(doc){
    this.didStartCallback(this);

имеет тип EpisodeFeedParser

и в EpisodeController.prototype.fetchEpisodes вы влияете EpisodeController.parserDidStart на parser.didStartCallback:

parser.didStartCallback = this.parserDidStart;

так что this.didStartCallback(this); на самом деле EpisodeController.parserDidStart(this)

, и в начале мы увидели, что этот последний this был типа EpisodeFeedParser.

QED

1 голос
/ 12 апреля 2011

Проблема в том, что didStartCallBack вызывается (и в контексте) «this», в момент выполнения, когда «this» ссылается на EpisodeFeedParser. Я исправил это с помощью .call (), хотя я не уверен, зачем вам нужно писать код на этом окольном пути, я уверен, что должна быть причина.

Важное изменение:

parse: function(episodeController){
  this.didStartCallback.call(episodeController, this);
}//parse

Полный код:

<html>
<head>
<script type="text/javascript">
    //An interesting context problem...
    //Why is it of type EpisodeFeedParser?

    // ---- EpisodeFeedParser
    var EpisodeFeedParser = function(url){
        this.url = url;
    };  
    EpisodeFeedParser.prototype = {
        url:null,
        didStartCallback:null,
        parse: function(episodeController){
            this.didStartCallback.call(episodeController, this);
        }//parse
    }//prototype


    // ---- EpisodeController
    var EpisodeController = function(){
        this.episodes = new Array();
        this.parser = null; //lazy
    };

    EpisodeController.prototype = { 
        parserDidStart: function(parser){
            console.log("here *this* is not of type EpisodeController but it is EpisodeFeedParser Why?");
            debugger;
            this.testEpi(); //**********ERROR HERE!***********
        },

        fetchEpisodes: function(urlString){
            if(urlString !== undefined){
                if(this.parser === null){
                    this.parser = new EpisodeFeedParser(urlString);
                    this.parser.didStartCallback = this.parserDidStart;
                }//if
                this.parser.parse(this);
            }//if
        },//fetchEpisodes

        testEpi: function(){
            console.log("it worked!");
        }
    }//EpisodeController.prototype


    // ---- Global Stuff
    function testEpisode(){
        var controller = new EpisodeController();
        controller.fetchEpisodes("myurl");
    }
</script>
</head>

<body>
<button type="button" onclick="testEpisode()">press me</button>
</body>
</html> 
0 голосов
/ 12 апреля 2011
EpisodeFeedParser.prototype.parse = function(doc){
    this.didStartCallback(this);
};

Я не понимаю, почему вы ожидаете, что this будет чем-то отличным от EpisodeFeedParser.

...