是否可以在不求助于 eval 的情况下在 JavaScript 中实现动态范围?

IT技术 javascript eval dynamic-scope
2021-01-24 01:44:53

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

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

6个回答

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

在 JavaScript 中,每当您使用:

  • 函数声明语句或函数定义表达式则局部变量将具有词法作用域

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

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

因此,要回答您的问题,在 JS 中this,该语言已经是动态范围的功能,您甚至不需要模拟另一个功能。

我很感激知道this谢谢你。
2021-03-30 01:44:53

属性查找属于原型链,它与动态范围非常匹配。只需传递您自己的动态范围变量环境即可使用,而不是使用 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
好吧,我终于明白你的代码了。您正在使用调用的构造函数dyn来模拟全局范围。每个函数都有一个被调用的形式参数env,它相当于该函数的激活对象。env对象由调用者提供。对于函数,f我们提供 的全局实例dyn因为g我们提供了全局的写时复制实例,dyn将第一个推到原型链上。这是一个直观的答案,范围查找确实类似于遍历原型链。加上一个用于寻找替代方案而不是诉诸使用eval. =)
2021-03-20 01:44:53
也感谢您与我分享该链接 - 虽然我没有从中学到任何我不知道的东西,但这是一个很好的姿态。赞赏。
2021-03-21 01:44:53
你的代码很混乱。介意提供有意义的评论来解释你在做什么吗?
2021-03-23 01:44:53
@AaditMShah 很高兴为您提供帮助。实际上,我的第一个想法(with用于扩展范围)不起作用......这一切都变得更好,因为它是 Javascript 的一个糟糕的特性 :)
2021-03-28 01:44:53
@AaditMShah 评论。并阅读面向对象的 JavaScript 简介
2021-04-08 01:44:53

我不这么认为。

这不是语言的工作方式。你必须使用变量以外的东西来引用这个状态信息。this我猜最“自然”的方式是使用 的属性

在您的情况下,如果您使用返回值,而不是尝试使用动态范围设置构造函数呢?

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());
我可以做到这一点,但正如我在对我的问题的最后评论中所解释的那样 - 使用动态范围允许我将尽可能多的变量注入到函数的范围内。您只能返回一个值。
2021-03-25 01:44:53

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

也许值得一提的是,在严格模式下,所有函数默认都没有发送给它们的“环境”(this为空)。在非严格模式下,全局对象是被this调用函数的默认值。

我想你可以这样看。
2021-04-05 01:44:53
人们已经说过thisArmanThilo如果你考虑一下,这this只是另一个论点。所以,你也不需要this只需使用一个额外的参数。这正是ephemient在他的回答中所描述的,这就是我接受它的原因。您的回答并没有真正增加任何value。
2021-04-06 01:44:53