2016-01-30 67 views
0

我最近偶然发现了这个问题,经过一段时间的阅读后,我无法找到满足此用例的答案。Javascript覆盖回调的范围

我想实现以下行为在JavaScript

// Lets assume we have some variable defined in global scope 
var a = {val: 0} 

// What I want here is a function that sets a.val = newVal 
// and then calls the callback. 
var start = function(newVal, cb) { 
    ??? 
} 

// such that 
start(1, function() { 
    setTimeout(function() { 
    console.log(a.val) // 1 
    }, 1000) 
}) 

// and 
start(2,function() { 
    console.log(a.val) // 2 
}) 

// but in the original scope 
console.log(a.val) // 0 

换句话说,我要寻找一个方式在不同的全球范围内“包装”的回调。我知道你可以做类似的事情,传递一个环境或使用它;但这种方法总是迫使回调函数来引用环境明确,把回调代码转换成类似

start(2,function() { 
    console.log(env.a.val) // 2 
}) 

我专门找直接从回调中保留使用全球参考的可能性的解决方案的开始。

随意使用任何ES6/ES7功能,可以以某种方式将其擦除或与节点兼容,但这并不意味着生产代码只是一个有趣的练习。

编辑: 我会解释一般问题,因为许多人建议这个解决方案可能不是我真正需要的。

我最近了解到STM(https://wiki.haskell.org/Software_transactional_memory) ,并想在js中玩类似的想法。 当然,js在一个线程上运行,但是想法是为在原子块中运行的不同回调提供相同级别的隔离。

用户拥有某种共享事务变量。这个 变量的操作必须以原子方式包装。隐藏的内容是,原子块中的操作不是在实际的TVar上执行,而是在一些MockTVar上执行,它只记录日志中的所有读取和写入操作。 当您调用done时,将检查日志以查看所执行的操作是否与TVars的当前状态一致;如果是现在在实际的TVars上执行的更新并且我们完成了(这被称为提交)。如果不是,日志被丢弃并且回调再次运行。这是代码

var x = new TVar(2) 

// this is process a 
process.nextTick(function() { 
    atomically(x, function(x, done) { 
    a = x.readTVar() 

    setTimeout(function() { 
     x.writeTVar(a+1) 
     console.log('Process a increased, x = ', x.readTVar()) 
     done() 
    }, 2000) 
    }) 
}) 

// this is process b 
process.nextTick(function() { 
    atomically(x, function(x, done) { 
    var a = x.readTVar() 
    x.writeTVar(a+1) 
    console.log('Process b increased, x = ', x.readTVar()) 
    done() 
}) 

})

在该示例性过程的一个小例子一个将尝试提交但由于处理过程b改变x的值(和提交该改变之前的),提交将失败,回调将再次运行。

正如你所看到的,我在回调中返回了mockTVars,但是我觉得这有点难看,原因有两个:1)如果你想锁定多个变量(并且你通常会这样做),我别无选择但要返回一系列的mockTVars,如果他想干净地使用它们,则迫使用户逐一提取它们。 2)如果用户希望能够在不失主意的情况下推理发生的事情,则确保传递给回调函数的mockTVar的名称与实际的TVar的名称匹配。我的意思是,在这条线

atomically(x, function(x, done) {..}) 

它是由用户使用相同的名称都是指实际的TVar和嘲笑的TVar(名称为x在这个例子中)。

我希望这个解释很有帮助。感谢所有花时间帮助我的人

+3

为什么你想这样做?确保这不是[xy问题](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – jeremy

+0

我认为这种类型的事情只能在命令lisp与宏或Emacs lisp动态变量。 – jcubic

+0

你基本上是问如何使用单个变量来存储多个值,这是不可能的。 – michael

回答

1

我还是希望你能描述一下你试图解决的实际问题,但是这里有一个想法,它使得全局对象的副本,将它传递给回调函数回调可以使用与全局相同的名称,然后它将“覆盖”对该范围的全局访问权限。

var a = {val: 0, otherVal: "hello"} ; 
 
    
 
    function start(newVal, cb) { 
 
     var copy = {}; 
 
     Object.assign(copy, a); 
 
     copy.val = newVal; 
 
     cb(copy); 
 
    } 
 
    
 
    log("Before start, a.val = " + a.val); 
 
    start(1, function(a) { 
 
     // locally scoped copy of "a" here that is different than the global "a" 
 
     log("Beginning of start, a.val = " + a.val) // 1 
 
     a.val = 2; 
 
     log("End of start, a.val = " + a.val) // 2 
 
    }); 
 

 
    log("After start, a.val = " + a.val); 
 
    
 
    function log(x) { 
 
     document.write(x + "<br>"); 
 
    }

+0

谢谢,但这不是我真正想要的,我特别试图避免在回调中传递变量。我实际上发现了一个veeeery丑陋的解决方案,它包括在a上设置值,然后在回调函数中调用'toString()',然后在启动函数中调用'toString()'。这基本上使得它的工作方式就好像回调是在启动函数中定义的,并给出了期望的范围。这种方法有效,但感觉有点像治疗比起病最为严重:P – nazrhom

+0

@ user2512250 - 你不能在一个范围内改变'a'的定义而不修改'a'或在其中定义一个新的本地范围。这些只是Javascript的规则。如果你排除了这两种选择,那么目前尚不清楚你要求什么。这些是你的选择,除了试图动态修改代码之外,还有一些非常严重的黑客攻击。 – jfriend00

+0

我不知道我明白你的意思。我发布的解决方案的问题更多的是在函数上调用'toString',然后调用该字符串的eval,而不是触及'a'的值。我想排除的是需要在回调中传递修改后的值。 – nazrhom