如何在JavaScript中模拟指针?
How to simulate pointers in JavaScript?
我正在创建一种可编译为 Swift、Rust 和 JavaScript(或至少尝试)的语言。 Rust 和 Swift 都使用 pointers/references/dereferencing/etc.,而 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
}
这意味着您创建的每个嵌套范围,您都必须手动启动 creating/managing 范围链。实际上那是行不通的,因为 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(transpilation),而不是首先尝试找出不那么直接的看似复杂得多的方法,并通过重新定义许多方法来避免 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)
}
所以这似乎真的行不通。
JavaScript 对象“通过引用”传递:
const obj = {
hello: "world"
};
console.log(obj.hello); // "world"
f(obj);
console.log(obj.hello); // "bob"
function f(s) {
s.hello = "bob";
}
因此,如果您想要像 C 或 Rust 或 Swift 这样的“引用”,请将所有内容都放在对象中。考虑
fn update(x) {
x++
}
fn main() {
let i = 0
update(&i)
log(i) #=> 1
}
在 JavaScript 中将是:
function update(obj) {
obj.x++
}
const i = { x: 0 };
update(i);
console.log(i.x); // 1
这个有用还是我误会了?
update2(x) {
update(x)
}
update(x) {
x.value++
}
main() {
let i = { value: 0 }
update2(i)
}
不用说,但是 JavaScript 没有 有一个通过引用传递参数的通用机制。
“引用”一词可能会引起一些混淆,因为 JavaScript 可以将对象传递给函数——是引用——但这是call-by-value 机制。 Call-by-reference 实际上意味着参数变量是调用者变量的 别名 ,这样 分配 到该别名等同于分配给调用者的变量。除了一些非常特殊的情况(比如 arguments
exotic object in non-strict 模式,或者 export
机制,或者 var
link with window
对象,none 其中以最佳实践方式帮助你),JavaScript 中没有这样的 variable-aliasing 机制。
这是一个关于如何在 non-strict 模式下在浏览器上下文中“利用”var
效果的示例:
function modify(ref) {
// Using the fact that global `var` variables are aliases
// for properties on the global object
// (This is not considered good practice)
globalThis[ref] = globalThis[ref] + 1;
}
var a = 1;
modify("a"); // We pass a reference to `a`
console.log(a);
简而言之,除了旧 JavaScript 语言中的一些糟糕设计(在草率模式下)外,在 JavaScript 中通常是不可能的。您所有成功的尝试,通过设置其属性之一,对给定对象执行 突变 。您不能希望有一个函数将 赋值 给参数变量,从而修改调用者的变量。这是不可能的——设计使然。请注意 assignment 和 mutation.
之间的重要区别
如果调用者的变量(不是属性)需要分配一个新值(不只是突变,而是真正的赋值),那么那个赋值 必须 发生在 那个 变量上——函数不能为调用者做的事情。
因此,在 JavaScript 中执行此类赋值的方法是,将函数 return 设为调用者需要重新赋值的任何内容,并且它仍然由调用者负责执行该分配:
function modify(value) {
return 3;
}
let value = 1;
value = modify(value);
console.log(value);
当涉及多个变量时,让函数 return 成为一个“打包”对象,调用者可以将其解构为自己的变量:
function modify(a, b) {
return [a + 1, b * 2];
}
let a = 1, b = 2;
[a, b] = modify(a, b);
console.log(a, b);
我正在创建一种可编译为 Swift、Rust 和 JavaScript(或至少尝试)的语言。 Rust 和 Swift 都使用 pointers/references/dereferencing/etc.,而 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
}
这意味着您创建的每个嵌套范围,您都必须手动启动 creating/managing 范围链。实际上那是行不通的,因为 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(transpilation),而不是首先尝试找出不那么直接的看似复杂得多的方法,并通过重新定义许多方法来避免 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)
}
所以这似乎真的行不通。
JavaScript 对象“通过引用”传递:
const obj = {
hello: "world"
};
console.log(obj.hello); // "world"
f(obj);
console.log(obj.hello); // "bob"
function f(s) {
s.hello = "bob";
}
因此,如果您想要像 C 或 Rust 或 Swift 这样的“引用”,请将所有内容都放在对象中。考虑
fn update(x) {
x++
}
fn main() {
let i = 0
update(&i)
log(i) #=> 1
}
在 JavaScript 中将是:
function update(obj) {
obj.x++
}
const i = { x: 0 };
update(i);
console.log(i.x); // 1
这个有用还是我误会了?
update2(x) {
update(x)
}
update(x) {
x.value++
}
main() {
let i = { value: 0 }
update2(i)
}
不用说,但是 JavaScript 没有 有一个通过引用传递参数的通用机制。
“引用”一词可能会引起一些混淆,因为 JavaScript 可以将对象传递给函数——是引用——但这是call-by-value 机制。 Call-by-reference 实际上意味着参数变量是调用者变量的 别名 ,这样 分配 到该别名等同于分配给调用者的变量。除了一些非常特殊的情况(比如 arguments
exotic object in non-strict 模式,或者 export
机制,或者 var
link with window
对象,none 其中以最佳实践方式帮助你),JavaScript 中没有这样的 variable-aliasing 机制。
这是一个关于如何在 non-strict 模式下在浏览器上下文中“利用”var
效果的示例:
function modify(ref) {
// Using the fact that global `var` variables are aliases
// for properties on the global object
// (This is not considered good practice)
globalThis[ref] = globalThis[ref] + 1;
}
var a = 1;
modify("a"); // We pass a reference to `a`
console.log(a);
简而言之,除了旧 JavaScript 语言中的一些糟糕设计(在草率模式下)外,在 JavaScript 中通常是不可能的。您所有成功的尝试,通过设置其属性之一,对给定对象执行 突变 。您不能希望有一个函数将 赋值 给参数变量,从而修改调用者的变量。这是不可能的——设计使然。请注意 assignment 和 mutation.
之间的重要区别如果调用者的变量(不是属性)需要分配一个新值(不只是突变,而是真正的赋值),那么那个赋值 必须 发生在 那个 变量上——函数不能为调用者做的事情。
因此,在 JavaScript 中执行此类赋值的方法是,将函数 return 设为调用者需要重新赋值的任何内容,并且它仍然由调用者负责执行该分配:
function modify(value) {
return 3;
}
let value = 1;
value = modify(value);
console.log(value);
当涉及多个变量时,让函数 return 成为一个“打包”对象,调用者可以将其解构为自己的变量:
function modify(a, b) {
return [a + 1, b * 2];
}
let a = 1, b = 2;
[a, b] = modify(a, b);
console.log(a, b);