Закрытие Javascript и видимость данных

Я пытаюсь склонить голову вокруг идеи классов, видимости и закрытия данных (в частности, в Javascript), и я нахожусь на странице docs jQuery для типов, он упоминает, что закрытие используется для скрытия данных:

Шаблон позволяет создавать объекты с помощью методов, которые работают с данными, которые не видны снаружи - сама основа объектно-ориентированного программирования.

Пример:

function create() {
  var counter = 0;
  return {
    increment: function() {
      counter++;
    },
    print: function() {
      console.log(counter);
    }
  }
}
var c = create();
c.increment();
c.print(); // 1

Объявив счетчик переменных с ключевым словом var, он уже локально ограничен внутри определения функции/класса. Насколько я знаю и могу сказать, он не доступен извне для начала. Я что-то пропустил с точки зрения видимости данных.

Во-вторых, есть ли преимущество в написании класса, как указано выше, например:

function create() {
  var counter = 0;
  this.increment = function() {
      counter++;
  }
  this.print = function() {
      console.log(counter);
  }
  return this;
}
var c = create();
c.increment();
c.print(); // 1

Как я понимаю, это более или менее семантически одно и то же: первый - это просто "стиль jQuery". Мне просто интересно, есть ли преимущество или какой-то другой нюанс, который я не совсем понимаю из первого примера. Если я прав, оба примера создают замыкания в том, что они обращаются к данным, объявленным вне их собственной области.

http://docs.jquery.com/Types#Closures

Ответ 1

Прежде всего, вы правы, что обе версии используют закрытие.

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

Второй стиль, которого я никогда не видел в Javascript. Обычно вы создаете create с помощью new вместо того, чтобы возвращать this в функции create(), например:

function create() {
  var counter = 0;
  this.increment = function() {
      counter++;
  }
  this.print = function() {
      console.log(counter);
  }
}
var c = new create();
c.increment();
c.print(); // 1

Ответ 2

Объявив счетчик переменных с ключевое слово var, оно уже локально область внутри функции/класса определение. Насколько я знаю и могу скажите, он недоступен из снаружи для начала. Я скучаю что-то из видимости данных перспектива.

Не так, чтобы переменная counter не была доступна извне функции для начала, она доступна для функций increment и print после выхода функции create, что делает closures настолько полезен.

Ответ 3

Ну, я не хочу вступать в религиозную войну за то, как создавать объекты в JavaScript, поскольку некоторые люди чувствуют себя уверенно, что есть правильный и неправильный способ сделать это.

Тем не менее, я хочу указать что-то в вашем втором наборе кода, который не слишком приятен, а именно тот факт, что вы присваиваете вещи новым свойствам объекта, содержащегося в ключе this, - понимаете ли вы, что этот объект есть? Это не пустой объект, если вы не используете синтаксис создания экземпляра:

var c = new create();

Когда вы это сделаете, ключевому слову this внутри тела функции конструктора назначен новый объект, как будто первая строка в теле была примерно такой:

this = {};

Но когда вы вызываете create() как функцию, как и в этом примере, вы изменяете область вне определения функции (как указано в @seanmonster в комментариях).

Ответ 4

Вам следует сравнить пример с этим фрагментом

function create() {
  this.counter = 0;
  this.increment = function() {
      this.counter++;
  };
  this.print = function() {
      console.log(counter);
  }
}

var c = new create();
c.increment();
c.print(); // 1

Итак, когда вызывается new create(), он инициализирует новый объект двумя способами и одной переменной экземпляра (а именно: counter). Javascript не имеет инкапсуляции per-se, поэтому вы можете получить доступ к c.counter следующим образом:

var c = new create();
c.increment();
c.counter = 0;
c.print(); // 0

Используя закрытие (как показано в ваших примерах), счетчик теперь больше поля экземпляра, а скорее локальной переменной. С одной стороны, вы не можете получить доступ извне функции create(). С другой стороны, increment() и print() могут получить доступ, потому что они закрываются по охватывающей области. Таким образом, мы получаем довольно хорошую эмуляцию объектной инкапсуляции.

Ответ 5

В вашем втором примере все еще используются блокировки, так как приращение и функции печати по-прежнему действуют на переменную, в противном случае это невозможно из-за области — к моменту вызова c.increment() функция create уже вышла.

Мне нравится первый пример, потому что он избегает ключевого слова "this", а в javascript "this" может быть сложно:— он не всегда ссылается на то, что кажется, как должно.

Ответ 6

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

Ответ 7

Этот синтаксис имеет больше смысла для меня, исходя из фона ООП:

Create = function {
  // Constructor info.

  // Instance variables
  this.count = 10;
}

Create.prototype = {

     // Class Methods
     sayHello : function() {
          return "Hello!";
     },

     incrementAndPrint : function() {
          this.count++;

          // Inner method call.
          this.print();
     },

     print : function() {
         return this.count;
     }


}

var c = new Create();
alert(c.incrementAndPrint());

Ответ 8

В вашем втором примере, когда вы вызываете create(), в рамках функции this - это глобальный объект (который всегда имеет место, когда вы вызываете "голую" функцию, не используя его как конструктор или доступа к нему в качестве свойства (например, вызов метода)). В браузерах глобальный объект window. Поэтому, когда вы вызываете создание последующих времен, оно создает новые блокировки, но затем назначает их тому же глобальному объекту, что и раньше, перезаписывая старые функции, чего вы не хотите:

var c = create(); // c === window
c.increment();
c.print(); // 1
var c2 = create(); // c2 === c === window
c.print(); // 0
c2.print(); // 0
increment(); // called on the global object
c.print(); // 1 
c2.print(); // 1

Решения, как указывали другие, заключаются в использовании new create().

Ответ 9

MYAPP = (function(){
   var v1,v2;
   return {
    method1:function(){},
    method2:function(){}
   };
})();

Я всегда использую такие блокировки, как это в своем приложении, как и все мои собственные определенные методы находятся в пространстве имен MYAPP, v1 и v2 доступны только с помощью методов в MYAPP. В моем приложении я часто пишу только "приложение".js ", все мои js-коды внутри. Я предполагаю, что вы можете определить метод под названием" registy" для определения частной переменной в MYAPP, тогда вы можете использовать его в своих методах. Все дополнительные переменные и методы должны определяться методом registy, когда вы хотите добавить дополнительные коды в html файл, как метод JQuery.extend. Я слышал, если вы используете слишком много закрытий в браузере IE, вам легко получить переполнение стека. (По-моему)