范围-哪些设计模式可以利用JavaScript的起吊行为?

Ben Cherry的出色文章充分说明了JavaScript的提升。 但是,我的问题是我无法为这个臭名昭著的混淆者构想一个用例。 请说明是否存在实际上利用此语言功能的设计模式。

其次,作用域提升是JavaScript所独有的吗?

更新---我为满足我好奇心的答案添加了赏金:哪些设计模式实际上利用了JavaScript的提升行为? 我了解JavaScript为什么支持吊装,但是我想知道如何利用此功能。

David Rivers asked 2020-07-25T14:55:00Z
9个解决方案
19 votes

可变吊装

起重最简单的用途之一是可变起重。 如果我们没有可变的提升,这将抛出module

var bar = foo; 
var foo;

这似乎并不立即有用,但是它允许我们执行以下操作:

var myCoolJS = myCoolJS || {};

这基本上意味着它的外观:modulerequire(如果存在),或者是新对象(如果不存在)。 如果foo.js不存在,则第二个exports不会抛出require('/foo'),因为该变量声明已被提升。

这样可以避免我们进行笨拙的module检查。

功能提升

当将多个脚本组合成一个脚本时,功能提升特别有用。 例如,我已经创建了CommonJS模块的轻量级构建时实现。 这提供了与node.js中发现的modulerequireexports相同的功能。 我构建了该工具,以允许所需的模块由多个文件组成。 例如,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...
Dagg Nabbit answered 2020-07-25T14:55:45Z
15 votes

这是用于提升的用途:

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

如果没有提升,该函数将无法编译,因为函数文字内部尚不存在value。 您必须单独声明变量或使用命名函数。

JavaScript还允许如下代码:

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

使用块作用域时,您必须在if之前添加另一行以声明value

公平地讲,此代码是基于功能范围而不是提升的。 而且JavaScript可以具有功能范围而无需提升。 Ruby处理起来更好:Ruby拥有变量的方法范围,但是在设置变量之前,变量不存在:

def test(b)
    # unlike JavaScript, value is not accessible here
    if b
        value = 2
    else
        value = 5
    end
    puts value
end
Jordan Miner answered 2020-07-25T14:56:23Z
11 votes

JavaScript没有块作用域(现在让我们忘记let),因此任何变量声明都是针对整个函数声明的,JavaScript确实具有作用域。

如果以这种方式考虑,JavaScript吊装可能更有意义。

如果您还记得有关吊装的信息,那么它不应成为错误和混乱的根源。 这只是您必须理解和记住的怪癖之一。

我不确定升降是否仅限于JavaScript。 我从未在其他地方听说过它,但这并不一定意味着它在其他语言中不存在。

alex answered 2020-07-25T14:56:57Z
9 votes

那篇文章的前两个例子写得不好。 错误的代码显然会导致错误和混乱。 让我为您提供这些示例的重构版本。 您会发现这里没有混乱...

示例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”。 明显。 这里也不要混淆。

Šime Vidas answered 2020-07-25T14:57:43Z
6 votes

“吊装”不是ECMAScript标准的一部分,但它确实表示在函数的开头声明了函数内部的变量,无论该函数在代码中的位置如何。

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

内部Javascript会在警报之前声明myvar,显示警报,然后将myvar分配给“本地值”。

因此,Javascript会将代码插入为:

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

这就是为什么“ Java的好部分”有一个指导原则,要求您在函数顶部声明变量。

来源:[http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-javascript-hoisting-explained/]

“请解释是否有一种设计模式实际上利用了这种语言功能。” “吊装”不是功能,而是Java语言解释器如何构造代码的结果,因为该语言使用函数作用域。

“哪些设计模式实际上利用了JavaScript的提升行为?”答:没有。

Fireblaze answered 2020-07-25T14:58:35Z
5 votes

我认为提升有用的一个方面是由于将函数视为一流对象这一事实。 例如:

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 ()
   {
      //...
   }
}

我想它们只能悬挂功能对象,但是我认为这与在语言中将功能视为头等公民的哲学是矛盾的。

Mike Christensen answered 2020-07-25T14:59:08Z
4 votes

这是一个真正的用例(尽管简化为伪代码),来自真正想利用野外吊装好处的人。

我最近写了这个脚本来处理简单的表单验证和提交。 每个函数声明尽可能地调用以下内容。 这具有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' );
  }
} );
Barney answered 2020-07-25T14:59:43Z
1 votes

基于语言的好奇心,我喜欢提问的风格。 显然,除非有人绝对确定住家地址不能被以后使用的人发现,否则没有人应该真正使用起吊功能。

我只能想象几个琐碎的案例。 要利用的基本属性是,可以声明(但未定义)变量,然后仅在一行代码中进行赋值,但事件在两个不同的不同点进行解释。

在循环末尾声明(当然不是.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被拒绝。

再次。 不要这样!

leebriggs answered 2020-07-25T15:00:22Z
0 votes

如果考虑其他语言的编写方式(C ++ / Java)以及它们的类模式的使用方式,则可以利用提升方式编写类似的模式来构建原型。

Alex Boisselle answered 2020-07-25T15:00:42Z
translate from https://stackoverflow.com:/questions/8597501/which-design-patterns-take-advantage-of-javascripts-hoisting-behavior