Is it possible to achieve dynamic scoping in JavaS

2019-01-03 05:46发布

JavaScript has lexical scoping which means that non-local variables accessed from within a function are resolved to variables present in the parents' scope of that function when it was defined. This is in contrast to dynamic scoping in which non-local variables accessed from within a function are resolved to variables present in the calling scope of that function when it is called.

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?

The above program prints 1 and then 2 in a lexically scoped language, and it prints 3 and then 1 in a dynamically scoped language. Since JavaScript is lexically scoped it will print 1 and then 2 as demonstrated below:

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

Although JavaScript doesn't support dynamic scoping we can implement it using eval as follows:

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

I would like to know if there exists another possible way to achieve the same result without resorting to eval.

Edit: This is what I'm trying to implement without using 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());

I know that it's not a very good example but the general idea is to use dynamic scoping to create closures. I think this pattern has a lot of potential.

7条回答
劫难
2楼-- · 2019-01-03 06:25

You can simulate dynamic scoping using global variables, if you have a way to do syntactic sugar (e.g. macros with gensyms) and if you have unwind-protect.

The macro can appear to rebind the dynamic variable by saving its value in a hidden lexical and then assigning a new value. The unwind-protect code ensures that no matter how that block terminates, the original value of the global will be restored.

Lisp pseudocode:

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

Of course, this is not a thread-safe way of doing dynamic scope, but if you aren't doing threading, it will do! We would hide it behind a macro like:

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

So if you have unwind-protect in Javascript, and a macro preprocessor to generate some syntactic sugar (so you're not manually open-coding all your saves and unwind-protected restores) it might be doable.

查看更多
Melony?
3楼-- · 2019-01-03 06:25

I know this doesn't exactly answer the question but it's too much code to put into a comment.

As an alternative approach, you may want to look into ExtJS's extend function. This is how it works:

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

With public properties instead of private variables:

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;
    }
});
查看更多
仙女界的扛把子
4楼-- · 2019-01-03 06:30

To add a note on this topic:

In JavaScript whenever you make use of:

  • function declaration statement or function definition expression then local variables will have Lexical Scoping.

  • Function constructor then local variables will refer to the global scope (top-level code)

  • this is the only built-in object in JavaScript that has a dynamic scoping and is set through the execution (or invocation) context.

So to answer to your question, In JS the this is already dynamically scoped feature of the language and you even don't need to emulate another one.

查看更多
爱情/是我丢掉的垃圾
5楼-- · 2019-01-03 06:34

I don't think so.

That is not how the language works. You have to use something other than variables to refer to this state information. The most "natural" way being to use properties of this, I guess.

查看更多
等我变得足够好
6楼-- · 2019-01-03 06:34

In your case, instead of trying to use dynamic scoping to set the constructor, what if you used the return value?

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());
查看更多
Juvenile、少年°
7楼-- · 2019-01-03 06:36

Why didn't anybody say this?

You can pass variables from the calling scope into the called function by binding it a context.

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 );

Perhaps it's worth mentioning that in strict mode, all functions have no "environment" sent to them by default (this is null). In non strict mode the global object is the default this value for a called function.

查看更多
登录 后发表回答