是否有可能实现在JavaScript动态作用域而不诉诸EVAL?是否有可能实现在JavaScript

2019-05-13 11:11发布

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

我知道,这不是一个很好的例子,但总的想法是使用动态作用域创建关闭。 我觉得这种模式有很大的潜力。

Answer 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


Answer 2:

添加便笺上这个话题:

在JavaScript每当你使用:

  • 函数的声明语句或函数定义的表达,则局部变量将有词法范围

  • Function构造函数 ,然后局部变量是指全球范围内(顶级代码)

  • this是唯一的内置对象在JavaScript具有动态作用域并通过执行(或调用)上下文集合。

因此,要回答你的问题,在JS的this已经是动态作用域的语言的功能,你甚至不需要模仿另一个。



Answer 3:

我不认为如此。

这是语言不是如何工作的。 你必须用其他的东西比变量来指向这个状态信息。 最“自然”的方式是使用属性this ,我猜。



Answer 4:

在你的情况,而不是试图用动态作用域设置构造函数,如果你使用的是什么返回值?

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


Answer 5:

为什么没有人说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为调用的函数值。



Answer 6:

您可以使用全局变量模拟动态作用域,如果你有办法做到语法糖(如宏,并与gensyms),如果你有放松保护。

宏可以出现在一个隐藏的词汇保存它的值,然后分配一个新值重新绑定动态变量。 退绕保护代码,保证了无论怎么说块终止,全球的原始值将被恢复。

Lisp的伪代码:

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

当然,这不是做动态范围的线程安全的方式,但如果你不这样做线程,它会做! 我们将其隐藏宏后面,如:

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

所以,如果你在JavaScript中放松身心,保护,和宏预处理程序生成一些语法糖(所以你手动不是开放式编码所有保存和放松保护恢复),这可能是可行的。



Answer 7:

我知道这并不完全回答这个问题,但太多的代码放到一个评论。

作为一个替代方法,你可能要考虑的ExtJS的extend功能。 这是如何工作的:

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

随着公共属性,而非私有变量:

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


文章来源: Is it possible to achieve dynamic scoping in JavaScript without resorting to eval?