【问题标题】:Is there a better way to simulate pointers in JavaScript?有没有更好的方法来模拟 JavaScript 中的指针?
【发布时间】:2012-04-23 13:05:07
【问题描述】:

我在 JavaScript 中使用dynamic scoping 模拟pointers 为follows:

var ptr = (function () {
    var ptr = "(" + String(function (value) {
    if (value === void 0) return upvalue;
    else upvalue = value;
}) + ")";

    return function (upvalue) {
        return ptr.replace(/upvalue/g, upvalue);
    };
}());

function swap(xptr, yptr) {
    var t = xptr();
    xptr(yptr());
    yptr(t);
}

var x = 2;
var y = 3;

alert([x, y]);
swap(eval(ptr("x")), eval(ptr("y")));
alert([x, y]);

有没有其他方法可以达到相同的结果(即不诉诸eval)?看起来样板文件太多了。

【问题讨论】:

  • 为什么要这样做?一般来说,如果您尝试将 JavaScript 编写为另一种语言,您最终会得到一些非常丑陋的代码。
  • 呃,这在很多层面上都是错误的......你有什么特别想解决的问题还是这是一个学术问题?
  • 如果您需要没有 eval 的指针,请使用数组。
  • @JamesMcLaughlin - 代码是否丑陋并不重要。它只是我在 JavaScript 之上构建的一种语言的样板(比如 CoffeeScript)。最终用户根本不需要与 JavaScript 样板进行交互。因此,如果它不漂亮也没关系。 =)
  • 也许我只是不明白您要解决什么问题。我没有看到 JavaScript 上下文中的指针有任何好处。但我确实看到您使用eval 并使用正则表达式变异字符串化函数体,这是不对的。一方面:内存管理语言中没有“内存位置”。进一步:已经编写了大而复杂的 JS 库,而无需“模拟”指针。这对我来说没有意义,在你指出一个合理的用例之前可能不会。

标签: javascript pointers closures simulate dynamic-scope


【解决方案1】:

由于您使用指针的唯一目的是取消引用它以访问另一个变量,因此您可以将其封装在一个属性中。

function createPointer(read, write) {
  return { get value() { return read(); }, set value(v) { return write(v); } };
}

要创建指针,请传递读取和写入指向的变量的访问器方法。

var i;
var p = createPointer(function() { return i; }, function(v) { i = v; });
// p is now a "pointer" to i

要取消引用指针,请访问它的值。换句话说,在 C 语言中,你会写 *p,在这里你写 p.value。

i = "initial";
alert(p.value); // alerts "initial"
p.value = "update";
alert(i); // alerts "update"
p.value += "2";
alert(i); // alerts "update2"

你可以创建多个指向同一个变量的指针。

var q = createPointer(function() { return i; }, function(v) { i = v; });
// q is also a "pointer" to i
alert(q.value); // alerts "update2"
q.value = "written from q";
alert(p.value); // alerts "written from q"

您可以通过简单地用另一个指针覆盖指针变量来更改指针指向的内容。

var j = "other";
q = createPointer(function() { return j; }, function(v) { j = v; });
// q is now a "pointer" to j

你可以通过指针交换两个变量。

function swap(x, y) {
    var t = x.value;
    x.value = y.value;
    y.value = t;
}

让我们使用i 和j 的指针来交换它们的值。

swap(p, q);
alert(i); // alerts "other"
alert(j); // alerts "written from q"

您可以创建指向局部变量的指针。

function example() {
    var myVar = "myVar as local variable from example";
    var r = createPointer(function() { return myVar; }, function(v) { myVar = v; });
    swap(p,r);
    alert(i); // alerts "myVar as local variable from example"
    alert(myVar); // alerts "other"
}
example();

通过闭包的魔力,这为您提供了一种模拟 malloc 的方法。

function malloc() {
    var i;
    return createPointer(function() { return i; }, function(v) { i = v; });
}
var p = malloc(); // p points to a variable we just allocated from the heap
p.value = 2; // write a 2 into it

你的魔术也有效:

var flowers = new Misdirection(
       createPointer(function() { return flowers; }, function(v) { flowers = v; }));
flowers.abracadabra();
alert(flowers);

