Мне интересно, привязываются ли методы dot-abstraction (например, dog.bark
) во время выполнения или во время компиляции. Мой вопрос касается следующего кода, который вызывает ошибку:
(true ? ''.toLowerCase : ''.toUpperCase)()
Мне интересно, привязываются ли методы dot-abstraction (например, dog.bark
) во время выполнения или во время компиляции. Мой вопрос касается следующего кода, который вызывает ошибку:
(true ? ''.toLowerCase : ''.toUpperCase)()
Это довольно просто, как только вы узнаете, как методы работают в javascript за кулисами.
toUpperCase
- метод. Это функция, которая работает с определенным объектом... обычно с помощью переменной this
.
Javascript - это прототип языка... означает, что функции, связанные с объектами, являются просто функциями и могут быть скопированы. Существует некоторая работа за кулисами, которая гарантирует, что this
устанавливается правильно, когда вы вызываете метод, но эта работа происходит только тогда, когда вы называете ее как метод... как в форме obj.method()
.
Другими словами: ''.toUpperCase()
гарантирует, что this
устанавливается в строку ''
, когда вы вызываете его.
Когда вы называете это как toUpperCase()
this
не настроено ни на что конкретно (разные среды в этом случае делают разные вещи с this
)
Что ваш код может быть переписан следующим образом:
var function_to_call;
if (true) {
function_to_call = ''.toLowerCase;
} else {
function_to_call = ''.toUpperCase;
}
function_to_call();
Поскольку ваш вызов функции: function_to_call()
не находится в синтаксисе object.method()
, вещь, которая устанавливает this
на правильный объект, не выполняется, а ваш вызов функции выполняется с this
, не установленным на то, что вы хотите.
Как указывали другие люди, вы можете использовать func.call(thing_to_make_this)
или func.apply()
, чтобы прикрепить к нему правильную вещь.
Я считаю гораздо более полезным использовать .bind()
- который, по моему мнению, крайне недоиспользуется. function_name.bind(this_object)
дает вам новую функцию, которая всегда будет привязана к this
:
// assuming function_to_call is set
function_that_works = function_to_call.bind(my_object)
function_that_works(); // equivalent to my_object.function_to_call()
и это означает, что вы можете передать функцию, возвращаемую из bind()
, как и обычную функцию, и она будет работать на нужном объекте. Это особенно полезно при обратных вызовах, поскольку вы можете создать анонимную функцию, связанную с объектом, в котором он был создан:
// this won't work because when this runs, 'this' doesn't mean what you think
setTimeout(function() { this.display_message('success'); }, 2000);
// this will work, because we have given setTimeout a pre-bound function.
setTimeout(function() { this.display_message('success'); }.bind(this), 2000);
TL; DR: вы не можете вызвать метод как функцию и ожидать, что он будет работать, потому что он не знает, что должно быть this
. Если вы хотите использовать эту функцию, вы должны использовать .call()
, .apply()
или .bind()
, чтобы убедиться, что this
правильно настроен к моменту выполнения функции.
Надеюсь, что это поможет.
(true ? ''.toLowerCase : ''.toUpperCase)()
эквивалентно:
String.prototype.toLowerCase.call()
String.prototype.toLowerCase.call(undefined)
Однако
true ? ''.toLowerCase() : ''.toUpperCase()
эквивалентно:
String.prototype.toLowerCase.call('')
В обоих случаях первый аргумент call
преобразуется в объект, к которому будет ссылаться this
in String.prototype.toLowerCase
.
undefined
не может быть преобразован в объект, но пустая строка может:
function logThis () { console.log(this); }
logThis.call('');
Поскольку эти методы применяются в контексте this
, а в вашем примере this
- undefined
Один из способов переопределить эту переменную с помощью метода bind
:
(true ? ''.toLowerCase : ''.toUpperCase).bind('Hello')();
это вернет hello
Потому что, когда вы выполняете (true ? ''.toLowerCase : ''.toUpperCase)()
, вы не вызываете функцию, привязанную к строке. Вы просто вызываете функцию без какого-либо контекста.
Рассмотрим следующий пример:
var obj = {
objname: "objname",
getName: function() {
return this.objname;
}
}
Когда вы вызываете его с помощью obj.getName()
, он правильно возвращает значение, но когда вы делаете что-то вроде этого:
var fn = obj.getName
fn() // returns undefined because `fn` is not bound to `obj`
В первом примере функция toLowerCase
отделяется от ее контекста (пустой объект строки), а затем вызывается. Поскольку вы не привязываете функцию к чему-либо, она имеет undefined
в качестве своего контекста.
Такое поведение существует для повторного использования кода через mix-ins:
var obj1 = {
name: "obj1",
getName: function() { return this.name; }
}
var obj2 = {
name: "obj2",
}
obj2.getName = obj1.getName //now obj2 has the getName method with correct context
console.log(obj2.getName())