为什么*这个*不是*这*?

时间:2011-04-12 17:46:41

标签: javascript scope this

我刚刚写了这段代码来代表这个让我失望的错误(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 个答案:

答案 0 :(得分:9)

这是Javascript经常被误解的方面。 (以及“this”,我的意思是this

您可以将this视为另一个无形地传递给您的函数的参数。所以当你写一个像

这样的函数时
function add (a,b) {
   return a+b;
}

你真的在写

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

这可能是显而易见的,不明显的是传入的,并命名为“this”。其规则如下。有四种方法可以调用一个函数,它们各自绑定一个不同的东西到this

经典函数调用

add(a,b);

在经典函数调用中,this绑定到全局对象。该规则现在普遍被视为一个错误,并且可能在将来的版本中设置为null。

构造函数调用

new add(a,b);

在构造函数调用中,this设置为一个新的新对象,其内部(和不可访问的)原型指针设置为add.prototype

方法调用

someobject.add(a,b);

在方法调用中,this设置为someobject。无论你最初定义添加的位置,它是在构造函数内部,特定对象的原型的一部分,还是其他什么都没关系。如果以这种方式调用函数,this将设置为您调用它的任何对象。这是你遇到的规则。

调用/应用调用

 add.call(someobject,a,b);

在调用/应用调用中,this设置为传入调用方法现在可见的第一个参数的任何内容。

你的代码中会发生什么:

 this.parser.didStartCallback = this.parserDidStart;

当你编写parserDidStart时,期望当你的方法调用它时它的this将是一个EpisodeController ......实际上你正在将它的this从EpisodeController更改为此。解析器。 特定代码行中没有发生这种情况。直到此处才开始实际切换:

this.didStartCallback(this);

此实例中的this是EpisodeParser,并且在运行此代码时,您已将parserDidStart命名为didStartCallback。当你在这里调用didStartCallback时,使用这段代码,你实际上是在说......

didStartCallback.call(这一点,这一点);

通过说这个.didStartCallback(),当你调用它时,你将它的this设置为...... this

你应该知道一个名为bind的函数,这里解释一下: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind

Bind从现有函数创建一个新函数,其this被固定(绑定)到您明确传入的任何对象。

答案 1 :(得分:2)

this传递给

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

的类型为EpisodeFeedParser

并在EpisodeController.prototype.fetchEpisodesEpisodeController.parserDidStart影响parser.didStartCallback

parser.didStartCallback = this.parserDidStart;

所以this.didStartCallback(this);实际上是EpisodeController.parserDidStart(this)

我们在开头看到最后一个this的类型为EpisodeFeedParser

Q.E.D

答案 2 :(得分:2)

尝试:

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

这将创建一个在正确范围内传递给parserDidStart的闭包。目前,当您致电this.parser.parse()时,它会将EpisodeFeedParser作为上下文传递,就像调用它一样。这是JavaScript中范围的怪癖之一,可能非常令人沮丧。

答案 3 :(得分:1)

问题是“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> 

答案 4 :(得分:0)

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

我不明白为什么你希望this不是EpisodeFeedParser