如何在JavaScript中模拟指针?
我正在创建一种可编译为 Swift、Rust 和 JavaScript 的语言(或者至少在尝试)。 Rust 和 Swift 都使用指针/引用/解除引用等,而 JavaScript 则不使用。因此,在类似 Rust 的语言中,您可能会这样做:
fn update(x) {
*x++
}
fn main() {
let i = 0
update(&i)
log(i) #=> 1
}
在类似 JavaScript 的语言中,如果您这样做,那么它将失败:
function update(x) {
x++
}
function main() {
let i = 0
update(i)
log(i) #=> 0
}
因为值在传入时被克隆(正如我们显然知道的那样)。
所以我首先考虑的是这样做:
function update(scopeWithI) {
scopeWithI.i++
}
function main() {
let i = 0
let scopeWithI = { i }
update(scopeWithI)
i = scopeWithI.i
log(i) #=> 1
}
但这需要进行大量额外的处理,而且似乎没有必要。相反,我可能会尝试编译为:
function update(scopeWithI) {
scopeWithI.i++
}
function main() {
let scope = {}
scope.i = 0
update(scope)
log(scope.i) #=> 1
}
这意味着您创建的每个嵌套作用域,您都必须开始手动创建/管理作用域链。实际上这是行不通的,因为 update
被硬编码为 i
。因此,您可能必须传入您想要的变量名称。
function update(scope, ...names) {
scope[names[0]]++
}
但接下来的情况就像是:
function update(scope, ...names) {
scope[names[0]]++
}
function main() {
let scope = {}
scope.i = 0
if random() > 0.5
let childScope = { scope }
childScope.x = 0
update(childScope, ['i'])
update(childScope, ['x'])
update(childScope, ['x'])
log(childScope.x) #=> 2
else
update(childScope, ['i'])
log(scope.i) #=> 1
}
看来这可能会让我们到达某个地方。
所以,通用的解决方案是将作用域作为函数的第一个参数。
function add(scope, name1, name2) {
return scope[name1] + scope[name2]
}
解除引用意味着直接从作用域读取值,而传递引用(如 Rust 或 C 中的 &name
)则意味着传递作用域和名称。
这样的事情会起作用吗?或者更准确地说,需要更改或添加什么?它需要变得比这更复杂吗?
我想尝试找到一种方法将面向指针的代码转换为JavaScript(转译),而不是首先尝试找出看起来不那么直接的复杂得多的方法,并通过重新定义a来避免JavaScript中的指针模拟很多方法。似乎避免在 JavaScript 中使用任何指针会更难弄清楚,所以我试图看看是否可以在 JavaScript 中模拟指针类型的系统。
为了避免指针模拟,您必须重新定义方法。
update(x) {
*x++
}
必须在各处更改该函数的外部用法。所以这样:
main() {
let i = 0
update(&i)
}
会变成:
main() {
let i = 0
i++ // inline the thing
}
对于这个简单的情况,这很好,但对于更复杂的函数,它开始看起来像宏,并且可能会变得复杂。
因此,我们没有改变外部用法,而是让您必须传递范围。
另一种方法可能是让每个变量都是一个具有值的对象,所以它更像是:
update(x) {
x.value++
}
main() {
let i = { value: 0 }
update(i)
}
那么我自己在想,如何处理对引用的引用呢?
update2(x) {
update(&x)
}
update(x) {
*x++
}
main() {
let i = 0
update2(&i)
}
在我描述的系统中,这将是这样的:
update2(x) {
// then what?
let y = { value: x }
update(y)
}
update(x) {
// like this?
x.value.value++
}
main() {
let i = { value: 0 }
update2(i)
}
所以看来这实际上行不通。
I am creating a language which is compilable to Swift, Rust, and JavaScript (or at least trying). Rust and Swift both use pointers/references/dereferencing/etc., while JavaScript does not. So in a Rust-like language, you might do something like this:
fn update(x) {
*x++
}
fn main() {
let i = 0
update(&i)
log(i) #=> 1
}
In a JavaScript-like language, if you did this then it would fail:
function update(x) {
x++
}
function main() {
let i = 0
update(i)
log(i) #=> 0
}
Because the value is cloned as it is passed in (as we obviously know).
So what I am thinking about is doing this at first:
function update(scopeWithI) {
scopeWithI.i++
}
function main() {
let i = 0
let scopeWithI = { i }
update(scopeWithI)
i = scopeWithI.i
log(i) #=> 1
}
But that is a lot of extra processing going on, and kind of unnecessary it seems. Instead I might try compiling to this:
function update(scopeWithI) {
scopeWithI.i++
}
function main() {
let scope = {}
scope.i = 0
update(scope)
log(scope.i) #=> 1
}
This would mean every nested scope you create, you would have to start manually creating/managing the scope chain. And actually that wouldn't work because update
is hardcoded to i
. So you might have to pass in what the variable name is you want.
function update(scope, ...names) {
scope[names[0]]++
}
But then it's like:
function update(scope, ...names) {
scope[names[0]]++
}
function main() {
let scope = {}
scope.i = 0
if random() > 0.5
let childScope = { scope }
childScope.x = 0
update(childScope, ['i'])
update(childScope, ['x'])
update(childScope, ['x'])
log(childScope.x) #=> 2
else
update(childScope, ['i'])
log(scope.i) #=> 1
}
So that seems like it might get us somewhere.
So then it's like, the generic solution is to have scope be the first parameter to a function.
function add(scope, name1, name2) {
return scope[name1] + scope[name2]
}
Dereferencing means reading a value directly from the scope, while passing a reference (like &name
in Rust or C), would mean passing the scope and the name.
Will something like this work? Or better put, what needs to be changed or added? Does it need to get any more complicated than this?
I would like to try and find a way to transform the pointer-oriented code into JavaScript (transpilation), without at first trying to figure out the seemingly much more complicated approach of not being so direct, and avoiding pointer simulation in JavaScript by redefining a lot of the methods. It seems that avoiding any pointer use in JavaScript would be way harder to figure out, so I am trying to see if a pointer sort of system would be possible to simulate in JavaScript.
To avoid pointer simulation, you would have to redefine methods.
update(x) {
*x++
}
Would have to change the outer usage of the function everywhere. So this:
main() {
let i = 0
update(&i)
}
Would become:
main() {
let i = 0
i++ // inline the thing
}
For this simple case it's fine, but for a more complicated function it starts to seem like macros and might get complicated.
So instead of changing the outer usage, we make it so you have to pass the scope.
Another approach might be to have every variable be an object with a value, so it's more like:
update(x) {
x.value++
}
main() {
let i = { value: 0 }
update(i)
}
So then I'm thinking to myself, how to handle references to references then?
update2(x) {
update(&x)
}
update(x) {
*x++
}
main() {
let i = 0
update2(&i)
}
In the system i described, that would be like:
update2(x) {
// then what?
let y = { value: x }
update(y)
}
update(x) {
// like this?
x.value.value++
}
main() {
let i = { value: 0 }
update2(i)
}
So it seems this wouldn't really work.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
不用说,JavaScript 确实不有通过引用传递参数的通用机制。
“引用”一词可能会产生一些混淆,因为在 JavaScript 中,人们可以将对象传递给函数——它们是引用——但这是一种按值调用机制。引用调用实际上意味着参数变量是调用者变量的别名,因此为该别名赋值相当于为调用者变量赋值。除了一些非常特殊的情况(例如 <非严格模式下的 code>arguments 奇异对象,或
export
机制,或带有var
链接window
对象,这些都不能以最佳实践方式帮助您解决您的情况),JavaScript 中没有这样的变量别名机制。下面是一个关于如何在非严格模式下在浏览器上下文中“利用”
var
效果的示例:简而言之,除了旧 JavaScript 语言中的一些糟糕设计(草率模式)之外,这在一般 JavaScript 中是不可能的。所有有效的尝试都是通过设置给定对象的属性之一来对给定对象执行突变。您不能希望有一个函数可以将参数变量分配给参数变量,从而修改调用者的变量。这是不可能的——按照设计。请注意赋值和变异之间的重要区别。
如果调用者的变量(不是属性)需要分配一个新值(不仅仅是突变,而是真正的赋值),那么该赋值必须 > 发生在那个变量上——函数无法为调用者做的事情。
因此,在 JavaScript 中执行此类赋值的方法是让函数返回调用者需要重新赋值的任何内容,并且执行该赋值仍然是调用者的责任:
当涉及多个变量时,让函数返回一个“打包”对象,调用者可以将其解构回自己的变量:
Needless to say, but JavaScript does not have a general mechanism to pass arguments by reference.
There can be some confusion around the term "reference", as in JavaScript one can pass objects to functions -- which are references -- but this is a call-by-value mechanism. Call-by-reference really means that the parameter variable is an alias for the caller's variable, such that assigning to that alias is equivalent to assigning to the caller's variable. Except for some very particular situations (like the
arguments
exotic object in non-strict mode, or theexport
mechanism, or thevar
link with thewindow
object, none of which helps you in your case in a best practice way), there is no such variable-aliasing mechanism in JavaScript.Here is an example on how to "exploit" the effect of
var
in a browser context, in non-strict mode:In short, apart from some bad design in the old JavaScript language (in sloppy mode), it just is not possible in JavaScript in general. All your attempts that work, perform a mutation on a given object, by setting one of its properties. You cannot hope to have a function that assigns to a parameter variable, thereby modifying the caller's variable. It just is not possible -- by design. Note the important distinction between assignment and mutation.
If the caller's variable (not a property) needs to be assigned a new value (not just mutation, but really assignment), then that assignment must happen to that variable -- something a function cannot do for the caller.
So the way to perform such an assignment in JavaScript, is that you make the function return whatever the caller needs to reassign, and it remains the responsibility of the caller to perform that assignment:
When you have more than one variable that is involved, let the function return a "packed" object, which the caller can destructure back into its own variables:
JavaScript 对象是“通过引用”传递的:
因此,如果您想要像 C、Rust 或 Swift 这样的“引用”,请将所有内容放入对象中。考虑一下
在 JavaScript 中它会是:
JavaScript objects are passed "by reference":
So if you want "references" like C or Rust or Swift put everything in objects. Consider
In JavaScript it would be:
这有效还是我误解了?
Does this work or am I misunderstanding?