Разница в привязке вызовов функций?

#javascript #binding #prototype

#javascript #привязка #прототип

Вопрос:

У меня есть объект «me» с определенными для него функциями.

 me.prototype.f = function(args){
    console.log("f called");
};
  

Мне нужно предоставить f в качестве обратного вызова в асинхронной функции.

 me.prototype.operation = function (){
    var self = this;
    var client; // an io client
    //client.on("data", self.f);                --a
    //client.on("data", self.f.bind(self))      --b
}
  

Есть ли какая-либо разница между a amp; b выше в этом случае и есть ли какая-либо ситуация, когда a) может завершиться неудачей?

В каких сценариях мы получаем проблему без привязки? Как правило, это когда значения могут меняться в зависимости от контекста. (например. закрытие с использованием значения индекса цикла)
Итак, каковы могут быть разные сценарии, в которых значения могут действовать по-разному.


Из ответа я пришел к следующим выводам. Пожалуйста, подтвердите то же самое :

a) Область видимости будет меняться по мере передачи функции в качестве ссылки и будет получать локальную ссылку, если контекст не привязан с помощью bind, call или apply .

б) Особенно в обратных вызовах / асинхронных функциях, если контекст изменен (например, setTimeout, контекст изменится, следовательно, значение self изменится во время вызова.)

c) Это не имеет никакого отношения к закрытию, поскольку при закрытии лексическая среда проверяется на предмет оценки значений, однако здесь оценивается контекст во время выполнения

Комментарии:

1.У вас возникнут проблемы, если вы используете this в своей f функции. Вот почему вы bind this должны функционировать.

2. Простой пример var a = {f: function(){console.log(this)}}; a.f(); var f = a.f; f(); . вы потеряли свое this во втором выполнении так же, как потеряли его с self.f

3. @Maxx Если я вызову функцию as client.on("data", self.f); , в этом случае также this будет сохранен мой, поскольку контекст self будет связан с вызовом функции (поскольку это функция-член). Тогда есть ли какая-либо разница в a) и b)

4. ваш self var абсолютно бесполезен в вашем примере, он ничего не делает. в a) вы передаете клиенту некоторую функцию f , в момент, когда ваши client вызовы f f ничего не знают о ее контексте. в b) вы привязываете свой контекст к f

5. self здесь имеется в виду экземпляр me и, следовательно, предоставление связанных с ним функций. Прокомментированы два случая, которые я хочу обсудить.

Ответ №1:

self.f является ссылкой на f функцию.

self.f.bind(self) это новая функция, которая при вызове вызывается f с self помощью as this .

Есть ли какая-либо разница между a amp; b выше в этом случае и есть ли какая-либо ситуация, когда a) может выйти из строя?

ДА. Поведение зависит от того, как вы выполняете вызов:

 function me() {}
me.prototype.f = function() {
  console.log(this);
};
me.prototype.operation = function() {
  client.on("this.f", this.f);
  client.on("this.f.bind(this)", this.f.bind(this));
};

var client = {
  on: function(whatever, callback) {
    var self = this;
    setTimeout(function() {
      console.log("=== "   whatever   " ===");
      self.f = callback;

      // `this` not specified
      //  `this.f` logs the global object, or `undefined` in strict mode
      //  `this.f.bind(this)` logs `me` instance
      callback();

      // `this` belongs to current function (passed to setTimeout)
      //  `this.f` logs the global object, or `undefined` in strict mode
      //  `this.f.bind(this)` logs `me` instance
      callback.call(this);

      // `self` is a reference to the client object
      //  `this.f` logs `client`
      //  `this.f.bind(this)` logs `me` instance
      callback.call(self);

      // `this` is determined by the calling object (client again)
      //  `this.f` logs `client`
      //  `this.f.bind(this)` logs `me` instance
      self.f();

    }, 1000);
  }
};


(new me()).operation();  

Обновить

Я решил добавить больше к ответу, поскольку вы добавили больше к вопросу. Мне жаль, что это получилось так долго.

Как this работает

Значение this определяется при вызове функции, а не при определении функции.

  • При вызове функции, которая не является свойством объекта this , будет находиться undefined в строгом режиме или ссылаться на глобальный объект в нестрогом режиме.

     function f1() {
      console.log(this === window);
    }
    function f2() {
      'use strict';
      console.log(this === undefined);
    }
    
    f1();  // true
    f2();  // true
      
  • При вызове функции, которая является свойством объекта, this
    будет ссылаться на этот объект.

     var object = {
      f: function() {
        console.log(this === object);
      }
    };
    object.f();   // true
      
  • При вызове функции с new префиксом this будет ссылаться на
    вновь созданный объект.

     var backup;
    function Example() {
      backup = this;
      this.value = 0;
    }
    
    var example = new Example();
    console.log(example === backup);  // true
    console.log(example.value);       // 0
      
  • При вызове функции, которая не является свойством объекта, но
    является частью цепочки прототипов объекта, this будет ссылаться на
    вызывающий объект.

     function Example() {}
    Example.prototype.f = function() {
      console.log(this === example);
    };
    
    var example = new Example();
    example.f();   // true
      

Изменение this

