48

Ben Cherryの優れた記事では、JavaScriptでの巻き上げについて適切に説明しています。しかし、私の問題は、この悪名高い混乱の加害者のユースケースを想像できないことです。この言語機能を実際に利用するデザインパターンがあるかどうかを説明してください。

第二に、スコープホイストはJavaScriptに固有のものですか?

更新---好奇心を満たす答えに賞金を追加しています:JavaScriptの巻き上げ動作を実際に利用しているデザインパターンはどれですか?JavaScriptが巻き上げをサポートしている理由は理解していますが、この機能をどのように活用できるか知りたいです。

4

9 に答える 9

21

可変巻き上げ

巻き上げの最も単純な用途の 1 つは可変巻き上げです。変数の巻き上げがない場合、これは次をスローしReferenceErrorます。

var bar = foo; 
var foo;

これはすぐには役に立たないように思えますが、次のようなことができます。

var myCoolJS = myCoolJS || {};

これは基本的に、それがどのように見えるかを意味します:存在する場合myCoolJSmyCoolJSそれであり、存在しない場合は新しいオブジェクトです。2 番目は、この変数宣言が巻き上げられているため、まだ存在しない場合はmyCoolJSスローしません。ReferenceErrormyCoolJS

これにより、厄介なtypeof myCoolJS != 'undefined'チェックを行う必要がなくなります。

機能の巻き上げ

関数ホイストは、複数のスクリプトを 1 つに結合する場合に特に役立ちます。たとえば、CommonJS モジュールの軽量なビルド時の実装を作成しました。これは、node.js にあるのと同じ、、、および機能を提供modulerequireますexports。必要なモジュールを複数のファイルで構成できるようにツールを作成しました。たとえば、 (「本体ファイル」) と(「ヘッダー ファイル」)require('/foo')の 2 つのファイルで構成されるモジュールが生成される可能性があります。foo.jsfoo.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...
于 2012-03-05T16:51:50.287 に答える
15

巻き上げの用途は次のとおりです。

(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
于 2012-03-03T04:32:36.053 に答える
11

JavaScript にはブロック スコープがありません (ここでは忘れましょうlet)。そのため、変数宣言はすべての関数に対して宣言され、JavaScriptにはスコープがあります。

そう考えると、JavaScript 巻き上げの方が理にかなっているかもしれません。

巻き上げについて覚えていれば、バグや混乱の原因にはなりません。これは、理解して覚えておく必要がある癖の 1 つにすぎません。

巻き上げがJavaScriptに限定されているかどうかはわかりません。他の言語では聞いたことがありませんが、必ずしも他の言語に存在しないというわけではありません。

于 2011-12-21T23:02:43.047 に答える
9

その記事の最初の 2 つの例は、単に書き方が悪いだけです。悪いコードは明らかにバグや混乱につながります。これらの例のリファクタリングされたバージョンを紹介しましょう。ここには混乱がないことがわかります...

例 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」が表示されます。明らかに。ここでも混乱はありません。

于 2011-12-21T23:32:40.987 に答える
7

「ホイスト」は 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/

「この言語機能を実際に利用したデザインパターンがあれば教えてください。」「巻き上げ」は機能ではなく、言語が関数スコープを使用するため、Javascript インタープリターがコードを構造化する方法の結果です。

「JavaScript の巻き上げ動作を実際に利用する設計パターンはどれですか?」 回答: なし。

于 2012-03-06T15:35:18.563 に答える
5

巻き上げが役立つのは、関数がファーストクラスのオブジェクトとして扱われるためだと思います。例えば:

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

持ち上げられた関数オブジェクトしか持てなかったと思いますが、それは関数を言語の第一級市民として扱うべきであるという哲学と矛盾すると思います。

于 2011-12-21T23:19:34.093 に答える
4

これは、実際に巻き上げの利点を実際に使用したい人からの実際の使用例 (疑似コードに縮小されていますが) です。

私は最近、単純なフォームの検証と送信を処理するためにこのスクリプトを作成しました。可能な限り、各関数宣言は以下を呼び出します。これには、読みやすさに関して 2 つの主な利点があります。

  1. 論理シーケンス: コードにはシーケンシャル フローがあります。これは、関数が宣言される前に常に呼び出されることを意味します。これは、複雑度の低い関数で使用すると、物事が比較的フラットに保たれ、ソースの直前に関数の呼び出しコンテキストを把握できるという利点があります。コードをたどるには、下にスクロールするだけでよく (上にスクロールする必要はありません)、可能な限り、まったくスクロールしないか、ほとんどスクロールしません。
  2. 低い参照オーバーヘッド: 私はすべての変数宣言を各スコープの先頭に保持することを好みます。これにより、読者は、関数の本体を読み取る前に、関数が必要とするすべての可動部分を認識することができますが、呼び出されたすべての関数のソース コードを読んでアイデアを得る人は誰もいません。現在のスコープの機能。このメソッドを使用すると、宣言前に関数参照に遭遇することはありません。最初はばかげているように聞こえますが、実際には認知オーバーヘッドが削減されます。暗黙のremember thisを使用して関数のソースが与えられることはありません—後で使用します—代わりに、呼び出されたコンテキストを知ってから関数ソースを読み取るだけですの。
$( 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' );
  }
} );
于 2014-01-28T13:35:07.067 に答える
1

言語への好奇心に基づいた質問スタイルが好きです。後で使用する可能性のある人が自宅の住所を発見できないことが絶対に確実である場合を除いて、ホイストを機能として実際に使用するべきではないことは明らかです。

いくつかの些細なケースしか想像できません。悪用する基本的な特性は、変数を宣言 (ただし未定義) してから 1 行のコードで代入できることですが、イベントは 2 つの異なるポイントで解釈されます。

ループの最後に宣言すると (もちろんスコープを設定するので .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 は拒否されます。

また。これをしないでください!

于 2012-03-07T01:01:55.217 に答える