Как вызвать общедоступную функцию из частной функции в шаблоне модуля JavaScript

Как вызвать общедоступную функцию из частной функции в шаблоне модуля JavaScript?

Например, в следующем коде

var myModule = (function() {
    var private1 = function(){
        // How to call public1() here?
        // this.public1() won't work
    }

    return {
        public1: function(){ /* do something */}
    }
})();

Этот вопрос был задан дважды раньше, с разными принятыми ответами для каждого.

  1. Сохраните ссылку на возвращаемый объект перед его возвратом, а затем используйте эту ссылку для доступа к общедоступному методу. См. ответ.
  2. Сохраните ссылку на общедоступный метод в замыкании и используйте ее для доступа к общедоступному методу. См. ответ.

Хотя эти решения работают, они неудовлетворительны с точки зрения ООП. Чтобы проиллюстрировать, что я имею в виду, давайте возьмем конкретную реализацию снеговика с каждым из этих решений и сравним их с литералом простого объекта.

Снеговик 1: Сохранить ссылку, чтобы вернуть объект

var snowman1 = (function(){
  var _sayHello = function(){
    console.log("Hello, my name is " + public.name());
  };

  var public = {
    name: function(){ return "Olaf"},
    greet: function(){
      _sayHello();
    }
  };
  return public;
})()

Snowman 2: Сохранить ссылку на общедоступную функцию

var snowman2 = (function(){
  var _sayHello = function(){
    console.log("Hello, my name is " + name());
  };
  var name = function(){ return "Olaf"};

  var public = {
    name: name,
    greet: function(){
      _sayHello();
    }
  };
  return public;
})()

Снеговик 3: литерал объекта

var snowman3 = {
    name: function(){ return "Olaf"},
    greet: function(){
      console.log("Hello, my name is " + this.name());
    }
}

Мы видим, что все три идентичны по функциональности и имеют одни и те же общедоступные методы.

Однако если мы запустим тест простого переопределения

var snowman = // snowman1, snowman2, or snowman3
snowman.name = function(){ return "Frosty";}
snowman.greet(); // Expecting "Hello, my name is Frosty"
                 // but snowman2 says "Hello, my name is Olaf"

мы видим, что № 2 терпит неудачу.

Если мы запустим тест переопределения прототипа,

var snowman = {};
snowman.__proto__ = // snowman1, snowman2, or snowman3
snowman.name = function(){ return "Frosty";}
snowman.greet(); // Expecting "Hello, my name is Frosty"
                 // but #1 and #2 both reply "Hello, my name is Olaf"

мы видим, что и № 1, и № 2 терпят неудачу.

Это действительно некрасивая ситуация. Просто потому, что я так или иначе рефакторил свой код, пользователь возвращенного объекта должен внимательно посмотреть, как я все реализовал, чтобы выяснить, может ли он / она переопределить методы моего объекта и ожидать, что это сработает. ! Хотя здесь мнения расходятся, мое собственное мнение состоит в том, что правильное поведение переопределения — это поведение простого литерала объекта.

Итак, это реальный вопрос:

Есть ли способ вызвать общедоступный метод из закрытого, чтобы результирующий объект действовал как литерал объекта в отношении поведения переопределения?


