2010-08-25 4 views
3

Я полный javascript newb, и я пытаюсь обмотать голову вокруг OLN. Я сталкиваюсь с тем, что при вызове метода объекта из другого метода на том же объекте значение локального значения «this» в вызываемом методе изменяется. Вот мой код:Javascript «это» значение меняется, но не может понять, почему

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). Тем не менее, это то, где он терпит неудачу, поскольку консоль Javascript от Chrome сообщает мне: «Uncaught TypeError: Object [object DOMWindow] не имеет метода« generate_generic_dots »».

я знаю достаточно, чтобы знать, что проблема заключается в том, что значение this в generate_0_4 является объектом DOMWindow, а не генератор (что this указывает в generateForSkillLevelAndCount, но я не могу понять, почему это, возможно, будет происходит

Update:.. Я обновил пример кода на предложение CMS, чтобы избавиться от eval, но та же ошибка возвращается, так что это не просто eval ошибка

+0

Ahhh, радости динамического обзора! – leppie

ответ

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); 
} 
+0

Это работает для меня в Firefox и Chrome. – cbednarski

+0

Отлично, это именно то, что мне нужно. Точная работа синтаксиса объектов JS для меня все еще остается загадкой, но то, что вы предложили, отлично работает. Благодаря! –

+0

Так что foo.bar() эквивалентно bar.call (foo)? –

3

Прежде всего, я бы поощряйте вас избегать eval, где вы «Т это нужно, например, в кулаке функции:

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

Вы можете использовать кронштейн обозначения свойств аксессор вместо eval, это лишнее в этом случае.

Теперь, я думаю, вы пытаетесь свой код на консоли в Chrome, и eval терпит неудачу, потому что консоль имеет ошибку, когда eval вызывается из ВыраженияФункции (например, generateForLevelSkillAndCount), оцененный код использует глобальный контекст его переменная среда и лексическая среда.

См. this answer для получения дополнительной информации об этой ошибке.

Edit: После повторного чтения коды, проблема происходит потому, что вы теряете ссылку базового объекта при назначении функции для вашей 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...

+0

Спасибо за информацию. Я не понимал, что могу использовать эти обозначения (хотя это имеет смысл, учитывая природу объектов Javascript). К сожалению, такая же ошибка после обновления кода (я также обновлю пример кода в вопросе). –

+0

Если использование ассемблера нотации для обозначения нот не работает одинаково, существует ли другой способ выполнения динамических вызовов функций? –

+0

@John Biesnecker Да, но вам они действительно нужны здесь? Похоже, что вы можете легко использовать обычные функции и/или таблицу поиска вместо жесткого кодирования '3' в' generate_0_4() '- без большого количества исправленного кода. – cbednarski

0

Я так же озадачен, как вы, но вы рассмотрели проверить, что происходит, если вы звоните generate_0_4 в явном виде, а не разбор его через eval()?

+0

Он работает, когда я вызываю 'generate_0_4' напрямую. Тем не менее, кубики после избавления от 'eval' за ответ CMS. –

0

Когда вы вызываете generate_0_4 динамически (используя неявный to_string()), он возвращается в 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>.

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

Спасибо за ответ, Дейв. Извините, я добрался до Бена выше вас, они оба работают. :) –

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); 

сделал бы трюк.

Смежные вопросы