Частные методы прототипа, которые могут совместно использовать область действия и получать доступ к экземпляру

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

Например, именно так я в настоящее время добиваюсь «приватных методов» (не обращайте внимания на то, что на самом деле делает код, просто посмотрите на структуру).

function InfoPreview() {
   this.element = document.createElement('div');
}

//Private Methods
InfoPreview.prototype.__newLine = function () {
  this.element.appendChild(createElement({tagName:'br'}));
}; 

InfoPreview.prototype.__padLeft = function(level) {
  var padding = createElement({tagName: 'span'});
  this.element.appendChild(padding);
  $(padding).width(level * 10);
};

InfoPreview.prototype.__print = function(string) {
  var span = createElement({ tagName: 'span', textContent: string });
  this.element.appendChild(span);
  this.element.style["margin-right"]='10px';
};

InfoPreview.prototype.__puts = function(string) {
  this.__print(string);
  this.__newLine();
};

//Public Methods
InfoPreview.prototype.update = function(info) {
  $(this.element).empty();
  for (var record in info) {
    this.__puts(record);
  }
};   

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

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

InfoPreview.prototype = (function() {
  var self = this, //<- `this` is actually the global object now.
      el = self.element;

  var newLine = function () {
    el.appendChild(createElement({tagName:'br'}));
  }; 

  var padLeft = function(level) {
    var padding = createElement({tagName: 'span'});
    el.appendChild(padding);
    $(padding).width(level * 10);
  };

  var print = function(string) {
    var span = createElement({ tagName: 'span', textContent: string });
    el.appendChild(span);
    el.style["margin-right"]='10px';
  };

  var puts = function(string) {
    print(string);
    newLine();
  };

  var update = function(info) {
    $(el).empty();
    for (var record in info) {
      puts(record);
    }
  };

  return {
    update: update
  }; 
})();

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


