Aadit M Shah Aadit M Shah - 1 month ago 11
Javascript Question

Is it possible to achieve dynamic scoping in JavaScript without resorting to eval?

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.

Answer

Attribute lookup falls through the prototype chain, which matches quite well to dynamic scopes. Just pass your own environment of dynamically-scoped variables to use around instead of using Javascript's lexical scoping.


// 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