function Misdirection(flowers) {
    this.abracadabra = function() {
        flowers.value = new Rabbit;
    };
}

function Rabbit() {
    this.toString = function() { return "Eh... what's up doc?" };
}

【讨论】:

  • 这正是我想要的。谢谢你。我想我对使用属性来模拟指针是正确的。然而,尽管如此令人兴奋,但我担心它可能无法在旧浏览器中工作(旧浏览器不支持 getter 和 setter),所以我们仍然必须使用我的方法作为后备。尽管如此,还是不​​胜感激。 =)
  • 您可以使用显式的get 和put 方法模拟属性。
  • 可能更短:function createPointer(r, w) { return Object.defineProperty({}, "value", {get: r, set: w}); }
【解决方案2】:

不幸的是,在 Javascript 中引用变量的唯一方法是直接访问它(我们不希望这样做,因为它进行静态绑定)或将其名称以字符串形式传递给 eval。

如果您真的想避免使用 eval,您可以尝试将变量放在充当作用域的对象中,因为这样您就可以使用[] 下标符号来访问给定名称的变量。请注意,如果您创建的所有指针都指向全局变量,那么情况已经如此,因为全局变量也被设为全局 window 对象的属性。


function pointer(scope, varname){
    return function(x){
        if(arguments.length <= 0){ //The explicit arguments.length lets us set the pointed variable to undefined too.
            return scope[varname];
        }else{
            return (scope[varname] = x);
        }
    }
};

var vars = {
    x: 1
};

var y = 2; // "normal" variables will only work if they are global.

swap( pointer(vars, 'x'), pointer(window, 'y') );