person I-Lin Kuo    schedule 20.12.2014    source источник
comment
Важное примечание к подводным камням, которые вы обсуждаете: мое понимание шаблона модуля состоит в том, что он действительно является альтернативой подходу полиморфизма прототипа/ООП в JS. Таким образом, я думаю, что для вас имеет смысл избегать использования их вместе - я не вижу в этом ловушки само по себе.   -  person EyasSH    schedule 21.12.2014
comment
@EyasSH - его можно использовать таким образом, однако я думаю, что это скорее артефакт, который можно использовать для своего рода наследования, а не для преднамеренной функции. Похоже, он используется в основном для эмуляции закрытых членов (или для того, чтобы не показывать то, с чем другим не следует играть), а также для повышения производительности.   -  person RobG    schedule 21.12.2014
comment
@RobG, верно, но вы можете объявить приватные функции в замыкании, придерживаясь шаблона прототипа. В этом случае можно использовать this. Я хочу сказать, что если вы используете паттерн модуля, я думаю, что странность взаимодействия между модулем и прототипом не является реальной причиной не использовать подход. Это может быть причиной того, чтобы вообще придерживаться прототипов, если вы думаете, что кто-то, кто использует ваш код, сделает это.   -  person EyasSH    schedule 21.12.2014
comment
Я думаю, вам следует добавить () в конце snowman1, snowman2 и snowman3, иначе они будут функциями. И, вероятно, в переопределении прототипа вы должны настроить snowman.name, иначе я не вижу, как он должен выводить Frosty.   -  person Oriol    schedule 21.12.2014
comment
Спасибо, @Oriol, я внес исправления   -  person I-Lin Kuo    schedule 21.12.2014
comment
Я не понимаю, почему вы хотите вызвать публичный метод из приватного в первую очередь. Мне кажется проблема XY.   -  person Mathletics    schedule 21.12.2014


Ответы (2)


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

Затем вы можете передать это значение вашему частному методу _sayHello, например. используя call, apply или в качестве аргумента:

var snowman4 = (function() {
    var _sayHello = function() {
        console.log("Hello, my name is " + this.name);
    };
    return {
        name: "Olaf",
        greet: function() {
            _sayHello.call(this);
        }
    };
})();

Теперь вы можете сделать

var snowman = Object.create(snowman4);
snowman.greet(); // "Hello, my name is Olaf"
snowman.name = "Frosty";
snowman.greet(); // "Hello, my name is Frosty"

А также

snowman4.greet(); // "Hello, my name is Olaf"
snowman4.name = "Frosty";
snowman4.greet(); // "Hello, my name is Frosty"
person Oriol    schedule 20.12.2014
comment
Этот ответ, похоже, не касается вопроса о том, как вызывать общедоступные функции из частных функций в шаблоне модуля, в частности, как _sayHello() вызывать name(). Обертывание литерала объекта во IIFE также не делает привилегированным функциюgreet(). - person I-Lin Kuo; 21.12.2014
comment
@ I-LinKuo Да, это привилегировано. Он может получить доступ к закрытым данным, объявленным внутри самовыполняющейся функции. - person Oriol; 21.12.2014
comment
Да, был привилегированным: он мог получить доступ к закрытым данным, объявленным внутри самовыполняющейся функции. Я сделал это с привилегированным методом вместо частного для упрощения, но это правда, что он не ответил на вопрос напрямую, поэтому я исправил его. - person Oriol; 21.12.2014

С шаблоном модуля вы прячете все врожденные свойства объекта в локальных переменных/функциях и обычно используете их в своих общедоступных функциях. Каждый раз, когда создается новый объект с шаблоном модуля, также создается новый набор открытых функций — с их собственным состоянием области действия.

С шаблоном прототипа у вас есть один и тот же набор методов, доступных для всех объектов определенного типа. Что меняется для этих методов, так это this объект - другими словами, это их состояние. Но this никогда не скрывается.

Излишне говорить, что их сложно смешать. Одним из возможных способов является извлечение методов, используемых приватами, в прототип результирующего объекта модуля с помощью Object.create. Например:

var guardian = function() {
    var proto = {
        greet: function () {
            console.log('I am ' + this.name());
        },
        name: function() {
            return 'Groot';
        }
    };
    var public = Object.create(proto);
    public.argue = function() {
        privateGreeting();
    };

    var privateGreeting = public.greet.bind(public);
    return public;
};

var guardian1 = guardian();
guardian1.argue(); // I am Groot
var guardian2 = guardian();
guardian2.name = function() {
  return 'Rocket';
};
guardian2.argue(); // I am Rocket
var guardian3 = guardian();
guardian3.__proto__.name = function() {
  return 'Star-Lord';
};
guardian3.argue(); // I am Star-Lord
person raina77ow    schedule 20.12.2014