28

JavaScript にはレキシカル スコープがあります。つまり、関数内からアクセスされる非ローカル変数は、関数が定義されたときにその関数の親のスコープに存在する変数に解決されます。これは、関数内からアクセスされる非ローカル変数が、呼び出されたときにその関数の呼び出しスコープに存在する変数に解決される動的スコープとは対照的です。

x=1
function g () { echo $x ; x=2 ; }
function f () { local x=3 ; g ; }
f # does this print 1, or 3?
echo $x # does this print 1, or 2?

上記のプログラムは、レキシカル スコープ言語で 1 と 2 を出力し、ダイナミック スコープ言語で 3 と 1 を出力します。JavaScript はレキシカル スコープであるため、以下に示すように 1 を出力し、次に 2 を出力します。

var print = x => console.log(x);

var x = 1;

function g() {
    print(x);
    x = 2;
}

function f() {
    var x = 3;
    g();
}

f();           // prints 1

print(x);      // prints 2

evalJavaScript は動的スコープをサポートしていませんが、次のように実装できます。

var print = x => console.log(x);

var x = 1;

function g() {
    print(x);
    x = 2;
}

function f() {
    // create a new local copy of `g` bound to the current scope
    // explicitly assign it to a variable since functions can be unnamed
    // place this code in the beginning of the function - manual hoisting
    var g_ = eval("(" + String(g) + ")");
    var x = 3;
    g_();
}

f();                         // prints 3

print(x);                    // prints 1

に頼らずに同じ結果を達成する別の方法があるかどうかを知りたいですeval

編集:これは、使用せずに実装しようとしているものですeval:

var print = x => console.log(x);

function Class(clazz) {
    return function () {
        var constructor;
        var Constructor = eval("(" + String(clazz) + ")");
        Constructor.apply(this, arguments);
        constructor.apply(this, arguments);
    };
}

var Rectangle = new Class(function () {
    var width, height;

    constructor = function (w, h) {
        width = w;
        height = h;
    };

    this.area = function () {
        return width * height;
    };
});

var rectangle = new Rectangle(2, 3);
print(rectangle.area());

あまり良い例ではないことは承知していますが、一般的な考え方は、動的スコープを使用してクロージャーを作成することです。このパターンの可能性は大きいと思います。

4

7 に答える 7

10

属性ルックアップは、動的スコープに非常によく一致するプロトタイプ チェーンを通過します。Javascript のレキシカル スコープを使用する代わりに、動的スコープ変数の独自の環境を渡して使用するだけです。


// Polyfill for older browsers.  Newer ones already have Object.create.
if (!Object.create) {
  // You don't need to understand this, but
  Object.create = function(proto) {
    // this constructor does nothing,
    function cons() {}
    // and we assign it a prototype,
    cons.prototype = proto;
    // so that the new object has the given proto without any side-effects.
    return new cons();
  };
}

// Define a new class
function dyn() {}
// with a method which returns a copy-on-write clone of the object.
dyn.prototype.cow = function() {
  // An empty object is created with this object as its prototype.  Javascript
  // will follow the prototype chain to read an attribute, but set new values
  // on the new object.
  return Object.create(this);
}

// Given an environment, read x then write to it.
function g(env) {
  console.log(env.x);
  env.x = 2;
}
// Given an environment, write x then call f with a clone.
function f(env) {
  env.x = 3;
  g(env.cow());
}

// Create a new environment.
var env = new dyn();
// env -> {__proto__: dyn.prototype}
// Set a value in it.
env.x = 1;
// env -> {x: 1}  // Still has dyn.prototype, but it's long so I'll leave it out.

f(env.cow());
// f():
//   env -> {__proto__: {x: 1}}  // Called with env = caller's env.cow()
//   > env.x = 3
//   env -> {x: 3, __proto__: {x: 1}}  // New value is set in current object
//   g():
//     env -> {__proto__: {x: 3, __proto__: {x: 1}}}  // caller's env.cow()
//     env.x -> 3  // attribute lookup follows chain of prototypes
//     > env.x = 2
//     env -> {x: 2, __proto__: {x: 3, __proto__: {x: 1}}}

