是否有可能在JavaScript中实现动态范围而无需使用eval?

Aad*_*hah 26 javascript eval dynamic-scope

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?
Run Code Online (Sandbox Code Playgroud)

上面的程序以词法范围的语言打印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
Run Code Online (Sandbox Code Playgroud)

虽然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
Run Code Online (Sandbox Code Playgroud)

我想知道是否存在另一种可能的方法来实现相同的结果而不诉诸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());
Run Code Online (Sandbox Code Playgroud)

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

Arm*_*ian 12

要添加关于此主题的注释:

在JavaScript中,只要您使用:

  • 函数声明语句或函数定义表达式然后局部变量将具有词法范围.

  • 函数构造函数然后局部变量将引用全局范围(顶级代码)

  • this 是JavaScript中唯一具有动态范围的内置对象,通过执行(或调用)上下文设置.

所以为了回答你的问题,在JS中,this这已经是语言的动态范围特征,你甚至不需要模仿另一个.

  • 我很高兴知道'这个'.谢谢. (3认同)

eph*_*ent 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();
  };
}
Run Code Online (Sandbox Code Playgroud)
// 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);
}
Run Code Online (Sandbox Code Playgroud)
// 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());
}
Run Code Online (Sandbox Code Playgroud)
// 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
Run Code Online (Sandbox Code Playgroud)