Упомянутое поведение часто не то, что мы хотим. Рассмотрим следующую ситуацию.

     function Counter() {
      this.count;
      this.limit;
      this.interval;
    }
    Counter.prototype.countTo = function(number) {
      this.count = 1;
      this.limit = number;
      this.interval = setInterval(update, 1000);

      function update() {
        console.log(this.count);
        if (  this.count > this.limit)
          clearInterval(this.interval);
      }
    };

    var counter = new Counter();
    counter.countTo(5);   // undefined, NaN, NaN, NaN, NaN, NaN...  

this получает свое значение после вызова функции, а не после ее определения. Интервальная функция будет вызвана позже и this будет ссылаться на глобальный объект. В большинстве случаев это не то, что нужно.

Изменение this неявно

Исторический способ решения setInterval проблемы — назначить ссылку this на переменную, видимую проблемной функцией, и использовать вместо нее эту переменную.

 function Counter() {}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;

  var self = this;

  function update() {
    console.log(self.count);
    if (  self.count > self.limit)
      clearInterval(self.interval);
  }
  this.interval = setInterval(update, 1000);
};

var counter = new Counter();
counter.countTo(5);  

То же самое с использованием замыкания, чтобы предотвратить загрязнение области действия функции.

 function Counter() {}

Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;

  this.interval = setInterval((function(self) {
    return function() {
      console.log(self.count);
      if (  self.count > self.limit)
        clearInterval(self.interval);
    };
  }(this)), 1000);
};

var counter = new Counter();
counter.countTo(5);  


Создание новой update функции всякий Counter раз, когда создается a, не самый эффективный способ сделать это. Допустим, мы хотим переместить его в прототип.

 function Counter() {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(this.update, 1000);
};
Counter.prototype.update = function() {
  console.log(this.count);
  if (  this.count > this.limit)
    clearInterval(this.interval);
};

var counter = new Counter();
counter.countTo(5); // undefined, NaN, NaN, NaN, NaN, NaN...  

Теперь проблема заключается в том update , чтобы узнать о self s, поскольку они больше не определены в одной области видимости, и update для всех s существует только одна функция Counter .

Мы можем создать функцию-посредник, которая будет заключать значение (копировать ссылку) this и использовать его для выполнения вызова.

 function Counter() {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;

  var update = (function(self) {
    return function() {
      self.update();
    };
  }(this));

  this.interval = setInterval(update, 1000);
};
Counter.prototype.update = function() {
  console.log(this.count);
  if (  this.count > this.limit)
    clearInterval(this.interval);
};

var counter = new Counter();
counter.countTo(5);  

Мы можем использовать функции со стрелками, которые связываются this лексически, то есть их значение определяется при определении функции, независимо от того, как и когда она вызывается.

 function Counter() {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(() => this.update(), 1000); // neat.
};
Counter.prototype.update = function() {
  console.log(this.count);
  if (  this.count > this.limit)
    clearInterval(this.interval);
};

var counter = new Counter();
counter.countTo(5);  

Изменение this явно

Допустим, наша update функция определена в другом месте, и мы хотим использовать ее вместо нашей функции-прототипа.

 function Counter(to) {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(update, 1000);
};

function update() {
  console.log(this.count);
  if (  this.count > this.limit)
    clearInterval(this.interval);
}

var counter = new Counter();
counter.countTo(5); // undefined, NaN, NaN, NaN, NaN, NaN...  

Используя ранее упомянутые подходы, мы могли бы прикрепить ссылку к update объекту или его прототипу и использовать, скажем, функцию со стрелкой для привязки this . Если нам не нравятся такого рода накладные расходы, в нашем распоряжении есть три мощные функции для явной настройки this :

Вызов bind функции приводит к созданию новой функции, для которой this установлен первый параметр.

 function Counter(to) {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(update.bind(this), 1000); // not bad.
};

function update() {
  console.log(this.count);
  if (  this.count > this.limit)
    clearInterval(this.interval);
}
var counter = new Counter();
counter.countTo(5);  

Использование call или apply для функции вызовет вызов с this установленным первым параметром.

 function Counter(to) {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  var self = this;
  this.interval = setInterval(function() {
    update.call(self);
  }, 1000);
};

function update() {
  console.log(this.count);
  if (  this.count > this.limit)
    clearInterval(this.interval);
}

var counter = new Counter();
counter.countTo(5);  

Комментарии:

1. Спасибо, я много экспериментировал с вышеизложенным. Возникли следующие наблюдения. a) Область видимости будет меняться по мере передачи функции в качестве ссылки и будет получать локальную ссылку, если контекст не привязан с помощью bind, call или apply . б) Особенно в обратных вызовах / асинхронных функциях, если контекст изменен (например, setTimeout , контекст изменится, следовательно, значение self изменится во время вызова.) в) Это не имеет никакого отношения к закрытию, поскольку при закрытии лексическая среда проверяется на предмет оценки значений, однако здесьоценивается контекст во время выполнения

2. Ваши наблюдения верны. Дело в том, что this определяется при вызове функции , а не при определении функции .

3. Во многих ситуациях это не то, что вы хотите, и чтобы обойти это, вы явно устанавливаете this значение, используя bind , call или apply , или простую функцию, используя сохраненную ссылку this из ее среды.

4. Одна вещь, которую я забыл упомянуть, — это функции со стрелками ES6 , которые связываются this лексически, то есть определяются определением функции .

5. Интересны последние два пункта. В simple function using the stored reference of this — вы имеете в виду вызов функции с сохраненной ссылкой в качестве одного из параметров.