console.log(env.x);
// env -> {x: 1}  // still unchanged!
// env.x -> 1
于 2012-04-08T07:08:54.223 に答える
2

なぜ誰も言わなかったのthisですか?

コンテキストをバインドすることにより、呼び出しスコープから呼び出された関数に変数を渡すことができます。

function called_function () {
   console.log(`My env ${this} my args ${arguments}`, this, arguments);
   console.log(`JS Dynamic ? ${this.jsDynamic}`);
}

function calling_function () {
   const env = Object.create(null);
   env.jsDynamic = 'really?';

   ... 

   // no environment
   called_function( 'hey', 50 );

   // passed in environment 
   called_function.bind( env )( 'hey', 50 );

おそらく、厳密モードでは、すべての関数にデフォルトで「環境」が送信されないことに言及する価値があります(thisnullです)。非厳密モードでは、グローバル オブジェクトはthis呼び出された関数のデフォルト値です。

于 2015-06-12T14:11:47.310 に答える
2

私はそうは思わない。

それは言語の仕組みではありません。この状態情報を参照するには、変数以外のものを使用する必要があります。のプロパティを使用するのが最も「自然な」方法thisだと思います。

于 2012-04-08T06:34:06.693 に答える
2

あなたの場合、動的スコープを使用してコンストラクターを設定しようとする代わりに、戻り値を使用するとどうなりますか?

function Class(clazz) {
    return function () {
        clazz.apply(this, arguments).apply(this, arguments);
    };
}

var Rectangle = new Class(function () {
    var width, height;

    this.area = function () {
        return width * height;
    };

    // Constructor
    return function (w, h) {
        width = w;
        height = h;
    };
});

var rectangle = new Rectangle(2, 3);
console.log(rectangle.area());
于 2012-04-08T07:58:35.770 に答える
0

これが質問に正確に答えるわけではないことはわかっていますが、コメントに入れるにはコードが多すぎます。

extend別のアプローチとして、ExtJS の機能を調べることもできます。これがどのように機能するかです:

var Rectangle = Ext.extend(Object, {
    constructor: function (w, h) {
        var width = w, height = h;
        this.area = function () {
            return width * height;
        };
    }
});

プライベート変数の代わりにパブリック プロパティを使用:

var Rectangle = Ext.extend(Object, {
    width: 0,
    height: 0,  

    constructor: function (w, h) {
        this.width = w;
        this.height = h;
    },

    area: function () {
        return this.width * this.height;
    }
});
于 2012-04-08T09:27:45.673 に答える
0

シンタックス シュガー (gensyms を含むマクロなど) を実行する方法があり、unwind-protect がある場合は、グローバル変数を使用して動的スコープをシミュレートできます。

マクロは、非表示のレキシカルに値を保存してから新しい値を割り当てることにより、動的変数を再バインドするように見える場合があります。unwind-protect コードは、そのブロックがどのように終了しても、グローバルの元の値が復元されることを保証します。

Lisp 疑似コード:

(let ((#:hidden-local dynamic-var))
  (unwind-protect
    (progn (setf dynamic-var new-value)
           body of code ...)
    (set dynamic-var #:hidden-local)))

もちろん、これは動的スコープを実行するためのスレッドセーフな方法ではありませんが、スレッド化を行っていない場合は問題ありません。次のようなマクロの背後に隠します。

(dlet ((dynamic-var new-value))
   body of code ...)

したがって、Javascript に unwind-protect があり、マクロ プリプロセッサを使用してシンタックス シュガーを生成する場合 (すべての保存と unwind-protected 復元を手動でオープンコーディングする必要がない場合) は実行可能かもしれません。

于 2012-04-08T06:34:23.687 に答える