【讨论】:

  • +1 允许指针指向undefined(尽管我认为null 是nothing 的更好占位符,因为undefined 专门用于没有'尚未定义)。不过,就像我对其他答案的评论一样,我需要指针指向全局或局部变量,而不是对象的属性。所以我想闭包是实现它的唯一方法。 :|
【解决方案3】:

类似的东西?

function swap(a,b,scope) {
    var t = scope[a];
    scope[a] = scope[b];
    scope[b] = t; 
}

x = 2;
y = 3;
alert([x,y]);
swap('x', 'y',this);
alert([x,y]);

【讨论】:

  • 没有。在这种情况下,我们要么必须使用全局变量,要么必须使用对象的属性。所有变量都是激活对象的属性,但我们无权访问激活对象,因此此方法不起作用。
【解决方案4】:

这是对对象进行处理的一种方法:

var obj = {
    x:2,
    y:3
},
swap = function(p1, p2){
    var t = obj[p1];
    obj[p1] = obj[p2];
    obj[p2] = t;
};

console.log( obj.x, obj.y );
swap('x', 'y');
console.log( obj.x, obj.y );

【讨论】:

  • 这个答案类似于下面@chumkiu 发布的答案。但是就像我说的,我需要使用变量而不是对象的属性;而且由于我们无法访问函数的激活对象,它只能用于全局变量(这正是我们不想要的)。
  • 变量是窗口对象的一个​​属性。你可以使用全局对象window.yourObject,就像所有的 JS 库一样。
  • 局部变量不是这样。另外,如果我们不使用大量变量填充全局范围会更好。
  • 如果您将我的代码包装在 (function(){...})() 中,则该变量不再是全局变量,您可以将它们用作内部的闭包。许多 JS 库都添加了一个全局变量:$、dojo、jQuery、... 以及它们自己的方法。
【解决方案5】:

编辑:

@Tomalak - 考虑以下 JavaScript 程序:

var flowers = new Misdirection;
flowers.abracadabra();
alert(flowers);

function Misdirection() {
    this.abracadabra = function () {
        this = new Rabbit;
    };
}

function Rabbit() {
    this.toString = function () {
        return "Eh... What's up, doc?";
    };
}

上述程序抛出ReferenceError: Cannot assign to 'this'。指针可以用来解决这个问题;虽然它不会更新 this 指针,但它会做下一件最好的事情 - 更新对 this 指针的唯一引用。

我们可以通过将this 替换为flowers 来使上述程序在不使用指针的情况下运行。然而,通过这样做,误导魔术只适用于构造函数的一个实例。指针允许我们使它适用于任意数量的实例。

使用Function.call、Function.apply 或Array.map 无法获得相同的结果。此外,如果构造函数返回一个显式值,那么无论如何覆盖 this 指针是没有用的。即使我从Misdirection 返回abracadabra 函数并调用flowers() 而不是flowers.abracadabra(),我在下面编写的程序(使用指针)也能工作。

原文:

在 JavaScript 中模拟指针是一个非常强大的技巧。例如,它可以用来表演魔术,如follows:

var flowers = new Misdirection(&flowers);
flowers.abracadabra();
alert(flowers);

function Misdirection(flowers) {
    this.abracadabra = function () {
        *flowers = new Rabbit;
    };
}

function Rabbit() {
    this.toString = function () {
        return "Eh... What's up, doc?";
    };
}

这一切都很好,很花哨,但是当我们 push the envelope 时,real power 在 JavaScript 中模拟指针的作用就体现出来了:

var Square = new Class(function (ctor, uber) {
    *ctor = constructor;

    var side;

    function constructor(length) {
        side = length;
    }

    this.area = function () {
        return side * side;
    };

    return &uber;
});

var Cube = new Class(function (ctor, uber) {
    *ctor = constructor;

    function constructor(side) {
        uber(side);
    }

    this.area = function () {
        return 6 * uber.area();
    };

    return &uber;
}, Square);

var cube = new Cube(5);
alert(cube.area());

function Class(claus, Uber) {
    Claus.__proto__ = Uber === void 0 ? Class.prototype : Uber;

    return Claus;

    function Claus() {
        var self = this;
        var called;

        var uber = Uber === void 0 ? function () {
            throw new Error("No uber class specified.");
        } : function () {
            if (!called) {
                called = "Cannot call uber class constructor more than once.";

                var args = Array.prototype.slice.call(arguments);
                args = Array.prototype.concat.call([null], args);
                var base = new (Function.prototype.bind.apply(Uber, args));

                self.__proto__.__proto__ = base;
                self.__proto__.__proto__.constructor = Claus;

                *uber = base;
            } else throw new Error(called);
        };

        var constructor = new Function;
        uber = claus.call(this, &constructor, uber);
        constructor.apply(this, arguments);
    };
}

显然这是太多样板文件,但它确实展示了闭包的强大功能。真正令人惊奇的是,我们可以使用这个样板来模拟 JavaScript 中的经典面向对象编程。例如,可以将以下代码转编译为上述程序(尽管我们需要编写一个完整的解析器来做到这一点):

class Square {
    var side;

    function constructor(length) {
        side = length;
    }

    this.area = function () {
        return side * side;
    };
}

class Cube extends Square {
    function constructor(side) {
        uber(side);
    }

    this.area = function () {
        return 6 * uber.area();
    };
}

var cube = new Cube(5);
alert(cube.area());

请注意,*ctor = constructor; 和 return &amp;uber; 行已被删除。这只是使构造函数和继承工作所必需的冗余代码。 Class 函数也没有写在源代码中,因为它是由转编译器自动添加的。

在上面的程序中展示了在 JavaScript 中模拟指针的美妙之处,其中 Cube 类中的变量 uber 最初是基类构造函数。然而,当它被调用时,它会被基类的实例所取代,该实例成为this 的原型。

这也意味着Cube 的实例不会是Square 的实例,除非并且直到从Cube 调用超级类构造函数。

【讨论】:

  • 看来你模拟的不是指针而是变量引用。这些可以通过属性轻松模拟。
  • 没有。我正在模拟的实际上是一个指针。初始化后的引用变量不需要取消引用。它们只是另一个变量的别名。此外,它们是常量变量。它们不能被修改。您在引用和引用变量之间感到困惑。引用是值的别名。参考变量是变量的别名。我正在模拟的是一个指向变量的指针。它与属性不同,不能使用属性来实现相同的用例。
  • 除了取消引用之外,您没有执行任何指针操作。 (例如,没有指针算法。)因此,您可以只使用 Javascript 属性来执行取消引用。
  • 我同意。谢谢您的回答。非常感谢。 =)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-27
  • 2020-05-30
  • 2010-12-27
  • 2013-11-16
  • 2014-06-04
相关资源
最近更新 更多