Какой шаблон (ы) дизайна использует преимущества JavaScript-поведения? - PullRequest
44 голосов
/ 22 декабря 2011

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

Во-вторых, уникальна ли область видимости для JavaScript?

ОБНОВЛЕНИЕ --- Я добавляю награду за ответ, который удовлетворяет мое любопытство: Какой шаблон (ы) проектирования на самом деле использует преимущества подъемного поведения JavaScript? Я понимаю почему JavaScript поддерживает подъем, но я хочу знать , как я могу воспользоваться этой функцией .

Ответы [ 9 ]

19 голосов
/ 05 марта 2012

Переменная подъема

Одним из самых простых видов использования подъема является переменный подъем.Если бы у нас не было подъема переменных, это выкинуло бы ReferenceError:

var bar = foo; 
var foo;

Это не кажется полезным сразу, но позволяет нам делать такие вещи:

var myCoolJS = myCoolJS || {};

Это в основном означает, как это выглядит: myCoolJS - это myCoolJS, если он существует, или новый объект, если он не существует.Второй myCoolJS не выбрасывает ReferenceError, если myCoolJS еще не существует, потому что это объявление переменной поднято.

Это избавляет нас от неловкой проверки typeof myCoolJS != 'undefined'.

Подъем функций

Подъем функций может быть особенно полезен, когда объединяет несколько скриптов в один ,Например, я создал легкую реализацию во время сборки модулей CommonJS .Это обеспечивает те же функции module, require и exports, которые находятся в файле node.js.Я построил инструмент, позволяющий составлять необходимые модули из нескольких файлов.Например, require('/foo') может привести к созданию модуля, состоящего из двух файлов, foo.js ("файл тела") и foo.h.js ("файл заголовка").

Это разрешит "файл тела"«не иметь представления о свободных переменных, предоставляемых средой модулей CommonJS;все это обрабатывается в заголовке.Это делает код многоразовым и легко тестируемым без сборки.Однако, поскольку заголовки добавляются к телу, мы используем функцию , поднимающую в файле тела, чтобы разрешить экспорт в заголовки.Например:

// dom.h.js

var util = require('util').util;

exports.css = css; // we can do this because "css" is hoisted from below

// ... other exports ...

...

// dom.js

function css(){}; // this would normally just be an object.

css.hasClass = function(element) { ... };
css.addClass = function(element) { ... };

// ...other code...
15 голосов
/ 03 марта 2012

Вот использование для подъема:

(function() {
    var factorial = function(n) {
        if(n == 0)
            return 1;
        return n * factorial(n - 1);
    };
})();

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

JavaScript также допускает код, подобный следующему:

var test = function(b) {
    if(b) {
        var value = 2;
    } else {
        var value = 5;
    }
    console.log(value);
};

При определении объема блока вам придется добавить еще одну строкуобъявить value перед if.

Чтобы быть справедливым, этот код работает из-за области действия функции, а не подъема.И JavaScript мог иметь область действия функции без подъема.Ruby справляется с этим лучше: в Ruby есть область видимости для переменных, но переменные не существуют, пока вы не установите их:

def test(b)
    # unlike JavaScript, value is not accessible here
    if b
        value = 2
    else
        value = 5
    end
    puts value
end
11 голосов
/ 22 декабря 2011

JavaScript не имеет области видимости блока (давайте пока что забудем о let), и поэтому любое объявление переменной объявляется для всей функции, из которых JavaScript имеет область действия.

Если вы думаете об этом таким образом, JavaScript-логика может иметь больше смысла.

Если вы помните о подъеме, это не должно быть источником ошибок и путаницы. Это просто одна из тех причуд, которые вы должны понимать и помнить.

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

9 голосов
/ 22 декабря 2011

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

Пример 1 - Оригинальный код

var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    alert(foo);
}
bar();

Пример 1 - Рефакторированный код (устранена путаница)

var foo = 1;

function bar() {
    var foo;

    if ( !foo ) {
        foo = 10;
    }

    alert( foo );
}

bar();

В сообщении отображается «10», и понятно почему.Здесь нет путаницы.

Пример 2 - Исходный код

var a = 1;
function b() {
    a = 10;
    return;
    function a() {}
}
b();
alert(a);

Пример 2 - Измененный код (устранена путаница)

var a = 1;

function b() {
    var a = function () {}; 
    a = 10;
    return; 
}

b();

alert( a );

В сообщении отображается «1».Очевидно.Здесь тоже нет путаницы.

6 голосов
/ 06 марта 2012

«Подъем» не является частью стандарта ECMAScript, но в нем говорится, что переменные внутри функции объявляются в начале функции независимо от того, где в функции она находится в коде.

Пример

(function() {
  alert(myvar); // undefined
  var myvar = 'local value';
})();

Внутренне Javascript объявит myvar перед предупреждением, покажет предупреждение, а затем назначит myvar «локальное значение».

Таким образом, Javascript будет интерпретировать этот код как:

(function() {
  var myvar;
  alert(myvar); // undefined
  myvar = 'local value';
})();

Именно поэтому в «Java the Good parts» есть руководство, в котором говорится, что вы должны объявить переменную в верхней части вашей функции.

Источник: http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-javascript-hoisting-explained/

"Пожалуйста, объясните, есть ли шаблон дизайна, который действительно использует эту языковую функцию ».«Подъем» - это не особенность, а скорее следствие того, как интерпретатор Javascript структурирует код, поскольку язык использует определение функций.

