一尘不染

是否可以在不使用eval的情况下在JavaScript中实现动态作用域?

javascript

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

尽管JavaScript不支持动态范围界定,但我们可以使用eval以下方法实现它:

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

我知道这不是一个很好的示例,但总体思路是使用动态作用域来创建闭包。我认为这种模式具有很大的潜力。


阅读 248

收藏
2020-05-01

共1个答案

一尘不染

属性查找贯穿原型链,与动态范围非常匹配。只需传递您自己的动态范围变量环境即可使用,而不是使用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
2020-05-01