简体   繁体   English

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

[英]Javascript 'this' value changing, but can't figure out why

I'm a total Javascript newb, and I'm trying to wrap my head around OLN. 我是一个完整的Javascript newb,我正试图围绕OLN。 What I'm encountering is that, when calling an object method from another method on the same object, the value of local value of 'this' in the called method is changing. 我遇到的是,当从同一个对象上的另一个方法调用一个对象方法时,被调用方法中'this'的本地值的值正在改变。 Here's my code: 这是我的代码:

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 */
    }
};

So, I call generator.generateForLevelSkillAndCount(0, 4, 20) and it works properly, calling generate_0_4(count) . 所以,我调用generator.generateForLevelSkillAndCount(0, 4, 20)并且它正常工作,调用generate_0_4(count) However, this is where it fails, with Chrome's Javascript console telling me "Uncaught TypeError: Object [object DOMWindow] has no method 'generate_generic_dots'." 然而,这是失败的地方,Chrome的Javascript控制台告诉我“未捕获的TypeError:对象[对象DOMWindow]没有方法'generate_generic_dots'。”

I know enough to know that the problem is that the value of this in generate_0_4 is a DOMWindow object, rather than generator (which is what this is pointing to in generateForSkillLevelAndCount but I can't figure out why that would possibly be happening. 我非常清楚,知道问题是值thisgenerate_0_4是DOMWindow对象,而不是生成器(这是this是指着generateForSkillLevelAndCount ,但我想不出为什么这样做可能发生。

Update: I updated the example code per CMS's suggestion to get rid of eval , but the same error is being returned, so it's not just an eval bug. 更新:我根据CMS的建议更新了示例代码以摆脱eval ,但是返回了相同的错误,因此它不仅仅是一个eval错误。

In JavaScript, the context object ( this ) is set to the "global object" ( window , in browsers) unless the method is accessed as an object property. 在JavaScript中,上下文对象( this )设置为“全局对象”( window ,在浏览器中),除非该方法作为对象属性进行访问。 Therefore: 因此:

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

Note that all three function calls are to the exact same function ! 请注意,所有三个函数调用都是完全相同的函数

So, in your code, you're assigning the desired method to functionCall and calling it directly, which causes the function to use window as its context object. 因此,在您的代码中,您将所需的方法分配给functionCall并直接调用它,这会导致该函数使用window作为其上下文对象。 There are two ways around this: access the method as an object property or use .call() or .apply() : 有解决此两种方法:访问该方法作为对象属性或使用.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);
}

First of all, I would encourage you to avoid eval where you don't need it, for example, in your fist function: 首先,我鼓励你在不需要的地方避免使用eval,例如,在你的拳头功能中:

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

You can use the bracket notation property accessor instead eval , it's unnecessary in this case. 您可以使用括号表示法属性访问器而不是eval ,在这种情况下不需要。

Now, I guess you are trying your code on the Chrome's Console, and eval is failing because the console has a bug, when eval is invoked from a FunctionExpression (such as generateForLevelSkillAndCount ), the evaluated code uses the Global context for its Variable Environment and Lexical Environment. 现在,我猜您正在Chrome控制台上尝试代码,并且eval失败,因为控制台有错误,当从FunctionExpression调用eval (例如generateForLevelSkillAndCount )时,评估的代码使用全局上下文作为其变量环境和词汇环境。

See this answer for more information on this bug. 有关此错误的更多信息,请参阅此答案

Edit : After re-reading your code, the problem happens because you lose the base object reference when you assign the function to your functionCall variable, you can either: 编辑 :重新读取代码后,问题发生是因为在将函数分配给functionCall变量时丢失了基础对象引用,您可以:

Invoke the function directly, without using that variable: 直接调用函数,而不使用该变量:

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

Or still use your variable, but persist the this value : 或者仍然使用您的变量,但保持this

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

More info on this ... 关于this更多信息......

You can control the execution context of the method call by using call() : 您可以使用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 */
  }
};

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

When you call generate_0_4 dynamically (using an implicit to_string() ) it is returned to generateForLevelSkillAndCount as an ad-hoc function. 当您动态调用generate_0_4 (使用隐式to_string() )时,它将作为ad-hoc函数返回到generateForLevelSkillAndCount Because it's in Window scope rather than Object scope, it can't reference this , and the internal call fails because this doesn't exist in that context. 因为它是在Window范围,而不是Object范围,但不能引用this ,而且由于内部调用失败, this并不在这方面存在的。

Here's how to see what's happening: 以下是如何查看正在发生的事情:

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

With generator.generateForLevelSkillAndCount(0, 4, 1); 使用generator.generateForLevelSkillAndCount(0, 4, 1); you get [object Window] or [object DOMWindow] . 你得到[object Window][object DOMWindow]

With generator.generate_0_4(1); 使用generator.generate_0_4(1); you get what you're expecting (and what works): [object Object] or #<an Object> . 你得到你期望的(以及有效的): [object Object]#<an Object>

This is a feature of Javascript: the value of this will depend on the object from which the function was called, not where it was defined. 这是JavaScript的一个特点:价值this将取决于不在它被定义从中调用该函数的对象上。 (Which makes sense when functions are first-class objects themselves.) this in most other contexts refers to the window object. (这是有意义的,当函数是第一类对象本身。) this在大多数其他情况下指的是窗口对象。

There are two common workarounds, using a wrapper function: 使用包装函数有两种常见的解决方法:

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

or using a closure: 或使用封闭:

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

In your case, though, simply replacing 但在你的情况下,只需更换

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

with

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

would do the trick. 会做的伎俩。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM