Подтвердить что ты не робот

Почему Javascript не корректно связывает мое точечное выражение?

Мне интересно, привязываются ли методы dot-abstraction (например, dog.bark) во время выполнения или во время компиляции. Мой вопрос касается следующего кода, который вызывает ошибку:

(true ? ''.toLowerCase : ''.toUpperCase)()
4b9b3361

Ответ 1

Это довольно просто, как только вы узнаете, как методы работают в 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 правильно настроен к моменту выполнения функции.

Надеюсь, что это поможет.

Ответ 2

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

Ответ 3

Поскольку эти методы применяются в контексте this, а в вашем примере this - undefined

Один из способов переопределить эту переменную с помощью метода bind:

(true ? ''.toLowerCase : ''.toUpperCase).bind('Hello')();

это вернет hello

Ответ 4

Потому что, когда вы выполняете (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`

Ответ 5

В первом примере функция 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())