Javascript'this'值改变了,但无法弄清楚原因

时间:2010-08-25 05:06:04

标签: javascript

我是一个完整的Javascript newb,我正试图围绕OLN。我遇到的是,当从同一个对象上的另一个方法调用一个对象方法时,被调用方法中'this'的本地值的值正在改变。这是我的代码:

var generator = {
    generateForLevelSkillAndCount : function(level, skill, count) {
        var functionCall = this['generate_' + level + '_' + skill];
        return functionCall(count);
    },
    generate_0_4 : function(count) {
        return this.generate_generic_dots(count, 3);
    },
    generate_generic_dots : function(count, maxDots) {
        /* do cool stuff and return it */
    }
};

所以,我致电generator.generateForLevelSkillAndCount(0, 4, 20)并且它正常工作,呼叫generate_0_4(count)。然而,这是失败的地方,Chrome的Javascript控制台告诉我“未捕获的TypeError:对象[对象DOMWindow]没有方法'generate_generic_dots'。”

我知道问题是thisgenerate_0_4的值是DOMWindow对象,而不是生成器(this指向generateForSkillLevelAndCount的值eval 1}}但我无法弄清楚为什么会发生这种情况。

更新:我根据CMS的建议更新了示例代码以摆脱eval,但是返回了同样的错误,因此它不仅仅是{{1}}错误。

6 个答案:

答案 0 :(得分:8)

在JavaScript中,上下文对象(this)在浏览器中设置为“全局对象”(window,除非该方法作为对象属性进行访问。因此:

var foo = { bar: function() { alert(this.baz); }, baz: 5 };
var bar = foo.bar;
var baz = 3;

foo.bar();    // alerts 5, from foo
foo["bar"](); // alerts 5, from foo
bar();        // alerts 3, from the global object

请注意,所有三个函数调用都是完全相同的函数

因此,在您的代码中,您将所需的方法分配给functionCall并直接调用它,这会导致函数使用window作为其上下文对象。有两种方法:将方法作为对象属性访问,或使用.call().apply()

function generateForLevelSkillAndCount1(level, skill, count) {
    return this['generate_' + level + '_' + skill](count);
}

function generateForLevelSkillAndCount2(level, skill, count) {
    var functionCall = this['generate_' + level + '_' + skill];
    return functionCall.call(this, count);
}

答案 1 :(得分:3)

首先,我鼓励你避免在你不需要它的地方,例如,在你的拳头功能中:

//...
generateForLevelSkillAndCount : function(level, skill, count) {
    var functionCall = this['generate_' + level + '_' + skill];
    return functionCall(count);
},
//...

您可以使用括号表示法属性访问器代替eval,在这种情况下不需要。

现在,我猜你正在Chrome控制台上试用你的代码,eval失败,因为当从FunctionExpression调用eval时,控制台有错误(例如generateForLevelSkillAndCount }),评估的代码使用全局上下文作为其变量环境和词汇环境。

有关此错误的详细信息,请参阅this answer

编辑:重新读取代码后,问题就出现了,因为在将函数分配给functionCall变量时丢失了基础对象引用,您可以:

直接调用函数,而不使用该变量:

//...
generateForLevelSkillAndCount : function(level, skill, count) {
    this['generate_' + level + '_' + skill](count);
},
//...

或仍然使用您的变量,但persist the this value

//...
generateForLevelSkillAndCount : function(level, skill, count) {
    var functionCall = this['generate_' + level + '_' + skill];
    return functionCall.call(this, count);
},
//...

更多信息on this...

答案 2 :(得分:1)

您可以使用call()

来控制方法调用的执行上下文
var generator = {
  generateForLevelSkillAndCount : function(level, skill, count) {
    return this['generate_' + level + '_' + skill].call(this, count);
  },
  generate_0_4 : function(count) {
    return this.generate_generic_dots.call(this, count, 3);
  },
  generate_generic_dots : function(count, maxDots) {
    /* do cool stuff and return it */
  }
};

答案 3 :(得分:0)

我和你一样困惑,但你是否考虑过如果你明确地调用generate_0_4而不是通过eval()解析会发生什么?

答案 4 :(得分:0)

当您动态调用generate_0_4时(使用隐式to_string()),它将作为ad-hoc函数返回到generateForLevelSkillAndCount。由于它位于Window范围而不是Object范围内,因此无法引用this,内部调用失败,因为在该上下文中不存在this

以下是如何查看正在发生的事情:

generate_0_4 : function(count) {
    throw(this);
    return this.generate_generic_dots(count, 3);
},

使用generator.generateForLevelSkillAndCount(0, 4, 1);,您可以获得[object Window][object DOMWindow]

使用generator.generate_0_4(1);,您可以得到您期望的内容(以及有效的内容):[object Object]#<an Object>

答案 5 :(得分:0)

这是Javascript的一个特性:this的值取决于调用函数的对象,而不是它定义的位置。 (当函数本身就是第一类对象时,这是有道理的。)在大多数其他上下文中,this指的是窗口对象。

使用包装函数有两种常见的解决方法:

function bind(func, obj) {
    return function() {
        func.apply(obj, arguments);
    }
}

或使用闭包:

var self = this;
function generate_blah() {
    // use self instead of this here
}

在你的情况下,只需更换

var functionCall = this['generate_' + level + '_' + skill];
return functionCall(count);

this['generate_' + level + '_' + skill](count);

会做到这一点。