Лучшие практики для доступа к закрытым методам в шаблоне раскрывающегося модуля

Почти в каждом учебном пособии по раскрытию шаблонов модулей, которое я видел, будет показан доступ открытого метода к частному методу без использования «method.call(this,...)».

Кажется, что это работает нормально, но если вы выполните трассировку (это) в частном методе, он покажет «Окно». Это похоже на потенциальную ошибку.

Должен ли я использовать call (или apply), или это приведет к ненужной сложности?

plunk: https://plnkr.co/edit/hc3ZPJyeHcT9bLbLaLpX?p=preview

РЕДАКТИРОВАТЬ: Я думаю, что использование «вызова» каким-то образом делает его «более безопасным», поскольку контекст представляет собой объект API, а не глобальное пространство имен.

var Module = (function () {

    var _privateVar = "PRIVATE!";

    var api = {
        publicMethod: publicMethod
    }

    return api;

    function publicMethod() {
        privateMethod();
        privateMethod.call(this);
    };

    function privateMethod(message) {
        console.log(_privateVar, this);
    };


})();

Module.publicMethod();

person Skyler    schedule 14.11.2016    source источник


Ответы (3)


Нет лучшей практики. Обе

function privateFunction(self, message) { // also popular: `that`
    // do something with `self`
}
…
privateFunction(this, …);

а также

function privateMethod(message) {
    // do something with `this`
}
…
privateMethod.call(this, …);

в порядке. Просто придерживайтесь одного стиля и сохраняйте целостность кода. Тем не менее, первый может быть более популярным, потому что многие частные функции на самом деле являются статическими и не нуждаются в экземпляре для работы, поэтому он упрощает использование явных параметров для экземпляра (вместо предположения/надежды, что функция вызывается с ожидаемое значение this).

person Bergi    schedule 14.11.2016
comment
Быть явным кажется лучшим. Тем не менее, я заметил одно небольшое преимущество использования вызова - при использовании отладчика вы можете удобно получить немедленный доступ к геттерам через этот объект - person Skyler; 15.11.2016

Я боролся с этим в течение последних нескольких дней и был счастлив столкнуться с этим вопросом. Однако после некоторых экспериментов ни один из ответов не кажется удовлетворительным хотя бы для одного случая: передача данных между публичной и приватной функциями.

Ниже приведены три примера использования call, bind и простого вызова. Связывание кажется единственным способом сохранить частный метод в локальной области, а также разрешить обмен данными между частными и общедоступными методами. Другое предложение о передаче контекста частному методу все равно оставит частный метод, выполняющийся вне модуля (если я правильно понял).

Интересно, что использование простого вызова (со строгим режимом или без него) позволяет обмениваться данными, даже если контекст частного метода «не определен»/«окно».

Использование «привязки»:

"use strict"; 
var home = (function(){

  var bob = function(){
    console.log("Bob's scope: " + Object.keys(this));  // "Bob's scope: init"
    var bob_says = "Hello";
    var fBound = alice.bind(this);
    var alice_reply = fBound(bob_says);
    console.log("Alice answered: " + alice_reply);     // "Alice answered: Hello Bob"
  };

  var alice = function(bob_greeting){
    var scope = this ? Object.keys(this) : this;
    console.log("Alice's scope: " + scope);            // "Alice's scope: init"
    console.log("Bob said: " + bob_greeting);          // "Bob said: Hello"
    return bob_greeting + " Bob"; 
  };

  return { init : bob };
})();

home.init();

Использование «вызова»:

"use strict"; 
var home = (function(){

  var bob = function(){
    console.log("Bob's scope: " + Object.keys(this));  // "Bob's scope: init"
    var bob_says = "Hello"; 
    var alice_reply = alice.call(this, bob_says);
    console.log("Alice answered: " + alice_reply);     // "Alice answered: undefined Bob"
  };

  var alice = function(self, bob_greeting){
    var scope = this ? Object.keys(this) : this;
    console.log("Alice's scope: " + scope);            // "Alice's scope: init"
    console.log("Bob said: " + bob_greeting);          // "Bob said: undefined"
    return bob_greeting + " Bob"; 
  };

  return { init : bob };
})();

home.init();

Без вызова или привязки:

"use strict";
var home = (function(){

  var bob = function(){
    console.log("Bob's scope: " + Object.keys(this));  // "Bob's scope: init"
    var bob_says = "Hello"; 
    var alice_reply = alice(bob_says);
    console.log("Alice answered: " + alice_reply);     // "Alice answered: Hello Bob"
  };

  var alice = function(bob_greeting){
    var scope = this ? Object.keys(this) : this;
    console.log("Alice's scope: " + scope);            // "Alice's scope: undefined"
    console.log("Bob said: " + bob_greeting);          // "Bob said: Hello"
    return bob_greeting + " Bob"; 
  };

  return { init : bob };
})();

home.init();

Обновление и возможный ответ. Мне было любопытно посмотреть, есть ли у Алисы доступ к закрытым переменным в «доме», когда Алиса вызывается без «вызова» или «привязки», и ее контекст не определен (или окно). Да, поэтому мой предварительный ответ на ОП (и на себя) состоит в том, чтобы просто вызвать приватную функцию в обычном режиме: без «привязки», без «вызова» и без необходимости передавать в контексте, но «использовать строгий», если это вас беспокоит чтобы увидеть контекст окна, всплывающий при запуске частной функции. Похоже, что в «окне» не создается новое свойство, и нет конфликтов, если в окне есть объект с тем же именем, что и приватная функция (независимо от того, включен или выключен строгий режим). Если и есть подводные камни, то я их еще не встречал.

"use strict";

var alice = "Hi, I'm global's Alice"; 

var home = (function(){

  var bob = function(){
    console.log("Bob's scope: " + Object.keys(this)); // "Bob's scope: init" 
    var bob_says = "Hello";
    var alice_reply = alice(bob_says);
    console.log("Alice answered: " + alice_reply);    // "Alice answered: Hello Bob. I'm in the garage."
  };

  var alice_location = "in the garage.";

  var alice = function(bob_greeting){
    var scope = this ? Object.keys(this) : this;
    console.log("Alice's scope: " + scope);           // "Alice's scope: undefined"
    console.log("Bob said: " + bob_greeting);         // "Bob said: Hello"
    return bob_greeting + " Bob. I'm " + alice_location; 
  };

  return { init : bob };

})();

home.init();

console.log(alice);
person Walt    schedule 04.02.2017

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

--

Вы можете попробовать исправить это следующим образом (создав "новый" объект):

const Module = (function () {
    var _privateVar = "PRIVATE!";

    const Api = function () {

    };

    function publicMethod() {
        privateMethod();
        privateMethod.call(this);
    };

    function privateMethod(message) {
        console.log(_privateVar, this);
    };

    Api.protoype.publicMethod = publicMethod;

    return Api;
});

const module = new Module();

console.log(module.publicMethod());
person Naftali aka Neal    schedule 14.11.2016
comment
Вы говорите попробовать, потому что на самом деле это нецелесообразно (чтобы обновить раскрывающийся шаблон модуля)? Кажется неудобным это делать. - person Skyler; 15.11.2016
comment
Вы не должны вызывать функцию, которая не является конструктором, с new. Ваша Module — это фабричная функция, которая возвращает объект, поэтому использование new бессмысленно. В любом случае это не меняет контекст privateMethod. - person Bergi; 15.11.2016
comment
Возвращает функцию, а не объект - person Naftali aka Neal; 15.11.2016
comment
@Neal Я написал объект, потому что new заботится об объекте и примитивных значениях, конечно, функция - это объект. - person Bergi; 15.11.2016