我正在创建一种可以编译成Swift、Rust和JavaScript(或者至少可以try )的语言.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
}

但这需要进行大量额外的处理,而且看起来有点不必要.相反,我可能会try 编译为以下内容:

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
}

所以这看起来可能会让我们得到somewhere美元.

那么就像,一般的解决方案是让Scope作为函数的第一个参数.

function add(scope, name1, name2) {
  return scope[name1] + scope[name2]
}

取消引用意味着直接从作用域读取值,而传递引用(如Rust或C中的&name)则意味着传递作用域和名称.

这样的东西行得通吗?或者更确切地说,需要改变或增加什么?还需要比这更复杂的事情吗?

我想try 并找到一种将面向指针的代码转换为JavaScript(转换)的方法,而不是首先try 找出看起来复杂得多的方法,即不那么直接,并通过重新定义许多方法来避免在JavaScript中进行指针模拟.似乎避免在JavaScript中使用任何指针要难得多,所以我正在try 看看是否有可能在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确实有一种通过引用传递参数的通用机制.

术语"引用"周围可能有一些念力,比如在JavaScriptare中,人们可以将对象传递给函数--这是一种按值调用的机制.按引用调用实际上意味着参数变量是调用方变量的alias,因此该别名的assigning等同于为调用方变量赋值.除了一些非常特殊的情况(比如arguments exotic object处于非严格模式,或者export机制,或者varwindow对象的链接,在您的情况下都不会以最佳实践的方式帮助您),JavaScript中没有这样的变量别名机制.

下面是一个关于如何在非严格模式下在浏览器上下文中"利用"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中通常是不可能的.所有有效的try ,通过设置给定对象的一个属性,对该对象执行mutation.您不能指望将函数assigns设置为参数变量,从而修改调用方的变量.这是不可能的--这是设计出来的.请注意assignmentmutation之间的重要区别.

如果需要为调用者的variable(不是属性)赋一个新值(不仅是Mutations ,实际上是assignment),那么赋值must会发生在that个变量上--这是函数不能为调用者做的事情.

因此,在JavaScript中执行这样的赋值的方法是,无论调用者需要重新赋值什么,您都要使函数return变为return,并且调用者仍然有责任执行该赋值:

function modify(value) {
    return 3;
}

let value = 1;
value = modify(value);
console.log(value);

当涉及多个变量时,让函数返回一个"打包的"对象,调用者可以将该对象分解回自己的变量:

function modify(a, b) {
    return [a + 1, b * 2];
}

let a = 1, b = 2;
[a, b] = modify(a, b);
console.log(a, b);

Javascript相关问答推荐

React中的表格中 Select Radio按钮

如何从defineExpose访问数据和方法

带对角线分隔符的图像滑动器

Fastify错误:CORS策略已阻止从起源api-dev.example.com上的MLhttp请求

我开始使用/url?q=使用Cheerio

MongoDB中的引用

为什么promise对js中的错误有一个奇怪的优先级?

在服务器上放置了Create Reaction App Build之后的空白页面

数字时钟在JavaScript中不动态更新

将内容大小设置为剩余可用空间,但如果需要,可在div上显示滚动条

基于props 类型的不同props ,根据来自接口的值扩展类型

NG/Express API路由处理程序停止工作

JavaScript不重定向配置的PATH

向数组中的对象添加键而不改变原始变量

使用插件构建包含chart.js提供程序的Angular 库?

从逗号和破折号分隔的给定字符串中查找所有有效的星期几

是否可以将Select()和Sample()与Mongoose结合使用?

匹配一个或多个可选重复的特定模式

Django导入问题,无法导入我的应用程序,但我已在设置中安装了它

在JavaScript中将Base64转换为JSON