«Какие шаблоны проектирования на самом деле используют преимущества поведения подъема JavaScript?» Ответ:ни один.

5 голосов
/ 22 декабря 2011

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

function foo()
{
   function a()
   {
      //...
   }

   function b()
   {
      //...
   }
}

также можно записать как:

function foo()
{
   var a = function ()
   {
      //...
   }

   var b = function ()
   {
      //...
   }
}

Без подъема следующее может привести к ошибке:

function foo()
{
   var a = function ()
   {
      b();
   }
   a(); //Error in function since b is not yet defined

   var b = function ()
   {
      //...
   }
}

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

4 голосов
/ 28 января 2014

Вот реальный вариант использования (хотя и сокращенный до псевдокода) для вас, от человека, который на самом деле хотел бы использовать преимущества подъема в дикой природе.

Недавно я написал этот скрипт для обработки простой проверки и отправки форм. Насколько это возможно, каждое объявление функции вызывает следующее. Это имеет 2 основных преимущества для удобства чтения:

  1. Логическая последовательность : существует последовательный поток в коде, что означает, что функции всегда будут вызываться до их объявления. Это является преимуществом в том, что при использовании с функциями низкой сложности он сохраняет относительно плоский уровень и дает представление о контексте вызова функции незадолго до ее источника. Вам только когда-либо придется прокручивать (никогда) вверх, чтобы следовать коду, и - насколько это возможно - прокручивать совсем или совсем немного.
  2. Низкие накладные расходы : мне нравится держать все мои объявления переменных в верхней части каждой области видимости, чтобы читатели знали обо всех движущихся частях, которые требуются функции, перед чтением ее тела, но никто не хочет читать исходный код каждой вызванной функции, чтобы получить представление о том, что делает текущая область. Используя этот метод, вы никогда не столкнетесь со ссылкой на функцию до ее объявления. Поначалу это звучит глупо, но на самом деле это уменьшает когнитивные издержки: вам никогда не дают источник функции с неявным , запомните это - мы будем использовать его позже - вместо этого вы только когда-либо читаете источник функции, как только вы знать контекст, в котором он был вызван.
$( function emailHandler(){
  var $form      = …
  var $email     = …
  var $feedback  = …
  var value      = …
  var validation = …

  // All initialisation is right here. Executes immediately.
  // Therefore, all future code will only ever be invoked
  // by call stacks passing through here.
  void function bindEvents(){
    $email.on( 'input', filterInput );

    $form.on( 'submit', preSubmit );
  }();

  function filterInput( inputEvent ){
    if( inputEvent && inputEvent.which === '13' ){
      return presubmit( inputEvent );
    }

    return validate();
  }

  function validate(){
    var presentValue = $email.val();

    if( validation.done || presentValue === value ){
        return;
    }
    else if( presentValue === placeholder || presentValue === '' ){
        validation.message = 'You must enter an email address to sign up';
        validation.valid   = false;
    }
    else if( !validation.pattern.test( presentValue ) ){
        validation.message = 'Please enter a valid email address';
        validation.valid   = false;
    }
    else {
        validation.message = '';
        validation.valid   = true;
    }

    validation.ever = true;

    clearFeedback();
  }

  function preSubmit( inputEvent ){
    if( inputEvent instanceof $.Event ){
      inputEvent.preventDefault();
    }

    if( !validation.ever ){
      validate();
    }
    if( validation.pending || validation.done ){
      return;
    }
    else if( validation.valid ){
      return submit();
    }
    else {
      return feedback();
    }
  }

  function submit(){
    $form.addClass( 'pending' );

    // Create an XHR base on form attributes
    $.ajax( {
      cache : false,
      url   : $form.attr( 'action' ),
      data  : $form.serialize(),
      type  : $form.attr( 'method' ).toUpperCase()
    } )
      .done( success )
      .fail( failure )
      .always( feedback );
  }

  function success( response ){
    validation.message = response.message;
    validation.valid   = response.valid;
  }

  function failure(){
    validation.message = 'Our server couldn\'t sign you up. Please try again later.';
    validation.error   = true;
  }

  function feedback(){
    clearFeedback();

    if( validation.message ){
      $feedback
        .html( validation.message )
        .appendTo( $placeholder );
    }

    if( !validation.valid || validation.error ){
      $form.addClass( 'invalid' );

      $email.trigger( 'focus' );
    }
    else {
      $form.addClass( 'done' );

      validation.done = true;
    }

    validation.pending = false;
  }

  function clearFeedback(){
    $form.removeClass( 'invalid pending done' );
  }
} );
1 голос
/ 07 марта 2012

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

Я могу только представить несколько тривиальных случаев.Основное свойство для использования - то, что переменная может быть объявлена ​​(но не определена), а затем назначена только в одной строке кода, но с событиями, интерпретируемыми в двух разных точках.

С объявлением в концецикл (не .forEach, поскольку он, конечно, устанавливает область видимости), вы можете использовать его для обнаружения первой итерации.

var notfirst = true;  // this is actually never referenced.

(function () {  
  var number, stack = [1, 2, 3, 4, 5];

  while (number = stack.pop()) {
    if (notfirst) console.log(number);
    var notfirst = true;
  }
})();

Выход от опустошения стека равен 4, 3, 2, 1. 5 отклонен.

Снова.Не делай этого!

0 голосов
/ 02 мая 2016

Если вы подумаете о том, как пишутся другие языки (C ++ / Java) и как используются их шаблоны классов, можно воспользоваться преимуществами подъема, чтобы написать аналогичный шаблон для создания прототипов.

...