person Luke    schedule 22.07.2015    source источник
comment
Когда вы позвоните instance.update(info), у вас будет доступ к this. Вы можете либо явно передать его частным функциям, либо вызвать их с помощью .call() для установки this.   -  person Pointy    schedule 22.07.2015
comment
@Pointy Ааа, так что это дало бы мне доступ к экземпляру внутри каждой функции, но знаете ли вы хороший способ кэшировать запросы, которые укоренены в экземпляре, и делиться ими между всеми функциями? ? (Вместо того, чтобы повторять один и тот же код инициализации в нескольких функциях)   -  person Luke    schedule 22.07.2015
comment
Так или иначе, вы будете жонглировать ссылкой на экземпляр. Я не уверен, что концепция кэширования действительно имеет смысл; для этих функций есть только одно закрытие, а не по одному для каждого экземпляра.   -  person Pointy    schedule 22.07.2015
comment
@Pointy Да, я думаю, ты прав. Я полагаю, что мог бы создавать функции-получатели для более длинных цепочек и кэшировать результаты в самих функциях-получателях (как неоптимальное решение)   -  person Luke    schedule 22.07.2015
comment
что вы имеете в виду под кешем?   -  person Tivie    schedule 22.07.2015
comment
@Tivie, что касается моего последнего комментария, я имею в виду кэширование функций (также известное как мемоизация). Что касается того, как я использовал его в своем вопросе, я просто имею в виду сохранение значения, например. набрав el вместо this.element. Это быстрее и для моих пальцев, и для процессора. Лучше было бы слово «псевдоним».   -  person Luke    schedule 22.07.2015
comment
@LukeP есть ли проблемы с использованием шаблона функции?   -  person Tivie    schedule 22.07.2015
comment
@LukeP, вы не можете кэшировать this и this.element на этом уровне, потому что тогда он кэширует одно значение для всех экземпляров, вы можете просто пропустить текущие this или element в качестве простого параметра, такого как var padLeft = function(el, level) {, и вызвать padLeft(this.element)   -  person Grundy    schedule 22.07.2015
comment
@Grundy Да, я понимаю. Вот почему я искал альтернативный образец. Я надеялся, что смогу найти хороший способ поделиться одной ссылкой на экземпляр, а также поделиться ссылками на вложенные переменные экземпляра между функциями. (Теперь я понимаю, что хорошее решение будет сложнее реализовать, чем я изначально думал)   -  person Luke    schedule 22.07.2015


Ответы (4)


Есть ли недостатки в использовании шаблона конструктора?

function Foo(constructorArg) {
    
    /* private variables */
    var privVar = 'I am private',
        cArg = constructorArg;
    
    /* public variables */
    this.pubVar = 'I am public';
    
    /* private function */
    function privFunc() {
        return 'I am a private function';
        
    }
    
    /* public function */
    this.publicFunc = function() {
        return 'I am a public function and I call privVar->"' + privVar + '" and privFunc->"' + privFunc() + '"';
    }
}

var foo = new Foo('something');

console.log('foo.pubVar', foo.pubVar); //ok
console.log('foo.publicFunc()', foo.publicFunc()); // ok

console.log('foo.privVar', foo.privVar); // undefined
console.log('foo.privFunc', foo.privFunc()); //error

Почему вы должны использовать его (как указано в комментариях):

Проще говоря, потому что это единственный (разумный) способ создания «настоящей частной области», о которой вы спрашивали.

Альтернативой является использование соглашения, которое сообщает разработчикам, какие свойства и методы являются приватными, обычно добавляя к ним префикс подчеркивания _, который вы уже реализовали, но не любили.

Обратите внимание, что конструктор и прототип — это разные вещи, и они позволяют вам делать разные вещи. Ничто не мешает вам смешать и то, и другое.

Использование памяти

Что касается использования памяти, то в современных js-движках, таких как Google V8 JavaScript Engine, шаблон конструктора может быть быстрее.

V8 имеет скрытые типы, созданные внутри для объектов во время выполнения; объекты с одним и тем же скрытым классом могут затем использовать один и тот же оптимизированный сгенерированный код.

Например:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

var p1 = new Point(11, 22);
var p2 = new Point(33, 44);
// At this point, p1 and p2 have a shared hidden class
p2.z = 55;
// warning! p1 and p2 now have different hidden classes!

Цепочка прототипов всегда требует двух поисков, так что это может быть даже немного медленнее. Примечание: резервная копия невозможна, jsperf.com не работает!

Шаблон конструктора грязный (sic)

Производительность была моей причиной. Я этого не понимал. Однако мне все еще кажется грязным

Я не знаю, почему вы считаете, что шаблон конструктора грязный. Может быть, это потому, что у него есть некоторые «особенности», ограничения и потенциальные ловушки, о которых вам следует знать.

  1. this может означать разные вещи
  2. Легко забыть новое ключевое слово, вызывающее странные и трудные для отладки ошибки из-за общего состояния.
  3. Вы не можете легко разделить свой объект на несколько файлов (не прибегая к инструменту сборки или стороннему инжектору)

Однако 1 и 2 также верны для стиля объявления прототипа, поэтому...

если вы чувствуете, что этого недостаточно, вы можете взглянуть на шаблон модуля.

person Tivie    schedule 22.07.2015
comment
Можете ли вы найти способ адаптировать это, чтобы я мог использовать этот шаблон с моими методами-прототипами? (я не могу) - person Luke; 22.07.2015
comment
Не без чрезвычайно сложного анти-шаблона. Зачем вам нужно использовать прототип? - person Tivie; 22.07.2015
comment
Это как раз то, что мне нужно для этого варианта использования. Я ищу общий шаблон, который я могу использовать с моими методами экземпляра. Я не хочу копировать методы в каждый отдельный экземпляр. Похоже, что единственный способ не только съесть свой пирог, но и съесть его — это написать собственную мини-инфраструктуру, позволяющую приватным функциям регистрировать зависимости с помощью механизма во внешней области видимости. (хотя это больше работы, чем я хочу сделать) - person Luke; 22.07.2015
comment
Я не понимаю... Вам не нужно копировать методы в каждый отдельный экземпляр. Каждый экземпляр Foo имеет методы, назначенные вами в конструкторе. - person Tivie; 22.07.2015
comment
Я не имел в виду вручную, я просто не хочу, чтобы их копировали. Я хочу, чтобы они использовали одни и те же методы. - person Luke; 22.07.2015
comment
Что вызывает вопрос, почему? Причина, по которой я спрашиваю, заключается в том, что чаще всего люди говорят о производительности. Хотя истинный шаблон прототипа, как правило, быстрее, чем шаблон this, он едва заметен, если вы не создаете МНОГО (я имею в виду, МНОГО, например, 10 000) объектов для запуска скрипта. Кроме того, новые движки Javascript используют скрытые классы. Проверьте html5rocks.com/en/tutorials/speed/v8. - person Tivie; 22.07.2015
comment
Однако, независимо от причины, то, что вы просите, невозможно, потому что вы хотите, чтобы замыкание неявно находилось в правильной области действия экземпляра объекта (что требует создания экземпляра при каждом вызове) без совместного использования состояния между экземплярами. Это все равно, что попросить, чтобы статический метод класса вел себя как динамический метод объекта. - person Tivie; 22.07.2015
comment
Производительность была моей причиной. Я этого не понимал. Однако он все еще кажется мне грязным. - person Luke; 22.07.2015
comment
Я понимаю, что то, что я просил, в текущем виде невозможно, поэтому и попросил альтернативный шаблон. У вас есть альтернатива, просто не та альтернатива, которую я изначально представлял. Если вы добавите объяснение, ПОЧЕМУ я должен использовать этот шаблон, в самом вопросе. плохо, вероятно, принять это - person Luke; 22.07.2015
comment
Я изучал это больше и наткнулся на несколько выступлений Дэйва Крокфорда, в которых он рекомендует отказываться от прототипов, ссылаясь на те же причины производительности, которые вы здесь указываете. Он использует раскрывающийся шаблон модуля в своем конструкторе и возвращает замороженный объект. - person Luke; 24.07.2015
comment
Что касается вашей специфики, учитывая ваш аргумент, вам даже больше не нужны прототипы, так что вы можете бросить new все вместе. Что касается this: красота этого шаблона в том, что в области верхнего уровня this действительно указывает на экземпляр! Таким образом, вы можете связать это с чем-то вроде self или that, и тогда частные функции будут закрыты для этой переменной. - person Luke; 24.07.2015

Внутри каждой функции у вас будет доступ к нужному this значению.

var Example = function() {};

Example.prototype = (function() {
  var privateUpdate = function() {
    document.getElementById('answer').innerHTML = this.foo;
  }
  
  return {
    update: privateUpdate
  }
})();

var e = new Example();
e.foo = 'bar';
e.update();
<div id="answer"></div>

person David P. Caldwell    schedule 22.07.2015
comment
После тестирования вам по-прежнему необходимо либо использовать call, либо явно передавать this при вызове частных функций из других частных функций. Кроме того, я не могу делиться состоянием между частными функциями только с этим - person Luke; 22.07.2015

В качестве варианта того, что предлагает Pointy, вы можете попробовать этот шаблон;

infoPreview.prototype = (function() {
  var self = null;

  var update = function(info) {
      ....
  };

  var firstUpdate = function(info) {
      self = this;
      functions.update = update;
      update(info);  
  }

  var functions = {
    update: firstUpdate
  }; 
  return functions;
})();
person Soren    schedule 22.07.2015
comment
Я тоже об этом думал, хотя это становится большим (возможно, ненужным) повторным вычислением по мере увеличения количества методов и переменных экземпляра, к которым мне нужен доступ. - person Luke; 22.07.2015
comment
это также не работает для нескольких объектов, потому что после вызова первого объекта он кэшируется и используется для второго объекта - person Grundy; 22.07.2015

Может быть, что-то подобное, без прототипирования:

https://jsfiddle.net/ynwun1xb

var Fn = function(el) {
    this.el = el;

    var myMethod = function() {
        console.log('do something in method with element', this.el);
    }.bind(this);

    return {
        myPublicMethod: function() {
            return myMethod();
        }
    }
}

var instancedFn = new Fn('first instance element')
    .myPublicMethod()
;

var instancedFn2 = new Fn('second instance element')
    .myPublicMethod()
;
person Olivier    schedule 22.07.2015